diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c2ba0d..75cc9c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,6 +87,17 @@ jobs: with: generate_release_notes: false body: | + ## 平台支援 + + | | Windows | macOS | Linux | + |---|:---:|:---:|:---:| + | `migao fix` CLI | ✅ | ✅ | ✅ | + | `migao-watch` 快捷鍵常駐 | ✅ | — | — | + + macOS / Linux 下載包僅含 `migao` CLI;`migao-watch` 為 Windows 專屬功能。 + + --- + ## 安裝方式 ### Windows(推薦) diff --git a/.gitignore b/.gitignore index 9d230cd..4ba6dc6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ scripts/cache/ !.agents/skills/test-smoke/** !.agents/skills/test-e2e/ !.agents/skills/test-e2e/** +scripts/** +debug.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 5288f88..5bb7e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.1] - 2026-06-11 + +- **Fixed:** After correcting `bopomofo-daqian` garbled text, `migao-watch` now switches the foreground window's input method back to Chinese mode automatically. Uses `ImmGetDefaultIMEWnd` + `WM_IME_CONTROL` (cross-process safe), with `WM_INPUTLANGCHANGEREQUEST` as a fallback for keyboard-layout switching. Resolves [#1](https://github.com/winterdrive/migao/issues/1). +- **Added:** Tray right-click menu now shows the installed version (`migao vX.Y.Z`) as a grayed-out header item. +- **Added:** `install.ps1` now accepts a `-Prerelease` switch to install the latest pre-release build for testing. + ## [0.5.0] - 2026-06-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 4c41173..19dd062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "migao" -version = "0.5.0" +version = "0.5.1" edition = "2021" description = "IME garbled text recovery — 翻譯米糕" license = "MIT AND Apache-2.0" @@ -34,7 +34,7 @@ clap = { version = "4", features = ["derive"] } arboard = "3" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "wingdi", "winbase", "winnt", "synchapi", "consoleapi", "wincon", "processthreadsapi"] } +winapi = { version = "0.3", features = ["winuser", "wingdi", "winbase", "winnt", "synchapi", "consoleapi", "wincon", "processthreadsapi", "imm"] } tray-icon = { version = "0.24.0", default-features = false } winreg = "0.56.0" image = { version = "0.25", default-features = false, features = ["png"] } diff --git a/README.md b/README.md index c8af328..cac76e2 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,16 @@ cargo build --release --- -## Windows Daily Use — Migao Watch +## Windows Daily Use — Migao Watch *(Windows only)* `migao-watch` runs silently in the system tray and fixes garbled text anywhere on Windows with a single hotkey. +> **macOS / Linux:** `migao-watch` is not yet available on non-Windows platforms. Contributions are welcome — open an [Issue](https://github.com/winterdrive/migao/issues) to discuss or claim the work. +> In the meantime, two alternatives exist: +> +> - **CLI pipe:** `pbpaste | migao fix | pbcopy` (macOS) or `xclip -o | migao fix | xclip -i` (Linux) +> - **AI agent:** IDEs with skill support (Claude Code, Cursor, etc.) can call `migao fix` directly — see [`skills/migao/SKILL.md`](skills/migao/SKILL.md) + ### Fix selected text in-place 1. Launch **Migao Watch** from Start Menu, or let it start at login. diff --git a/docs/README.ja.md b/docs/README.ja.md index c228f2f..effd0a9 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -43,7 +43,10 @@ Uninstall: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Uninstall ``` -## Windows Daily Use +## Windows Daily Use *(Windows only)* + +`migao-watch` is currently Windows-only. macOS / Linux contributions are welcome — open an [Issue](https://github.com/winterdrive/migao/issues) to discuss. +Alternatives: **CLI pipe** `pbpaste | migao fix | pbcopy` (macOS) / `xclip -o | migao fix | xclip -i` (Linux); **AI agent** see [`skills/migao/SKILL.md`](../skills/migao/SKILL.md) 1. Start Menu から **Migao Watch** を起動します。自動起動を有効にしている場合は不要です。 2. 入力方式を切り替え忘れて文字化けしたテキストを選択します。多くのエディタでは **Ctrl+A** で入力欄全体を選択できます。 diff --git a/docs/README.ko.md b/docs/README.ko.md index 84eca93..485bd83 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -43,7 +43,10 @@ Uninstall: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Uninstall ``` -## Windows Daily Use +## Windows Daily Use *(Windows only)* + +`migao-watch` is currently Windows-only. macOS / Linux contributions are welcome — open an [Issue](https://github.com/winterdrive/migao/issues) to discuss. +Alternatives: **CLI pipe** `pbpaste | migao fix | pbcopy` (macOS) / `xclip -o | migao fix | xclip -i` (Linux); **AI agent** see [`skills/migao/SKILL.md`](../skills/migao/SKILL.md) 1. Start Menu에서 **Migao Watch**를 실행합니다. 자동 실행을 켜 둔 경우에는 따로 실행할 필요가 없습니다. 2. 입력기를 바꾸지 않아 잘못 입력된 텍스트를 선택합니다. 많은 에디터에서는 **Ctrl+A**로 입력 칸 전체를 선택할 수 있습니다. diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 30e74d7..6135efb 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -41,7 +41,10 @@ irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1 | iex & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Uninstall ``` -## Windows 日常使用 +## Windows 日常使用 *(仅限 Windows)* + +`migao-watch` 目前仅支援 Windows。macOS / Linux 欢迎贡献——请至 [Issues](https://github.com/winterdrive/migao/issues) 讨论或认领。 +目前有两种替代方案:**CLI pipe:** `pbpaste | migao fix | pbcopy`(macOS)或 `xclip -o | migao fix | xclip -i`(Linux);**AI agent:** 见 [`skills/migao/SKILL.md`](../skills/migao/SKILL.md) 1. 从开始菜单启动 **Migao Watch**,或让它开机自动启动。 2. 照常打字。如果忘记切换输入法而产生乱码,选中它;在很多编辑器里,**Ctrl+A** 可以选中整个输入框。 diff --git a/docs/README.zh-TW.md b/docs/README.zh-TW.md index 4a03140..2372175 100644 --- a/docs/README.zh-TW.md +++ b/docs/README.zh-TW.md @@ -59,10 +59,16 @@ cargo build --release --- -## Windows 常駐程式 — `migao-watch` +## Windows 常駐程式 — `migao-watch` *(僅限 Windows)* `migao-watch` 靜默執行於系統匣,在任何 Windows 應用程式中,按一個快捷鍵即可就地修正亂碼。 +> **macOS / Linux:** `migao-watch` 目前僅支援 Windows,歡迎貢獻其他平台版本——請至 [Issues](https://github.com/winterdrive/migao/issues) 討論或認領。 +> 目前有兩種替代方案: +> +> - **CLI pipe:** `pbpaste | migao fix | pbcopy`(macOS)或 `xclip -o | migao fix | xclip -i`(Linux) +> - **AI agent:** 支援 skill 的 IDE(Claude Code、Cursor 等)可直接呼叫 `migao fix`——見 [`skills/migao/SKILL.md`](../skills/migao/SKILL.md) + ### 使用流程 1. 從開始選單啟動 **Migao Watch**(或設定開機自動啟動) diff --git a/docs/en.html b/docs/en.html index 873680f..aae948c 100644 --- a/docs/en.html +++ b/docs/en.html @@ -211,8 +211,9 @@

How It Works

What's Included

-

System Tray Hotkey

+

System Tray Hotkey Windows only

migao-watch runs silently in the background — no terminal needed. Works in any app: LINE, browser, Word, VS Code.

+

macOS / Linux support is not yet available. Contributions welcome — open an Issue to discuss.

CLI Tool

diff --git a/docs/index.html b/docs/index.html index e4de98c..104665c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -211,8 +211,9 @@

Windows 日常使用流程

包含什麼

-

系統匣熱鍵

+

系統匣熱鍵 Windows only

migao-watch 常駐背景,不需要開終端機,在任何應用程式(LINE、瀏覽器、Word)都有效。

+

macOS / Linux 歡迎貢獻——Issues 討論或認領。

CLI 工具

diff --git a/examples/ime_test.rs b/examples/ime_test.rs new file mode 100644 index 0000000..23b2069 --- /dev/null +++ b/examples/ime_test.rs @@ -0,0 +1,131 @@ +//! One-shot probe: can we switch Microsoft Bopomofo from English mode back to +//! Chinese mode from another process? +//! +//! Steps it performs automatically after a 5-second countdown: +//! 1. read conversion mode via WM_IME_CONTROL (ImmGetDefaultIMEWnd) +//! 2. try to set Chinese mode via IMC_SETCONVERSIONMODE / IMC_SETOPENSTATUS +//! 3. read back; if unchanged, inject a single Shift as fallback +//! +//! User procedure (one action only): +//! open Notepad, switch Bopomofo to 英 (English mode), run +//! `cargo run --example ime_test`, click into Notepad, wait. +//! Then report whether the indicator ended up 中 or 英, plus this output. + +#[cfg(windows)] +fn main() { + use std::io::Write as _; + use std::{mem, thread, time::Duration}; + use winapi::shared::basetsd::DWORD_PTR; + use winapi::shared::minwindef::{LPARAM, WPARAM}; + use winapi::shared::windef::HWND; + use winapi::um::winuser::{ + GetClassNameW, GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId, SendInput, + SendMessageTimeoutW, INPUT, INPUT_KEYBOARD, KEYEVENTF_KEYUP, SMTO_ABORTIFHUNG, + }; + + #[link(name = "imm32")] + extern "system" { + fn ImmGetDefaultIMEWnd(hwnd: HWND) -> HWND; + } + + const WM_IME_CONTROL: u32 = 0x0283; + const IMC_GETCONVERSIONMODE: WPARAM = 0x0001; + const IMC_SETCONVERSIONMODE: WPARAM = 0x0002; + const IMC_GETOPENSTATUS: WPARAM = 0x0005; + const IMC_SETOPENSTATUS: WPARAM = 0x0006; + const IME_CMODE_NATIVE: DWORD_PTR = 0x0001; + const VK_SHIFT: u16 = 0x10; + + unsafe fn ime_msg(ime_wnd: HWND, cmd: WPARAM, lparam: LPARAM) -> Option { + let mut result: DWORD_PTR = 0; + let ok = SendMessageTimeoutW( + ime_wnd, + WM_IME_CONTROL, + cmd, + lparam, + SMTO_ABORTIFHUNG, + 500, + &mut result, + ); + if ok == 0 { + None + } else { + Some(result) + } + } + + unsafe fn inject_shift() { + let mut inputs: [INPUT; 2] = mem::zeroed(); + for (i, input) in inputs.iter_mut().enumerate() { + input.type_ = INPUT_KEYBOARD; + let ki = input.u.ki_mut(); + ki.wVk = VK_SHIFT; + ki.dwFlags = if i == 0 { 0 } else { KEYEVENTF_KEYUP }; + } + SendInput(2, inputs.as_mut_ptr(), mem::size_of::() as i32); + } + + println!("請確認:記事本已開啟,微軟注音在「英」模式。"); + println!("倒數 5 秒內請點進記事本視窗,之後不用做任何事。"); + for i in (1..=5u64).rev() { + print!(" {i}..."); + std::io::stdout().flush().ok(); + thread::sleep(Duration::from_secs(1)); + } + println!(); + + unsafe { + let hwnd = GetForegroundWindow(); + let mut buf = [0u16; 256]; + let len = GetClassNameW(hwnd, buf.as_mut_ptr(), buf.len() as i32); + let class = String::from_utf16_lossy(&buf[..len.max(0) as usize]); + let tid = GetWindowThreadProcessId(hwnd, std::ptr::null_mut()); + let hkl = GetKeyboardLayout(tid) as usize; + let ime_wnd = ImmGetDefaultIMEWnd(hwnd); + println!("前景視窗 class={class:?} HKL={hkl:#010x} ime_wnd={ime_wnd:?}"); + + let conv_before = ime_msg(ime_wnd, IMC_GETCONVERSIONMODE, 0); + let open_before = ime_msg(ime_wnd, IMC_GETOPENSTATUS, 0); + println!("讀取: conv={conv_before:?} open={open_before:?}"); + + let conv = conv_before.unwrap_or(0); + let r1 = ime_msg( + ime_wnd, + IMC_SETCONVERSIONMODE, + (conv | IME_CMODE_NATIVE) as LPARAM, + ); + let r2 = ime_msg(ime_wnd, IMC_SETOPENSTATUS, 1); + println!("設定: SETCONVERSIONMODE={r1:?} SETOPENSTATUS={r2:?}"); + + thread::sleep(Duration::from_millis(500)); + let conv_after = ime_msg(ime_wnd, IMC_GETCONVERSIONMODE, 0); + let open_after = ime_msg(ime_wnd, IMC_GETOPENSTATUS, 0); + println!("讀回: conv={conv_after:?} open={open_after:?}"); + + let message_method_worked = match (conv_before, conv_after) { + (Some(b), Some(a)) => (b & IME_CMODE_NATIVE) == 0 && (a & IME_CMODE_NATIVE) != 0, + _ => false, + }; + + if message_method_worked { + println!("結果: 訊息法看起來有效,未注入 Shift。"); + } else { + println!("結果: 訊息法無效或讀不到,3 秒後注入 Shift 作為備案..."); + thread::sleep(Duration::from_secs(3)); + inject_shift(); + thread::sleep(Duration::from_millis(500)); + let conv_final = ime_msg(ime_wnd, IMC_GETCONVERSIONMODE, 0); + println!("Shift 注入後讀回: conv={conv_final:?}"); + } + } + + println!(); + println!(">>> 現在請看右下角語言列:是「中」還是「英」? <<<"); + println!("把這個答案+上面全部輸出貼回給我。15 秒後自動關閉..."); + thread::sleep(Duration::from_secs(15)); +} + +#[cfg(not(windows))] +fn main() { + eprintln!("Windows only"); +} diff --git a/install.ps1 b/install.ps1 index ac5b704..b6c09ac 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,11 +1,12 @@ #!/usr/bin/env pwsh # Migao Windows Installer / Uninstaller # -# Install: irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1 | iex -# Uninstall: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Uninstall -# (or: .\install.ps1 -Uninstall) +# Install (latest): irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1 | iex +# Install (pre-release): & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Prerelease +# Uninstall: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/winterdrive/migao/main/install.ps1))) -Uninstall +# (or: .\install.ps1 -Uninstall) -param([switch]$Uninstall) +param([switch]$Uninstall, [switch]$Prerelease) $ErrorActionPreference = "Stop" $repo = "winterdrive/migao" @@ -127,7 +128,16 @@ Stop-MigaoWatch $previousProgressPreference = $ProgressPreference $ProgressPreference = "SilentlyContinue" try { - $release = Invoke-RestMethod "https://api.github.com/repos/$repo/releases/latest" + if ($Prerelease) { + $releases = Invoke-RestMethod "https://api.github.com/repos/$repo/releases" + $release = $releases | Where-Object { $_.prerelease -eq $true } | Select-Object -First 1 + if (-not $release) { + Write-Error "No pre-release found. Check https://github.com/$repo/releases" + exit 1 + } + } else { + $release = Invoke-RestMethod "https://api.github.com/repos/$repo/releases/latest" + } } finally { $ProgressPreference = $previousProgressPreference } diff --git a/src/bin/watch.rs b/src/bin/watch.rs index a907259..7fef3cb 100644 --- a/src/bin/watch.rs +++ b/src/bin/watch.rs @@ -19,18 +19,25 @@ mod win { use tray_icon::menu::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem}; use tray_icon::{TrayIcon, TrayIconBuilder}; use winapi::shared::minwindef::TRUE; + use winapi::shared::windef::HWND; use winapi::um::consoleapi::SetConsoleCtrlHandler; use winapi::um::processthreadsapi::GetCurrentThreadId; use winapi::um::wincon::{CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT}; use winapi::um::winuser::{ - DispatchMessageW, GetClassNameW, GetForegroundWindow, GetMessageW, KillTimer, - PostThreadMessageW, RegisterHotKey, SendInput, SetTimer, UnregisterHotKey, INPUT, - KEYBDINPUT, KEYEVENTF_KEYUP, MOD_ALT, MOD_CONTROL, MSG, WM_APP, WM_HOTKEY, WM_QUIT, - WM_TIMER, + DispatchMessageW, GetAsyncKeyState, GetClassNameW, GetForegroundWindow, GetKeyboardLayout, + GetKeyboardLayoutList, GetMessageW, GetWindowThreadProcessId, KillTimer, PostMessageW, + PostThreadMessageW, RegisterHotKey, SendInput, SendMessageTimeoutW, SetTimer, + UnregisterHotKey, INPUT, KEYBDINPUT, KEYEVENTF_KEYUP, MOD_ALT, MOD_CONTROL, MSG, + SMTO_ABORTIFHUNG, WM_APP, WM_HOTKEY, WM_INPUTLANGCHANGEREQUEST, WM_QUIT, WM_TIMER, }; use winreg::enums::{HKEY_CURRENT_USER, KEY_READ, KEY_SET_VALUE}; use winreg::RegKey; + #[link(name = "imm32")] + extern "system" { + fn ImmGetDefaultIMEWnd(hwnd: HWND) -> HWND; + } + const HOTKEY_ID: i32 = 1; const VK_R: u32 = 0x52; const VK_CTRL: u16 = 0x11; @@ -41,6 +48,15 @@ mod win { const VK_Z: u16 = 0x5A; const CYCLE_WINDOW: Duration = Duration::from_secs(3); + const VK_SHIFT: u16 = 0x10; + + // IME mode switching after a correction (issue #1). + const LANGID_ZH_TW: usize = 0x0404; + const WM_IME_CONTROL: u32 = 0x0283; + const IMC_GETCONVERSIONMODE: usize = 0x0001; + const IMC_SETCONVERSIONMODE: usize = 0x0002; + const IMC_SETOPENSTATUS: usize = 0x0006; + const IME_CMODE_NATIVE: usize = 0x0001; static MAIN_THREAD_ID: AtomicU32 = AtomicU32::new(0); static PAUSED: AtomicBool = AtomicBool::new(false); @@ -224,6 +240,99 @@ mod win { } } + /// Sends WM_IME_CONTROL to a default IME window with a timeout so an + /// unresponsive target can never hang the worker thread. + fn ime_msg(ime_wnd: HWND, cmd: usize, lparam: isize) -> Option { + let mut result: usize = 0; + let ok = unsafe { + SendMessageTimeoutW( + ime_wnd, + WM_IME_CONTROL, + cmd, + lparam, + SMTO_ABORTIFHUNG, + 500, + &mut result, + ) + }; + if ok == 0 { + None + } else { + Some(result) + } + } + + /// Best-effort switch of the foreground window's input method back to + /// Chinese after a successful Bopomofo correction (issue #1). + /// + /// Layer 1: keyboard layout is not Chinese (user typed with ENG active) + /// → WM_INPUTLANGCHANGEREQUEST, the same mechanism as Win+Space. + /// Layer 2: MS Bopomofo active but in English mode → WM_IME_CONTROL on the + /// default IME window. The message is processed inside the target + /// process, so it works where ImmGetContext (process-local) cannot. + /// Layer 3: fallback blind Shift toggle. Safe direction: a correction just + /// succeeded, so the IME must have been in English mode. Waits for + /// the user to physically release Ctrl/Alt/R first — otherwise the + /// IME sees Ctrl+Alt+Shift and ignores the toggle. + fn switch_ime_to_chinese() { + unsafe { + let hwnd = GetForegroundWindow(); + if hwnd.is_null() { + return; + } + + // Layer 1: wrong keyboard layout entirely. + let tid = GetWindowThreadProcessId(hwnd, std::ptr::null_mut()); + if (GetKeyboardLayout(tid) as usize) & 0xFFFF != LANGID_ZH_TW { + let n = GetKeyboardLayoutList(0, std::ptr::null_mut()); + let mut layouts = vec![std::ptr::null_mut(); n.max(0) as usize]; + let n = GetKeyboardLayoutList(n, layouts.as_mut_ptr()); + if let Some(&hkl) = layouts[..n.max(0) as usize] + .iter() + .find(|&&h| (h as usize) & 0xFFFF == LANGID_ZH_TW) + { + PostMessageW(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, hkl as isize); + } + return; + } + + // Layer 2: Chinese IME active but in English mode. + let ime_wnd = ImmGetDefaultIMEWnd(hwnd); + if !ime_wnd.is_null() { + if let Some(conv) = ime_msg(ime_wnd, IMC_GETCONVERSIONMODE, 0) { + if conv & IME_CMODE_NATIVE != 0 { + return; // already in Chinese mode + } + ime_msg( + ime_wnd, + IMC_SETCONVERSIONMODE, + (conv | IME_CMODE_NATIVE) as isize, + ); + ime_msg(ime_wnd, IMC_SETOPENSTATUS, 1); + if let Some(after) = ime_msg(ime_wnd, IMC_GETCONVERSIONMODE, 0) { + if after & IME_CMODE_NATIVE != 0 { + return; // verified switched + } + } + } + } + + // Layer 3: blind Shift toggle, only after the hotkey keys are + // physically released. + let deadline = Instant::now() + Duration::from_millis(1500); + while [VK_CTRL as i32, VK_ALT as i32, VK_R as i32] + .iter() + .any(|&vk| GetAsyncKeyState(vk) as u16 & 0x8000 != 0) + { + if Instant::now() >= deadline { + return; // keys still held; give up rather than mis-toggle + } + thread::sleep(Duration::from_millis(20)); + } + batch(&mut vec![ki(VK_SHIFT, 0), ki(VK_SHIFT, KEYEVENTF_KEYUP)]); + } + } + fn paste(clipboard: &mut arboard::Clipboard, text: &str) -> bool { if clipboard.set_text(text).is_err() { return false; @@ -347,6 +456,9 @@ mod win { ); if paste(clipboard, &candidates[0]) { + if ime == "bopomofo-daqian" { + switch_ime_to_chinese(); + } let orig = truncate(&text, 25); let fixed = truncate(&candidates[0], 25); let summary = format!("{orig} → {fixed}"); @@ -395,6 +507,9 @@ mod win { } // ── Tray icon ──────────────────────────────────────────────────────── + let version_item = + MenuItem::new(format!("migao v{}", env!("CARGO_PKG_VERSION")), false, None); + let sep_top = PredefinedMenuItem::separator(); let pause_item = MenuItem::new("Pause", true, None); let login_item = CheckMenuItem::new("Launch at Login", true, is_autostart_enabled(), None); let update_item = MenuItem::new("Check for Updates", true, None); @@ -403,6 +518,8 @@ mod win { let sep = PredefinedMenuItem::separator(); let menu = Menu::new(); menu.append_items(&[ + &version_item, + &sep_top, &pause_item, &login_item, &update_item,