diff --git a/bin/cli.ts b/bin/cli.ts index 4978850..9522005 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -160,6 +160,11 @@ program .default(DEFAULT.keepBinary) .hideHelp(), ) + .addOption( + new Option('--multi-instance', 'Allow multiple app instances') + .default(DEFAULT.multiInstance) + .hideHelp(), + ) .addOption( new Option('--installer-language ', 'Installer language') .default(DEFAULT.installerLanguage) diff --git a/bin/defaults.ts b/bin/defaults.ts index caa7b2e..a4e9dd0 100644 --- a/bin/defaults.ts +++ b/bin/defaults.ts @@ -27,6 +27,7 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = { wasm: false, enableDragDrop: false, keepBinary: false, + multiInstance: false, }; // Just for cli development diff --git a/bin/helpers/merge.ts b/bin/helpers/merge.ts index 7c3da7a..badf5ae 100644 --- a/bin/helpers/merge.ts +++ b/bin/helpers/merge.ts @@ -63,6 +63,7 @@ export async function mergeConfig( title, wasm, enableDragDrop, + multiInstance, } = options; const { platform } = process; @@ -325,6 +326,7 @@ StartupNotify=true await fsExtra.writeFile(injectFilePath, ''); } tauriConf.pake.proxy_url = proxyUrl || ''; + tauriConf.pake.multi_instance = multiInstance; // Configure WASM support with required HTTP headers if (wasm) { diff --git a/bin/types.ts b/bin/types.ts index 9241a31..7ed61f7 100644 --- a/bin/types.ts +++ b/bin/types.ts @@ -87,6 +87,9 @@ export interface PakeCliOptions { // Keep raw binary file alongside installer, default false keepBinary: boolean; + + // Allow multiple instances, default false (single instance) + multiInstance: boolean; } export interface PakeAppOptions extends PakeCliOptions { diff --git a/dist/cli.js b/dist/cli.js index ea17329..21b9ce9 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -343,26 +343,21 @@ function generateLinuxPackageName(name) { .replace(/-+/g, '-'); } function generateIdentifierSafeName(name) { - // Support Chinese characters (CJK Unified Ideographs: U+4E00-U+9FFF) - // and other common Unicode letter categories - const cleaned = name - .replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '') - .toLowerCase(); - // If result is empty after cleaning, generate a fallback based on original name + const cleaned = name.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '').toLowerCase(); if (cleaned === '') { - // Convert to ASCII-safe representation using character codes const fallback = Array.from(name) - .map(char => { + .map((char) => { const code = char.charCodeAt(0); - // Keep ASCII alphanumeric, convert others to their hex codes - if ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122)) { + if ((code >= 48 && code <= 57) || + (code >= 65 && code <= 90) || + (code >= 97 && code <= 122)) { return char.toLowerCase(); } return code.toString(16); }) .join('') - .slice(0, 50); // Limit length to avoid extremely long names - return fallback || 'pake-app'; // Ultimate fallback + .slice(0, 50); + return fallback || 'pake-app'; } return cleaned; } @@ -387,7 +382,7 @@ 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, } = options; + 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 { platform } = process; const platformHideOnClose = hideOnClose ?? platform === 'darwin'; const tauriConfWindowOptions = { @@ -602,6 +597,7 @@ StartupNotify=true await fsExtra.writeFile(injectFilePath, ''); } tauriConf.pake.proxy_url = proxyUrl || ''; + tauriConf.pake.multi_instance = multiInstance; // Configure WASM support with required HTTP headers if (wasm) { tauriConf.app.security = { @@ -1181,6 +1177,7 @@ const DEFAULT_PAKE_OPTIONS = { wasm: false, enableDragDrop: false, keepBinary: false, + multiInstance: false, }; async function checkUpdateTips() { @@ -1704,6 +1701,9 @@ program .addOption(new Option('--keep-binary', 'Keep raw binary file alongside installer') .default(DEFAULT_PAKE_OPTIONS.keepBinary) .hideHelp()) + .addOption(new Option('--multi-instance', 'Allow multiple app instances') + .default(DEFAULT_PAKE_OPTIONS.multiInstance) + .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 3462536..da90e25 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -350,6 +350,17 @@ pake https://github.com --name GitHub --keep-binary **Output**: Creates both installer and standalone executable (`AppName-binary` on Unix, `AppName.exe` on Windows). +#### [multi-instance] + +Allow the packaged app to run more than one instance at the same time. Default is `false`, which means launching a second instance simply focuses the existing window. Enable this when you need to open several windows of the same app simultaneously. + +```shell +--multi-instance + +# Example: Allow multiple chat windows +pake https://chat.example.com --name ChatApp --multi-instance +``` + #### [installer-language] Set the Windows Installer language. Options include `zh-CN`, `ja-JP`, More at [Tauri Document](https://tauri.app/distribute/windows-installer/#internationalization). Default is `en-US`. diff --git a/docs/cli-usage_CN.md b/docs/cli-usage_CN.md index b1005d3..a3f7fe0 100644 --- a/docs/cli-usage_CN.md +++ b/docs/cli-usage_CN.md @@ -349,6 +349,17 @@ pake https://github.com --name GitHub --keep-binary **输出结果**:同时创建安装包和独立可执行文件(Unix 系统为 `AppName-binary`,Windows 为 `AppName.exe`)。 +#### [multi-instance] + +允许打包后的应用同时运行多个实例。默认为 `false`,此时再次启动只会聚焦已有窗口。启用该选项后,可以同时打开同一个应用的多个窗口。 + +```shell +--multi-instance + +# 示例:允许聊天应用同时开多个窗口 +pake https://chat.example.com --name ChatApp --multi-instance +``` + #### [installer-language] 设置 Windows 安装包语言。支持 `zh-CN`、`ja-JP`,更多在 [Tauri 文档](https://tauri.app/distribute/windows-installer/#internationalization)。默认为 `en-US`。 diff --git a/src-tauri/pake.json b/src-tauri/pake.json index 756d359..f76f783 100644 --- a/src-tauri/pake.json +++ b/src-tauri/pake.json @@ -30,5 +30,6 @@ }, "system_tray_path": "icons/icon.png", "inject": [], - "proxy_url": "" + "proxy_url": "", + "multi_instance": false } diff --git a/src-tauri/src/app/config.rs b/src-tauri/src/app/config.rs index b04ffa7..591b9f5 100644 --- a/src-tauri/src/app/config.rs +++ b/src-tauri/src/app/config.rs @@ -59,6 +59,8 @@ pub struct PakeConfig { pub system_tray: FunctionON, pub system_tray_path: String, pub proxy_url: String, + #[serde(default)] + pub multi_instance: bool, } impl PakeConfig { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 54b1be6..4b43c9a 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 multi_instance = pake_config.multi_instance; let window_state_plugin = WindowStatePlugin::default() .with_state_flags(if init_fullscreen { @@ -35,19 +36,25 @@ pub fn run_app() { .build(); #[allow(deprecated)] - tauri_app + let mut app_builder = tauri_app .plugin(window_state_plugin) .plugin(tauri_plugin_oauth::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_notification::init()) - .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + .plugin(tauri_plugin_notification::init()); + + // Only add single instance plugin if multiple instances are not allowed + if !multi_instance { + app_builder = app_builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { if let Some(window) = app.get_webview_window("pake") { let _ = window.unminimize(); let _ = window.show(); let _ = window.set_focus(); } - })) + })); + } + + app_builder .invoke_handler(tauri::generate_handler![ download_file, download_file_by_binary,