diff options
| author | Gilles Castel <66gilles99@gmail.com> | 2019-03-15 23:02:18 +0100 |
|---|---|---|
| committer | Gilles Castel <66gilles99@gmail.com> | 2019-03-15 23:02:18 +0100 |
| commit | c6663accf5497c01c7cf95034a064c1e9ca50555 (patch) | |
| tree | 56d368b56b83ccc0f05369b0e844f65b64b64076 | |
| parent | 278de2d7f69898b07bdd69edba7d84f9ddf2acab (diff) | |
Complete rewrite!
| -rw-r--r-- | Pipfile | 20 | ||||
| -rw-r--r-- | Pipfile.lock | 43 | ||||
| -rw-r--r-- | README.md | 17 | ||||
| -rw-r--r-- | __init__.py | 0 | ||||
| -rw-r--r-- | constants.py | 53 | ||||
| -rw-r--r-- | disabled.py | 16 | ||||
| -rw-r--r-- | main.py | 123 | ||||
| -rw-r--r-- | mode.py | 2 | ||||
| -rw-r--r-- | normal.py | 213 | ||||
| -rw-r--r-- | press.py | 13 | ||||
| -rw-r--r-- | styles.py | 84 | ||||
| -rw-r--r-- | testing.py | 34 | ||||
| -rw-r--r-- | vim.py | 9 |
13 files changed, 219 insertions, 408 deletions
diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 3d94c0f..0000000 --- a/Pipfile +++ /dev/null @@ -1,20 +0,0 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - - -[packages] - -evdev = "*" -xlib = "*" - - -[dev-packages] - - - -[requires] - -python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index a22d616..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,43 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "5364a12c57cbeca5c66a33ca1fccbd7cdf27cc83c2279c959a97b0ff1a140903" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "evdev": { - "hashes": [ - "sha256:2dd67291be20e70643e8ef6f2381efc10e0c6e44a32abb3c1db74996ea3b0351" - ], - "index": "pypi", - "version": "==1.1.2" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "xlib": { - "hashes": [ - "sha256:60b7cd5d90f5d5922d9ce27b61589c07d970796558d417461db7b66e366bc401", - "sha256:8eee67dad83ef4b82bbbfa85d51eeb20c79d12b119fe25aa1d27bd602ff82212" - ], - "index": "pypi", - "version": "==0.21" - } - }, - "develop": {} -} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec789a3 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ + +2019-03-15 +- Replaying events works +- Inkscape doesnt hang with Ctrl + C (Changed int(time.time()) to X.CurrentTime) +- Grabbing affects which events get to inkscape, however, we watch for all keypress and release events (as indicated in change_attributes) +- TODO: + * w: pencil + * x: snap + * text/disable mode (\` or y) + * f: bezier + * z: delete/undo? + * Modifier commands? rb: remove border, etc? + +2019-03-09 +- Current state: replaying events is hard. +- Inkscape hangs when Ctrl + C / V +- Can't succeed in only capturing non-ctrl and shift events? diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/__init__.py +++ /dev/null diff --git a/constants.py b/constants.py index ae9ad52..20c4140 100644 --- a/constants.py +++ b/constants.py @@ -1,57 +1,6 @@ -KEYSYM_MAP = { - 65307: "ESC", - 32: "SPACE", - 39: "'", - 44: ",", - 45: "-", - 46: ".", - 47: "/", - 48: "0", - 49: "1", - 50: "2", - 51: "3", - 52: "4", - 53: "5", - 54: "6", - 55: "7", - 56: "8", - 57: "9", - 59: ";", - 61: "=", - 91: "[", - 92: "\\", - 93: "]", - 96: "`", - 97: "a", - 98: "b", - 99: "c", - 100: "d", - 101: "e", - 102: "f", - 103: "g", - 104: "h", - 105: "i", - 106: "j", - 107: "k", - 108: "l", - 109: "m", - 110: "n", - 111: "o", - 112: "p", - 113: "q", - 114: "r", - 115: "s", - 116: "t", - 117: "u", - 118: "v", - 119: "w", - 120: "x", - 121: "y", - 122: "z", -} - TARGET = 'image/x-inkscape-svg' + NORMAL = '' VIM = 'Vim' STYLE = 'Style' diff --git a/disabled.py b/disabled.py deleted file mode 100644 index 6c22879..0000000 --- a/disabled.py +++ /dev/null @@ -1,16 +0,0 @@ -from Xlib import X -from constants import KEYSYM_MAP, NORMAL -from mode import mode -import normal - -def disabled_mode(event, keysym, manager): - if keysym in KEYSYM_MAP: - character = KEYSYM_MAP.get(keysym, 0) - - if event.type == X.KeyPress: - if character == '`': - mode(NORMAL) - manager.teardown() - manager.listen(normal.normal_mode) - elif event.type == X.KeyRelease: - pass @@ -1,76 +1,61 @@ +import Xlib from Xlib.display import Display -from Xlib import X -from Xlib.ext import record -from Xlib.protocol import rq +from Xlib import X, XK +from Xlib.ext import xtest +from Xlib.protocol import event +from normal import normal_mode import time -from constants import KEYSYM_MAP, NORMAL -from mode import mode -from normal import normal_mode +MASK = X.Mod2Mask -class ShortcutManager(): +class Manager(): def __init__(self): - self.disp2 = Display() - - def is_inkscape(self): - window = self.disp2.get_input_focus().focus - cls = window.get_wm_class() - return cls and cls[0] == 'inkscape' - - def meta_handler(self, handler): - def handle(reply): - print('yes') - data = reply.data - while len(data): - event, data = rq.EventField(None).parse_binary_value( - data, self.disp.display, None, None) - - if not self.is_inkscape(): - return - - keysym = self.disp.keycode_to_keysym(event.detail, 0) - handler(event, keysym, self) - - return handle - - def listen(self, handler): self.disp = Display() - root = self.disp.screen().root - self.ctx = self.disp.record_create_context( - # datum_flags - 0, - # clients - [record.AllClients], - # rangers - [{ - 'core_requests': (0, 0), - 'core_replies': (0, 0), - 'ext_requests': (0, 0, 0, 0), - 'ext_replies': (0, 0, 0, 0), - 'delivered_events': (0, 0), - 'device_events': (0x02, 0x05), - 'errors': (0, 0), - 'client_started': False, - 'client_died': False, - }]) - - self.disp.record_enable_context(self.ctx, self.meta_handler(handler)) - self.disp.record_free_context(self.ctx) - - try: - while True: - time.sleep(1) - # Infinite wait, doesn't do anything as no events are grabbed - event = root.display.next_event() - except: - print('exception!') - - def teardown(self): - self.disp.record_disable_context(self.ctx) - -sm = ShortcutManager() -mode('INIT') -time.sleep(1) -mode(NORMAL) -sm.listen(normal_mode) + self.screen = self.disp.screen() + self.root = self.screen.root + self.inkscape = next( + w for w in self.root.query_tree().children + if w.get_wm_class() and w.get_wm_class()[0] == 'inkscape' + ) + self.mode = normal_mode + + def event(self, name, detail, state): + return name( + time = X.CurrentTime, + root = self.root, + window = self.inkscape, + same_screen = 0, child = Xlib.X.NONE, + root_x = 0, root_y = 0, event_x = 0, event_y = 0, + state = state, + detail = detail + ) + + def press(self, key, mask=X.NONE): + keysym = XK.string_to_keysym(key) + keycode = self.disp.keysym_to_keycode(keysym) + self.inkscape.send_event(self.event(event.KeyPress, keycode, mask), propagate = True) + self.inkscape.send_event(self.event(event.KeyRelease, keycode, mask), propagate = True) + self.disp.flush() + self.disp.sync() + + def grab(self): + self.inkscape.grab_key(X.AnyKey, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeAsync) + self.inkscape.change_attributes(event_mask = X.KeyReleaseMask | X.KeyPressMask) + + def ungrab(self): + self.inkscape.ungrab_key(X.AnyKey, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeAsync) + + def listen(self): + self.grab() + while True: + evt = self.disp.next_event() + if evt.type in [X.KeyPress, X.KeyRelease]: #ignore X.MappingNotify(=34) + keycode = evt.detail + keysym = self.disp.keycode_to_keysym(keycode, 0) + char = XK.keysym_to_string(keysym) + self.disp.allow_events(X.ReplayKeyboard, X.CurrentTime) + self.mode(self, evt, char) + +m = Manager() +m.listen() diff --git a/mode.py b/mode.py deleted file mode 100644 index 7f585b2..0000000 --- a/mode.py +++ /dev/null @@ -1,2 +0,0 @@ -def mode(name): - print(name) @@ -1,118 +1,111 @@ -from press import press -from Xlib import X -from evdev import ecodes +from Xlib import X, XK, display from clipboard import copy -from constants import KEYSYM_MAP, NORMAL, VIM, SAVE_OBJECT, OBJECT, SAVE_STYLE, STYLE, DISABLED - -from mode import mode +from constants import TARGET from vim import open_vim import styles -import disabled +from time import sleep pressed = set() -shift = False -def normal_mode(event, keysym, manager): - global shift +events = [] + +def print_event(self, event): + + updown = '' + if event.type == X.KeyPress: + updown = '⇓' + if event.type == X.KeyRelease: + updown = '⇑' + + mods = [] + if event.state & X.ShiftMask: + mods.append('Shift') if event.state & X.ControlMask: - # there are modifiers - # eg. X.ControlMask - # ~or X.ShiftMask~ - return + mods.append('Control') + + keycode = event.detail + keysym = self.disp.keycode_to_keysym(keycode, 0) + char = XK.keysym_to_string(keysym) + + return ''.join((updown, '+'.join(mods), ('+' + char if char else ''))) + +def event_to_string(self, event): + mods = [] + if event.state & X.ShiftMask: + mods.append('Shift') + + if event.state & X.ControlMask: + mods.append('Control') + + keycode = event.detail + keysym = self.disp.keycode_to_keysym(keycode, 0) + char = XK.keysym_to_string(keysym) + + return ''.join(mod + '+' for mod in mods) + (char if char else '?') + +def replay(self): + for e in events: + self.inkscape.send_event(e, propagate = True) + + self.disp.flush() + self.disp.sync() + # print('Replayed: ', ', '.join(print_event(self, e) for e in events)) + events.clear() + pressed.clear() + +def normal_mode(self, event, char): + events.append(event) + # print('Events: ' + ', '.join(print_event(self, e) for e in events)) + + if event.type == X.KeyPress: + if char: + pressed.add(event_to_string(self, event)) - if keysym in KEYSYM_MAP: - character = KEYSYM_MAP.get(keysym, 0) - - if event.type == X.KeyPress: - if event.state & X.ShiftMask: - shift = True - - pressed.add(character) - - elif event.type == X.KeyRelease: - - if 'ESC' in pressed: - press(ecodes.KEY_F1) - pressed.clear() - - if character in pressed: - if len(pressed) >= 2: - fire(pressed) - else: - key = pressed.pop() - if key == 'w': - press(ecodes.KEY_F6) # pencil - if key == 'e': - press(ecodes.KEY_F5) # ellipse - if key == 'r': - press(ecodes.KEY_F4) # rectangle - if key == 't': - manager.teardown() - mode(VIM) - compile_latex = shift # shift-t compiles latex - open_vim(compile_latex) - mode(NORMAL) - manager.listen(normal_mode) - if key == 'y': - press(ecodes.KEY_F8) #text - - if key == '`': - manager.teardown() - mode(DISABLED) - manager.listen(disabled.disabled_mode) - - if shift and key == 'a': - mode(SAVE_OBJECT) - manager.teardown() - styles.save_object_mode() - shift = False - mode(NORMAL) - manager.listen(normal_mode) - elif key == 'a': - mode(OBJECT) - manager.teardown() - manager.listen(styles.object_mode) - - if shift and key == 's': - mode(SAVE_STYLE) - manager.teardown() - styles.save_style_mode() - shift = False - manager.listen(normal_mode) - mode(NORMAL) - elif key == 's': - mode(STYLE) - manager.teardown() - manager.listen(styles.style_mode) - - - if key == 'd': - press(ecodes.KEY_F7) # dropper - if key == 'f': - press(ecodes.KEY_F6, [ecodes.KEY_LEFTSHIFT]) # bezier - if key == 'h': - press(ecodes.KEY_H, [ecodes.KEY_LEFTSHIFT]) # flip - - if key == 'z': - press(ecodes.KEY_DELETE) #delete - - if key == 'x': - press(ecodes.KEY_5, [ecodes.KEY_LEFTSHIFT]) # snap - - if key == 'v': - press(ecodes.KEY_V, [ecodes.KEY_LEFTSHIFT]) # flip - - pressed.clear() - - if shift: - shift = False - - if not (event.state & X.ShiftMask): - shift = False - - -def fire(combination): + elif event.type == X.KeyRelease: + handled = False + + if len(pressed) >= 2: + fire(self, pressed) + handled = True + + if len(pressed) == 1: + ev = next(iter(pressed)) + + if ev == 't': + open_vim(self, compile_latex=False) + handled = True + + if ev == 'Shift+t': + open_vim(self, compile_latex=True) + handled = True + + if ev == 'a': + self.mode = styles.object_mode + handled = True + + if ev == 'Shift+a': + styles.save_object_mode(self) + handled = True + + if ev == 's': + self.mode = styles.style_mode + handled = True + + if ev == 'Shift+s': + styles.save_style_mode(self) + handled = True + + if handled: + events.clear() + pressed.clear() + else: + replay(self) + else: + pass + # print("hu?") + +def fire(self, combination): pt = 1.327 # pixels w = 0.4 * pt thick_width = 0.8 * pt @@ -155,9 +148,13 @@ def fire(combination): if 'f' in combination: style['fill'] = 'black' style['fill-opacity'] = 0.12 + style['marker-end'] = 'none' + style['marker-start'] = 'none' if 'b' in combination: style['fill'] = 'black' style['fill-opacity'] = 1 + style['marker-end'] = 'none' + style['marker-start'] = 'none' if not {'f', 'b'} & combination: style['fill'] = 'none' style['fill-opacity'] = 1 @@ -195,5 +192,5 @@ markerHeight="1.690" markerWidth="0.911"> ) svg += f'<inkscape:clipboard style="{style_string}" /></svg>' - copy(svg, target='image/x-inkscape-svg') - press(ecodes.KEY_V, [ecodes.KEY_LEFTSHIFT, ecodes.KEY_LEFTCTRL]) + copy(svg, target=TARGET) + self.press('v', X.ControlMask | X.ShiftMask) diff --git a/press.py b/press.py deleted file mode 100644 index 9cec61a..0000000 --- a/press.py +++ /dev/null @@ -1,13 +0,0 @@ -from evdev.uinput import UInput -from evdev import ecodes - -def press(key, modifiers=[], uinput=UInput()): - for mod in modifiers: - uinput.write(ecodes.EV_KEY, mod, 1) - uinput.write(ecodes.EV_KEY, key, 1) - uinput.write(ecodes.EV_KEY, key, 0) - for mod in modifiers: - uinput.write(ecodes.EV_KEY, mod, 0) - - # synchronize ... - uinput.syn() @@ -1,28 +1,24 @@ -from evdev import ecodes from pathlib import Path from time import sleep import os from Xlib import X from clipboard import copy, get -from constants import KEYSYM_MAP, TARGET, NORMAL -from press import press +from constants import TARGET from rofi import rofi import normal -from mode import mode - pressed = [] script_path = Path(os.path.realpath(__file__)).parents[0] data_dirs = { - 'style': script_path / 'data' / 'styles', - 'object': script_path / 'data' / 'objects', + 'style': script_path / '..' / 'data' / 'styles', + 'object': script_path / '..' / 'data' / 'objects', } -def check(what, manager, name): +def check(what, self, name): files = list(data_dirs[what].iterdir()) names = [f.stem for f in files] @@ -31,54 +27,50 @@ def check(what, manager, name): if len(filtered) == 0: pressed.clear() - return back_to_normal(manager) + return back_to_normal(self) if len(filtered) == 1: index = filtered[0] copy(files[index].read_text(), target=TARGET) if what == 'style': - press(ecodes.KEY_V, [ecodes.KEY_LEFTCTRL, ecodes.KEY_LEFTSHIFT]) + self.press('v', X.ShiftMask | X.ControlMask) else: - press(ecodes.KEY_V, [ecodes.KEY_LEFTCTRL]) + self.press('v', X.ControlMask) sleep(0.5) # Give the user some time when an object is added. - return back_to_normal(manager) + return back_to_normal(self) -def back_to_normal(manager): - mode(NORMAL) +def back_to_normal(self): + self.mode = normal.normal_mode pressed.clear() - manager.teardown() - manager.listen(normal.normal_mode) - -def paste_mode(what, event, keysym, manager): +def paste_mode(what, self, event, char): + print('paste mode') if event.state & X.ControlMask: # there are modifiers # eg. X.ControlMask # ~or X.ShiftMask~ return - if keysym in KEYSYM_MAP: - character = KEYSYM_MAP.get(keysym, 0) - - if event.type == X.KeyPress: - if character == 'ESC': - if len(pressed) == 0: - return back_to_normal(manager) - else: - pressed.clear() - else: - pressed.append(character) - return check(what, manager, ''.join(pressed)) - - elif event.type == X.KeyRelease: - pass - -def save_mode(what): - sleep(0.1) - press(ecodes.KEY_C, [ecodes.KEY_LEFTCTRL]) - sleep(0.1) + if not char: + return + + if event.type != X.KeyRelease: + return + + if char == 'Escape': + if len(pressed) == 0: + return back_to_normal(self) + else: + pressed.clear() + else: + pressed.append(char) + return check(what, self, ''.join(pressed)) + + +def save_mode(what, self): + self.press('c', X.ControlMask) svg = get(TARGET) if not 'svg' in svg: return @@ -106,14 +98,14 @@ def save_mode(what): (directory / f'{name}.svg').write_text(get(TARGET)) -def style_mode(event, keysym, manager): - paste_mode('style', event, keysym, manager) +def style_mode(self, event, char): + paste_mode('style', self, event, char) -def object_mode(event, keysym, manager): - paste_mode('object', event, keysym, manager) +def object_mode(self, event, char): + paste_mode('object', self, event, char) -def save_style_mode(): - save_mode('style') +def save_style_mode(self): + save_mode('style', self) -def save_object_mode(): - save_mode('object') +def save_object_mode(self): + save_mode('object', self) diff --git a/testing.py b/testing.py deleted file mode 100644 index dd6f3f8..0000000 --- a/testing.py +++ /dev/null @@ -1,34 +0,0 @@ -from Xlib.display import Display -from Xlib import X -import time -import signal -import sys - -disp=Display() -screen=disp.screen() -root=screen.root - -def handle_event(evt): - print(evt) - -def main(): - inkscapes = [ - w for w in screen.root.query_tree().children - if w.get_wm_class() and w.get_wm_class()[0] == 'inkscape' - ] - - print(inkscapes) - - for inkscape in inkscapes: - inkscape.grab_key(10, X.NONE, True,X.GrabModeAsync, X.GrabModeAsync) - - signal.signal(signal.SIGALRM, lambda a,b:sys.exit(1)) - signal.alarm(10) - # grab_key(62, X.NONE) - while True: - evt=root.display.next_event() - if evt.type in [X.KeyPress, X.KeyRelease]: #ignore X.MappingNotify(=34) - handle_event(evt) - -if __name__ == '__main__': - main() @@ -3,10 +3,9 @@ import tempfile import subprocess from constants import TARGET from clipboard import copy -from press import press -from evdev import ecodes +from Xlib import X -def open_vim(compile_latex): +def open_vim(self, compile_latex): f = tempfile.NamedTemporaryFile(mode='w+', delete=False) f.write('$$') @@ -72,5 +71,5 @@ def open_vim(compile_latex): stdin=svg ) - press(ecodes.KEY_V, [ecodes.KEY_LEFTCTRL]) - press(ecodes.KEY_ESC) + self.press('v', X.ControlMask) + self.press('Escape') |
