mirror of
https://github.com/jtgans/g13gui.git
synced 2025-06-20 08:23:50 -04:00
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:
parent
0a0ecfc9bc
commit
6ddee4ae88
63
g13gui/g13gui/input.py
Normal file
63
g13gui/g13gui/input.py
Normal 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
|
76
g13gui/g13gui/observer/subject.py
Normal file
76
g13gui/g13gui/observer/subject.py
Normal 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()
|
@ -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__':
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user