From c4feb2ab959b367944c80a7fb44a954443133f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Mon, 4 Jan 2016 08:34:59 +0800 Subject: [PATCH 1/4] fist step --- btk.py | 89 +++++++++++++++++++++++++++------------------------------ dbus.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 dbus.py diff --git a/btk.py b/btk.py index 65125c1..9d2ef5e 100644 --- a/btk.py +++ b/btk.py @@ -1,14 +1,10 @@ #! /usr/bin/env python from __future__ import print_function -import dbus -import dbus.mainloop.glib -import dbus.service -try: - from gi.repository import GObject as gobject -except ImportError: - import gobject +from gi.repository import GLib, Gio +from pydbus import SessionBus, SystemBus +from dbus import Server import os import sys @@ -21,7 +17,6 @@ from IPython import embed import struct -mainloop = None keyboard_dev_paths = glob.glob('/dev/input/by-path/*event-kbd') mouse_dev_paths = glob.glob('/dev/input/by-path/*event-mouse') @@ -53,9 +48,9 @@ class HIDConnection: def __init__(self, ctrl_fd): self.ctrl_fd = ctrl_fd - self.ctrl_io_id = gobject.io_add_watch( + self.ctrl_io_id = GLib.io_add_watch( self.ctrl_fd, - gobject.IO_IN, + GLib.IO_IN, self.ctrl_data_cb ) @@ -109,77 +104,77 @@ def register_intr_sock(self, sock): def close(self): pass -class HIDProfile(dbus.service.Object): +class HIDProfile(Server): + ''' + + + + + + + + + + + + + + ''' conns = {} sock = None def __init__(self, bus, path, sock): - dbus.service.Object.__init__(self, bus, path) - if (sock): - self.sock = sock + Server.__init__(self, bus, path) + self.sock = sock - @dbus.service.method("org.bluez.Profile1", - in_signature="", out_signature="") def Release(self): print("Release") - mainloop.quit() + self.quit() - @dbus.service.method("org.bluez.Profile1", - in_signature="o", out_signature="") - def RequestDisconnection(self, path): + def RequestDisconnection(self, device): print('RequestDisconnection') - conns.pop(path).close() + conns.pop(device).close() - @dbus.service.method("org.bluez.Profile1", - in_signature="oha{sv}", out_signature="") - def NewConnection(self, path, fd, properties): + def NewConnection(self, device, fd, fd_properties): print("new control connectin") - self.conns[path] = HIDConnection(fd.take()) + embed() + self.conns[device] = HIDConnection(fd.take()) def new_intr_conn(ssock, ip_type): sock, info = ssock.accept() print("interrput connection:", info) - self.conns[path].register_intr_sock(sock) - + self.conns[device].register_intr_sock(sock) return False - gobject.io_add_watch(self.sock, gobject.IO_IN, new_intr_conn) - + GLib.io_add_watch(self.sock, GLib.IO_IN, new_intr_conn) def main(): - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - - bus = dbus.SystemBus() - obj_path = '/cn/lvht/bluez/HIDProfile' + bus = SystemBus() + bus.own_name('net.lvht.btk') + obj_path = '/net/lvht/btk/HIDProfile' sock = bt.BluetoothSocket(bt.L2CAP) sock.setblocking(False) try: sock.bind(('', PSM_INTR)) except: - print("Someone has the bluetooth HID socket open, it might be bluetoothd") print("For bluez5 add --noplugin=input to the bluetoothd commandline") - print("For bluez4 add PluginsDisable=input to /etc/bluetooth/main.conf") print("Else there is another application running that has it open.") sys.exit(errno.EACCES) sock.listen(1) - profile = HIDProfile(bus, obj_path, sock) + profile = HIDProfile(bus.con, obj_path, sock) opts = { - "PSM": dbus.UInt16(PSM_CTRL), - "ServiceRecord": open('./sdp_record.xml', 'r').read(), - "RequireAuthentication": dbus.Boolean(True), - "RequireAuthorization": dbus.Boolean(False), + "PSM": GLib.Variant.new_uint16(PSM_CTRL), + "ServiceRecord": GLib.Variant.new_string(open('./sdp_record.xml', 'r').read()), + "RequireAuthentication": GLib.Variant.new_boolean(True), + "RequireAuthorization": GLib.Variant.new_boolean(False), } - dbus.Interface( - bus.get_object("org.bluez", "/org/bluez"), - "org.bluez.ProfileManager1" - ).RegisterProfile(obj_path, str(uuid.uuid4()), opts) - - gobject.MainLoop().run() + manager = bus.get('org.bluez')['.ProfileManager1'] + manager.RegisterProfile(obj_path, str(uuid.uuid4()), opts) + profile.run() if __name__ == '__main__': main() - diff --git a/dbus.py b/dbus.py new file mode 100644 index 0000000..7adaa23 --- /dev/null +++ b/dbus.py @@ -0,0 +1,70 @@ +from gi.repository import GLib, Gio + +class Server(object): + def __init__(self, bus, path): + self.loop = GLib.MainLoop() + + interface_info = Gio.DBusNodeInfo.new_for_xml(self.__doc__).interfaces[0] + + method_outargs = {} + method_inargs = {} + for method in interface_info.methods: + method_outargs[method.name] = '(' + ''.join([arg.signature for arg in method.out_args]) + ')' + method_inargs[method.name] = tuple(arg.name for arg in method.in_args) + + self.method_inargs = method_inargs + self.method_outargs = method_outargs + + bus.register_object(object_path=path, interface_info=interface_info, method_call_closure=self.on_method_call) + + def run(self): + self.loop.run() + + def quit(self): + self.loop.quit() + + def on_method_call(self, + connection, + sender, + object_path, + interface_name, + method_name, + parameters, + invocation): + + import IPython + IPython.embed() + kwargs = dict(zip(self.method_inargs[method_name], parameters.unpack())) + result = getattr(self, method_name)(**kwargs) + + if type(result) is list: + result = tuple(result) + elif not type(result) is tuple: + result = (result,) + + invocation.return_value(GLib.Variant(self.method_outargs[method_name], result)) + +class Foo(Server): + ''' + + + + + + + + + + + ''' + def HelloWorld(self, a, b): + return ('+' + a, '+{}'.format(b)) + + +if __name__ == '__main__': + from pydbus import SessionBus + bus = SessionBus() + bus.own_name(name = 'net.lvht') + + foo = Foo(bus=bus.con, path='/net/lvht/Foo') + foo.run() From 983903c71cc8d92312abc0812b038ecb77e81325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Tue, 5 Jan 2016 05:01:25 +0800 Subject: [PATCH 2/4] remove dependence for python-dbus --- agent.py | 132 ++++++++++++++++++++++------------------------- btk.py | 4 +- dbus.py | 18 ++++--- requirements.txt | 1 + 4 files changed, 75 insertions(+), 80 deletions(-) diff --git a/agent.py b/agent.py index 6c57a8b..81eece9 100644 --- a/agent.py +++ b/agent.py @@ -1,9 +1,6 @@ from __future__ import print_function -#from future.builtins import input + import sys -import dbus -import dbus.service -import dbus.mainloop.glib import evdev as ev import glob @@ -11,57 +8,77 @@ import uuid import bluetooth as bt -try: - from gi.repository import GObject as gobject -except ImportError: - import gobject +from gi.repository import GLib +from pydbus import SystemBus +from dbus import Server -BUS_NAME = 'org.bluez' -AGENT_INTERFACE = 'org.bluez.Agent1' -AGENT_PATH = "/test/agent" +bus = SystemBus() def ask(prompt): return 1111 # input(prompt) def set_trusted(path): - props = dbus.Interface(bus.get_object("org.bluez", path), - "org.freedesktop.DBus.Properties") + props = bus.get('org.bluez', path)['org.freedesktop.DBus.Properties'] print('trust', path) - props.Set("org.bluez.Device1", "Trusted", True) + props.Set("org.bluez.Device1", "Trusted", GLib.Variant.new_boolean(True)) def dev_connect(path): - dev = dbus.Interface(bus.get_object("org.bluez", path), - "org.bluez.Device1") + dev = bus.get('org.bluez', path)['org.bluez.Device1'] dev.Connect() -class Rejected(dbus.DBusException): - _dbus_error_name = "org.bluez.Error.Rejected" -class Agent(dbus.service.Object): - @dbus.service.method(AGENT_INTERFACE, - in_signature="", out_signature="") +class Agent(Server): + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' def Release(self): print("Release") - @dbus.service.method(AGENT_INTERFACE, - in_signature="os", out_signature="") def AuthorizeService(self, device, uuid): print("AuthorizeService (%s, %s)" % (device, uuid)) authorize = ask("Authorize connection (yes/no): ") if (authorize == "yes"): return - raise Rejected("Connection rejected by user") - @dbus.service.method(AGENT_INTERFACE, - in_signature="o", out_signature="s") def RequestPinCode(self, device): print("RequestPinCode (%s)" % (device)) set_trusted(device) return ask("Enter PIN Code: ") - @dbus.service.method(AGENT_INTERFACE, - in_signature="o", out_signature="u") def RequestPasskey(self, device): print("RequestPasskey (%s)" % (device)) passkey = "" @@ -81,60 +98,35 @@ def RequestPasskey(self, device): passkey = passkey + key set_trusted(device) - return dbus.UInt32(passkey) + return int(passkey) - @dbus.service.method(AGENT_INTERFACE, - in_signature="ouq", out_signature="") def DisplayPasskey(self, device, passkey, entered): print("DisplayPasskey (%s, %06u entered %u)" % (device, passkey, entered)) - @dbus.service.method(AGENT_INTERFACE, - in_signature="os", out_signature="") def DisplayPinCode(self, device, pincode): print("DisplayPinCode (%s, %s)" % (device, pincode)) - @dbus.service.method(AGENT_INTERFACE, - in_signature="ou", out_signature="") def RequestConfirmation(self, device, passkey): print("RequestConfirmation (%s, %06d)" % (device, passkey)) confirm = ask("Confirm passkey ([y]/n): ") - if (confirm != "n"): - set_trusted(device) - return - raise Rejected("Passkey doesn't match") - @dbus.service.method(AGENT_INTERFACE, - in_signature="o", out_signature="") def RequestAuthorization(self, device): print("RequestAuthorization (%s)" % (device)) auth = ask("Authorize? ([y]/n): ") - if (auth != "n"): - return - raise Rejected("Pairing rejected") - @dbus.service.method(AGENT_INTERFACE, - in_signature="", out_signature="") def Cancel(self): print("Cancel") if __name__ == '__main__': - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - bus = dbus.SystemBus() - - props = dbus.Interface( - bus.get_object("org.bluez", '/org/bluez/hci0'), - "org.freedesktop.DBus.Properties" - ) - props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(True)) - props.Set("org.bluez.Adapter1", "Discoverable", dbus.Boolean(True)) - + props = bus.get('org.bluez', '/org/bluez/hci0')['org.freedesktop.DBus.Properties'] + props.Set("org.bluez.Adapter1", "Powered", GLib.Variant.new_boolean(True)) + props.Set("org.bluez.Adapter1", "Discoverable", GLib.Variant.new_boolean(True)) capability = "KeyboardOnly" - path = "/test/agent" - agent = Agent(bus, path) - obj = bus.get_object('org.bluez', "/org/bluez"); - manager = dbus.Interface(obj, "org.bluez.AgentManager1") + path = "/net/lvht/btk/agent" + agent = Agent(bus.con, path) + manager = bus.get('org.bluez')['.AgentManager1'] manager.RegisterAgent(path, capability) manager.RequestDefaultAgent(path) @@ -144,18 +136,16 @@ def Cancel(self): sock.bind(('', PSM_INTR)) sock.listen(1) - obj_path = '/ml/jlyu/HIDProfile' - profile = HIDProfile(bus, obj_path, sock) + obj_path = '/net/lvht/btk/HIDProfile' + profile = HIDProfile(bus.con, obj_path, sock) opts = { - "PSM": dbus.UInt16(PSM_CTRL), - "ServiceRecord": open('./sdp_record.xml', 'r').read(), - "RequireAuthentication": dbus.Boolean(True), - "RequireAuthorization": dbus.Boolean(True), + "PSM": GLib.Variant.new_uint16(PSM_CTRL), + "ServiceRecord": GLib.Variant.new_string(open('./sdp_record.xml', 'r').read()), + "RequireAuthentication": GLib.Variant.new_boolean(True), + "RequireAuthorization": GLib.Variant.new_boolean(True), } - dbus.Interface( - bus.get_object("org.bluez", "/org/bluez"), - "org.bluez.ProfileManager1" - ).RegisterProfile(obj_path, str(uuid.uuid4()), opts) + manager = bus.get('org.bluez')['.ProfileManager1'] + manager.RegisterProfile(obj_path, str(uuid.uuid4()), opts) - gobject.MainLoop().run() + GLib.MainLoop().run() diff --git a/btk.py b/btk.py index 9d2ef5e..3fbb53e 100644 --- a/btk.py +++ b/btk.py @@ -14,7 +14,6 @@ import glob from inputdev import Keyboard, Mouse -from IPython import embed import struct keyboard_dev_paths = glob.glob('/dev/input/by-path/*event-kbd') @@ -137,8 +136,7 @@ def RequestDisconnection(self, device): def NewConnection(self, device, fd, fd_properties): print("new control connectin") - embed() - self.conns[device] = HIDConnection(fd.take()) + self.conns[device] = HIDConnection(fd) def new_intr_conn(ssock, ip_type): sock, info = ssock.accept() diff --git a/dbus.py b/dbus.py index 7adaa23..8a03e5e 100644 --- a/dbus.py +++ b/dbus.py @@ -10,7 +10,7 @@ def __init__(self, bus, path): method_inargs = {} for method in interface_info.methods: method_outargs[method.name] = '(' + ''.join([arg.signature for arg in method.out_args]) + ')' - method_inargs[method.name] = tuple(arg.name for arg in method.in_args) + method_inargs[method.name] = tuple(arg.signature for arg in method.in_args) self.method_inargs = method_inargs self.method_outargs = method_outargs @@ -32,17 +32,23 @@ def on_method_call(self, parameters, invocation): - import IPython - IPython.embed() - kwargs = dict(zip(self.method_inargs[method_name], parameters.unpack())) - result = getattr(self, method_name)(**kwargs) + args = list(parameters.unpack()) + for i, sig in enumerate(self.method_inargs[method_name]): + if sig is 'h': + msg = invocation.get_message() + fd_list = msg.get_unix_fd_list() + args[i] = fd_list.get(args[i]) + + result = getattr(self, method_name)(*args) if type(result) is list: result = tuple(result) elif not type(result) is tuple: result = (result,) - invocation.return_value(GLib.Variant(self.method_outargs[method_name], result)) + out_args = self.method_outargs[method_name] + if out_args != '()': + invocation.return_value(GLib.Variant(out_args, result)) class Foo(Server): ''' diff --git a/requirements.txt b/requirements.txt index 9e10bea..0a352e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ future evdev PyBluez +pydbus From d0c87da7dff4254580e8813d40e189bce84256c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Wed, 6 Jan 2016 08:51:32 +0800 Subject: [PATCH 3/4] merge hid profile boot code --- agent.py | 37 ++++++++++--------------------------- btk.py | 4 ++-- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/agent.py b/agent.py index 81eece9..e2810d5 100644 --- a/agent.py +++ b/agent.py @@ -1,22 +1,16 @@ from __future__ import print_function -import sys import evdev as ev import glob -from btk import HIDProfile, PSM_CTRL, PSM_INTR -import uuid import bluetooth as bt from gi.repository import GLib - from pydbus import SystemBus from dbus import Server -bus = SystemBus() - def ask(prompt): - return 1111 # input(prompt) + return 1111 def set_trusted(path): props = bus.get('org.bluez', path)['org.freedesktop.DBus.Properties'] @@ -27,7 +21,6 @@ def dev_connect(path): dev = bus.get('org.bluez', path)['org.bluez.Device1'] dev.Connect() - class Agent(Server): ''' @@ -118,11 +111,12 @@ def RequestAuthorization(self, device): def Cancel(self): print("Cancel") -if __name__ == '__main__': +def open_hci(): props = bus.get('org.bluez', '/org/bluez/hci0')['org.freedesktop.DBus.Properties'] props.Set("org.bluez.Adapter1", "Powered", GLib.Variant.new_boolean(True)) props.Set("org.bluez.Adapter1", "Discoverable", GLib.Variant.new_boolean(True)) +def register_agent(): capability = "KeyboardOnly" path = "/net/lvht/btk/agent" agent = Agent(bus.con, path) @@ -130,22 +124,11 @@ def Cancel(self): manager.RegisterAgent(path, capability) manager.RequestDefaultAgent(path) +if __name__ == '__main__': + bus = SystemBus() + open_hci() + register_agent() + print('start hid') - sock = bt.BluetoothSocket(bt.L2CAP) - sock.setblocking(False) - sock.bind(('', PSM_INTR)) - sock.listen(1) - - obj_path = '/net/lvht/btk/HIDProfile' - profile = HIDProfile(bus.con, obj_path, sock) - - opts = { - "PSM": GLib.Variant.new_uint16(PSM_CTRL), - "ServiceRecord": GLib.Variant.new_string(open('./sdp_record.xml', 'r').read()), - "RequireAuthentication": GLib.Variant.new_boolean(True), - "RequireAuthorization": GLib.Variant.new_boolean(True), - } - manager = bus.get('org.bluez')['.ProfileManager1'] - manager.RegisterProfile(obj_path, str(uuid.uuid4()), opts) - - GLib.MainLoop().run() + import btk + btk.loop() diff --git a/btk.py b/btk.py index 3fbb53e..f9abe4e 100644 --- a/btk.py +++ b/btk.py @@ -146,7 +146,7 @@ def new_intr_conn(ssock, ip_type): GLib.io_add_watch(self.sock, GLib.IO_IN, new_intr_conn) -def main(): +def loop(): bus = SystemBus() bus.own_name('net.lvht.btk') obj_path = '/net/lvht/btk/HIDProfile' @@ -175,4 +175,4 @@ def main(): profile.run() if __name__ == '__main__': - main() + loop() From 9320cf010f9957c43b6a507f51da52c3f7a8d3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Wed, 6 Jan 2016 08:57:48 +0800 Subject: [PATCH 4/4] update README --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 488f5c0..e506343 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ HIDP协议栈全文可以从蓝牙官网下载到,但是是英文的,啃吧 - 无法支持多设备连接 - 无法很优雅地处理设备断掉连接的情况 -- 无法独自完成设备之间的认证和授权(依赖GNOME)工作 - 主程序不支持配置选项 - 不支持systemd集成 @@ -43,18 +42,18 @@ HIDP协议栈全文可以从蓝牙官网下载到,但是是英文的,啃吧 - 蓝牙硬件 - BlueZ(>=5) - python(>=2.7) +- [PyGObject](https://live.gnome.org/PyGObject) - [evdev](https://pypi.python.org/pypi/evdev) -- [dbus-python](https://pypi.python.org/pypi/dbus-python) +- [pydbus]() - [PyBlueZ](https://pypi.python.org/pypi/PyBluez) - iOS或者Android设备 -系统准备好之后需要对源代码做少许修改。我的电脑的键盘设备节点是`/dev/input/event3`,所以我就将路径硬编码到程序中了。大家需要找到自己的键盘设备节点,然后对btk.py中的第13行做相应更改。 - -```py -keyboard = kb.Keyboard('/dev/input/event3') +首先,运行脚本: +```bash +sudo python agent.py ``` -启动蓝牙之后以root用户运行btk.py脚本就可以了。 +然后使用手机搜索蓝牙,找到你的主机点击配对。这时agent.py脚本会提示你输入配对码。直接输入并回车即可开启体验之旅。 键鼠HID报告描述符 =================