HID Device端的逻辑稍微复杂一点,需要先设计一下。
首先,我需要一个GUI窗口,它不用显示什么,而是要帮我捕获鼠标和键盘事件。当这个GUI窗口在前端时,如果我的鼠标在这个GUI窗口上移动或点击,或者按下键盘按键,这个GUI窗口必须能接收到这些Input事件。然后,把这些Input事件转译成HID Report,并调用之前实现的蓝牙接口,通过HID Interrupt通道,发送到蓝牙主机那里。
所以,我需要两个角色:一个Input事件的捕获者,以及一个HID事件的报告者。当捕获者抓到一个Input事件后,把它告诉报告者,由报告者调用蓝牙接口发送出去。
Python原生支持的Tkinter模块就能够提供我所需要的GUI支持。下面我就先来实现Input事件的捕获者。
import Tkinter,sys,time import log class Point: def __init__(self, x=0, y=0): self.x = x self.y = y return def __str__(self): return 'Point({0},{1})'.format(self.x,self.y) pass class Key: def __init__(self, char='', keycode=0, keysym='', keysym_num=0, meta=0): self.char = char self.keycode = keycode self.keysym = keysym self.keysym_num = keysym_num self.meta = meta self.ch_val = 0 if len(self.char): self.ch_val = ord(self.char[0]) return def __str__(self): return 'Key({0}({1}), VK={2}, SYM={3}, SYMNUM={4}, META={5})'.format( self.char,self.ch_val,self.keycode, self.keysym,self.keysym_num,self.meta) pass class Reporter: INPUTEVT_MOUSEMOVE = 0 INPUTEVT_MOUSELEFTDOWN = 1 INPUTEVT_MOUSELEFTUP = 2 INPUTEVT_MOUSERIGHTDOWN = 3 INPUTEVT_MOUSERIGHTUP = 4 INPUTEVT_MOUSEMIDDLEDOWN = 5 INPUTEVT_MOUSEMIDDLEUP = 6 INPUTEVT_MOUSEWHEEL = 7 INPUTEVT_KEYDOWN = 8 INPUTEVT_KEYUP = 9 INPUTEVT_MAX = 10 def isValid(self): return True def reportInputEvent(self, itype, e): log.i('itype:{0}, event:{1}'.format(itype, e)) return pass class InputReceptor(Tkinter.Tk): m_lastPoint = None # instance of class Point. m_reporter = None # it reports the input event to elsewhere. def __init__(self, reporter=Reporter()): Tkinter.Tk.__init__(self) self.title("Bluetooth HID Client Test") self.geometry('640x480+100+100') self.minsize(640, 480) self.bind('<Motion>', self.onMouseMoveREL) self.bind('<Button-1>', self.onMouseLeftDown) self.bind('<ButtonRelease-1>', self.onMouseLeftUp) self.bind('<Button-3>', self.onMouseRightDown) self.bind('<ButtonRelease-3>', self.onMouseRightUp) self.bind('<KeyPress>', self.onKeyDown) self.bind('<KeyRelease>', self.onKeyUp) self.bind('<Enter>', self.onEnter) # pointer enters the widget. self.bind('<Leave>', self.onLeave) # pointer leaves the widget. self.m_reporter = reporter return def onMouseMoveREL(self, e): if not self.m_lastPoint: self.m_lastPoint = Point(e.x, e.y) return delta = Point(e.x - self.m_lastPoint.x, e.y - self.m_lastPoint.y) self.m_lastPoint = Point(e.x, e.y) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_MOUSEMOVE, delta) return def onMouseLeftDown(self, e): self.onMouseMoveREL(e) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_MOUSELEFTDOWN, Point()) return def onMouseLeftUp(self, e): self.onMouseMoveREL(e) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_MOUSELEFTUP, Point()) return def onMouseRightDown(self, e): self.onMouseMoveREL(e) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_MOUSERIGHTDOWN, Point()) return def onMouseRightUp(self, e): self.onMouseMoveREL(e) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_MOUSERIGHTUP, Point()) return def onKeyDown(self, e): key = Key(e.char, e.keycode, e.keysym, e.keysym_num, e.state) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_KEYDOWN, key) return def onKeyUp(self, e): key = Key(e.char, e.keycode, e.keysym, e.keysym_num, e.state) self.m_reporter.reportInputEvent(Reporter.INPUTEVT_KEYUP, key) return def onEnter(self, e): return def onLeave(self, e): self.m_lastPoint = None return pass
class Point 和 class Key 是两种数据类型,用来承载鼠标和键盘事件的数据。
class Reporter 只是一个基类,真正的 Reporter 必须继承它,并重新实现它的两个函数。
class InputReceptor 在初始化时创建了真正用于捕获Input事件的GUI窗口,并保存了从构造函数中传入的Reporter的引用。
HID的鼠标设备大多报告的是相对位移,因此 onMouseMoveREL() 成员函数对绝对位置坐了相对化处理,当然这里借助了'<Leave>'这个额外的GUI事件。
对于鼠标点击事件,我暂且把它拆分成Move和Down/Up这两个动作,也就是先报告一个Move事件,把指针Move到位,然后再报告一个按键Down/Up事件。
其他的逻辑都比较易懂。
接下来是另一个关键角色:报告者。
报告者需要报告鼠标事件和键盘事件。鼠标事件相对容易,比较麻烦的是键盘事件。这是因为Linux系统规定的Keycode与USB HID协议规定的Keycode,取值是不同的。例如,在Linux系统中,键盘A键的Keycode是38,而在USB HID协议的规定里,A键的键值是15。我在Linux内核HID相关的代码里,找到了HID Keycode to Linux Keycode的数组,不过我需要的是Linux Keycode to HID Keycode的数组,所以我用写了一个python函数做了一次转换。
# copied from linux/net/bluetooth/hidp/core.c file. hidkey_to_linuxkey = [ 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106, 105, 108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113, 115, 114, 0, 0, 0, 121, 0, 89, 93, 124, 92, 94, 95, 0, 0, 0, 122, 123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115, 114, 113, 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140 ] def show_keycode(keycode_array): size = len(keycode_array) for i in range(size): if (i % 14) == 0: print ' ' print '{0:3d},'.format(keycode_array[i]), pass return def reverse_keycode_array(old_array): size = max(old_array) + 1 print 'keycode array size = {0}'.format(size) new_array = [ 0 ] * size for i in range(len(old_array)): new_array[old_array[i]] = i pass new_array[0] = 0 show_keycode(new_array) return reverse_keycode_array(hidkey_to_linuxkey)
得到的结果:
# reverse from hidkey_to_linuxkey. linuxkey_to_hidkey = [ 0, 41, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 45, 46, 42, 43, 20, 26, 8, 21, 23, 28, 24, 12, 18, 19, 47, 48, 40, 224, 4, 22, 7, 9, 10, 11, 13, 14, 15, 51, 52, 53, 225, 50, 29, 27, 6, 25, 5, 17, 16, 54, 55, 56, 229, 85, 226, 44, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 83, 71, 95, 96, 97, 86, 92, 93, 94, 87, 89, 90, 91, 98, 99, 0, 148, 100, 68, 69, 135, 146, 147, 138, 136, 139, 140, 88, 228, 84, 70, 230, 0, 74, 82, 75, 80, 79, 77, 81, 78, 73, 76, 0, 239, 238, 237, 102, 103, 0, 72, 0, 133, 144, 145, 137, 227, 231, 101, 243, 121, 118, 122, 119, 124, 116, 125, 244, 123, 117, 0, 251, 0, 248, 0, 0, 0, 0, 0, 0, 0, 240, 0, 249, 0, 0, 0, 0, 0, 241, 242, 0, 236, 0, 235, 232, 234, 233, 0, 0, 0, 0, 0, 0, 250, 0, 0, 247, 245, 246, 0, 0, 0, 0, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, ]
有了这个反向数组,当给定一个Linux Keycode时,就可以查表得到其对应的HID Keycode值。于是键盘事件就有了着落。
以下是BthReporter的全部实现。
class MouseEvent: BTN_LEFT = 0x0 BTN_RIGHT = 0x1 BTN_MIDDLE = 0x2 def __init__(self, buttons, x, y, wheel): self.buttons = buttons self.x = x self.y = y self.wheel = wheel return pass class KeybdEvent: KEY_RIGHTMETA = 126 KEY_RIGHTALT = 100 KEY_RIGHTSHIFT = 54 KEY_RIGHTCTRL = 97 KEY_LEFTMETA = 125 KEY_LEFTALT = 56 KEY_LEFTSHIFT = 42 KEY_LEFTCTRL = 29 def __init__(self, modi, keys): self.modifier = modi self.keys = keys return pass class BthReporter(Reporter): m_handle = 0 m_btnState = 0 m_keyModif = 0 m_keyPress = [] def __init__(self): self.m_bth = ctypes.CDLL(os.path.abspath('libbthidd.so')) self.m_bth.bthidd_intr_send.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int] self.m_handle = self.m_bth.bthidd_init() if self.m_handle: self.m_bth.bthidd_accept(self.m_handle) log.i('BthReporter is initiated.') return def __del__(self): if self.m_handle: self.m_bth.bthidd_exit(self.m_handle) return def isValid(self): return (self.m_handle != 0) def m_reportMouseEvent(self, me): pkt = bytearray() pkt.append(0xA1) # always 0xA1 pkt.append(1) # mouse report id pkt.append(me.buttons) # buttons pkt.append(me.x & 0xff) # x pkt.append(me.y & 0xff) # y pkt.append(me.wheel) # wheel self.m_bth.bthidd_intr_send(self.m_handle, str(pkt), len(pkt)) return def m_reportKeybdEvent(self, ke): pkt = bytearray() pkt.append(0xA1) # always 0xA1 pkt.append(2) # keyboard report id pkt.append(ke.modifier) # modifier keys (shift, ctrl, ...) for i in range(len(ke.keys)): pkt.append(ke.keys[i]) for i in range(8-len(ke.keys)): pkt.append(0) log.i('m_reportKeybdEvent(): modi={0}, keys={1}'.format(ke.modifier, ke.keys)) self.m_bth.bthidd_intr_send(self.m_handle, str(pkt), len(pkt)) return def m_isMetaKey(self, keycode): if keycode == KeybdEvent.KEY_RIGHTMETA: return True if keycode == KeybdEvent.KEY_RIGHTALT: return True if keycode == KeybdEvent.KEY_RIGHTSHIFT: return True if keycode == KeybdEvent.KEY_RIGHTCTRL: return True if keycode == KeybdEvent.KEY_LEFTMETA: return True if keycode == KeybdEvent.KEY_LEFTALT: return True if keycode == KeybdEvent.KEY_LEFTSHIFT: return True if keycode == KeybdEvent.KEY_LEFTCTRL: return True return False def m_getModifier(self, keycode): 'return: 0 means keycode is not a modifier key.' modifier = 0 if keycode == KeybdEvent.KEY_RIGHTMETA: modifier = 1<<7 if keycode == KeybdEvent.KEY_RIGHTALT: modifier = 1<<6 if keycode == KeybdEvent.KEY_RIGHTSHIFT: modifier = 1<<5 if keycode == KeybdEvent.KEY_RIGHTCTRL: modifier = 1<<4 if keycode == KeybdEvent.KEY_LEFTMETA: modifier = 1<<3 if keycode == KeybdEvent.KEY_LEFTALT: modifier = 1<<2 if keycode == KeybdEvent.KEY_LEFTSHIFT: modifier = 1<<1 if keycode == KeybdEvent.KEY_LEFTCTRL: modifier = 1<<0 return modifier def m_changeKeyModif(self, isDown, modifier): if isDown: self.m_keyModif |= modifier else : self.m_keyModif &= (~modifier & 0xff) return def m_changeKeyPress(self, isDown, keycode): for i in range(len(self.m_keyPress)): if self.m_keyPress[i] == linuxkey_to_hidkey[keycode]: if not isDown: del self.m_keyPress[i] break else: # append the newly pressed key. if isDown: self.m_keyPress.append(linuxkey_to_hidkey[keycode]) pass return # overwite def reportInputEvent(self, itype, e): if not self.m_handle: return if itype == Reporter.INPUTEVT_MOUSEMOVE: self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseMove: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSELEFTDOWN: self.m_btnState |= (1<<MouseEvent.BTN_LEFT) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseLD: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSELEFTUP: self.m_btnState &= (0x7 & ~(1<<MouseEvent.BTN_LEFT)) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseLU: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSERIGHTDOWN: self.m_btnState |= (1<<MouseEvent.BTN_RIGHT) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseRD: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSERIGHTUP: self.m_btnState &= (0x7 & ~(1<<MouseEvent.BTN_RIGHT)) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseRU: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSEMIDDLEDOWN: self.m_btnState |= (1<<MouseEvent.BTN_MIDDLE) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseMD: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSEMIDDLEUP: self.m_btnState &= (0x7 & ~(1<<MouseEvent.BTN_MIDDLE)) self.m_reportMouseEvent(MouseEvent(self.m_btnState,e.x,e.y,0)) log.i('MouseMU: {0} btns={1}'.format(e, self.m_btnState)) pass elif itype == Reporter.INPUTEVT_MOUSEWHEEL: self.m_reportMouseEvent(MouseEvent(self.m_btnState,0,0,e)) log.i('MouseMW: wheel={0}'.format(e)) pass elif itype == Reporter.INPUTEVT_KEYDOWN: log.i('KeyDN: {0}'.format(e)) keycode = e.keycode - 8 # it just works ... but why? if keycode >= len(linuxkey_to_hidkey): log.e('keycode = {0}, out of hidkey range!'.format(e.keycode)) return modifier = self.m_getModifier(keycode) if modifier: self.m_changeKeyModif(True, modifier) else: self.m_changeKeyPress(True, keycode) self.m_reportKeybdEvent(KeybdEvent(self.m_keyModif, self.m_keyPress)) pass elif itype == Reporter.INPUTEVT_KEYUP: log.i('KeyUP: {0}'.format(e)) keycode = e.keycode - 8 # it just works ... but why? if keycode >= len(linuxkey_to_hidkey): log.e('keycode = {0}, out of hidkey range!'.format(e.keycode)) return modifier = self.m_getModifier(keycode) if modifier: self.m_changeKeyModif(False, modifier) else: self.m_changeKeyPress(False, keycode) self.m_reportKeybdEvent(KeybdEvent(self.m_keyModif, self.m_keyPress)) pass else: Reporter.reportInputEvent(self, itype, e) pass return pass
程序主函数实现起来非常简单:
def hidd_main_entry(): reporter = BthReporter() if not reporter.isValid(): log.e('BthReporter is invalid! exit.') return gui = InputReceptor(reporter) gui.mainloop() return
结束。