Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/lachesis/app/components/bubble-nav.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useFullscreen, useWindowSize } from '@vueuse/core'
import { useWindowSize } from '@vueuse/core'
import { Goal, ListTodo } from '@lucide/vue'
import type { FocusOutsideEvent } from 'reka-ui'

Expand Down
68 changes: 68 additions & 0 deletions apps/lachesis/app/composables/useFullscreen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { getCurrentWindow } from '@tauri-apps/api/window'
import { invoke, isTauri } from '@tauri-apps/api/core'
import { useFullscreen as useFullscreenBase } from '@vueuse/core'

const isTauriFullscreen = ref(false)
let isInitialized = false

export function useFullscreen(
...args: Parameters<typeof useFullscreenBase>
): ReturnType<typeof useFullscreenBase> {
if (!isTauri()) {
return useFullscreenBase(...args)
}

if (import.meta.client && !isInitialized) {
isInitialized = true
invoke<boolean>('is_fullscreen')
.then((fs) => {
isTauriFullscreen.value = fs
})
.catch(() => {})

getCurrentWindow().onResized(async () => {
try {
isTauriFullscreen.value = await invoke<boolean>('is_fullscreen')
} catch {}
})
}

async function enter() {
try {
const res = await invoke<boolean>('set_fullscreen', { fullscreen: true })
isTauriFullscreen.value = res
} catch {
await getCurrentWindow().setFullscreen(true)
isTauriFullscreen.value = true
}
}

async function exit() {
try {
const res = await invoke<boolean>('set_fullscreen', { fullscreen: false })
isTauriFullscreen.value = res
} catch {
await getCurrentWindow().setFullscreen(false)
isTauriFullscreen.value = false
}
}

async function toggle() {
try {
const res = await invoke<boolean>('toggle_fullscreen')
isTauriFullscreen.value = res
} catch {
const next = !isTauriFullscreen.value
await getCurrentWindow().setFullscreen(next)
isTauriFullscreen.value = next
}
}

return {
isFullscreen: isTauriFullscreen,
isSupported: ref(true),
enter,
exit,
toggle,
} as unknown as ReturnType<typeof useFullscreenBase>
}
1 change: 0 additions & 1 deletion apps/lachesis/app/layouts/shell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { isTauri } from '@tauri-apps/api/core'
import { platform, type Platform } from '@tauri-apps/plugin-os'
import { getCurrentWebviewWindow, type WebviewWindow } from '@tauri-apps/api/webviewWindow'
import type { UnlistenFn } from '@tauri-apps/api/event'
import { useFullscreen } from '@vueuse/core'

const { backgrounds, activeBackgroundId } = useBackgrounds()
const { windowTransparency, nativeDecorations } = useSettings()
Expand Down
2 changes: 1 addition & 1 deletion apps/lachesis/app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CircleAlert, Copy, Expand, Lightbulb, LightbulbOff, Shrink } from '@lucide/vue'
import { useFullscreen, useLocalStorage } from '@vueuse/core'
import { useLocalStorage } from '@vueuse/core'
import { toast } from 'vue-sonner'
import EmailVerifyButton from '~/components/email-verify-button.vue'
import { getDailyQuote } from '~/lib/quotes'
Expand Down
1 change: 1 addition & 0 deletions apps/lachesis/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions apps/lachesis/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ cef = ["dep:tauri-runtime-cef"]

[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging",
"Win32_Graphics_Gdi",
"Win32_Graphics_Dwm",
] }

7 changes: 5 additions & 2 deletions apps/lachesis/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main"],
"windows": [
"main"
],
"permissions": [
"core:default",
"core:window:allow-start-dragging",
Expand All @@ -11,8 +13,9 @@
"core:window:allow-minimize",
"core:window:allow-close",
"core:window:allow-show",
"core:window:allow-set-fullscreen",
"os:default",
"store:default",
"opener:default"
]
}
}
153 changes: 153 additions & 0 deletions apps/lachesis/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::sync::atomic::{AtomicBool, Ordering};

static WAS_MAXIMIZED: AtomicBool = AtomicBool::new(false);

#[tauri::command]
pub fn get_os() -> String {
let platform = tauri_plugin_os::type_();
Expand All @@ -20,3 +24,152 @@ pub fn get_os() -> String {
tauri_plugin_os::version()
)
}

#[tauri::command]
pub fn is_fullscreen(window: tauri::WebviewWindow) -> Result<bool, String> {
window.is_fullscreen().map_err(|e| e.to_string())
}

#[tauri::command]
pub fn set_fullscreen(window: tauri::WebviewWindow, fullscreen: bool) -> Result<bool, String> {
let current_fs = window.is_fullscreen().unwrap_or(false);
if current_fs == fullscreen {
return Ok(fullscreen);
}

// Check if native decorations are enabled
let is_decorated = window.is_decorated().unwrap_or(false);
if is_decorated {
// Native decorations handle fullscreen transitions natively with full OS smoothness
window
.set_fullscreen(fullscreen)
.map_err(|e| e.to_string())?;
return Ok(fullscreen);
}

// Custom decorations (frameless window) handling
if fullscreen {
let is_max = window.is_maximized().unwrap_or(false);
WAS_MAXIMIZED.store(is_max, Ordering::SeqCst);

#[cfg(windows)]
{
if is_max {
if let Ok(hwnd) = window.hwnd() {
unsafe {
use windows_sys::Win32::Foundation::{BOOL, HWND};
use windows_sys::Win32::Graphics::Dwm::{
DwmSetWindowAttribute, DWMWA_TRANSITIONS_FORCEDISABLED,
};
use windows_sys::Win32::Graphics::Gdi::{
GetMonitorInfoW, MonitorFromWindow, MONITORINFO, MONITOR_DEFAULTTONEAREST,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{
GetWindowLongPtrW, SetWindowLongPtrW, SetWindowPos, GWL_STYLE, HWND_TOP,
SWP_FRAMECHANGED, SWP_NOZORDER, SWP_SHOWWINDOW, WS_MAXIMIZE,
};

let raw_hwnd = hwnd.0 as isize as HWND;
let disable: BOOL = 1;
DwmSetWindowAttribute(
raw_hwnd,
DWMWA_TRANSITIONS_FORCEDISABLED as u32,
&disable as *const _ as *const _,
std::mem::size_of::<BOOL>() as u32,
);

// Remove WS_MAXIMIZE style in memory so Windows doesn't clamp the resize
let style = GetWindowLongPtrW(raw_hwnd, GWL_STYLE);
SetWindowLongPtrW(raw_hwnd, GWL_STYLE, style & !(WS_MAXIMIZE as isize));

// Get the monitor bounds
let monitor = MonitorFromWindow(raw_hwnd, MONITOR_DEFAULTTONEAREST);
let mut monitor_info: MONITORINFO = std::mem::zeroed();
monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
GetMonitorInfoW(monitor, &mut monitor_info);

let rc = monitor_info.rcMonitor;
let width = rc.right - rc.left;
let height = rc.bottom - rc.top;

// Direct single-step resize to full monitor bounds (covering taskbar)
SetWindowPos(
raw_hwnd,
HWND_TOP,
rc.left,
rc.top,
width,
height,
SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW,
);

let _ = window.set_fullscreen(true);

let enable: BOOL = 0;
DwmSetWindowAttribute(
raw_hwnd,
DWMWA_TRANSITIONS_FORCEDISABLED as u32,
&enable as *const _ as *const _,
std::mem::size_of::<BOOL>() as u32,
);

return Ok(true);
}
}
}
}

window.set_fullscreen(true).map_err(|e| e.to_string())?;
} else {
let was_max = WAS_MAXIMIZED.swap(false, Ordering::SeqCst);

#[cfg(windows)]
{
if was_max {
if let Ok(hwnd) = window.hwnd() {
unsafe {
use windows_sys::Win32::Foundation::{BOOL, HWND};
use windows_sys::Win32::Graphics::Dwm::{
DwmSetWindowAttribute, DWMWA_TRANSITIONS_FORCEDISABLED,
};

let raw_hwnd = hwnd.0 as isize as HWND;
let disable: BOOL = 1;
DwmSetWindowAttribute(
raw_hwnd,
DWMWA_TRANSITIONS_FORCEDISABLED as u32,
&disable as *const _ as *const _,
std::mem::size_of::<BOOL>() as u32,
);

let _ = window.set_fullscreen(false);
let _ = window.maximize();

let enable: BOOL = 0;
DwmSetWindowAttribute(
raw_hwnd,
DWMWA_TRANSITIONS_FORCEDISABLED as u32,
&enable as *const _ as *const _,
std::mem::size_of::<BOOL>() as u32,
);

return Ok(false);
}
}
}
}

window.set_fullscreen(false).map_err(|e| e.to_string())?;
if was_max {
let _ = window.maximize();
}
}

Ok(fullscreen)
}

#[tauri::command]
pub fn toggle_fullscreen(window: tauri::WebviewWindow) -> Result<bool, String> {
let is_fs = window.is_fullscreen().map_err(|e| e.to_string())?;
set_fullscreen(window, !is_fs)
}
20 changes: 13 additions & 7 deletions apps/lachesis/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ pub fn run() {
let builder = tauri::Builder::default();

builder
.invoke_handler(tauri::generate_handler![commands::get_os])
.invoke_handler(tauri::generate_handler![
commands::get_os,
commands::is_fullscreen,
commands::set_fullscreen,
commands::toggle_fullscreen
])
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_os::init())
Expand All @@ -34,16 +39,17 @@ pub fn run() {
}

// if nativeDecorations is true, set native decors to true
if app
let use_native_decors = app
.store("settings.json")
.ok()
.and_then(|store| store.get("nativeDecorations"))
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
let _ = app
.get_webview_window("main")
.map(|window| window.set_decorations(true));
.unwrap_or(false);

if use_native_decors {
if let Some(window) = app.get_webview_window("main") {
let _ = window.set_decorations(true);
}
}

setup_tray(app.app_handle())?;
Expand Down