ทำให้วินโดวส์เปลี่ยนภาษาด้วย Caps Lock ในระดับฮาร์ดแวร์

by lew
10 March 2013 - 18:58

ฟีเจอร์หนึ่งที่สำคัญมากของลินุกซ์ คือ ความสามารถในการปรับแต่งปุ่มเปลี่ยนภาษาให้เป็นปุ่มอะไรก็ได้ รวมถึงปุ่ม Caps Lock (ปุ่มที่มักสร้างความสับสนเวลาพิมพ์รหัสผ่านต่างๆ เสมอ) และส่วนตัวผมเองแล้วจะมีคอมพิวเตอร์สองเครื่อง เครื่องหนึ่งเป็นวินโดวส์ และเครื่องหนึ่งเป็นลินุกซ์เพื่อเรียนรู้หลายระบบปฎิบัติการไปพร้อมๆ กัน และข้อขัดใจที่สุดของวินโดวส์คือมันบังคับให้เปลี่ยนภาษาคีย์บอร์ดด้วยปุ่ม Alt+Shift หรือปุ่ม grave accent (~) เท่านั้น ซึ่งทั้งสองตัวไม่ใช่ตัวเลือกที่ผมสะดวก

ทางออกที่ง่ายๆ ของการปรับให้ใช้ Caps Lock ได้อีกครั้งคือการใช้โปรแกรม Auto Hotkeys (ดู nuuneoi.com) แต่จากการใช้งานจริงพบว่ามีข้อจำกัดเช่นไม่สามารถใช้ในบางหน้าจอเช่นจอล็อกอินได้ รวมถึงมีปัญหากับบางหน้าเว็บอยู่เสมอๆ

ทางออกหนึ่งที่ผมคิดไว้ คือ หากเราสามารถสร้างคีย์บอร์ดที่ยิงสัญญาณปุ่ม Alt+Shift ได้เองทุกครั้งที่มีการเปิดไฟ Caps Lock ก็จะสามารถกำหนดปุ่มเปลี่ยนภาษาเป็น Caps Lock แทนได้ ที่จริงแล้วด้วยแนวคิดนี้เราสามารถเปลี่ยนปุ่ม Caps Lock เป็นปุ่มใดๆ แทนก็ได้

ฮาร์ดแวร์: Atmel AT90USB162

จากการค้นคว้าระยะเวลาหนึ่ง แนวคิดของเรื่องนี้ก็เป็นจริง โดยสิ่งที่ผมต้องการคือไมโครคอนโทรลเลอร์ที่ไม่มีอินพุตใดๆ แต่มีส่วนสื่อสาร USB ในตัว และพบว่าอุปกรณ์แนวนี้มีอยู่จำนวนมากในตลาด เพราะมันคือ PlayStation Jailbreak ที่ภายในเป็นชิป AT90USB162 ต่อกับพอร์ต USB เพื่อส่งคำสั่ง USB ที่เป็นบั๊กของเครื่อง PlayStation 3 ให้สามารถรันซอฟต์แวร์จากผู้ผลิตอื่นที่ไม่ใช่โซนี่ได้ ในกรณีของผม ผมสั่ง ps3ukey มาลองใช้งาน โดยที่ตัวเครื่องไม่มีเฟิร์มแวร์มาให้ (น่าจะเพื่อเลี่ยงปัญหาทางกฎหมายในหลายประเทศ)

ชิป AT90USB162 นั้นเป็นชิปที่น่าสนใจ ในตัวมันเองมี bootloader มาให้ในตัว ทำให้สามารถเขียนโปรแกรมลงไปใหม่ได้ผ่าน USB ทันที หากรีเซ็ตชิปโดยหากดึงสัญญาณขา HWB (Hardware Boot) เป็น 0 ขณะที่กำลังรีเซ็ต คอมพิวเตอร์จะมองเห็นเป็นอุปกรณ์ USB เฉพาะของทาง Atmel เองและสามารถลงไดร์เวอร์เพื่อลงเฟิร์มแวร์ใหม่ได้ทางโปรแกรม FLIP ของทาง Atmel หรือโปรแกรม dfu-programmer ที่เป็นโอเพนซอร์สในลินุกซ์หลายดิสโทร (โดยส่วนตัวผมว่าการลง dfu-programmer บนลินุกซ์ง่ายกว่ามาก)

ซอฟต์แวร์: LUFA

แม้ว่าตัวชิป AT90USB162 จะมีส่วนสื่อสารด้วย USB อยู่ในตัวก็ตาม แต่การเรียกฟังก์ชั่นการทำงานของฮาร์ดแวร์โดยตรงก็ยังเป็นเรื่องยาก และต้องเข้าใจพื้นฐานการทำงานของโปรโตคอล USB โดยไม่จำเป็น ในส่วนนี้มีโครงการโอเพนซอร์สที่ชื่อว่า LUFA (Lightweight USB Framework for AVRs) ที่ช่วยจัดการการทำงานในระดับล่างให้

ตัว LUFA เองนั้นมีฐานข้อมูลของชิป และบอร์ดพัฒนาต่างๆ จำนวนมาก หากเราพัฒนาแอพพลิเคชั่นบนชิปและบอร์ดที่ความสามารถใกล้เคียงกัน ก็มีความเป็นไปได้ว่าจะสามารถย้ายซอฟต์แวร์ไปรันบนแพลตฟอร์มอื่นๆ ได้โดยเปลี่ยนเพียงค่าคอนฟิคเท่านั้น

ตัว LUFA เฟรมเวิร์คสำหรับการพัฒนาอุปกรณ์ USB มาให้สองแบบ คือ ClassDriver และ LowLevel สำหรับการพัฒนาทั่วไปนั้นการใช้ ClassDriver จะเขียนโค้ดสั้นกว่าและเข้าใจง่ายกว่ามาก ในส่วนของโครงการเดโมนั้นมีการสร้างคีย์บอร์ดจากบอร์ดพัฒนามาให้เรียบร้อย ทำให้เราสามารถสร้างจอยสติ๊กที่ส่งค่าเป็นคีย์บอร์ด WASD ได้

ในลินุกซ์นั้นการคอมไพล์โครงการเหล่านี้ทำได้ง่ายๆ ด้วยการติดตั้งแพ็กเกจ gcc-avr และ avr-libc พร้อมกับแพ็กเกจ build-essential (สำหรับการ make) เสียก่อน จากนั้นก็สั่ง make ได้เลย

ดัดแปลง

โครงการเดโมของ LUFA สำหรับการเดโมคีย์บอร์ดนั้นจะใช้ชิป AT90USB1287 ที่อยู่บนบอร์ด USBKEY ของ Atmel เอง เริ่มต้นสิ่งที่ต้องแก้ คือ makefile ที่ชื่อชิป (ช่อง MCU) ต้องเปลี่ยนเป็น at90usb162 ส่วนบอร์ดนั้นเนื่องจากเราไม่ได้ใช้ความสามารถอื่น เช่น LED ก็ไม่จำเป็นต้องเปลี่ยน

ไฟล์ที่ต้องแก้ต่อมาคือ Keyboard.h ที่จะต้องคอมเมนต์หรือลบสามไฟล์ออก เพื่อให้คอมไพล์ผ่าน

{syntaxhighlighter brush:cpp} #include <LUFA/Drivers/USB/USB.h>
//#include <LUFA/Drivers/Board/Joystick.h>
//#include <LUFA/Drivers/Board/Buttons.h>
//#include <LUFA/Drivers/Board/LEDs.h>{/syntaxhighlighter}

จากนั้นกลับมาดูที่ไฟล์ Keyboard.c สิ่งแรกที่เราต้องทำคือการคอมเมนต์หรือลบฟังก์ชั่นที่เกี่ยวกับ LED, joystick และ button ออกทั้งหมดตามที่เราคอมเมนต์ไฟล์ header ทิ้งไป เช่น ฟังก์ชั่นที่เกี่ยวกับ LED

ในการทำงานจริงเราต้องการเพิ่มกระบวนการสองอย่าง ได้แก่ การตรวจสอบว่ามีการกด Caps Lock หรือไม่ผ่านทางสัญญาณไฟ Caps Lock สังเกตว่าเมื่อเรากด Caps Lock บนคีย์บอร์ดตัวหนึ่ง ไฟจะติดบนคีย์บอร์ดทุกตัว เพราะระบบปฎิบัติการจะยิงข้อความไปบอกให้คีย์บอร์ดทุกตัวเปิดไฟ เราจะอาศัยพฤติกรรมนี้ในการเปลี่ยนปุ่ม Caps Lock เป็นปุ่มเปลี่ยนภาษา ภายใต้เฟรมเวิร์ค ClassDriver นั้นกำหนดให้ฟังก์ชั่น CALLBACK_HID_Device_ProcessHIDReport เป็นฟังก์ชั่นรับคำสั่งจากเครื่องแม่ (USB Host)

{syntaxhighlighter brush:cpp}static bool isGotCapLockSignal = false;
static bool isCancelledCaplock = false;
void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
const uint8_t ReportID,
const uint8_t ReportType,
const void* ReportData,
const uint16_t ReportSize)
{
uint8_t LEDMask = LEDS_NO_LEDS;
uint8_t* LEDReport = (uint8_t*)ReportData;
if (*LEDReport & HID_KEYBOARD_LED_CAPSLOCK){
PORTD = 0xFF;
isGotCapLockSignal = true;
}else{
PORTD = 0x00;
}
}{/syntaxhighlighter}

เริ่มต้นจากการสร้างตัวแปรสองตัว เพื่อเก็บสถานะว่าเราได้รับคำสั่งให้เปิดไฟ Caps Lock หรือไม่ ถ้าได้รับก็จะส่งสัญญาณ Caps Lock กลับไป เพื่อล้างคำสั่งเดิมทิ้ง หลังจากล้างคำสั่งแล้วจึงส่งคำสั่งใหม่ตามที่เราต้องการ (ในกรณีนี้คือคำสั่งเปลี่ยนภาษา) สำหรับฟังก์ชั่นในการส่งสัญญาณการกดปุ่มบนคีย์บอร์ด ในเฟรมเวิร์ค ClassDriver จะใช้ชื่อฟังก์ชั่น CALLBACK_HID_Device_CreateHIDReport

{syntaxhighlighter brush:cpp}bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
uint8_t* const ReportID,
const uint8_t ReportType,
void* ReportData,
uint16_t* const ReportSize)
{
USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData;
static uint8_t sendcount = 0;
uint8_t UsedKeyCodes = 0;
if(isGotCapLockSignal){
KeyboardReport->KeyCode[UsedKeyCodes++] = HID_KEYBOARD_SC_CAPS_LOCK;
isGotCapLockSignal = false;
isCancelledCaplock = true;
PORTD = 0xFF;
PORTB = 0xFF;
}else if(isCancelledCaplock){
KeyboardReport->Modifier = HID_KEYBOARD_MODIFIER_LEFTSHIFT | HID_KEYBOARD_MODIFIER_LEFTALT;
if(sendcount < 50){
PORTD = 0x00;
PORTB = 0xFF;
sendcount++;
}else{
isGotCapLockSignal = false;
isCancelledCaplock = false;
sendcount = 0;
}
}else{
PORTD = 0x00;
PORTB = 0x00;
}
*ReportSize = sizeof(USB_KeyboardReport_Data_t);
return true;
}{/syntaxhighlighter}

ฟังก์ชั่นนี้จะทำหน้าที่สองอย่างได้แก่ส่งสัญญาณ Caps Lock เมื่อได้รับสัญญาณให้เปิดไฟ ซึ่งหลังจากส่งสัญญาณกลับไป ก็จะได้รับสัญญาณให้ปิดไฟตามมา จากนั้นจึงเข้าสู่ช่วงคำสั่ง Alt+Shift เพื่อเปลี่ยนภาษา จากการทดลองพบว่าจะต้องเปลี่ยนภาษานานเป็นระยะเวลาหนึ่ง หากกระบวนการเร็วเกินไป จะกลายเป็นเปลี่ยนภาษาไปมา ในกรณีนี้คือให้ส่งสัญญาณเปลี่ยนภาษาไป 50 รอบคำสั่ง

การแฟลชโปรแกรมใน Ubuntu

ใน Ubuntu รุ่น 12.10 ที่ผมใช้งาน ไม่รู้จักชิป AT90USB162 นี้แต่แรก ทำให้ต้องเขียนกฎการใช้งานใหม่ ลงในไฟล์ /etc/udev/rules.d/60-at90usb162.rules

# This file gives all users permission to update firmware for an AT90USB162
# device in DFU mode
SUBSYSTEM=="usb", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ffa", GROUP="users", MODE="0666"

หลังจากนั้นสั่ง service udev restart เป็นอันเรียบร้อย

เมื่อ make เฟิร์มแวร์ที่เราต้องการเสร็จแล้ว จะได้ไฟล์ออกมาจำนวนมาก แต่ไฟล์ที่ใช้งานจริงๆ คือ ไฟล์ Keyboard.nex เอาไว้สำหรับอัพโหลดขึ้นชิป เมื่อเสียบ dongle เข้าสู่บอร์ด หากไม่แน่ใจให้กดปุ่มโปรแกรมบนบอร์ดอีกครั้ง แล้วตรวจสอบสถานะด้วยคำสั่ง

sudo dfu-programmer at90usb162 get

หากมีการตอบกลับเลขรุ่น bootloader ก็แสดงว่าใช้งานได้แล้ว สามารถสั่งคำสั่งแฟลชเฟิร์มแวร์ลงไปต่อได้

sudo dfu-programmer at90usb162 erase
sudo dfu-programmer at90usb162 flash Keyboard.hex
sudo dfu-programmer at90usb162 reset

ตัว dongle ก็จะได้รับเฟิร์มแวร์ใหม่พร้อมใช้งาน

พัฒนาต่อ

แม้โครงการนี้จะพัฒนาขึ้นจากความรำคาญเล็กๆ แต่ในความเป็นจริงแล้วการเรียนรู้การพัฒนาอุปกรณ์ USB ด้วยตัวเองเปิดความเป็นไปได้จำนวนมาก เช่น การพัฒนาอุปกรณ์อินพุตอัตโนมัติรูปแบบต่างๆ ที่เราเห็นจำนวนมากได้แก่ เครื่องอ่านบาร์โค้ด และบัตรแม่เหล็กต่างๆ เราอาจจะพัฒนาเครื่องบันทึกค่าต่างๆ ในสิ่งแวดล้อมเช่นอุณหภูมิแล้วส่งค่าผ่านทาง USB สามารถบันทึกค่าลงโปรแกรม notepad ได้โดยง่าย ตลอดจนเราอาจจะสำรวจความปลอดภัยในรูปแบบต่างๆ เช่น การที่โปรแกรมบนเดสก์ทอปไม่มีการป้องกันการโจมตีแบบ brute force แฮกเกอร์อาจจะส่งรหัสผ่านมาเป็นสัญญาณคีย์บอร์ดอย่างรวดเร็วเป็นจำนวนมากๆ ได้

อุปกรณ์เช่นนี้ยังทำให้การออกแบบระบบรักษาความปลอดภัย ต้องคำนึงถึงการปิดพอร์ต USB ที่ไม่ใช้งานทั้งหมด แฮกเกอร์ที่อาจจะเป็นคนภายในด้วยกันเองอาจจะเสียบพอร์ต USB เอาไว้เพื่อตั้งเวลาให้ทำงานเมื่อถึงเวลาที่ต้องการ หรือมีสัญญาณกระตุ้นบางอย่าง เช่น ไฟ Caps Lock ที่ใช้ในบทความนี้ แล้วส่งคำสั่งตามต้องการโดยที่ผู้ใช้จริงเป็นผู้ล็อกอินระบบให้

Blognone Jobs Premium