diff --git a/CLAUDE.md b/CLAUDE.md index d5575c6..999fdc0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,12 +57,14 @@ pnpm run dev # Development with hot reload - **CLI Tool** (`bin/`): Main entry point, builders, options processing - **Tauri App** (`src-tauri/`): Rust application, window/tray management, injection logic - **Config Files**: `pake.json`, `tauri.conf.json`, platform-specific configs +- **Injection System** (`src-tauri/src/inject/event.js`): Custom event handlers, shortcuts, downloads, notifications ## Documentation Guidelines - **Main README**: Common parameters only - **CLI Documentation** (`docs/cli-usage.md`): ALL parameters with examples - **Rare parameters**: Full docs in CLI usage, minimal in main README +- **NO technical documentation files**: Do not create separate technical docs, design docs, or implementation notes - keep technical details in memory/conversation only ## Platform Specifics diff --git a/bin/builders/BaseBuilder.ts b/bin/builders/BaseBuilder.ts index e204c57..0d71fc0 100644 --- a/bin/builders/BaseBuilder.ts +++ b/bin/builders/BaseBuilder.ts @@ -123,14 +123,12 @@ export default abstract class BaseBuilder { `cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}`, timeout, buildEnv, - this.options.debug, ); } else { await shellExec( `cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}`, timeout, buildEnv, - this.options.debug, ); } spinner.succeed(chalk.green('Package installed!')); @@ -169,11 +167,24 @@ export default abstract class BaseBuilder { ...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }), }; + // Warn users about potential AppImage build failures on modern Linux systems. + // The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't + // recognize the .relr.dyn section introduced in glibc 2.38+. + if (process.platform === 'linux' && this.options.targets === 'appimage') { + if (!buildEnv.NO_STRIP) { + logger.warn( + '⚠ Building AppImage on Linux may fail due to strip incompatibility with glibc 2.38+', + ); + logger.warn( + '⚠ If build fails, retry with: NO_STRIP=1 pake --targets appimage', + ); + } + } + await shellExec( `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv, - this.options.debug, ); // Copy app @@ -280,6 +291,12 @@ export default abstract class BaseBuilder { fullCommand += ` --target ${target}`; } + // Enable verbose output in debug mode to help diagnose build issues. + // This provides detailed logs from Tauri CLI and bundler tools. + if (this.options.debug) { + fullCommand += ' --verbose'; + } + return fullCommand; } diff --git a/bin/builders/LinuxBuilder.ts b/bin/builders/LinuxBuilder.ts index bc0189b..2b12cbe 100644 --- a/bin/builders/LinuxBuilder.ts +++ b/bin/builders/LinuxBuilder.ts @@ -78,6 +78,13 @@ export default class LinuxBuilder extends BaseBuilder { fullCommand += ` --features ${features.join(',')}`; } + // Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set. + // AppImage builds often fail with minimal error messages from linuxdeploy, + // so verbose mode helps diagnose issues like strip failures and missing dependencies. + if (this.options.targets === 'appimage' && (this.options.debug || process.env.PAKE_VERBOSE)) { + fullCommand += ' --verbose'; + } + return fullCommand; } diff --git a/bin/cli.ts b/bin/cli.ts index 3581570..2a74625 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -99,6 +99,11 @@ program .default(DEFAULT.alwaysOnTop) .hideHelp(), ) + .addOption( + new Option('--maximize', 'Start window maximized') + .default(DEFAULT.maximize) + .hideHelp(), + ) .addOption( new Option('--dark-mode', 'Force Mac app to use dark mode') .default(DEFAULT.darkMode) @@ -164,6 +169,11 @@ program .default(DEFAULT.multiInstance) .hideHelp(), ) + .addOption( + new Option('--start-to-tray', 'Start app minimized to tray') + .default(DEFAULT.startToTray) + .hideHelp(), + ) .addOption( new Option('--installer-language ', 'Installer language') .default(DEFAULT.installerLanguage) diff --git a/bin/defaults.ts b/bin/defaults.ts index a4e9dd0..a78501b 100644 --- a/bin/defaults.ts +++ b/bin/defaults.ts @@ -5,6 +5,7 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = { height: 780, width: 1200, fullscreen: false, + maximize: false, resizable: true, hideTitleBar: false, alwaysOnTop: false, @@ -28,6 +29,7 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = { enableDragDrop: false, keepBinary: false, multiInstance: false, + startToTray: false, }; // Just for cli development diff --git a/bin/helpers/merge.ts b/bin/helpers/merge.ts index e9fb65a..311fd92 100644 --- a/bin/helpers/merge.ts +++ b/bin/helpers/merge.ts @@ -49,6 +49,7 @@ export async function mergeConfig( width, height, fullscreen, + maximize, hideTitleBar, alwaysOnTop, appVersion, @@ -71,6 +72,7 @@ export async function mergeConfig( wasm, enableDragDrop, multiInstance, + startToTray, } = options; const { platform } = process; @@ -81,6 +83,7 @@ export async function mergeConfig( width, height, fullscreen, + maximize, resizable, hide_title_bar: hideTitleBar, activation_shortcut: activationShortcut, @@ -92,6 +95,7 @@ export async function mergeConfig( title: title || null, enable_wasm: wasm, enable_drag_drop: enableDragDrop, + start_to_tray: startToTray && showSystemTray, }; Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions }); diff --git a/bin/helpers/rust.ts b/bin/helpers/rust.ts index 4dfb6ee..dde4d20 100644 --- a/bin/helpers/rust.ts +++ b/bin/helpers/rust.ts @@ -83,7 +83,6 @@ export async function installRust() { IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac, 300000, undefined, - true, ); spinner.succeed(chalk.green('✔ Rust installed successfully!')); ensureRustEnv(); diff --git a/bin/types.ts b/bin/types.ts index 7ed61f7..4559207 100644 --- a/bin/types.ts +++ b/bin/types.ts @@ -24,6 +24,9 @@ export interface PakeCliOptions { // Whether the window can be fullscreen, default false fullscreen: boolean; + // Start window maximized, default false + maximize: boolean; + // Enable immersive header, default false. hideTitleBar: boolean; @@ -90,6 +93,9 @@ export interface PakeCliOptions { // Allow multiple instances, default false (single instance) multiInstance: boolean; + + // Start app minimized to tray, default false + startToTray: boolean; } export interface PakeAppOptions extends PakeCliOptions { diff --git a/bin/utils/shell.ts b/bin/utils/shell.ts index f8d6fcc..7c14980 100644 --- a/bin/utils/shell.ts +++ b/bin/utils/shell.ts @@ -5,12 +5,13 @@ export async function shellExec( command: string, timeout: number = 300000, env?: Record, - showOutput: boolean = false, ) { try { const { exitCode } = await execa(command, { cwd: npmDirectory, - stdio: showOutput ? 'inherit' : ['inherit', 'pipe', 'inherit'], + // Use 'inherit' to show all output directly to user in real-time. + // This ensures linuxdeploy and other tool outputs are visible during builds. + stdio: 'inherit', shell: true, timeout, env: env ? { ...process.env, ...env } : process.env, @@ -28,6 +29,8 @@ export async function shellExec( let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`; + // Provide helpful guidance for common Linux AppImage build failures + // caused by strip tool incompatibility with modern glibc (2.38+) if ( process.platform === 'linux' && (errorMessage.includes('linuxdeploy') || @@ -35,10 +38,18 @@ export async function shellExec( errorMessage.includes('strip')) ) { errorMsg += - '\n\nLinux AppImage build error. Try one of these solutions:\n' + - ' 1. Run with: NO_STRIP=true pake --targets appimage\n' + - ' 2. Use DEB format instead: pake --targets deb\n' + - ' 3. See detailed solutions: https://github.com/tw93/Pake/blob/main/docs/faq.md'; + '\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + + 'Linux AppImage Build Failed\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' + + 'Cause: Strip tool incompatibility with glibc 2.38+\n' + + ' (affects Debian Trixie, Arch Linux, and other modern distros)\n\n' + + 'Quick fix:\n' + + ' NO_STRIP=1 pake --targets appimage --debug\n\n' + + 'Alternatives:\n' + + ' • Use DEB format: pake --targets deb\n' + + ' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' + + ' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; } throw new Error(errorMsg); diff --git a/dist/cli.js b/dist/cli.js index e053014..3be1727 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -23,7 +23,7 @@ import sharp from 'sharp'; import * as psl from 'psl'; var name = "pake-cli"; -var version = "3.4.0"; +var version = "3.4.1"; var description = "🤱🏻 Turn any webpage into a desktop app with one command. 🤱🏻 一键打包网页生成轻量桌面应用。"; var engines = { node: ">=18.0.0" @@ -201,11 +201,13 @@ const IS_MAC = platform$1 === 'darwin'; const IS_WIN = platform$1 === 'win32'; const IS_LINUX = platform$1 === 'linux'; -async function shellExec(command, timeout = 300000, env, showOutput = false) { +async function shellExec(command, timeout = 300000, env) { try { const { exitCode } = await execa(command, { cwd: npmDirectory, - stdio: showOutput ? 'inherit' : ['inherit', 'pipe', 'inherit'], + // Use 'inherit' to show all output directly to user in real-time. + // This ensures linuxdeploy and other tool outputs are visible during builds. + stdio: 'inherit', shell: true, timeout, env: env ? { ...process.env, ...env } : process.env, @@ -219,15 +221,25 @@ async function shellExec(command, timeout = 300000, env, showOutput = false) { throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`); } let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`; + // Provide helpful guidance for common Linux AppImage build failures + // caused by strip tool incompatibility with modern glibc (2.38+) if (process.platform === 'linux' && (errorMessage.includes('linuxdeploy') || errorMessage.includes('appimage') || errorMessage.includes('strip'))) { errorMsg += - '\n\nLinux AppImage build error. Try one of these solutions:\n' + - ' 1. Run with: NO_STRIP=true pake --targets appimage\n' + - ' 2. Use DEB format instead: pake --targets deb\n' + - ' 3. See detailed solutions: https://github.com/tw93/Pake/blob/main/docs/faq.md'; + '\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + + 'Linux AppImage Build Failed\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' + + 'Cause: Strip tool incompatibility with glibc 2.38+\n' + + ' (affects Debian Trixie, Arch Linux, and other modern distros)\n\n' + + 'Quick fix:\n' + + ' NO_STRIP=1 pake --targets appimage --debug\n\n' + + 'Alternatives:\n' + + ' • Use DEB format: pake --targets deb\n' + + ' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' + + ' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' + + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; } throw new Error(errorMsg); } @@ -351,7 +363,7 @@ async function installRust() { const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup'; const spinner = getSpinner('Downloading Rust...'); try { - await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac, 300000, undefined, true); + await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac, 300000, undefined); spinner.succeed(chalk.green('✔ Rust installed successfully!')); ensureRustEnv(); } @@ -448,13 +460,14 @@ async function mergeConfig(url, options, tauriConf) { await fsExtra.copy(sourcePath, destPath); } })); - const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, } = options; + const { width, height, fullscreen, maximize, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, startToTray, } = options; const { platform } = process; const platformHideOnClose = hideOnClose ?? platform === 'darwin'; const tauriConfWindowOptions = { width, height, fullscreen, + maximize, resizable, hide_title_bar: hideTitleBar, activation_shortcut: activationShortcut, @@ -466,6 +479,7 @@ async function mergeConfig(url, options, tauriConf) { title: title || null, enable_wasm: wasm, enable_drag_drop: enableDragDrop, + start_to_tray: startToTray && showSystemTray, }; Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions }); tauriConf.productName = name; @@ -774,10 +788,10 @@ class BaseBuilder { logger.info(`✺ Located in China, using ${packageManager}/rsProxy CN mirror.`); const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml'); await fsExtra.copy(projectCnConf, projectConf); - await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}`, timeout, buildEnv, this.options.debug); + await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}`, timeout, buildEnv); } else { - await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}`, timeout, buildEnv, this.options.debug); + await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}`, timeout, buildEnv); } spinner.succeed(chalk.green('Package installed!')); if (!tauriTargetPathExists) { @@ -806,7 +820,16 @@ class BaseBuilder { ...this.getBuildEnvironment(), ...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }), }; - await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv, this.options.debug); + // Warn users about potential AppImage build failures on modern Linux systems. + // The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't + // recognize the .relr.dyn section introduced in glibc 2.38+. + if (process.platform === 'linux' && this.options.targets === 'appimage') { + if (!buildEnv.NO_STRIP) { + logger.warn('⚠ Building AppImage on Linux may fail due to strip incompatibility with glibc 2.38+'); + logger.warn('⚠ If build fails, retry with: NO_STRIP=1 pake --targets appimage'); + } + } + await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv); // Copy app const fileName = this.getFileName(); const fileType = this.getFileType(target); @@ -865,6 +888,11 @@ class BaseBuilder { if (target) { fullCommand += ` --target ${target}`; } + // Enable verbose output in debug mode to help diagnose build issues. + // This provides detailed logs from Tauri CLI and bundler tools. + if (this.options.debug) { + fullCommand += ' --verbose'; + } return fullCommand; } /** @@ -1178,6 +1206,12 @@ class LinuxBuilder extends BaseBuilder { if (features.length > 0) { fullCommand += ` --features ${features.join(',')}`; } + // Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set. + // AppImage builds often fail with minimal error messages from linuxdeploy, + // so verbose mode helps diagnose issues like strip failures and missing dependencies. + if (this.options.targets === 'appimage' && (this.options.debug || process.env.PAKE_VERBOSE)) { + fullCommand += ' --verbose'; + } return fullCommand; } getBasePath() { @@ -1227,6 +1261,7 @@ const DEFAULT_PAKE_OPTIONS = { height: 780, width: 1200, fullscreen: false, + maximize: false, hideTitleBar: false, alwaysOnTop: false, appVersion: '1.0.0', @@ -1249,6 +1284,7 @@ const DEFAULT_PAKE_OPTIONS = { enableDragDrop: false, keepBinary: false, multiInstance: false, + startToTray: false, }; async function checkUpdateTips() { @@ -1731,6 +1767,9 @@ program .addOption(new Option('--always-on-top', 'Always on the top level') .default(DEFAULT_PAKE_OPTIONS.alwaysOnTop) .hideHelp()) + .addOption(new Option('--maximize', 'Start window maximized') + .default(DEFAULT_PAKE_OPTIONS.maximize) + .hideHelp()) .addOption(new Option('--dark-mode', 'Force Mac app to use dark mode') .default(DEFAULT_PAKE_OPTIONS.darkMode) .hideHelp()) @@ -1774,6 +1813,9 @@ program .addOption(new Option('--multi-instance', 'Allow multiple app instances') .default(DEFAULT_PAKE_OPTIONS.multiInstance) .hideHelp()) + .addOption(new Option('--start-to-tray', 'Start app minimized to tray') + .default(DEFAULT_PAKE_OPTIONS.startToTray) + .hideHelp()) .addOption(new Option('--installer-language ', 'Installer language') .default(DEFAULT_PAKE_OPTIONS.installerLanguage) .hideHelp()) diff --git a/docs/cli-usage.md b/docs/cli-usage.md index ef99a34..63db433 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -155,6 +155,15 @@ screen. --fullscreen ``` +#### [maximize] + +Determine whether the application launches with a maximized window. Default is `false`. Use the following command to enable +maximize. + +```shell +--maximize +``` + #### [activation-shortcut] Set the activation shortcut for the application. Default is empty, so it does not take effect. You can customize the activation shortcut with the following commands, e.g. `CmdOrControl+Shift+P`. Usage can refer to [available-modifiers](https://www.electronjs.org/docs/latest/api/accelerator#available-modifiers). @@ -288,6 +297,19 @@ Hide window instead of closing the application when clicking close button. Platf --hide-on-close false ``` +#### [start-to-tray] + +Start the application minimized to system tray instead of showing the window. Must be used with `--show-system-tray`. Default is `false`. + +```shell +--start-to-tray + +# Example: Start hidden to tray (must use with --show-system-tray) +pake https://github.com --name GitHub --show-system-tray --start-to-tray +``` + +**Note**: Double-click the tray icon to show/hide the window. If used without `--show-system-tray`, this option is ignored. + #### [title] Set the window title bar text. macOS shows no title if not specified; Windows/Linux fallback to app name. diff --git a/docs/cli-usage_CN.md b/docs/cli-usage_CN.md index 591dc64..a140731 100644 --- a/docs/cli-usage_CN.md +++ b/docs/cli-usage_CN.md @@ -154,6 +154,14 @@ pake https://github.com --name GitHub --fullscreen ``` +#### [maximize] + +设置应用程序是否在启动时最大化窗口,默认为 `false`。使用以下命令可以设置应用程序启动时窗口最大化。 + +```shell +--maximize +``` + #### [activation-shortcut] 设置应用程序的激活快捷键。默认为空,不生效,可以使用以下命令自定义激活快捷键,例如 `CmdOrControl+Shift+P`,使用可参考 [available-modifiers](https://www.electronjs.org/docs/latest/api/accelerator#available-modifiers)。 @@ -287,6 +295,19 @@ pake https://github.com --name GitHub --hide-on-close false ``` +#### [start-to-tray] + +启动时将应用程序最小化到系统托盘而不是显示窗口。必须与 `--show-system-tray` 一起使用。默认为 `false`。 + +```shell +--start-to-tray + +# 示例:启动时隐藏到托盘(必须与 --show-system-tray 一起使用) +pake https://github.com --name GitHub --show-system-tray --start-to-tray +``` + +**注意**:双击托盘图标可以显示/隐藏窗口。如果不与 `--show-system-tray` 一起使用,此选项将被忽略。 + #### [title] 设置窗口标题栏文本,macOS 未指定时不显示标题,Windows/Linux 回退使用应用名称。 diff --git a/package.json b/package.json index aac5378..db709ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pake-cli", - "version": "3.4.0", + "version": "3.4.1", "description": "🤱🏻 Turn any webpage into a desktop app with one command. 🤱🏻 一键打包网页生成轻量桌面应用。", "engines": { "node": ">=18.0.0" diff --git a/src-tauri/pake.json b/src-tauri/pake.json index f76f783..97ad31a 100644 --- a/src-tauri/pake.json +++ b/src-tauri/pake.json @@ -15,7 +15,9 @@ "hide_on_close": true, "incognito": false, "enable_wasm": false, - "enable_drag_drop": false + "enable_drag_drop": false, + "maximize": false, + "start_to_tray": false } ], "user_agent": { diff --git a/src-tauri/src/app/config.rs b/src-tauri/src/app/config.rs index 591b9f5..9246b7b 100644 --- a/src-tauri/src/app/config.rs +++ b/src-tauri/src/app/config.rs @@ -5,6 +5,7 @@ pub struct WindowConfig { pub url: String, pub hide_title_bar: bool, pub fullscreen: bool, + pub maximize: bool, pub width: f64, pub height: f64, pub resizable: bool, @@ -18,6 +19,7 @@ pub struct WindowConfig { pub title: Option, pub enable_wasm: bool, pub enable_drag_drop: bool, + pub start_to_tray: bool, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index a66a427..7a01a76 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -3,13 +3,13 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tauri::{ menu::{MenuBuilder, MenuItemBuilder}, - tray::TrayIconBuilder, + tray::{TrayIconBuilder, TrayIconEvent}, AppHandle, Manager, }; use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut}; use tauri_plugin_window_state::{AppHandleExt, StateFlags}; -pub fn set_system_tray(app: &AppHandle, show_system_tray: bool) -> tauri::Result<()> { +pub fn set_system_tray(app: &AppHandle, show_system_tray: bool, tray_icon_path: &str) -> tauri::Result<()> { if !show_system_tray { app.remove_tray_by_id("pake-tray"); return Ok(()); @@ -44,7 +44,30 @@ pub fn set_system_tray(app: &AppHandle, show_system_tray: bool) -> tauri::Result } _ => (), }) - .icon(app.default_window_icon().unwrap().clone()) + .on_tray_icon_event(|tray, event| match event { + TrayIconEvent::Click { button, .. } => { + if button == tauri::tray::MouseButton::Left { + if let Some(window) = tray.app_handle().get_webview_window("pake") { + let is_visible = window.is_visible().unwrap_or(false); + if is_visible { + window.hide().unwrap(); + } else { + window.show().unwrap(); + window.set_focus().unwrap(); + } + } + } + } + _ => {} + }) + .icon(if tray_icon_path.is_empty() { + app.default_window_icon().unwrap_or_else(|| panic!("Failed to get default window icon")).clone() + } else { + tauri::image::Image::from_path(tray_icon_path).unwrap_or_else(|_| { + // If custom tray icon fails to load, fallback to default + app.default_window_icon().unwrap_or_else(|| panic!("Failed to get default window icon")).clone() + }) + }) .build(app)?; tray.set_icon_as_template(false)?; diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 2aca7b9..9933b17 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -43,6 +43,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) -> .user_agent(user_agent) .resizable(window_config.resizable) .fullscreen(window_config.fullscreen) + .maximized(window_config.maximize) .inner_size(window_config.width, window_config.height) .always_on_top(window_config.always_on_top) .incognito(window_config.incognito); @@ -65,14 +66,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) -> .additional_browser_args("--enable-unsafe-webgpu"); } - if !config.proxy_url.is_empty() { - if let Ok(proxy_url) = Url::from_str(&config.proxy_url) { - window_builder = window_builder.proxy_url(proxy_url); - #[cfg(debug_assertions)] - println!("Proxy configured: {}", config.proxy_url); - } - } - + // Platform-specific configuration must be set before proxy on Windows/Linux #[cfg(target_os = "macos")] { let title_bar_style = if window_config.hide_title_bar { @@ -87,7 +81,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) -> } } - // Windows and Linux share the same configuration + // Windows and Linux: set data_directory before proxy_url #[cfg(not(target_os = "macos"))] { window_builder = window_builder @@ -96,5 +90,14 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) -> .theme(None); } + // Set proxy after platform-specific configs (required for Windows/Linux) + if !config.proxy_url.is_empty() { + if let Ok(proxy_url) = Url::from_str(&config.proxy_url) { + window_builder = window_builder.proxy_url(proxy_url); + #[cfg(debug_assertions)] + println!("Proxy configured: {}", config.proxy_url); + } + } + window_builder.build().expect("Failed to build window") } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4b43c9a..6e7b6fc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -24,6 +24,7 @@ pub fn run_app() { let hide_on_close = pake_config.windows[0].hide_on_close; let activation_shortcut = pake_config.windows[0].activation_shortcut.clone(); let init_fullscreen = pake_config.windows[0].fullscreen; + let start_to_tray = pake_config.windows[0].start_to_tray && show_system_tray; // Only valid when tray is enabled let multi_instance = pake_config.multi_instance; let window_state_plugin = WindowStatePlugin::default() @@ -62,15 +63,18 @@ pub fn run_app() { ]) .setup(move |app| { let window = set_window(app, &pake_config, &tauri_config); - set_system_tray(app.app_handle(), show_system_tray).unwrap(); + set_system_tray(app.app_handle(), show_system_tray, &pake_config.system_tray_path).unwrap(); set_global_shortcut(app.app_handle(), activation_shortcut).unwrap(); // Show window after state restoration to prevent position flashing - let window_clone = window.clone(); - tauri::async_runtime::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; - window_clone.show().unwrap(); - }); + // Unless start_to_tray is enabled, then keep it hidden + if !start_to_tray { + let window_clone = window.clone(); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + window_clone.show().unwrap(); + }); + } Ok(()) })