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('', self.onMouseMoveREL)
self.bind('', self.onMouseLeftDown)
self.bind('', self.onMouseLeftUp)
self.bind('', self.onMouseRightDown)
self.bind('', self.onMouseRightUp)
self.bind('', self.onKeyDown)
self.bind('', self.onKeyUp)
self.bind('', self.onEnter) # pointer enters the widget.
self.bind('', 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() 成员函数对绝对位置坐了相对化处理,当然这里借助了'
对于鼠标点击事件,我暂且把它拆分成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<= 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
结束。