Nhật ký phát triển GõKey - Tuần 4
Thật là khó tin khi lần đầu tiên mình có một tuần bận việc ở công ty sấp mặt, không có cả thời gian để làm side project luôn
Số là mình vừa được chuyển từ vị trí Frontend sang Backend, tất nhiên là vẫn ở công ty hiện tại chứ không có nhảy việc, nhưng vừa phải bơi theo task vừa phải học lại đủ thứ để theo kịp chúng đồng nghiệp, sấp mặt. May mắn là vẫn được giữ nguyên title, nên lương không đổi Anyway, lan man quá, chuyện này chắc để dành kể ở một bài viết khác. Hy vọng tuần này có nhiều thời gian hơn để làm GõKey.
Chức năng system tray đã đề cập ở tuần trước thì tạm thời mình gác lại để tập trung vào các chức năng khác (vì làm mấy cái khác dễ hơn )
Ngày 02/07/2023
Chức năng tuỳ chỉnh hotkey (phần 1)
Hiện tại thì GõKey đang được hardcode để nhận diện tổ hợp phím ⌃ ⌘ Space
cho việc bật/tắt chế độ gõ tiếng Việt, cho nên hôm nay mình implement vài thứ lặt vặt để chuẩn bị cho việc tuỳ chỉnh hotkey.
Mục tiêu là, về sau, chúng ta có thể define ra một file chứa thông tin cấu hình cho bộ gõ, file này sẽ có dạng như sau:
input_method = "TELEX"
shortcut_key = "super+ctrl+space"
Người dùng có thể thay đổi file này "bằng tay" hoặc thay đổi từ UI của bộ gõ. Để handle việc cấu hình phím tắt (shortcut_key
), thì mình làm các bước sau:
-
Implement một struct mới tên là
Hotkey
để cấu hình phím tắt.Struct này có khả năng nhận diện một string đầu vào dưới dạng
"<key> + <key> + ..."
, ví dụ như"super + ctrl + H"
,"super + shift + alt + enter"
, và chuyển nó thành 1 biến kiểuchar
và 1 biến kiểuKeyModifier
.
let hotkey = Hotkey::from("super+ctrl+space");
Khi Event Tap nhận được một event mới, thì chúng ta sẽ kiểm tra cặp giá trị (KeyModifier
, Char
) từ Event Tap với struct này, nếu các giá trị match với nhau thì tức là hotkey đang được nhấn:
fn event_handler(...) -> bool { match keycode { Some(keycode) => { if hotkey.is_match(modifiers, &keycode) { // hotkey pressed }
...
Ở bên dưới thì KeyModifier
chỉ là một giá trị số nguyên dương 32-bit (u32
), nên hàm Hotkey::is_match
chỉ đơn giản là thực hiện phép so sánh giữa các giá trị cơ bản như u32
, char
:
pub fn is_match(&self, modifiers: KeyModifier, keycode: &char) -> bool { return self.modifiers == modifiers && self.keycode.eq_ignore_ascii_case(keycode);
}
-
Render một giá trị kiểu
Hotkey
trên UI.Sau khi tạo một object
Hotkey
rồi thì mình muốn tận dụng luôn object này để hiên trị dãy phím tắt đã được cấu hình trên UI luôn, để làm được việc này, thì chỉ cần implement trait[std::fmt::Display](https://doc.rust-lang.org/std/fmt/trait.Display.html)
cho structHotkey
, và sau đó chúng ta có thể sử dụng hàmHotkey::to_string()
để hiển thị dãy phím tắt và render lên UI.
impl Display for Hotkey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.modifiers.is_control() { // Phím ⌃ Control } if self.modifiers.is_super() { // Phím ⌘ Cmd trên macÓ } ... write!(f, "{}", keys) }
}
Bằng cách này thì chúng ta còn có thể kiểm soát được thứ tự render của các phím modifiers. Ví dụ input khi khởi tạo Hotkey
được viết dưới dạng "super+ctrl+k"
, nhưng khi hiển thị, phím Control thường được render trước phím ⌘ Cmd.
Đến đây thì còn có một điểm tiện lợi nữa, đó là, trước đây chúng ta chia mã nguồn của từng hệ điều hành thành từng module riêng lẽ, ví dụ src/platform/macos.rs
cho macOS, src/platform/windows.rs
cho Windows. Nên việc define kí tự hiển thị cho từng phím modifier có thể được đưa vào từng module riêng của từng hệ điều hành, ví dụ phím Super của macOS là phím ⌘ Cmd, nhưng trên Windows thì nó là phím ⊞ Win, việc này có thể được handle bằng cách define kí tự hiển thị cho phím Super riêng ở riêng mỗi hệ điều hành:
// file macos.rs
pub const SYMBOL_SUPER: &str = "⌘"; // file windows.rs
pub const SYMBOL_SUPER: &str = "⊞";
Phần implement chi tiết các bạn có thể xem tại commit a16e2cc trên Github. Sau đây là screenshot tạm:
Với cấu trúc hiện tại, toàn bộ thông tin của bộ gõ (như kiểu gõ, chế độ tiếng Việt,…) được giữ trong INPUT_STATE
, nên có thể coi đây là single source of truth, ngoài ra không có bất cứ chỗ nào có thể chứa các thông tin này (để tiện quản lý).
À trừ một trường hợp, đó là UIDataAdapter
, có chứa một bản tham chiếu của trạng thái gõ tiếng Việt (INPUT_STATE.is_enabled()
) và chế độ gõ hiện tại (INPUT_STATE.typing_method()
) để làm nhiệm vụ hiển thị trên UI. Từ phía UI, khi có thay đổi xảy ra, thì các giá trị sẽ được ghi trở lại vào UIDataAdapter
và từ đó mình phải catch được những gì đã thay đổi, truyền lại đến INPUT_STATE
Để bảo đảm INPUT_STATE
vẫn là single source of truth, thì mình chỉ có thể sử dụng ConfigManager
để khởi tạo giá trị ban đầu cho INPUT_STATE
khi bộ gõ khởi động, và thực hiện thao tác ghi config vào file từ các hàm setter của INPUT_STATE
, và sau một hồi suy nghĩ thì mình nghĩ là sự tồn tại của ConfigManager
có vẻ hơi thừa, đáng ra chỉ cần dùng mỗi ConfigStore
Nhưng mà thôi, cứ tạm note lại rồi sang tuần sau làm tiếp. 😔
Có một điều khá vui là dạo gần đây có nhiều bạn đã clone project về và tự compile để sử dụng, và report bug. Mặc dù thấy bug thì khá mệt nhưng mà mình rất vui vì ít ra như thế tức là đã bắt đầu có user rất cảm ơn các bạn đã nhiệt tình ủng hộ dự án, và mình hy vọng sẽ nhận được nhiều bug report hơn nữa.
Như đã đề cập ở đầu bài thì mấy tuần gần đây dự án tiến triển rất chậm, nên devlog ra không đều và không nhiều, rất mong các bạn thông cảm. Sau khi ổn định lại thì mình sẽ chăm viết devlog hơn :D Ngoài ra, devlog này chỉ cover những gì xảy ra trong dự án GõKey, rất nhiều những vấn đề hay ho về gõ tiếng Việt được xử lý từ phía vi-rs, mình nghĩ các bạn nên gây áp lực để tác giả vi-rs chịu ngồi xuống và viết devlog hàng tuần giống như mình :smiling_imp:.