Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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(推薦)
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ scripts/cache/
!.agents/skills/test-smoke/**
!.agents/skills/test-e2e/
!.agents/skills/test-e2e/**
scripts/**
debug.log
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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"] }
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion docs/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** で入力欄全体を選択できます。
Expand Down
5 changes: 4 additions & 1 deletion docs/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**로 입력 칸 전체를 선택할 수 있습니다.
Expand Down
5 changes: 4 additions & 1 deletion docs/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** 可以选中整个输入框。
Expand Down
8 changes: 7 additions & 1 deletion docs/README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**(或設定開機自動啟動)
Expand Down
3 changes: 2 additions & 1 deletion docs/en.html
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ <h2>How It Works</h2>
<h2>What's Included</h2>
<div class="grid">
<div class="card">
<h3>System Tray Hotkey</h3>
<h3>System Tray Hotkey <small style="font-weight:normal;opacity:.6;">Windows only</small></h3>
<p>migao-watch runs silently in the background — no terminal needed. Works in any app: LINE, browser, Word, VS Code.</p>
<p style="font-size:.85em;opacity:.65;margin-top:.5em;">macOS / Linux support is not yet available. Contributions welcome — <a href="https://github.com/winterdrive/migao/issues" target="_blank">open an Issue</a> to discuss.</p>
</div>
<div class="card">
<h3>CLI Tool</h3>
Expand Down
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ <h2>Windows 日常使用流程</h2>
<h2>包含什麼</h2>
<div class="grid">
<div class="card">
<h3>系統匣熱鍵</h3>
<h3>系統匣熱鍵 <small style="font-weight:normal;opacity:.6;">Windows only</small></h3>
<p>migao-watch 常駐背景,不需要開終端機,在任何應用程式(LINE、瀏覽器、Word)都有效。</p>
<p style="font-size:.85em;opacity:.65;margin-top:.5em;">macOS / Linux 歡迎貢獻——<a href="https://github.com/winterdrive/migao/issues" target="_blank">Issues</a> 討論或認領。</p>
</div>
<div class="card">
<h3>CLI 工具</h3>
Expand Down
131 changes: 131 additions & 0 deletions examples/ime_test.rs
Original file line number Diff line number Diff line change
@@ -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<DWORD_PTR> {
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::<INPUT>() 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");
}
20 changes: 15 additions & 5 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
Loading
Loading