Reading keycodes from the mouse

xkb
sezanzeb 4 years ago committed by sezanzeb
parent 24f0019845
commit df7ce29573

@ -28,7 +28,7 @@ from argparse import ArgumentParser
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0') gi.require_version('GLib', '2.0')
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk, GLib
from keymapper.data import get_data_path from keymapper.data import get_data_path
from keymapper.X import create_setxkbmap_config, apply_preset, \ from keymapper.X import create_setxkbmap_config, apply_preset, \
@ -36,7 +36,7 @@ from keymapper.X import create_setxkbmap_config, apply_preset, \
from keymapper.presets import get_presets, find_newest_preset, \ from keymapper.presets import get_presets, find_newest_preset, \
delete_preset, rename_preset delete_preset, rename_preset
from keymapper.logger import logger, update_verbosity, log_info from keymapper.logger import logger, update_verbosity, log_info
from keymapper.linux import get_devices from keymapper.linux import get_devices, KeycodeReader
window = None window = None
@ -45,29 +45,46 @@ window = None
# TODO check for sudo rights # TODO check for sudo rights
def gtk_iteration():
"""Iterate while events are pending."""
while Gtk.events_pending():
Gtk.main_iteration()
class SingleKeyMapping: class SingleKeyMapping:
"""A single, configurable key mapping.""" """A single, configurable key mapping."""
def __init__(self, delete_callback, key_code=None, character=None): def __init__(
self, device, delete_callback, keycode_reader,
key_code=None, character=None,
):
"""Construct a row and add it to the list in the GUI.""" """Construct a row and add it to the list in the GUI."""
self.widget = None self.widget = None
self.device = device
self.delete_callback = delete_callback self.delete_callback = delete_callback
self.keycode_reader = keycode_reader
self.put_together(key_code, character) self.put_together(key_code, character)
def get_widget(self): def get_widget(self):
"""Return the widget that wraps all the widgets of the row.""" """Return the widget that wraps all the widgets of the row."""
return self.widget return self.widget
def get_code(self): def start_watching_key_codes(self, *args):
return int(self.key_code.get_text()) print('start_watching_key_codes')
self.keycode_reader.clear()
GLib.timeout_add(100, self.get_newest_keycode)
def get_character(self): def get_newest_keycode(self):
return self.character.get_text() code = self.keycode_reader.read()
if code is not None:
self.key_code.set_label(str(code))
return self.key_code.is_focus()
def put_together(self, key_code, character): def put_together(self, key_code, character):
"""Create all GTK widgets.""" """Create all GTK widgets."""
delete_button = Gtk.EventBox() delete_button = Gtk.EventBox()
delete_button.add(Gtk.Image.new_from_icon_name( delete_button.add(Gtk.Image.new_from_icon_name(
'window-close', Gtk.IconSize.BUTTON 'window-close',
Gtk.IconSize.BUTTON
)) ))
delete_button.connect( delete_button.connect(
'button-press-event', 'button-press-event',
@ -76,13 +93,19 @@ class SingleKeyMapping:
delete_button.set_margin_start(5) delete_button.set_margin_start(5)
delete_button.set_margin_end(5) delete_button.set_margin_end(5)
key_code_input = Gtk.Entry() key_code_input = Gtk.ToggleButton()
key_code_input.set_alignment(0.5)
key_code_input.set_width_chars(4)
key_code_input.set_has_frame(False)
key_code_input.set_input_purpose(Gtk.InputPurpose.NUMBER)
if key_code is not None: if key_code is not None:
key_code_input.set_text(key_code) key_code_input.set_label(key_code)
key_code_input.connect(
'focus-in-event',
self.start_watching_key_codes
)
# make the togglebutton go back to its normal state when doing
# something else in the UI
key_code_input.connect(
'focus-out-event',
lambda *args: key_code_input.set_active(False)
)
character_input = Gtk.Entry() character_input = Gtk.Entry()
character_input.set_alignment(0.5) character_input.set_alignment(0.5)
@ -117,6 +140,8 @@ class Window:
self.selected_preset = None self.selected_preset = None
self.mappings = [] self.mappings = []
self.keycode_reader = KeycodeReader(gtk_iteration)
gladefile = get_data_path('key-mapper.glade') gladefile = get_data_path('key-mapper.glade')
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_from_file(gladefile) builder.add_from_file(gladefile)
@ -131,8 +156,11 @@ class Window:
self.select_newest_preset() self.select_newest_preset()
print(1)
css_provider = Gtk.CssProvider() css_provider = Gtk.CssProvider()
print(2)
css_provider.load_from_path(get_data_path('style.css')) css_provider.load_from_path(get_data_path('style.css'))
print(3)
Gtk.StyleContext.add_provider_for_screen( Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(), Gdk.Screen.get_default(),
css_provider, css_provider,
@ -145,6 +173,7 @@ class Window:
def on_close(self, *_): def on_close(self, *_):
"""Safely close the application.""" """Safely close the application."""
self.keycode_reader.stop_reading()
Gtk.main_quit() Gtk.main_quit()
def select_newest_preset(self): def select_newest_preset(self):
@ -196,10 +225,20 @@ class Window:
def read_mapping(row): def read_mapping(row):
box = row.get_children()[0] box = row.get_children()[0]
columns = box.get_children() columns = box.get_children()
# TODO test if columns[0] is a number code = columns[0].get_label()
# and if one of them is empty
# validate
character = columns[1].get_text()
if code == '' or character == '':
return
try:
code = int(code)
except ValueError:
return
# add to mapping
mappings.append(( mappings.append((
int(columns[0].get_text()), int(columns[0].get_label()),
columns[1].get_text() columns[1].get_text()
)) ))
@ -243,6 +282,9 @@ class Window:
self.mappings = [] self.mappings = []
self.populate_presets() self.populate_presets()
GLib.idle_add(
lambda: self.keycode_reader.start_reading(self.selected_device)
)
def on_create_preset_clicked(self, button): def on_create_preset_clicked(self, button):
"""Create a new preset and select it.""" """Create a new preset and select it."""
@ -267,8 +309,11 @@ class Window:
key_list = self.get('key_list') key_list = self.get('key_list')
for mapping in mappings: for mapping in mappings:
mapping = SingleKeyMapping( mapping = SingleKeyMapping(
self.selected_device,
self.on_row_removed, self.on_row_removed,
mapping[0], mapping[1] self.keycode_reader,
mapping[0],
mapping[1]
) )
key_list.insert(mapping.get_widget(), -1) key_list.insert(mapping.get_widget(), -1)
@ -277,7 +322,11 @@ class Window:
self.add_empty() self.add_empty()
def add_empty(self): def add_empty(self):
empty = SingleKeyMapping(self.on_row_removed) empty = SingleKeyMapping(
self.selected_device,
self.on_row_removed,
self.keycode_reader
)
key_list = self.get('key_list') key_list = self.get('key_list')
key_list.insert(empty.get_widget(), -1) key_list.insert(empty.get_widget(), -1)

@ -43,13 +43,6 @@ from keymapper.presets import get_presets
from keymapper.linux import get_devices, can_grab from keymapper.linux import get_devices, can_grab
def get_keycode(device, letter):
"""Get the keycode that is configured for the given letter."""
# TODO I have no idea how to do this
# in /usr/share/X11/xkb/keycodes the mapping is made
return ''
def ensure_symlink(): def ensure_symlink():
"""Make sure the symlink exists. """Make sure the symlink exists.
@ -135,15 +128,6 @@ def apply_preset(device, preset):
# only all virtual devices of the same hardware device # only all virtual devices of the same hardware device
continue continue
"""# get the path in /dev for that
path = [
path for name, path
in zip(group['devices'], group['paths'])
if name == xinput_name
][0]
if not can_grab(path):
logger.error('Something else is')"""
symbols = '/usr/share/X11/xkb/symbols/' symbols = '/usr/share/X11/xkb/symbols/'
layout_path = get_usr_path(device, preset) layout_path = get_usr_path(device, preset)
with open(layout_path, 'r') as f: with open(layout_path, 'r') as f:

@ -22,9 +22,11 @@
"""Device stuff that is independent from the display server.""" """Device stuff that is independent from the display server."""
import evdev import re
import subprocess import subprocess
import evdev
from keymapper.logger import logger from keymapper.logger import logger
@ -43,6 +45,66 @@ def can_grab(path):
return p.returncode == 1 return p.returncode == 1
class KeycodeReader:
def __init__(self, iterate):
self.iterate = iterate
self.keep_reading = False
self.currently_reading = False
self.newest_keycode = None
def clear(self):
"""Next time when reading don't return the previous keycode."""
self.newest_keycode = None
def start_reading(self, device):
"""Start a loop that keeps reading keycodes.
This keeps the main loop running, however, it is blocking for the
function that calls this until stop_reading is called from somewhere
else.
"""
# stop the current loop
if self.currently_reading:
self.stop_reading()
while self.currently_reading:
self.iterate()
# start the next one
logger.debug('Starting reading keycodes for %s', device)
self.keep_reading = True
self.currently_reading = True
# all the virtual devices of the hardware.
# Watch over each one of them
paths = _devices[device]['paths']
virtual_devices = [
evdev.InputDevice(path)
for path in paths[:1]
]
while self.keep_reading:
for virtual_device in virtual_devices:
event = virtual_device.read_one()
if event is not None and event.type == evdev.ecodes.EV_KEY:
# this happens to report key codes that are 8 lower
# than the ones reported by xev
self.newest_keycode = event.code + 8
self.iterate()
# done
logger.debug('Stopped reading keycodes for %s', device)
self.currently_reading = False
def stop_reading(self):
"""Stop the loop that keeps reading keycodes."""
self.keep_reading = False
self.newest_keycode = None
def read(self):
"""Get the newest key."""
return self.newest_keycode
def get_devices(): def get_devices():
"""Group devices and get relevant infos per group. """Group devices and get relevant infos per group.

Loading…
Cancel
Save