g13gui: Actually finish the migration

Add an evdev listener for input so we can use linux "keysyms" for keyboard
events, since uinput uses it.
This commit is contained in:
June Tate-Gans 2021-05-02 23:48:52 -05:00
parent 0a0ecfc9bc
commit 6ddee4ae88
5 changed files with 170 additions and 34 deletions

63
g13gui/g13gui/input.py Normal file
View File

@ -0,0 +1,63 @@
import threading
import gi
import glob
import asyncio
gi.require_version('Gtk', '3.0')
from gi.repository import GObject
import evdev
from evdev import InputDevice
from evdev import ecodes
from select import select
class InputReader(threading.Thread, GObject.GObject):
INPUT_PATH = '/dev/input'
def __init__(self):
threading.Thread.__init__(self, daemon=True)
GObject.GObject.__init__(self)
self.reset()
def reset(self):
self._isRunning = False
self._keyStates = {}
@GObject.Signal(name='evdev-key-released')
def keyReleased(self, keyCode, time):
print('key released: %d')
@GObject.Signal(name='evdev-key-pressed')
def keyPressed(self, keyCode, time):
print('key pressed: %d')
def run(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
devices = [InputDevice(path) for path in evdev.list_devices()]
keyboards = [dev for dev in devices if ecodes.EV_KEY in dev.capabilities()]
print('Have keyboards: %s' % (keyboards))
while self._isRunning:
r, w, x = select(keyboards, [], [])
print('keyboards listening!')
for fd in r:
for event in keyboards[fd].read():
if event.type != ecodes.EV_KEY:
continue
keyCode = event.keycode
keyDown = event.keystate == event.key_down
lastKeyState = self._keystates.get(keyCode, False)
if keyDown and not lastKeyState:
self.emit('evdev-key-pressed',
keyCode, event.event.timestamp())
elif not keyDown and lastKeyState:
self.emit('evdev-key-released',
keyCode, event.event.timestamp())
def shutdown(self):
self._isRunning = False

View File

@ -0,0 +1,76 @@
import enum
class ChangeType(enum.Enum):
ADD = 0
REMOVE = 1
MODIFY = 2
class Subject(object):
"""Simple class to handle the subject-side of the Observer pattern."""
AllKeys = ''
def registerObserver(self, observer, subscribedKeys=AllKeys):
"""Registers an Observer class as an observer of this object"""
if subscribedKeys != Subject.AllKeys:
subscribedKeys = frozenset(subscribedKeys)
if '_observers' not in self.__dict__:
self._observers = {observer: subscribedKeys}
else:
self._observers[observer] = subscribedKeys
def removeObserver(self, observer):
"""Removes an observer from this object"""
if '_observers' in self.__dict__:
if observer in self._observers:
del self._observers[observer]
def addChange(self, type, key, data=None):
"""Schedules a change notification for transmitting later.
type[ChangeType]: the type of change that occurred.
key[string]: a required name for what field changed.
data[object]: an optional context-dependent object, dict, or
None, specifying what changed. In the case of an ADD or MODIFY,
whatChanged should be the new data. In the case of a DELETE, it should
be the old data (or None).
"""
if '_changes' not in self.__dict__:
self._changes = [(type, key, data)]
else:
self._changes.append((type, key, data))
def clearChanges(self):
"""Removes all scheduled changes from the change buffer."""
self._changes = []
def notifyChange(self):
raise NotImplementedError('Use Subject.notifyChanged instead')
def notifyChanged(self):
"""Notifies all observers of scheduled changes in the change buffer.
This method actually does the work of iterating through all observers
and all changes and delivering them to the Observer's onSubjectChanged
method.
It is safe to call this if there are no changes to send in the buffer,
or there are no observers to send changes to. Note that calling this
when no observers are registered will still flush the change buffer.
"""
if '_observers' in self.__dict__ and '_changes' in self.__dict__:
for observer, subscribedKeys in self._observers.items():
for type, key, data in self._changes:
if subscribedKeys == Subject.AllKeys or key in subscribedKeys:
observer.onSubjectChanged(self, type, key, data)
self._changes = []
def setProperty(self, propertyName, value, notify=True):
propertyName = '_' + propertyName
self.__dict__[propertyName] = value
self.addChange(ChangeType.MODIFY, propertyName, value)
if notify:
self.notifyChanged()

View File

@ -8,6 +8,8 @@ from g13gui.bitwidgets.fonts_tests import *
from g13gui.bitwidgets.label_tests import * from g13gui.bitwidgets.label_tests import *
from g13gui.bitwidgets.display_tests import * from g13gui.bitwidgets.display_tests import *
from g13gui.bitwidgets.screen_tests import * from g13gui.bitwidgets.screen_tests import *
from g13gui.g13.displaydevice_tests import *
from g13gui.g13.manager_tests import *
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -44,7 +44,7 @@ class G13Button(Gtk.MenuButton, GtkObserver):
bindings = self._prefs.selectedProfile().keyBinding(self._keyName) bindings = self._prefs.selectedProfile().keyBinding(self._keyName)
if len(bindings) > 0: if len(bindings) > 0:
keybinds = G13ToGDK(bindings) keybinds = BindsToKeynames(bindings)
accelerator = '+'.join(keybinds) accelerator = '+'.join(keybinds)
shortcut = Gtk.ShortcutsShortcut( shortcut = Gtk.ShortcutsShortcut(
shortcut_type=Gtk.ShortcutType.ACCELERATOR, shortcut_type=Gtk.ShortcutType.ACCELERATOR,

View File

@ -1,17 +1,16 @@
import gi import gi
from g13gui.observer import GtkObserver from g13gui.observer.gtkobserver import GtkObserver
from g13gui.model.bindings import BindsToKeynames
from g13gui.model.bindings import G13ToGDK from g13gui.model.bindings import KeycodeIsModifier
from g13gui.model.bindings import GDKToG13 from g13gui.input import InputReader
from g13gui.model.bindings import G13DKeyIsModifier
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, GObject, Gdk from gi.repository import Gtk, GObject, Gdk
MAX_DELAY_BETWEEN_PRESSES_MILLIS = 250 MAX_DELAY_BETWEEN_PRESSES_SECONDS = 0.250
class G13ButtonPopover(Gtk.Popover, GtkObserver): class G13ButtonPopover(Gtk.Popover, GtkObserver):
@ -21,6 +20,11 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self._prefs = prefs self._prefs = prefs
self._prefs.registerObserver(self, {'selectedProfile'}) self._prefs.registerObserver(self, {'selectedProfile'})
self._inputReader = InputReader()
self._inputReader.connect('evdev-key-pressed', self.keypress)
self._inputReader.connect('evdev-key-released', self.keyrelease)
self._keyName = keyName self._keyName = keyName
self._modifiers = set() self._modifiers = set()
@ -52,8 +56,6 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self._box.pack_start(button, True, True, 6) self._box.pack_start(button, True, True, 6)
self._box.show_all() self._box.show_all()
self.connect("key-press-event", self.keypress)
self.connect("key-release-event", self.keyrelease)
self.connect("show", self.shown) self.connect("show", self.shown)
self.connect("closed", self.closed) self.connect("closed", self.closed)
button.connect("pressed", self.clear) button.connect("pressed", self.clear)
@ -61,7 +63,7 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self.buildBindingDisplay() self.buildBindingDisplay()
def shown(self, widget): def shown(self, widget):
self.grab_add() self._inputReader.start()
def rebuildBindingDisplay(self): def rebuildBindingDisplay(self):
if self._bindingBox: if self._bindingBox:
@ -76,13 +78,11 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self._box.reorder_child(self._bindingBox, 1) self._box.reorder_child(self._bindingBox, 1)
if len(self._currentBindings) > 0: if len(self._currentBindings) > 0:
keybinds = G13ToGDK(self._currentBindings) keybinds = BindsToKeynames(self._currentBindings)
accelerator = '+'.join(keybinds) accelerator = '+'.join(keybinds)
shortcut = Gtk.ShortcutsShortcut( label = Gtk.Label(accelerator)
shortcut_type=Gtk.ShortcutType.ACCELERATOR, label.set_halign(Gtk.Align.CENTER)
accelerator=accelerator) self._bindingBox.pack_start(label, True, True, 6)
shortcut.set_halign(Gtk.Align.CENTER)
self._bindingBox.pack_start(shortcut, True, True, 6)
else: else:
label = Gtk.Label() label = Gtk.Label()
label.set_markup("<i>No binding. Press a key to bind.</i>") label.set_markup("<i>No binding. Press a key to bind.</i>")
@ -90,31 +90,26 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self._bindingBox.show_all() self._bindingBox.show_all()
def keypress(self, buttonMenu, eventKey): def keypress(self, keyCode, timestamp):
pressDelta = eventKey.time - self._lastPressTime print('keypress: %s' % repr(keyCode))
if pressDelta > MAX_DELAY_BETWEEN_PRESSES_MILLIS:
pressDelta = timestamp - self._lastPressTime
if pressDelta > MAX_DELAY_BETWEEN_PRESSES_SECONDS:
self._modifiers = set() self._modifiers = set()
self._consonantKey = None self._consonantKey = None
binding = Gdk.keyval_name(eventKey.keyval) key = keyCode
if len(binding) == 1: print('Hardware keycode: %s' % (key))
binding = binding.upper()
if binding == 'Meta_L':
binding = 'Alt_L'
if binding == 'Meta_R':
binding = 'Alt_R'
binding = GDKToG13(binding) if KeycodeIsModifier(key):
self._modifiers.add(key)
if G13DKeyIsModifier(binding):
self._modifiers.add(binding)
else: else:
self._consonantKey = binding self._consonantKey = key
self._lastPressTime = eventKey.time self._lastPressTime = timestamp
return True return True
def keyrelease(self, buttonMenu, eventKey): def keyrelease(self, keyCode):
self._currentBindings = sorted(list(self._modifiers)) self._currentBindings = sorted(list(self._modifiers))
if self._consonantKey: if self._consonantKey:
self._currentBindings += [self._consonantKey] self._currentBindings += [self._consonantKey]
@ -128,7 +123,7 @@ class G13ButtonPopover(Gtk.Popover, GtkObserver):
self.hide() self.hide()
def closed(self, buttonMenu): def closed(self, buttonMenu):
self.grab_remove() self._inputReader.shutdown()
self._prefs.selectedProfile().bindKey(self._keyName, self._prefs.selectedProfile().bindKey(self._keyName,
self._currentBindings) self._currentBindings)
self.hide() self.hide()