Sử dụng bàn phím Windows trên macOS

Sử dụng bàn phím Windows trên macOS
Photo by tookapic from Pixabay

Năm ngoái tôi mới mua bàn phím mới, nhưng đáng tiếc bàn phím đó của tôi chỉ tương thích với Windows mà không tương thích với macOS. Tôi nghĩ rằng sẽ có nhiều người như tôi, vì mua một bàn phím Windows luôn dễ dàng và có nhiều lựa chọn. Chuyện “tương thích” không phải là vấn đề gì quá lớn và hoàn toàn có thể khắc phục được. Trong bài viết này, tôi sẽ chia sẻ cách mình đã khắc phục những vấn đề về tương thích như thế nào.

Vấn đề

Về cơ bản thì bàn phím dành cho macOS hay Windows cũng có chung layout. Một bàn phím Windows khi sử dụng với macOS cũng không phải vấn đề gì quá lớn. Bỏ qua việc các phím có ký hiệu khác nhau thì hầu hết các phím được nhận đúng và hoạt động bình thường.

Tôi nghe nói bàn phím bluetooth, nhất là các bàn phím dùng BLE (Bluetooth Low Energy) có thể sẽ gặp vấn đề về kết nối. Cụ thể là macOS trước khi login sẽ chỉ cho một số thiết bị hạn chế dùng BLE kết nối (ví dụ bàn phím magic keyboard, magic mouse của chính Apple) còn đồ hãng khác thì hên xui. Xui thì sẽ không thể dùng được bàn phím cho tới khi login xong.

Tuy nhiên, do đặc thù của mỗi hệ điều hành mà sẽ có sai khác nhất định dù không nhiều. Qua quá trình sử dụng, tôi nhận ra vẫn còn tồn tại một số vấn đề như:

  • Các phím F1~F12 là các phím chức năng bình thường. Những phím này không thể dùng để điều chỉnh âm lượng, độ sáng màn hình (các phím media) như hàng phím này trên MacBook. Đây thực ra không phải là vấn đề của bàn phím Windows hay Mac, mà là bàn phím của Apple và không phải Apple 😂.
  • Phím Windows được nhận thành Command, Alt thành Option, Ctrl thành Control thế nhưng việc sắp xếp Command và Option lại ngược nhau.
  • Phím Menu được nhận thành Application trên macOS và nút này chẳng có tác dụng gì.
  • Các phím PrintScreen, ScrollLock, Pause được nhận thành F13, F14 và F15. F14 và F15 thì được gán phím tắt để tăng/giảm độ sáng màn hình còn F13 thì hiện tại không có tác dụng.
  • Phím Insert được nhận thành Help và cũng không có tác dụng.
  • Phím Home/End trên macOS được nhận bình thường nhưng hoạt động của nó không giống với Windows. Home/End trên macOS để di chuyển về đầu/cuối văn bản (chứ không phải đầu/cuối dòng như Windows).

Tôi đã sử dụng Karabiner-Elements để giải quyết những sai khác còn lại về phím bấm giữa hai hệ điều hành. Thực sự thì Karabiner-Element còn tốt hơn thế, nó cung cấp những cấu hình phức tạp có thể thiết lập riêng cho từng app. Nói chung là bản thân phần mềm này không có gì để chê. Nhưng vấn đề ở đây là, với máy tính được công ty cấp tôi không thể tùy tiện cài đặt phần mềm được (do các yêu cầu về bảo mật).

Một phần nhờ Karabiner-Viewer (được cài đặt kèm theo Karabiner-Elements) mà tôi biết được các phím được nhận là phím gì trên macOS 😁.

Vậy là tôi phải tìm cách khắc phục bằng những thứ mà macOS cung cấp sẵn. Thực ra thì có một cách đơn giản hơn rất nhiều, đó là mua một bàn phím của Apple, hoặc một bàn phím tương thích với macOS. Như vậy thì ngay từ đầu sẽ không có vấn đề gì 😁, chỉ có vấn đề nhỏ là sẽ phải bỏ thêm nhiều $ hơn.

Ngoài ra, tôi được biết có một số bàn phím cho phép remap bằng QMK hay VIA gì đó, cấu hình sẽ được lưu trữ luôn trên bàn phím mà không phải dùng phần mềm nào cả. Đây cũng là một cách hay để biến một bàn phím bất kỳ thành một bàn phím tương thích với macOS. Nhưng bàn phím của tôi thì không có tính năng này 💔.

Giải quyết vấn đề

Command và Option bị ngược

Vấn đề này thì rất đơn giản. Có lẽ Apple cũng đã nghĩ đến vấn đề này với người dùng từ rất lâu rồi, nên trong Settings của hệ điều hành cho phép điều chỉnh lại. Tôi chỉ đơn giản là đổi ngược lại giữa Command và Option là xong.

modifiers

Ngoài ra, để đỡ nhầm thì tôi cũng tháo hai phím Windows với Alt ra đổi chỗ cho nhau. Với bàn phím cơ thì việc này không có gì khó khăn cả.

PrintScreen, ScrollLock, Pause/Break

Như đã nói ở phần trước, những phím này được nhận thành F13, F14 và F15. F14 và F15 đã được gán thành phím tắt để tăng giảm độ sáng màn hình, còn F13 thì chưa có tác dụng gì.

Tôi đã gán phím F13 thành phím tắt để chuyển đổi qua lại giữa các chế độ gõ khác nhau (Anh, Việt, Nhật). Tôi đang ở Nhật và làm việc với tiếng Nhật tương đối nhiều thế nhưng tôi vẫn mua một bàn phím layout ANSI (US) vì tôi làm lập trình còn nhiều hơn 🤣. Bàn phím này thiếu mất hai phím かな英数 chuyển đổi giữa kiểu gõ Nhật và Anh. Tuy nhiên sau khi gán cho F13 thì tôi thấy nó còn tiện hơn hai phím kia.

F13

Không biết có phải bug hay không, nhưng nếu chọn F13 cho option phía trên (thay cho Control+Space) thì khi ấn F13 sẽ không giống với ấn Control+Space mặc định. Ấn F13 chỉ còn chuyển đổi qua lại giữa hai chế độ gõ gần nhất mà thôi, không thể chuyển sang chế độ thứ 3 được.

Insert, Home, End

Phím Insert trên macOS được nhận thành Help. Nhưng cũng không quan trọng lắm, vì thực ra Insert hay Help thì trên macOS cũng đều không có tác dụng gì cả. Ngay cả trên Windows, Insert cũng ít được dùng. Chỉ có một số phần mềm như Word dùng phím Insert để chuyển đổi qua lại giữa chế độ gõ overtype (ghi đè) và insert (viết chèn thêm) mà thôi.

Với một bàn phím của Apple thì vị trí đó là vị trí phím Fn nhưng không hiểu sao Insert không được nhận thành phím đó luôn 😂.

Với các phím Home, End thì lại là một câu chuyện khác. Các phím này trên macOS hoạt động khác với Windows, chứ không phải bị nhận nhầm thành phím khác. Cá nhân tôi thì thích cách Home/End hoạt động như Windows hơn.

Rất may là macOS cho phép bind những phím này tương đối dễ dàng. Thực ra bản thân macOS đã có những binding riêng cho các phím. Chi tiết về việc này được Apple mô tả ở đây.

Tôi tìm được một bảng tổng hợp các tổ hợp phím mặc định của macOS ở đây.

Người dùng có thể dễ dàng thiết lập binding của riêng mình bằng cách tạo ra một file DefaultKeyBinding.dict trong thư mục ~/Library/KeyBindings/ (nếu chưa có thư mục này thì tạo mới 😂). Đúng ra file này nên là một file plist, nhưng cú pháp của plist khó hiểu và khó viết hơn nên dùng một file dict sẽ là phù hợp với hầu hết mọi người (Cú pháp này có từ thời hệ điều hành NeXTSTEP và vẫn được giữ lại đến tận bây giờ 👍).

Nội dung file này sẽ bind các phím hoặc tổ hợp phím trên macOS. Cú pháp của file dict cực kỳ đơn giản, giống như gán biến trong lập trình vậy:

<tổ hợp phím> = <hành động>;

Với các phím chữ và số thì rất đơn giản, chỉ cần viết phím đó ra là xong. Với các phím khác thì phức tạp hơn (cần phải dùng mã unicode của phím đó). Dưới đây là mã một số phím như vậy, tôi chỉ quan tâm tới Help (Insert), Home và End nên đây là mã của chúng:

Home : \UF729
End  : \UF72B
Help : \UF746

Còn rất nhiều phím khác nữa, bạn nào có nhu cầu bind nhiều phím hơn thì có thể tham khảo thêm ở đây. Ngoài ra có một điểm mà tài liệu của Apple mô tả không đúng. Họ nói rằng có thể dùng enum đã định nghĩa trong NSEvent.h (ví dụ NSHomeFunctionKey, NSHelpFunctionKey) nhưng tôi đã thử và nó không hoạt động. Tôi cũng chẳng biết cách nào để nó hoạt động, nên cuối cùng vẫn phải dùng mã unicode ở trên.

Một điểm hơi lại là các phím này, dùng tiền tố \U. Không biết đây có phải là cú pháp của Objective-C hay không. Với các ngôn ngữ lập trình mà tôi biết, ký tự unicode mà dùng mã có 4 chữ số hex thì thường là tiền tố \u còn \U thì tiếp theo phải là 8 chữ số.

Dưới đây là một số cú pháp dành cho các phím modifier trong trường hợp người dùng muốn bind một tổ hợp phím:

^ : Ctrl
$ : Shift
~ : Option (Alt)
@ : Command (Apple)
# : Numeric Keypad

Các hành động mà có thể gán cho các phím thì tham khảo ở đây. Đây là toàn bộ file binding của tôi, đơn giản và dễ hiểu:

{
    "\Uf729"   = "moveToBeginningOfLine:";                       /* Home         */
    "$\Uf729"  = "moveToBeginningOfLineAndModifySelection:";     /* Shift + Home */
    "@\UF729"  = "moveToBeginningOfDocument:";                   /* Cmd  + Home  */
    "@$\UF729" = "moveToBeginningOfDocumentAndModifySelection:"; /* Shift + Cmd  + Home */

    "\UF72B"   = "moveToEndOfLine:";                             /* End          */
    "$\UF72B"  = "moveToEndOfLineAndModifySelection:";           /* Shift + End  */
    "@\UF72B"  = "moveToEndOfDocument:";                         /* Cmd  + End   */
    "@$\UF72B" = "moveToEndOfDocumentAndModifySelection:";       /* Shift + Cmd  + End */

    "$\UF728"  = "cut:";                                         /* Shift + Del  */
    "$\UF746"  = "paste:";                                       /* Shift + Ins */
    "@\UF746"  = "copy:";                                        /* Cmd  + Ins  */
}

Cách làm này đáp ứng được đúng nguyện vọng của tôi. Nhưng nó vẫn có một số nhược điểm nhất định. Đầu tiên, việc này chỉ có bind các phím hay tổ hợp phím với các hành động trong soạn thảo văn bản mà thôi. Không thể dùng nó để bind các hành động phức tạp hơn, tác động đến hệ thống mạnh hơn được.

Một nhược điểm nữa, là không phải ứng dụng nào trên macOS cũng đáp ứng binding này. Rất nhiều phần mềm có binding riêng của nó và nó sẽ bỏ qua luôn binding của máy. Một trong những phần mềm như thế là VSCode, thế nhưng binding của VSCode cũng ổn rồi nên tôi không gặp vấn đề gì. Mà thực ra thì không ổn thì đây cũng là cách tốt nhất rồi 😂.

Phím Menu

Phím này được nhận thành Application trên macOS, nhưng thực ra cả Menu hay Application đều không có tác dụng gì. Giờ đây, mong muốn của tôi là gán phím này thành phím Fn. Không thể dùng cách trên để giải quyết được, nhưng rất may là macOS lại có một cách khác.

Kể từ phiên bản 10.12 Sierra, macOS đã bổ sung một lệnh cho phép remap phím này thành phím khác. Không có một giao diện nào cho phép làm việc này, người dùng chỉ có cách duy nhất là gõ lệnh. Lệnh đó là hidutil, cho phép kiểm soát IOHIDFamily driver (chính là driver điều khiển bàn phím cũng như nhiều thiết bị khác).

Driver này là open source, người dùng có thể tìm đọc mã nguồn của nó trong file IOHIDKeyboardFilter.mm.

Chắc hẳn kỹ sư bổ sung tính năng này cho macOS cũng đã gặp rất nhiều vấn đề với bàn phím, và khá chắc là anh này không dùng bàn phím của Apple 😂. Dù sao thì rất cảm ơn người nào đã mang tính năng quan trọng này lên macOS. Việc sử dụng lệnh này được mô tả trong Technical Note TN2450.

Nội dung file này có nhiều điểm không chính xác (cũng khó trách vì lâu rồi tài liệu này không được cập nhật). Link đến tiêu chuẩn tín hiệu của USB đã chết, nhưng tôi tìm được một link khác ở đây. Một vấn đề nữa là tài liệu nói rằng các phím có mã bắt đầu là 0x7 nhưng điều này hoàn toàn sai.

Mọi phím trên bàn phím được điều khiển bởi HID Page và Usage ID. Tôi đoán đây có lẽ là hoạt động ở tầng thấp của hệ điều hành (làm việc với tín hiệu) sau đó macOS sẽ chuyển đổi những mã tín hiệu này thành key code cho từng phím và các ứng dụng sẽ dùng key code để phản hồi lại các phím bấm từ người dùng.

Để có thể remap thành công thì phải tìm trong tài liệu HID Usage Tables mã của các phím. Thế nhưng rất may là tôi không phải vất vả như thế. Karabiner-Viewer (cài đặt kèm với Karabiner-Elements) đã cho tôi biết các phím có mã như thế nào. Một số phím thì có Page ID là 0x07 (Keyboard Page), một số khác thì là 0x0c (Consumer Page), một số phím do Apple tự định nghĩa thì có Page ID là 0xff (Vendor Page).

Sử dụng Page ID và Usage ID có được, tôi có thể dễ dàng remap phím Application (Menu) thành Fn như sau. Page ID và Usage ID trong lệnh này dùng 64 bit code, nên cần phải padding thêm 0 ở đầu. Riêng Page ID thì không padding cũng không sao 😁.

$ hidutil property --set '{"UserKeyMapping": [{"HIDKeyboardModifierMappingSrc": 0x0700000065, "HIDKeyboardModifierMappingDst": 0xff00000003}]}'

Chạy lệnh này xong sẽ thấy ngay kết quả (Fn trên macOS mặc định dùng để chuyển đổi chế gỗ gõ tiếng Anh, Nhật, Việt nhưng tôi thiết lập lại thành chức năng thêm emoij 😆). Tuy nhiên, nếu chạy lệnh này thì sau khi restart máy, tác dụng của nó sẽ biến mất.

Nếu muốn remap chỉ áp dụng cho bàn phím ngoài (không remap bàn phím đi kèm máy) thì có thể thêm tùy chọn --matching '{"ProductID":<productID>}' vào lệnh hidutil. ProductID có thể lấy được từ System Report > USB.

Để có thể giữ remap này mọi lúc, chúng ta cần đến sự hỗ trợ của LaunchAgents. Việc cần làm là chúng ta tạo một file plist (lần này nhất định phải là plist 😁) trong thư mục ~/Library/LaunchAgents. File này có thể đặt tên bất kỳ (ở đây tôi đặt tên là my.local.remapping.plist) với nội dung như sau:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>my.local.remapping</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/hidutil</string>
        <string>property</string>
        <string>--set</string>
        <string>{"UserKeyMapping":[
            {
              "HIDKeyboardModifierMappingSrc": 0x0700000065,
              "HIDKeyboardModifierMappingDst": 0xff00000003
            }
        ]}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Nội dung file cũng không có gì quá cao siêu. Mỗi khi người dùng login vào máy, launchd sẽ chạy lệnh hidutil (nhờ vào key RunAtLoad) và remap các phím cho chúng ta.

Các phím chức năng F1~F12

Các phím này trên macOS được nhận đúng là F1~F12. Tương tự như phím Menu ở trên, tôi cũng có thể remap các phím này thành các phím media như bàn phím Apple được. Với macOS, chúng ta có thể lấy được mã của các phím này bằng lệnh:

$ ioreg -l | grep "FnFunctionUsageMap" | grep -Eo "0x[0-9a-fA-F]+,0x[0-9a-fA-F]+"
0x0007003a,0x00ff0005
0x0007003b,0x00ff0004
0x0007003c,0xff010010
0x0007003d,0xff010004
0x0007003e,0x00ff0009
0x0007003f,0x00ff0008
0x00070040,0x000c00b4
0x00070041,0x000c00cd
0x00070042,0x000c00b3
0x00070043,0x000c00e2
0x00070044,0x000c00ea
0x00070045,0x000c00e9

Đây là danh sách các phím F1~F12 kèm với các chức năng của nó trên bàn phím mặc định của Apple như tăng/giảm độ sáng màn hình, v.v… Các máy đời mới dùng chip Apple Silicon có thể có kết quả khác, vì tôi được biết những máy này đã đổi lại F4, F5, F6 thành spotlight, dictation và do not disturb (còn trên máy của tôi chúng là Launchpad, tăng/giảm độ sáng bàn phím).

Một lưu ý nhỏ là những giá trị trên kia dùng 32-bit code, 16 bit đầu là Page ID, còn lại là Usage ID. Để có thể dùng với hidutil thì cần thêm padding để đủ 128 bit code.

Sau này tôi tìm được một trang giúp chúng ta sinh ra plist dùng cho LaunchAgent ở đây. Dùng ứng dụng này có thể remap rất nhiều phím mà không cần phải tìm HID code cho từng phím, rất là tiện 👍.

Tuy nhiên, cá nhân tôi cuối cùng lại quyết định giữ nguyên các phím F1~F12. Nguyên nhân là vì việc remap không giúp các phím này có thêm chức năng media control giống như bàn phím của Apple (ví dụ ấn F12 để tăng âm lượng, Fn+F12 để ấn F12) mà chỉ đơn giản là remap phím này thành phím khác (và sẽ mất chức năng của phím gốc).

Với bàn phím của Apple, Fn+F12 không phải là một tổ hợp phím, mà là một chức năng riêng. Ấn F12, bàn phím sẽ gửi tín hiệu là mã của phím tăng âm lượng, ấn Fn+F12, bàn phím sẽ gửi tín hiệu là F12. Nhưng bàn phím của tôi không được thiết kế như thế, và một khi remap thì sẽ mất luôn những phím ban đầu. Vì vậy tôi quyết định giữ nguyên F1~F12 vì tôi dùng chúng thường xuyên hơn.

Update 2023-04-20: Tôi đã tự code một phần mềm cho phép các phím F1~F12 hoạt động tương tự như các phím media của MacBook. Đây là một phần của bộ gõ tiếng Việt mà tôi phát triển dựa trên OpenKey (mã nguồn ở đây).

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.