btk_server.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/python3
  2. #
  3. # YAPTB Bluetooth keyboard emulator DBUS Service
  4. #
  5. # Adapted from
  6. # www.linuxuser.co.uk/tutorials/emulate-bluetooth-keyboard-with-the-raspberry-pi
  7. #
  8. #
  9. # from __future__ import absolute_import, print_function, unicode_literals
  10. from __future__ import absolute_import, print_function
  11. from optparse import OptionParser, make_option
  12. import os
  13. import sys
  14. import uuid
  15. import dbus
  16. import dbus.service
  17. import dbus.mainloop.glib
  18. import time
  19. import socket
  20. # import bluetooth
  21. # from bluetooth import *
  22. import gtk
  23. from dbus.mainloop.glib import DBusGMainLoop
  24. #
  25. # define a bluez 5 profile object for our keyboard
  26. #
  27. class BTKbBluezProfile(dbus.service.Object):
  28. fd = -1
  29. @dbus.service.method("org.bluez.Profile1", in_signature="", out_signature="")
  30. def Release(self):
  31. print("Release")
  32. mainloop.quit()
  33. @dbus.service.method("org.bluez.Profile1", in_signature="", out_signature="")
  34. def Cancel(self):
  35. print("Cancel")
  36. @dbus.service.method("org.bluez.Profile1", in_signature="oha{sv}", out_signature="")
  37. def NewConnection(self, path, fd, properties):
  38. self.fd = fd.take()
  39. print("NewConnection(%s, %d)" % (path, self.fd))
  40. for key in properties.keys():
  41. if key == "Version" or key == "Features":
  42. print(" %s = 0x%04x" % (key, properties[key]))
  43. else:
  44. print(" %s = %s" % (key, properties[key]))
  45. @dbus.service.method("org.bluez.Profile1", in_signature="o", out_signature="")
  46. def RequestDisconnection(self, path):
  47. print("RequestDisconnection(%s)" % (path))
  48. if (self.fd > 0):
  49. os.close(self.fd)
  50. self.fd = -1
  51. def __init__(self, bus, path):
  52. dbus.service.Object.__init__(self, bus, path)
  53. #
  54. # create a bluetooth device to emulate a HID keyboard,
  55. # advertize a SDP record using our bluez profile class
  56. #
  57. class BTKbDevice():
  58. # change these constants
  59. MY_ADDRESS = "B8:27:EB:B9:FE:EC"
  60. MY_DEV_NAME = "ThanhLe_Keyboard"
  61. # define some constants
  62. P_CTRL = 17 # Service port - must match port configured in SDP record
  63. P_INTR = 19 # Service port - must match port configured in SDP record#Interrrupt port
  64. # dbus path of the bluez profile we will create
  65. PROFILE_DBUS_PATH = "/bluez/yaptb/btkb_profile"
  66. # file path of the sdp record to laod
  67. SDP_RECORD_PATH = sys.path[0] + "/sdp_record.xml"
  68. UUID = "00001124-0000-1000-8000-00805f9b34fb"
  69. def __init__(self):
  70. print("\033[0;35Setting up BT device\033[0m")
  71. self.init_bt_device()
  72. self.init_bluez_profile()
  73. # configure the bluetooth hardware device
  74. def init_bt_device(self):
  75. print("Configuring for name " + BTKbDevice.MY_DEV_NAME)
  76. # set the device class to a keybord and set the name
  77. os.system("hciconfig hci0 up")
  78. os.system("hciconfig hci0 class 0x002540")
  79. os.system("hciconfig hci0 name " + BTKbDevice.MY_DEV_NAME)
  80. # make the device discoverable
  81. os.system("hciconfig hci0 piscan")
  82. # set up a bluez profile to advertise device capabilities from a loaded service record
  83. def init_bluez_profile(self):
  84. print("Configuring Bluez Profile")
  85. # setup profile options
  86. service_record = self.read_sdp_service_record()
  87. opts = {
  88. "ServiceRecord": service_record,
  89. "Role": "server",
  90. "RequireAuthentication": False,
  91. "RequireAuthorization": False,
  92. "AutoConnect": True
  93. }
  94. # retrieve a proxy for the bluez profile interface
  95. bus = dbus.SystemBus()
  96. manager = dbus.Interface(bus.get_object(
  97. "org.bluez", "/org/bluez"), "org.bluez.ProfileManager1")
  98. profile = BTKbBluezProfile(bus, BTKbDevice.PROFILE_DBUS_PATH)
  99. manager.RegisterProfile("/org/bluez/hci0", BTKbDevice.UUID, opts)
  100. print("Profile registered ")
  101. # read and return an sdp record from a file
  102. def read_sdp_service_record(self):
  103. print("Reading service record")
  104. try:
  105. fh = open(BTKbDevice.SDP_RECORD_PATH, "r")
  106. except:
  107. sys.exit("Could not open the sdp record. Exiting...")
  108. return fh.read()
  109. # listen for incoming client connections
  110. # ideally this would be handled by the Bluez 5 profile
  111. # but that didn't seem to work
  112. def listen(self):
  113. print("Waiting for connections")
  114. self.scontrol = socket.socket(
  115. socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
  116. self.sinterrupt = socket.socket(
  117. socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
  118. self.scontrol.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  119. self.sinterrupt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  120. # bind these sockets to a port - port zero to select next available
  121. self.scontrol.bind((socket.BDADDR_ANY, self.P_CTRL))
  122. self.sinterrupt.bind((socket.BDADDR_ANY, self.P_INTR))
  123. # Start listening on the server sockets
  124. self.scontrol.listen(5) # Limit of 1 connection
  125. self.sinterrupt.listen(5)
  126. self.ccontrol, cinfo = self.scontrol.accept()
  127. print ("Got a connection on the control channel from " + cinfo[0])
  128. self.cinterrupt, cinfo = self.sinterrupt.accept()
  129. print ("Got a connection on the interrupt channel from " + cinfo[0])
  130. # send a string to the bluetooth host machine
  131. def send_string(self, message):
  132. try:
  133. self.cinterrupt.send(bytes(message))
  134. except OSError as err:
  135. error(err)
  136. # define a dbus service that emulates a bluetooth keyboard
  137. # this will enable different clients to connect to and use
  138. # the service
  139. class BTKbService(dbus.service.Object):
  140. def __init__(self):
  141. print("Setting up service")
  142. # set up as a dbus service
  143. bus_name = dbus.service.BusName( "org.yaptb.btkbservice", bus=dbus.SystemBus())
  144. dbus.service.Object.__init__(self, bus_name, "/org/yaptb/btkbservice")
  145. # create and setup our device
  146. self.device = BTKbDevice()
  147. # start listening for connections
  148. self.device.listen()
  149. @dbus.service.method('org.yaptb.btkbservice', in_signature='yay')
  150. def send_keys(self, modifier_byte, keys):
  151. print("Get send_keys request through dbus")
  152. cmd_str = ""
  153. cmd_str += chr(0xA1)
  154. cmd_str += chr(0x01)
  155. cmd_str += chr(modifier_byte)
  156. cmd_str += chr(0x00)
  157. count = 0
  158. for key_code in keys:
  159. if(count < 6):
  160. cmd_str += chr(key_code)
  161. count += 1
  162. print("key msg: " + cmd_str)
  163. self.device.send_string(cmd_str)
  164. @dbus.service.method('org.yaptb.btkbservice', in_signature='yay')
  165. def send_mouse(self, modifier_byte, keys):
  166. print("Get send_mouse request through dbus")
  167. self.device.send_string(bytes(keys))
  168. # main routine
  169. if __name__ == "__main__":
  170. # we an only run as root
  171. try:
  172. if not os.geteuid() == 0:
  173. sys.exit("Only root can run this script")
  174. DBusGMainLoop(set_as_default=True)
  175. myservice = BTKbService()
  176. gtk.main()
  177. except KeyboardInterrupt:
  178. sys.exit()