Skip to content

fix(PIMETextService): support Per-Monitor High DPI font scaling (#960) - #962

Open
PCMan wants to merge 1 commit into
masterfrom
fix/issue-960-per-monitor-dpi
Open

PCMan wants to merge 1 commit into
masterfrom
fix/issue-960-per-monitor-dpi

Conversation

@PCMan

@PCMan PCMan commented Oct 18, 2026

Copy link
Copy Markdown
Member

Resolves #960

Summary of Changes

  • Per-Monitor DPI Resolution: Query GetDpiForWindow via dynamic resolution from user32.dll on the composition or IME window, falling back to device caps (LOGPIXELSY) and 96 DPI.
  • Font Scaling: Compute logical font height dynamically as -MulDiv(candFontSize_, dpi, 72) instead of using a static font created at startup.
  • Per-Window DPI Tracking: Track currentCandDpi_ and currentMsgDpi_ independently, updating and applying font scaling only when moving across monitors with differing DPI or when font settings (candFontSize_, candFontName_) are updated.
  • Clean Architecture & Lifetime: Removed global shared font_ and updateFont_ flag from TextService, allowing ImeWindow::setFont to manage font handle lifetime cleanly without risk of double-free or use-after-free.
  • Type Safety: Converted casts to modern C++ static_cast and reinterpret_cast.

Verification

  • Tested Win32 and x64 compilation of PIMETextService.vcxproj locally with 0 errors.

Generated with assistance from Antigravity.

WHAT:
* Dynamically resolve and query GetDpiForWindow on the target composition or IME window with fallback to GetDeviceCaps(LOGPIXELSY) and 96 DPI.
* Scale candidate and tooltip font height dynamically based on monitor DPI (-MulDiv(candFontSize, dpi, 72)).
* Cache current DPI per candidate/message window and update font only when monitor DPI changes or when font settings are modified.
* Replace shared font_ and updateFont_ member variables with independent window font updates.
* Use static_cast and reinterpret_cast instead of C-style casts.

WHY:
* Fixes #960. Currently PIMETextService creates a single font at startup based on system primary DC DPI. On Windows 10/11 multi-monitor systems with mixed DPI scaling (e.g., 100% and 150%/200%), candidate and message windows rendered with incorrect font sizing and blurry text when moved between monitors.

Generated with assistance from Antigravity.
@ilwu

ilwu commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Code review(沒有裝機實測,手邊沒有能編譯的環境,但有雙螢幕混合 DPI 的機器,之後有簽章的 build 可以幫測)。兩點:

1. HFONT 會隨每次候選視窗開關漏掉一個 GDI handle。
updateCandidateFont() 每次 DPI 改變就 candidateWindow_->setFont(CreateFontIndirect(&lf)),而 hideCandidates() 會把 currentCandDpi_ 歸零,所以每次 createCandidateWindow() 都會建一個新的 HFONT。libIME2 的 ImeWindow::setFont() 會刪掉前一個 font,但 ImeWindow::~ImeWindow() 是空的,視窗被銷毀時最後那個 HFONT 沒人刪。舊版是 TextService 擁有單一 font_、在解構時 DeleteObject,所以沒這個問題;這版變成每彈一次候選視窗(和每次 showMessage())漏一個 GDI 物件,長時間開著的 Word/瀏覽器會慢慢累積到 process 的 GDI 上限。
最簡單的修法是在 libIME2 ImeWindow::~ImeWindow() 補 if (font_) ::DeleteObject(font_);(建構子給的是 stock font,DeleteObject 對 stock object 是無害的),或是讓 TextService 自己保留 candFont_ / msgFont_ 兩個 handle 來管生命週期。

2. 分支的 libIME2 submodule 指到舊的 commit。
PR 的 libIME2 是 6446eed,master 已經是 78e6104(多了 64494f6 C++17、bfc1d65 ARM64X forwarder 的 registerServer 多載)。直接合併會把 submodule 倒退回去,#965 的 ARM64 安裝流程會壞掉;rebase 時 git submodule update 一下就好。

其他部分(GetDpiForWindow 動態載入保留 Win10 1607 以前的 fallback、以 composition window 的 DPI 為準、字型設定變更時歸零 DPI 快取強制重建)看起來沒問題。

(由 Claude Code (Claude Fable 5.1) 協助審閱)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Candidate and message windows do not adapt to Per-Monitor High DPI

2 participants