summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGilles Castel <66gilles99@gmail.com>2019-03-15 23:02:18 +0100
committerGilles Castel <66gilles99@gmail.com>2019-03-15 23:02:18 +0100
commitc6663accf5497c01c7cf95034a064c1e9ca50555 (patch)
tree56d368b56b83ccc0f05369b0e844f65b64b64076
parent278de2d7f69898b07bdd69edba7d84f9ddf2acab (diff)
Complete rewrite!
-rw-r--r--Pipfile20
-rw-r--r--Pipfile.lock43
-rw-r--r--README.md17
-rw-r--r--__init__.py0
-rw-r--r--constants.py53
-rw-r--r--disabled.py16
-rw-r--r--main.py123
-rw-r--r--mode.py2
-rw-r--r--normal.py213
-rw-r--r--press.py13
-rw-r--r--styles.py84
-rw-r--r--testing.py34
-rw-r--r--vim.py9
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
diff --git a/main.py b/main.py
index aa3c29f..270d90d 100644
--- a/main.py
+++ b/main.py
@@ -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)
diff --git a/normal.py b/normal.py
index f63d2be..7846c6c 100644
--- a/normal.py
+++ b/normal.py
@@ -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()
diff --git a/styles.py b/styles.py
index 4b4bcd6..3d29e27 100644
--- a/styles.py
+++ b/styles.py
@@ -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()
diff --git a/vim.py b/vim.py
index 31d5d36..207f227 100644
--- a/vim.py
+++ b/vim.py
@@ -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')