PythonプログラミングBluetooth HID Mouse and Keyboard(四)


HIDデバイス側の論理は少し複雑で、まず設計する必要があります.
まず、何も表示するのではなく、マウスとキーボードのイベントをキャプチャするGUIウィンドウが必要です.このGUIウィンドウがフロントエンドにあるとき、私のマウスがこのGUIウィンドウで移動したりクリックしたり、キーボードボタンを押したりした場合、このGUIウィンドウはこれらのInputイベントを受信できる必要があります.その後、これらのInputイベントをHID Reportに変換し、以前に実装されたBluetoothインタフェースを呼び出し、HID Interruptチャネルを介してBluetoothホストに送信します.
だから、Inputイベントのキャプチャ者とHIDイベントのレポート者の2つの役割が必要です.キャプチャ者がInputイベントを捕まえた後、それを報告者に伝え、報告者がBluetoothインタフェースを呼び出して送信します.
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は、マウスとキーボードイベントのデータを運ぶ2つのデータ型です.
class Reporterはベースクラスにすぎず、本当のReporterはそれを継承し、2つの関数を再実現する必要があります.
class InputReceptorは、初期化時に本当にInputイベントをキャプチャするためのGUIウィンドウを作成し、コンストラクション関数から入力されたReporterの参照を保存します.
HIDのマウス装置の多くは相対変位を報告しているので,onMouseMoveREL()メンバー関数は絶対位置に対して相対化処理を行い,もちろんここでは''という追加のGUIイベントを借りた.
マウスクリックイベントについては、MoveとDown/Upの2つの動作に分割します.つまり、まず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

終わります.