Skip to content

Chờ sự kiện surrounding text thay vì ngủ theo hằng số (mặc định tắt), kèm vá luật theo app #476 - #477

Closed
nguyenphivn wants to merge 3 commits into
LotusInputMethod:devfrom
nguyenphivn:experiment/wait-surrounding-event
Closed

nguyenphivn wants to merge 3 commits into
LotusInputMethod:devfrom
nguyenphivn:experiment/wait-surrounding-event

Conversation

@nguyenphivn

Copy link
Copy Markdown
Contributor

Chào bạn, mình gửi PR này ở dạng draft vì phần thử nghiệm còn 7 công tắc cấu hình để ai muốn cũng đo lại được — mình để bạn xem hướng trước rồi mới dọn.

PR có hai commit tách bạch:

  1. fix: giữ luật theo app khi nạp lại cấu hình — chính là issue Luật theo app tụt về mặc định chung sau khi nạp lại cấu hình, không cần đổi cửa sổ #476 bạn đã xem.
  2. thử nghiệm: chờ sự kiện surrounding text thay vì ngủ theo hằng sốmặc định TẮT, không đổi hành vi của ai cho tới khi bật công tắc.

Bạn muốn tách hẳn ra hai PR thì mình tách, commit 1 đứng độc lập được.

Vấn đề

Chế độ uinput bắn phím xoá qua nhân rồi giao chữ mới qua Wayland. Hai đường khác nhau nên chữ mới vượt mặt phím xoá, gõ nhanh là mất chữ. Bản hiện tại chờ bằng cách ngủ 2 ms mỗi phím xoá rồi thử lại 3 lần × 2 ms.

Chỗ ngủ đó không thể đúng được, và đây là chỗ mình mất nhiều thời gian nhất mới hiểu ra: fcitx5 chỉ có MỘT vòng lặp sự kiện, mà tin InputContextSurroundingTextUpdated lại đi qua đúng cái vòng lặp đang bị sleep_for chặn. Ngủ bao lâu cũng không thấy tin tới — vòng thử lại 3 × 2 ms vì thế vô nghĩa về mặt cấu trúc, không phải vì hằng số nhỏ quá.

Chứng minh bằng đo: tăng hằng số ngủ lên gấp 12,5 lần trên đường surrounding text, Firefox đi từ 8/15 lên 9/15 — nằm trong nhiễu.

Cách chữa

Trả vòng lặp về ngay, đăng ký nghe InputContextSurroundingTextUpdated, kiểm bằng nội dung ảnh ô nhập chứ không dùng realtextLen, quá hạn thì giao như cũ. Phím người dùng gõ trong lúc chờ vẫn vào buffered_keys_ như trước.

Cách đo

Bàn phím ảo uinput gõ 15 chuỗi tiếng Việt vào từng ô nhập thật, đọc lại nội dung ô rồi so với chuỗi mong đợi. Đối chứng luôn là cùng một bản dựng, tắt công tắc — không so giữa hai bản build khác nhau. Mỗi vòng khai trước con số dự đoán rồi mới chạy, đoán trật thì ghi lại.

Hành trình đo — mỗi vòng sửa vì đo thấy gì

Mình để nguyên phần này vì nó giải thích vì sao mã có mấy nhánh trông kỳ:

Vòng Đo thấy gì Sửa gì
v1 Người nghe sự kiện sống qua lần thay chữ sau, bắt tin trễ của phím trước → giao sớm, 0/15 Đăng ký nghe đúng lúc bắn phím xoá, huỷ ngay sau đó
v4 Ở nhịp 20 ms, ảnh cũ tụt hai phím trông y hệt trạng thái đã xoá xong (đươợ) Ảnh giống hệt ảnh lúc bắn phím xoá thì không tính là xong
v6 Firefox tụt lại thì ảnh không đáng tin nữa Sau một lần quá hạn, bỏ kiểm ngay cho tới khi có tin khớp
v7 Konsole khai có surrounding text nhưng gửi ảnh RỖNG suốt (valid=1 len=0) — chờ chỉ tốn trọn hạn mỗi dấu Ảnh rỗng thì bỏ chờ, đi đường ngủ cũ
v7.1 Hạn 40 ms nổ ở 64 ms, hạn 200 nổ ở 243, tệ nhất 430 addTimeEvent accuracy 0 của sd-event = mặc định 250 ms; đặt 1 ms
v8+v9 Ở nhịp 5 ms, ảnh lúc bắn luôn chậm một phím nên trông như xong; nhưng tin tới 0 ms sau khi quá hạn lại là bộ đệm cũ bắt kịp (trươnờ) Dùng thời gian: chỉ tin sau ngưỡng 8 ms × số phím xoá (đúng ngưỡng của chế độ Slow, đã đo 60/60)
v10 16/27 lần quá hạn còn lại là tin "xong" tới TRƯỚC ngưỡng rồi app im luôn Đặt thêm một mốc đúng tại ngưỡng, khớp thì giao ngay
v11–v12 Thanh địa chỉ Edge đóng băng ảnh trong lúc xoá, không bao giờ khớp Phát hiện đóng băng thì bỏ chờ, ngủ theo hằng số của Slow, thăm dò lại mỗi 4 lần
v12c Hạ hạn 200 → 100 → 50 ms vẫn 60/60 hai lượt Chọn 50 ms; dưới 40 ms là thấp hơn ngưỡng an toàn của Slow nên dừng

Hai lần mình đoán sai và phải ghi lại: v11 dự đoán trung vị 15–20 ms, đo ra 46 ms vì ngủ chồng hai lần; và một lần kết luận nhầm rằng Electron trên X11 không nối được fcitx5 — thủ phạm thật là một hộp thoại của app che mất ô soạn, đã rút lại.

Số đo cuối

Ô nhập Nhịp 50 ms Nhịp 5 ms Chữ hiện trễ (trung vị)
Firefox, 4 ô soạn 60/60 60/60 (cả nhịp ngẫu nhiên 5–120 ms) 25 ms
Kate, Konsole, Alacritty 15/15 mỗi app 15/15 mỗi app 6–8 ms
KDialog (Qt), Meld (GTK3), Electron 15/15 mỗi app 15/15 mỗi app 6–7 ms
Edge, 4 ô trong trang 60/60 30/30 6 ms
Edge thanh địa chỉ 15/15 14/15 27 ms

Bản hiện tại trên Firefox: 9–14/15.

Về #190 — lặp chữ đầu trên thanh địa chỉ Chromium

Mình dùng bản này hằng ngày trên KDE Wayland, chế độ Uinput (Smooth), và lỗi lặp chữ đầu ở thanh địa chỉ Chromium không còn xuất hiện nữa. Gõ ở những chỗ trước đây hay gợn — Facebook, Google Chat — giờ đều mượt.

Số đo cũng đi cùng hướng: thanh địa chỉ Edge 15/15 ở nhịp 50 ms, tức tốc độ gõ tay, không lặp lần nào.

Nói cho đủ: ở nhịp 5 ms — nhanh hơn người gõ nhiều lần, chỉ máy mới gõ được thế — bộ đo vẫn bắt được 2/15 ca lặp kiểu dđược, dđồng hồ. Nên mình để đây như một báo cáo dùng thật, không dám khẳng định đã đóng được #190 trong mọi hoàn cảnh. Rất mong bạn và những người đang gặp #190 bật công tắc lên thử giúp — chỉ cần đặt WaitSurroundingEvent=True trong lotus.conf, không cần đổi gì khác.

Đánh đổi, mình khai thẳng

Đường này giữ chữ đang treo qua một nhịp của vòng lặp, nên nếu người dùng đổi cửa sổ trong vòng 50 ms sau khi bấm phím dấu thì ô CŨ mất chữ đó. Mình đã thử vá bằng cách giao nốt lúc mất tiêu điểm: nhật ký ghi đúng nhưng chữ vẫn không tới nơi, vì fcitx5 không giao được chuỗi vào input context đang mất tiêu điểm. Đường ngủ cũ không có khe này vì nó chặn vòng lặp.

Hai điều đã đo và yên tâm được: chữ không rơi sang cửa sổ mới, và cửa sổ mới vẫn gõ bình thường.

Hàng đợi buffered_keys_ (trần 50 phím) thì an toàn: với hạn 50 ms phải gõ hơn 1000 phím/giây mới tràn.

Còn thiếu

  • Chưa có ca test tự động cho đường mới; ctest hiện tại 8/8 (bài smooth_buffered_key_replay phải chạy trong network namespace riêng vì fcitx5-lotus-server đang giữ socket).
  • Đo trên một máy KDE Wayland, chưa thử GNOME hay X11 thuần.
  • Nếu bạn thấy hướng ổn, mình sẽ dọn 7 công tắc còn một cái duy nhất và viết ca test trước khi bạn gộp.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TvNe693ge85eB4QPs6hpjV

nguyenphivn and others added 2 commits September 8, 2026 21:13
`LotusState::setEngine()` ghi thẳng `realMode = config.mode` — mặc định CHUNG.
Nhưng `setEngine()` chạy trong hàm dựng của MỌI `LotusState` và trong
`refreshEngine()` cho MỌI input context, mà cả hai đều không biết cửa sổ nào
đang gõ. Kết quả: một input context mới (ứng dụng khác vừa mở) hoặc một lần nạp
lại cấu hình sẽ thổi bay luật riêng của cửa sổ đang hoạt động, cho tới lần đổi
focus kế tiếp.

Sửa:
- `setEngine()` không còn ghi `realMode`.
- `refreshEngine()` đặt lại chế độ theo đúng luật của app đang có focus qua
  `setMode(getAppRule(getProgramName(ic)), ic)`. `reloadConfig()` đã gọi
  `loadAppRules()` trước `populateConfig()` nên luật đọc ở đây là luật mới.
- Không có cửa sổ nào đang gõ thì giữ mặc định chung như hành vi cũ; lần focus
  kế tiếp `activate()` đặt lại cho đúng.

Sau thay đổi này `realMode` chỉ còn được ghi ở `setMode()`, tức mọi lần ghi đều
đi qua tra luật theo app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvNe693ge85eB4QPs6hpjV
…c định TẮT)

Chế độ uinput bắn phím xoá qua nhân rồi giao chữ mới qua Wayland. Hai đường khác
nhau nên chữ mới có thể vượt mặt phím xoá, gõ nhanh là mất chữ. Bản hiện tại chờ
bằng cách ngủ 2 ms mỗi phím xoá rồi thử lại 3 lần × 2 ms.

Chỗ ngủ đó không thể đúng được: fcitx5 chỉ có MỘT vòng lặp sự kiện, mà tin "ô nhập
đã đổi" (InputContextSurroundingTextUpdated) lại đi qua đúng vòng lặp đang bị chặn.
Ngủ bao lâu cũng không thấy tin tới; vòng thử lại 3 × 2 ms vì thế vô nghĩa về mặt
cấu trúc.

Thay bằng: trả vòng lặp về ngay, đăng ký nghe sự kiện đó, kiểm bằng NỘI DUNG ảnh ô
nhập, quá hạn thì giao như cũ. Phím người gõ trong lúc chờ vẫn vào buffered_keys_
như trước.

Bảy công tắc cấu hình, tất cả mặc định giữ nguyên hành vi cũ (WaitSurroundingEvent
mặc định false). Để nguyên để ai muốn đo lại có chỗ vặn:

  WaitSurroundingEvent        bật đường chờ sự kiện (mặc định false)
  WaitSurroundingTimeoutMs    hạn chờ, 50
  WaitSurroundingShortMs      hạn rút sau hai lần quá hạn liền, 40
  WaitSurroundingProbeEvery   app đóng băng ảnh thì thăm dò lại mỗi N lần, 4
  WaitSurroundingMinPerKeyMs  ngưỡng tin ảnh "trông như xong", 8 ms mỗi phím xoá
  SurrDeleteSleepMs           hằng số ngủ cũ của đường surrounding text, 4
  SurrCommitSleepMs           hằng số ngủ cũ sau khi giao chữ, 3

Số đo trên KDE Wayland (CachyOS), bàn phím ảo uinput, cùng một bản dựng, đối chứng
là tắt công tắc:

  Firefox 4 ô soạn      60/60 ở nhịp 5 ms và ở nhịp ngẫu nhiên 5-120 ms
                        (bản hiện tại: 9-14/15)
  Kate, Konsole,        15/15 mỗi app ở nhịp 50 ms và 5 ms
  Alacritty, KDialog,
  Meld, Electron
  Edge 4 ô trang        60/60 ở nhịp 50 ms
  Edge thanh địa chỉ    15/15 ở nhịp 50 ms; 14/15 ở nhịp 5 ms
                        (ca sai là lỗi Chromium LotusInputMethod#190, có ở cả bản hiện tại)

Chữ hiện trễ thêm: trung vị 6-8 ms ở app thường, 25 ms ở Firefox.

Đánh đổi đã đo được, xin khai thẳng: đường này giữ chữ đang treo qua một nhịp của
vòng lặp, nên nếu người dùng đổi cửa sổ trong vòng 50 ms sau khi bấm phím dấu thì
ô CŨ mất chữ đó. Đã thử vá bằng cách giao nốt lúc mất tiêu điểm — nhật ký ghi đúng
nhưng chữ vẫn không tới nơi, vì fcitx5 không giao được vào input context đang mất
tiêu điểm. Đường ngủ cũ không có khe này vì nó chặn vòng lặp. Chữ KHÔNG rơi sang
cửa sổ mới (đã đo), và cửa sổ mới vẫn gõ bình thường.

ctest 8/8 (bài smooth_buffered_key_replay cần chạy trong network namespace riêng
vì fcitx5-lotus-server đang giữ socket).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EahWp9MhUvHaPU2SpWPTLa
@nguyenphivn

Copy link
Copy Markdown
Contributor Author

Mình đóng PR này và mở lại ở #492.

Lý do: commit đầu của nó là vá cho #476, mà bạn đã tự vá ở 939f01a rồi nên nó thành thừa. #492 tách thẳng từ dev hiện tại, còn đúng một commit (phần chờ sự kiện, mặc định tắt), ctest 9/9 gồm cả app_rule_reset bạn mới thêm.

Nội dung mô tả và số đo giữ nguyên, mình chỉ bỏ phần đã trùng.

@github-project-automation github-project-automation Bot moved this from Backlog to Done in Kanban Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant