Skip to content

Instantly share code, notes, and snippets.

@antoniusf
Created December 26, 2020 01:23
Show Gist options
  • Save antoniusf/e78b2ffc6ae0e4bee7b8c63148655162 to your computer and use it in GitHub Desktop.
Save antoniusf/e78b2ffc6ae0e4bee7b8c63148655162 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Copyright (c) 2010 Joshua Harlan Lifton.
# See LICENSE.txt for details.
#
# keyboardcontrol.py - Abstracted keyboard control.
#
# Uses OS appropriate module.
"""Keyboard capture and control.
This module provides an interface for basic keyboard event capture and
emulation. Set the key_up and key_down functions of the
KeyboardCapture class to capture keyboard input. Call the send_string
and send_backspaces functions of the KeyboardEmulation class to
emulate keyboard input.
"""
import sys
from .wayland import Display, make_event
from .wayland import interfaces
from dataclasses import dataclass, field
@dataclass
class SurroundingText:
text: str
cursor: int
anchor: int
@dataclass
class InputMethodState:
active: bool = field(default=False)
surrounding_text: SurroundingText = field(default=None)
change_cause: interfaces.zwp_text_input_v3._enum_change_cause = field(default=interfaces.zwp_text_input_v3._enum_change_cause.input_method)
# TODO: content hint/purpose
class InputMethod(interfaces.zwp_input_method_v2):
def __init__(self, _id, display):
super().__init__(_id, display)
self.state = InputMethodState()
self.pending_state = InputMethodState()
self.serial = 0
def handle_activate(self):
self.pending_state.active = True
def handle_deactivate(self):
self.pending_state.active = False
def handle_surrounding_text(self, text, cursor, anchor):
self.pending_state.surrounding_text = SurroundingText(
text=text,
cursor=cursor,
anchor=anchor)
def handle_text_change_cause(self, cause):
self.pending_state.change_cause = cause
def handle_done(self):
print("State:", self.pending_state)
self.state = self.pending_state
self.pending_state = InputMethodState(
active=self.state.active)
self.serial += 1
class KeyboardCapture(object):
"""Listen to keyboard events."""
# Supported keys.
SUPPORTED_KEYS_LAYOUT = '''
Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12
` 1 2 3 4 5 6 7 8 9 0 - = \\ BackSpace Insert Home Page_Up
Tab q w e r t y u i o p [ ] Delete End Page_Down
a s d f g h j k l ; ' Return
z x c v b n m , . / Up
space Left Down Right
'''
SUPPORTED_KEYS = tuple(SUPPORTED_KEYS_LAYOUT.split())
def suppress_keyboard(self, keys):
pass
def start(self):
pass
def cancel(self):
pass
class KeyboardEmulation(object):
"""Emulate printable key presses and backspaces."""
def __init__(self):
print("\n\n\nhelloo\n\n\n")
display = Display()
registry = display.get_registry()
display.roundtrip()
display.set_interface_class("zwp_input_method_v2", InputMethod)
seat = registry.bind_interface("wl_seat")
im_manager = registry.bind_interface("zwp_input_method_manager_v2")
input_method = im_manager.get_input_method(seat)
self.display = display
self.input_method = input_method
self.buffer = ""
@input_method
def handle_deactivate():
self.buffer = ""
def send_backspaces(self, number_of_backspaces):
self.display.dispatch_events()
if self.input_method.state.active:
if number_of_backspaces <= len(self.buffer):
self.buffer = self.buffer[:-number_of_backspaces]
cursor = len(self.buffer.encode("utf-8"))
self.input_method.set_preedit_string(self.buffer, cursor, cursor)
else:
leftover = number_of_backspaces - len(self.buffer)
self.buffer = ""
self.input_method.set_preedit_string(self.buffer, 0, 0)
self.input_method.delete_surrounding_text(leftover, 0)
self.input_method.commit(self.input_method.serial)
def send_string(self, string):
self.display.dispatch_events()
if self.input_method.state.active:
self.buffer += string
commit = self.buffer[:-20]
pre_edit = self.buffer[-20:]
self.buffer = pre_edit
if commit:
self.input_method.commit_string(commit)
cursor = len(pre_edit.encode("utf-8"))
self.input_method.set_preedit_string(pre_edit, cursor, cursor)
self.input_method.commit(self.input_method.serial)
#else:
# print("adding string to buffer")
# self.buffer.extend(string.encode("utf-8"))
def send_key_combination(self, combo_string):
pass
if __name__ == '__main__':
import time
kc = KeyboardCapture()
ke = KeyboardEmulation()
pressed = set()
status = 'pressed: '
def test(key, action):
global status
print(key, action)
if 'pressed' == action:
pressed.add(key)
elif key in pressed:
pressed.remove(key)
new_status = 'pressed: ' + '+'.join(pressed)
if status != new_status:
ke.send_backspaces(len(status))
ke.send_string(new_status)
status = new_status
kc.key_down = lambda k: test(k, 'pressed')
kc.key_up = lambda k: test(k, 'released')
kc.suppress_keyboard(KeyboardCapture.SUPPORTED_KEYS)
kc.start()
print('Press CTRL-c to quit.')
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
kc.cancel()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment