Nhật ký phát triển GõKey - Tuần 1
Gõ tiếng Việt trên máy tính là một chủ đề khá thú vị và xây dựng một bộ gõ tiếng Việt là ước mơ của mình từ khi mới chập chững học lập trình.
Giai đoạn 2006 thì mình có tham gia một diễn đàn công nghệ, và được may mắn theo dõi một số cuộc thảo luận về vấn đề bộ gõ, nhờ đó mà cũng lờ mờ nắm được ý tưởng về các kĩ thuật xử lý như Backspace giả, Clipboard... nhưng ở thời điểm đó, mình chỉ biết rồi thôi.
Thực ra xây dựng bộ gõ không phải là một vấn đề quá khó, cái khó nhất có lẽ là sẽ có rất nhiều behavior cần phải xử lý. Thêm nữa, vì chưa thấy có nhiều tài liệu nói về vấn đề xây dựng bộ gõ, nên ban đầu mình nảy ra ý tưởng làm một bộ gõ đơn giản rồi viết bài hướng dẫn, mô tả kĩ thuật, hy vọng sẽ lôi kéo được thêm nhiều người quan tâm đến chủ đề này
Lúc bắt tay vào làm rồi thì tự nhiên thấy đây là một dự án khá thú vị, nên có lẽ mình sẽ làm tiếp một thời gian nữa .
Dưới đây là vài ghi chú mình viết lại mỗi ngày khi thực hiện dự án này, đôi chỗ có thể sẽ khó hiểu, tuy nhiên nếu đọc chơi thì mình nghĩ chắc cũng không thành vấn đề, vì thế nên mình post lại lên blog.
Trước khi bắt đầu, mình rêcômmnd các bạn đọc qua vài bài viết về vấn đề bộ gõ / xử lý tiếng Việt:
- Vietnamese Keyboard Engine with Prolog, lewtds
- Ước mơ bộ gõ kiểu Unikey trên Linux, lewtds
- Vấn đề về IME trên Linux, zerox-dg
- Bỏ dấu trong tiếng Việt, zerox-dg
- Chuyện gõ tiếng Việt trên Linux, huytd
Mã nguồn của dự án GõKey các bạn có thể tham khảo tại đây: https://github.com/huytd/goxkey/
Ngày 10/01/2023
Commit: 82aa2
Sau khi có ý tưởng thực hiện một bộ gõ thì việc đầu tiên mình làm là... đọc source của một vài bộ gõ đã có trước đó, chủ yếu là ibus-bogo và OpenKey.
Mục tiêu chính sẽ là làm bộ gõ cho macOS, đơn giản vì mình đang xài macOS. Ngôn ngữ sử dụng sẽ là Rust, lý do thì rất hiển nhiên, mình là fanboi của Rút. JK. Đặc thù của dự án này đòi hỏi phải tương tác với những API bên dưới của hệ điều hành, việc gõ phím đòi hỏi tốc độ xử lý nhanh và ứng dụng phải chạy thật ổn định. Rust hội tụ đủ tất cả mọi yếu tố cần thiết đó: dễ dàng sử dụng các low level API từ hệ điều hành giống như C/C++, hiệu suất hoạt động miễn bàn, có rất nhiều feature/thư viện cho phép mình làm implement những gì mình muốn một cách nhanh chóng mà lại còn đảm bảo tính an toàn, xài Rust làm cho mình yên tâm đến mức có thể nghĩ là: Code compile được là chương trình sẽ hoạt động một cách ổn định và hiệu quả (đừng quá tin vào câu này =))). Mình không phải là một lập trình viên giỏi, và Rust có thể hướng cho mình viết code an toàn hơn, không giống như khi xài C/C++, mình có thể tạm yên tâm với performance, nhưng chưa bao giờ yên tâm về độ an toàn, mọi nước đi mình luôn sợ sẽ gây ra SEGFAULT Trong khi xài JS hay Python thì mình có thể yên tâm về productivity nhưng chưa bao giờ yên tâm về performance.
Tất nhiên những cái benefits trên khi xài Rust, mình vẫn có thể có nếu xài Go. Vấn đề duy nhất là mình không có rành Go
Trước khi bắt đầu thì mình cũng lờ mờ biết là, với kĩ thuật Backspace giả, bằng cách nào đó, phải bắt được thông tin các phím được nhấn, rồi "dịch" các phím đó ra thành một từ tiếng Việt có nghĩa, rồi gửi phím Backspace để xoá từ đã được gõ, rồi gửi tiếp nội dung từ tiếng Việt đó để thay thế.
Tuy nhiên bắt thông tin phím như thế nào thì chưa biết. Rất may là mã nguồn của OpenKey được trình bày tương đối rõ ràng nên mình cũng không tốn nhiều thời gian để tìm ra thứ cần tìm:
eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0, eventMask, OpenKeyCallback, NULL);
Như vậy, từ khoá ở đây là CGEventTapCreate
, một API đến từ
framework CoreGraphics của macOS.
Để dùng được API của macOS trong Rust thì cần phải viết binding cho các API đó, hoặc là dùng các binding có sẵn, tuy nhiên đang máu nên mình chọn cách tự viết binding, tiện để hiểu thêm những gì xảy ra bên dưới.
Sau một hồi loay hoay thì mình cũng build được một chiếc prototype tạm coi được:
Ngày 13/01/2023
Commit: f68750
Ngày xưa thời còn đi học, cuối tuần là thời điểm mà mình có thể dành toàn bộ thời gian để làm điều mình thích (ôm máy tính, code). Còn bây giờ, cuối tuần là thời điểm mà mình khó tập trung cho việc code nhất vì phải deal với 2 bạn quỷ nhỏ. Cho nên hôm nay không có nhiều progress lắm.
Việc duy nhất làm được hôm nay là tổ chức lại cấu trúc code cho dễ nhìn hơn, tách biệt giữa phần logic xử lý nhập liệu và phần logic giao tiếp với từng hệ điều hành.
Ngày 14/01/2023
Commit: 95fbc7
Dành phần lớn thời gian ngày hôm nay loay hoay tìm cách cải thiện tốc độ gõ. Thế quái nào mà, mang tiếng xài Rust, ấy thế mà performance lại cùi bắp không tưởng được, không thể chấp nhận được...
Một giải pháp được nghĩ ra đó là, khi gửi phím (trong hàm send_backspace
và send_string
), thay vì phải liên tục tạo
thêm event object mới, thì mình sẽ tìm cách sử dụng lại các event object đã được tạo ra trước đó.
// thay vì:
let event = CGEvent::new_keyboard_event(...); // thì dùng:
static ref BACKSPACE_DOWN: CGEvent = CGEvent::new_keyboard_event(...);
Nhưng có một vấn đề, đó là kiểu CGEvent
từ trong binding của Servo không được define để có thể share giữa nhiều thread
với nhau. Thế là mình phải xài dark magic, tạo ra một kiểu SharedBox<T>
, để wrap kiểu CGEvent
, implement các trait
Sync
và Send
:
struct SharedBox<T>(T);
unsafe impl<T> Send for SharedBox<T> {}
unsafe impl<T> Sync for SharedBox<T> {}
impl<T> SharedBox<T> { unsafe fn new(v: T) -> Self { Self(v) }
} static ref BACKSPACE_DOWN: SharedBox<CGEvent> = unsafe { SharedBox::new(CGEvent::new_keyboard_event( EVENT_SOURCE.0.clone(), KeyCode::DELETE, true ).unwrap())
};
Mục đích của SharedBox<T>
là để vỗ ngực bảo đảm với Rust rằng:
- Mình: "Anh bảo đảm với chú là kiểu T có thể share giữa các thread một cách an toàn, chú cứ yên tâm công tác!"
- Rust: "D' tin!"
Anyway, mặc cho cái phát minh SharedBox
xịn xò mà mình chắc là ai cũng phải thán phục, bug mất chữ vẫn không được fix. Lâu lâu
khi gõ thì vẫn xảy ra lỗi khó chịu như gõ "tiếng" trên màn hình sẽ hiện ra "ttiến".
Trong suốt quá trình này, @zerox-dg vẫn rất nhiệt tình và chăm chỉ fix bug từ phía vi-rs
, update version mới liên tục.
Mỗi lần update là một lần đổi interface, mình phải update theo mệt nghỉ =))
Kết thúc tuần 1. Tình hình bộ gõ vẫn chưa có tiến triển gì mấy ngoài một bản prototype