mirror of
https://github.com/Alkaid-Benetnash/EmuBTHID.git
synced 2026-01-21 16:00:28 +01:00
240 lines
9.1 KiB
Python
Executable file
240 lines
9.1 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
import sys
|
|
import os
|
|
import time
|
|
from BluetoothHID import BluetoothHIDService
|
|
from evdev_xkb_map import evdev_xkb_map, modkeys
|
|
import keymap
|
|
from Xlib import X, display, Xutil
|
|
from dbus.mainloop.glib import DBusGMainLoop
|
|
|
|
"""
|
|
Change this CONTROLLER_MAC to the mac of your own device
|
|
"""
|
|
CONTROLLER_MAC = "5C:87:9C:96:BE:5E"
|
|
|
|
usbhid_map = {}
|
|
with open("keycode.txt") as f:
|
|
for line in f.read().splitlines():
|
|
if not line.startswith(";") and len(line) > 1:
|
|
l = line.split(maxsplit=1)
|
|
usbhid_keycode = int(l[0])
|
|
usbhid_keyname = l[1]
|
|
usbhid_map[usbhid_keycode] = usbhid_keyname
|
|
|
|
|
|
# Application window (only one)
|
|
class Window(object):
|
|
def __init__(self, display):
|
|
self.d = display
|
|
self.objects = []
|
|
|
|
# Find which screen to open the window on
|
|
self.screen = self.d.screen()
|
|
|
|
self.window = self.screen.root.create_window(
|
|
50, 50, 640, 480, 2,
|
|
self.screen.root_depth,
|
|
X.InputOutput,
|
|
X.CopyFromParent,
|
|
|
|
# special attribute values
|
|
background_pixel=self.screen.white_pixel,
|
|
event_mask=(X.ExposureMask |
|
|
X.StructureNotifyMask |
|
|
X.ButtonPressMask |
|
|
X.ButtonReleaseMask |
|
|
X.Button1MotionMask) |
|
|
X.KeyPressMask |
|
|
X.KeyReleaseMask,
|
|
colormap=X.CopyFromParent,
|
|
)
|
|
|
|
self.gc = self.window.create_gc(
|
|
foreground=self.screen.black_pixel,
|
|
background=self.screen.white_pixel,
|
|
)
|
|
|
|
# Set some WM info
|
|
|
|
self.WM_DELETE_WINDOW = self.d.intern_atom('WM_DELETE_WINDOW')
|
|
self.WM_PROTOCOLS = self.d.intern_atom('WM_PROTOCOLS')
|
|
|
|
self.window.set_wm_name('EmuBTHID')
|
|
self.window.set_wm_icon_name('EmuBTHID')
|
|
self.window.set_wm_protocols([self.WM_DELETE_WINDOW])
|
|
self.window.set_wm_hints(flags=Xutil.StateHint,
|
|
initial_state=Xutil.NormalState)
|
|
|
|
self.window.set_wm_normal_hints(flags=(Xutil.PPosition | Xutil.PSize
|
|
| Xutil.PMinSize),
|
|
min_width=20,
|
|
min_height=20)
|
|
# Map the window, making it visible
|
|
self.window.map()
|
|
|
|
def grab(self):
|
|
print("Grab!")
|
|
ret = self.window.grab_pointer(False, X.ButtonReleaseMask | X.ButtonPressMask | X.PointerMotionMask,
|
|
X.GrabModeAsync, X.GrabModeAsync, self.window, X.NONE, X.CurrentTime)
|
|
ret = self.window.grab_keyboard(False, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
|
|
|
|
def ungrab(self):
|
|
print("UnGrab!")
|
|
self.d.ungrab_pointer(X.CurrentTime)
|
|
self.d.ungrab_keyboard(X.CurrentTime)
|
|
|
|
# Main loop, handling events
|
|
def loop(self, send_call_back):
|
|
kbd_state = bytearray([
|
|
0xA1,
|
|
0x01, # Report ID
|
|
0x00, # Modifier keys
|
|
0x00, # preserve
|
|
0x00, # 6 key
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00
|
|
])
|
|
mouse_state = bytearray([
|
|
0xA1,
|
|
0x02, # Report ID
|
|
0x00, # mouse button, in this byte XXXXX(button2)(button1)(button0)
|
|
0x00, # X displacement
|
|
0x00, # Y displacement
|
|
])
|
|
expose_count = 0
|
|
grab_trigger_hint = ('KEY_LEFTCTRL', 'KEY_LEFTALT', 'KEY_LEFTSHIFT', 'KEY_B')
|
|
grab_trigger = set(keymap.keytable[k] for k in grab_trigger_hint)
|
|
grab_cnt = len(grab_trigger)
|
|
grabbed = False
|
|
geometry = self.window.get_geometry()
|
|
prev_x = None
|
|
prev_y = None
|
|
hint_x = geometry.width // 5
|
|
hint_y = geometry.height // 5
|
|
hint_str = 'Press Ctrl+Alt+Shift+B to Grab'.encode()
|
|
while 1:
|
|
e = self.d.next_event()
|
|
|
|
# Window has been destroyed, quit
|
|
if e.type == X.DestroyNotify:
|
|
print("Destroy")
|
|
sys.exit(0)
|
|
|
|
if e.type == X.KeyPress:
|
|
usbhid_keycode = evdev_xkb_map[e.detail]
|
|
# print("key pressed: {}".format(usbhid_map[usbhid_keycode]))
|
|
if usbhid_keycode in modkeys:
|
|
kbd_state[2] |= modkeys[usbhid_keycode]
|
|
# import ipdb
|
|
# ipdb.set_trace()
|
|
else:
|
|
for i in range(4, 10):
|
|
if kbd_state[i] == 0x00:
|
|
kbd_state[i] = usbhid_keycode
|
|
break
|
|
send_call_back(bytes(kbd_state))
|
|
if usbhid_keycode in grab_trigger:
|
|
grab_cnt -= 1
|
|
print(grab_cnt)
|
|
if (grab_cnt == 0):
|
|
if grabbed:
|
|
self.ungrab()
|
|
grabbed = False
|
|
hint_str = 'Press Ctrl+Alt+Shift+B to Grab'.encode()
|
|
self.window.image_text(self.gc, hint_x, hint_y, hint_str)
|
|
|
|
else:
|
|
self.grab()
|
|
grabbed = True
|
|
hint_str = 'Press Ctrl+Alt+Shift+B to UnGrab'.encode()
|
|
self.window.image_text(self.gc, hint_x, hint_y, hint_str)
|
|
|
|
if e.type == X.KeyRelease:
|
|
usbhid_keycode = evdev_xkb_map[e.detail]
|
|
# print("key released: {}".format(usbhid_map[evdev_xkb_map[e.detail]]))
|
|
if usbhid_keycode in modkeys:
|
|
kbd_state[2] &= ~modkeys[usbhid_keycode]
|
|
else:
|
|
for i in range(4, 10):
|
|
if kbd_state[i] == usbhid_keycode:
|
|
kbd_state[i] = 0x00
|
|
break
|
|
if usbhid_keycode in grab_trigger:
|
|
grab_cnt += 1
|
|
print(grab_cnt)
|
|
send_call_back(bytes(kbd_state))
|
|
|
|
# Some part of the window has been exposed,
|
|
# redraw all the objects.
|
|
if e.type == X.Expose:
|
|
expose_count += 1
|
|
print("Exposed : {}".format(expose_count))
|
|
geometry = self.window.get_geometry()
|
|
hint_x = geometry.width // 5
|
|
hint_y = geometry.height // 5
|
|
self.window.image_text(self.gc, hint_x, hint_y, hint_str)
|
|
|
|
# Left button pressed, start to draw
|
|
if e.type == X.ButtonPress:
|
|
# print("Button press: {}".format(e.detail))
|
|
if (e.detail <= 3):
|
|
mouse_state[2] |= 1 << (e.detail - 1)
|
|
send_call_back(bytes(mouse_state))
|
|
|
|
if e.type == X.ButtonRelease:
|
|
# print("Button release: {}".format(e.detail))
|
|
mouse_state[2] &= ~(1 << (e.detail - 1))
|
|
send_call_back(bytes(mouse_state))
|
|
|
|
if e.type == X.ClientMessage:
|
|
if e.client_type == self.WM_PROTOCOLS:
|
|
fmt, data = e.data
|
|
if fmt == 32 and data[0] == self.WM_DELETE_WINDOW:
|
|
sys.exit(0)
|
|
if e.type == X.MotionNotify:
|
|
#print("Motion: ({x},{y})".format(x=e.event_x, y=e.event_y))
|
|
if prev_x is not None and prev_y is not None:
|
|
pos_x = max(-128, min(int((e.event_x - prev_x) * 2), 127))
|
|
mouse_state[3] = pos_x if pos_x >= 0 else (256 + pos_x)
|
|
pos_y = max(-128, min(int((e.event_y - prev_y) * 2), 127))
|
|
mouse_state[4] = pos_y if pos_y >= 0 else (256 + pos_y)
|
|
#print(" ({},{})".format(mouse_state[3], mouse_state[4]))
|
|
send_call_back(bytes(mouse_state))
|
|
if e.event_x == geometry.width - 1:
|
|
self.window.warp_pointer(1, e.event_y)
|
|
prev_x = 1
|
|
elif e.event_x == 0:
|
|
self.window.warp_pointer(geometry.width - 2, e.event_y)
|
|
prev_x = geometry.width - 2
|
|
else:
|
|
prev_x = e.event_x
|
|
if e.event_y == geometry.height - 1:
|
|
self.window.warp_pointer(e.event_x, 1)
|
|
prev_y = 1
|
|
elif e.event_y == 0:
|
|
self.window.warp_pointer(e.event_x, geometry.height - 2)
|
|
prev_y = geometry.height - 2
|
|
else:
|
|
prev_y = e.event_y
|
|
|
|
if __name__ == '__main__':
|
|
DBusGMainLoop(set_as_default=True)
|
|
service_record = open("sdp_record_kbd.xml").read()
|
|
d = display.Display()
|
|
d.change_keyboard_control(auto_repeat_mode=X.AutoRepeatModeOff)
|
|
try:
|
|
bthid_srv = BluetoothHIDService(service_record, CONTROLLER_MAC)
|
|
Window(d).loop(bthid_srv.send)
|
|
#Window(d).loop(print)
|
|
finally:
|
|
d.change_keyboard_control(auto_repeat_mode=X.AutoRepeatModeOn)
|
|
d.get_keyboard_control()
|
|
d.ungrab_keyboard(X.CurrentTime)
|
|
d.ungrab_pointer(X.CurrentTime)
|
|
print("Exit")
|