Fix: Exit Button Works

This commit is contained in:
Youwes09
2026-05-16 15:31:13 -05:00
parent 93cedca6b5
commit 6b56db7cf2
9 changed files with 121 additions and 42 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ stdenv.mkDerivation {
pname = "moku-frontend"; pname = "moku-frontend";
inherit version src; inherit version src;
fetcherVersion = 1; fetcherVersion = 1;
hash = "sha256-eRuSSRhNmJ09mp/uhbG+NFeiOZ5dTOdJ94OwdP6IkN0="; hash = "sha256-vM//1/qe9nKDwwlmFbqvBFqF8cCjIIdNKEtktyzBFB8=";
}; };
buildPhase = "pnpm build"; buildPhase = "pnpm build";
+1
View File
@@ -13,6 +13,7 @@
"@tauri-apps/api": "^2.11.0", "@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-http": "^2.5.8", "@tauri-apps/plugin-http": "^2.5.8",
"@tauri-apps/plugin-os": "^2.3.2", "@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.5", "@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "~2.4.2", "@tauri-apps/plugin-store": "~2.4.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
+10
View File
@@ -17,6 +17,9 @@ importers:
'@tauri-apps/plugin-os': '@tauri-apps/plugin-os':
specifier: ^2.3.2 specifier: ^2.3.2
version: 2.3.2 version: 2.3.2
'@tauri-apps/plugin-process':
specifier: ^2.3.1
version: 2.3.1
'@tauri-apps/plugin-shell': '@tauri-apps/plugin-shell':
specifier: ^2.3.5 specifier: ^2.3.5
version: 2.3.5 version: 2.3.5
@@ -289,6 +292,9 @@ packages:
'@tauri-apps/plugin-os@2.3.2': '@tauri-apps/plugin-os@2.3.2':
resolution: {integrity: sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==} resolution: {integrity: sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==}
'@tauri-apps/plugin-process@2.3.1':
resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==}
'@tauri-apps/plugin-shell@2.3.5': '@tauri-apps/plugin-shell@2.3.5':
resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==} resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==}
@@ -763,6 +769,10 @@ snapshots:
dependencies: dependencies:
'@tauri-apps/api': 2.11.0 '@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-process@2.3.1':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-shell@2.3.5': '@tauri-apps/plugin-shell@2.3.5':
dependencies: dependencies:
'@tauri-apps/api': 2.11.0 '@tauri-apps/api': 2.11.0
+1
View File
@@ -2008,6 +2008,7 @@ name = "moku"
version = "0.9.3" version = "0.9.3"
dependencies = [ dependencies = [
"dirs 5.0.1", "dirs 5.0.1",
"libc",
"reqwest 0.12.28", "reqwest 0.12.28",
"serde", "serde",
"serde_json", "serde_json",
+1
View File
@@ -28,6 +28,7 @@ serde_json = "1"
walkdir = "2" walkdir = "2"
sysinfo = "0.32" sysinfo = "0.32"
dirs = "5" dirs = "5"
libc = "0.2"
urlencoding = "2" urlencoding = "2"
tokio = { version = "1", features = ["rt-multi-thread"] } tokio = { version = "1", features = ["rt-multi-thread"] }
reqwest = { version = "0.12", features = ["blocking"] } reqwest = { version = "0.12", features = ["blocking"] }
+4 -1
View File
@@ -2,7 +2,9 @@
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default", "identifier": "default",
"description": "Default permissions for Moku", "description": "Default permissions for Moku",
"windows": ["main"], "windows": [
"main"
],
"permissions": [ "permissions": [
"core:default", "core:default",
"core:tray:default", "core:tray:default",
@@ -31,6 +33,7 @@
"core:window:allow-outer-position", "core:window:allow-outer-position",
"core:window:allow-scale-factor", "core:window:allow-scale-factor",
"process:default", "process:default",
"process:allow-exit",
"process:allow-restart", "process:allow-restart",
"http:default", "http:default",
"http:allow-fetch", "http:allow-fetch",
+91 -2
View File
@@ -2,11 +2,20 @@ mod commands;
mod server; mod server;
use std::sync::Mutex; use std::sync::Mutex;
use tauri::{Manager, WindowEvent}; use tauri::{
menu::{Menu, MenuItem, PredefinedMenuItem},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
Manager, WindowEvent,
};
use tauri_plugin_shell::process::CommandChild; use tauri_plugin_shell::process::CommandChild;
pub struct ServerState(pub Mutex<Option<CommandChild>>); pub struct ServerState(pub Mutex<Option<CommandChild>>);
fn do_quit(app: &tauri::AppHandle) {
server::kill_tachidesk(app);
app.exit(0);
}
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
@@ -44,7 +53,87 @@ pub fn run() {
commands::biometric::windows_hello_authenticate, commands::biometric::windows_hello_authenticate,
commands::biometric::windows_hello_available, commands::biometric::windows_hello_available,
]) ])
.setup(|_app| Ok(())) .setup(|app| {
let lock_path = app
.path()
.app_data_dir()
.unwrap_or_default()
.join(".moku.lock");
let lock_file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(&lock_path)
.ok();
let already_running = lock_file.as_ref().map(|f| {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
unsafe { libc::flock(f.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) != 0 }
}
#[cfg(windows)]
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
};
let handle = HANDLE(f.as_raw_handle() as isize);
let mut overlapped = windows::Win32::System::IO::OVERLAPPED::default();
!unsafe {
LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &mut overlapped)
}.as_bool()
}
#[cfg(not(any(unix, windows)))]
{ false }
}).unwrap_or(false);
if already_running {
app.handle().exit(0);
return Ok(());
}
std::mem::forget(lock_file);
let show = MenuItem::with_id(app, "show", "Show Moku", true, None::<&str>)?;
let sep = PredefinedMenuItem::separator(app)?;
let quit = MenuItem::with_id(app, "quit", "Quit Moku", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show, &sep, &quit])?;
TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.show_menu_on_left_click(false)
.tooltip("Moku")
.on_menu_event(|app, event| match event.id.as_ref() {
"show" => {
if let Some(win) = app.get_webview_window("main") {
let _ = win.show();
let _ = win.set_focus();
}
}
"quit" => do_quit(app),
_ => {}
})
.on_tray_icon_event(|tray, event| {
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} = event
{
let app = tray.app_handle();
if let Some(win) = app.get_webview_window("main") {
let _ = win.show();
let _ = win.set_focus();
}
}
})
.build(app)?;
Ok(())
})
.on_window_event(|window, event| { .on_window_event(|window, event| {
if let WindowEvent::Destroyed = event { if let WindowEvent::Destroyed = event {
server::kill_tachidesk(window.app_handle()); server::kill_tachidesk(window.app_handle());
+8 -36
View File
@@ -3,9 +3,6 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import { defaultWindowIcon } from "@tauri-apps/api/app";
import { TrayIcon } from "@tauri-apps/api/tray";
import { Menu } from "@tauri-apps/api/menu";
import { platform } from "@tauri-apps/plugin-os"; import { platform } from "@tauri-apps/plugin-os";
import { store, updateSettings, setActiveDownloads } from "@store/state.svelte"; import { store, updateSettings, setActiveDownloads } from "@store/state.svelte";
import { downloadStore } from "@features/downloads/store/downloadState.svelte"; import { downloadStore } from "@features/downloads/store/downloadState.svelte";
@@ -48,8 +45,13 @@
} }
async function doQuit() { async function doQuit() {
if (store.settings.autoStartServer) await invoke("kill_server").catch(() => {}); if (store.settings.autoStartServer) {
await win.destroy(); await Promise.race([
invoke("kill_server").catch(() => {}),
new Promise(res => setTimeout(res, 2000)),
]);
}
await invoke("exit_app");
} }
async function doHide() { async function doHide() {
@@ -123,36 +125,6 @@
applyZoom(); applyZoom();
}); });
const menu = await Menu.new({
items: [
{
id: "show",
text: "Show Moku",
action: async () => {
await win.show();
await win.setFocus();
},
},
{
id: "quit",
text: "Quit",
action: doQuit,
},
],
});
await TrayIcon.new({
icon: await defaultWindowIcon(),
menu,
menuOnLeftClick: false,
tooltip: "Moku",
action: async (e) => {
if (e.type === "Click") {
await win.show();
await win.setFocus();
}
},
});
const unlistenClose = await win.listen("tauri://close-requested", handleCloseRequested); const unlistenClose = await win.listen("tauri://close-requested", handleCloseRequested);
@@ -215,7 +187,7 @@
{/if} {/if}
<div id="app-shell" class="root"> <div id="app-shell" class="root">
{#if !store.activeChapter}<TitleBar />{/if} {#if !store.activeChapter}<TitleBar onClose={handleCloseRequested} />{/if}
<div class="content"> <div class="content">
{#if store.activeChapter}<Reader />{:else}<Layout />{/if} {#if store.activeChapter}<Reader />{:else}<Layout />{/if}
</div> </div>
+4 -2
View File
@@ -3,6 +3,8 @@
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import { platform } from "@tauri-apps/plugin-os"; import { platform } from "@tauri-apps/plugin-os";
const { onClose }: { onClose: () => void } = $props();
const win = getCurrentWindow(); const win = getCurrentWindow();
const os = platform(); const os = platform();
const isMac = os === "macos"; const isMac = os === "macos";
@@ -31,7 +33,7 @@
<button onclick={() => win.toggleMaximize()} title="Maximize" aria-label="Maximize"> <button onclick={() => win.toggleMaximize()} title="Maximize" aria-label="Maximize">
<svg width="9" height="9" viewBox="0 0 9 9"><rect x="0.75" y="0.75" width="7.5" height="7.5" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" /></svg> <svg width="9" height="9" viewBox="0 0 9 9"><rect x="0.75" y="0.75" width="7.5" height="7.5" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" /></svg>
</button> </button>
<button class="close" onclick={() => win.close()} title="Close" aria-label="Close"> <button class="close" onclick={onClose} title="Close" aria-label="Close">
<svg width="10" height="10" viewBox="0 0 10 10"> <svg width="10" height="10" viewBox="0 0 10 10">
<line x1="1" y1="1" x2="9" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <line x1="1" y1="1" x2="9" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<line x1="9" y1="1" x2="1" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <line x1="9" y1="1" x2="1" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
@@ -50,7 +52,7 @@
<polyline points="4,9 1,9 1,6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <polyline points="4,9 1,9 1,6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
</button> </button>
<button class="close" onclick={() => win.close()} title="Close" aria-label="Close"> <button class="close" onclick={onClose} title="Close" aria-label="Close">
<svg width="10" height="10" viewBox="0 0 10 10"> <svg width="10" height="10" viewBox="0 0 10 10">
<line x1="1" y1="1" x2="9" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <line x1="1" y1="1" x2="9" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<line x1="9" y1="1" x2="1" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <line x1="9" y1="1" x2="1" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />