Skip to content

Commit

Permalink
new conditional APIs for multimaps
Browse files Browse the repository at this point in the history
  • Loading branch information
joshgoebel committed Jun 5, 2022
1 parent 955fff0 commit e219e45
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 46 deletions.
4 changes: 2 additions & 2 deletions tests/test_modmap_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def setup_function(module):
reset_configuration()

def test_weird_abc_to_ctrl_alt_del():
multipurpose_modmap({
multipurpose_modmap("default",{
Key.A: [Key.A, Key.LEFT_CTRL],
Key.B: [Key.B, Key.LEFT_ALT],
})
Expand All @@ -61,7 +61,7 @@ def test_weird_abc_to_ctrl_alt_del():


def test_enter_is_enter_and_control():
multipurpose_modmap(
multipurpose_modmap("default",
# Enter is enter when pressed and released. Control when held down.
{Key.ENTER: [Key.ENTER, Key.RIGHT_CTRL]}
)
Expand Down
57 changes: 33 additions & 24 deletions xkeysnail/config_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import itertools

from .key import Action, Combo, Key, Modifier
from .lib.modmap import Modmap
from .lib.modmap import Modmap, MultiModmap
from .lib.keymap import Keymap
from sys import exit
from .logger import *
Expand All @@ -18,8 +18,7 @@

# multipurpose keys
# e.g, {Key.LEFT_CTRL: [Key.ESC, Key.LEFT_CTRL, Action.RELEASE]}
_multipurpose_map = None
_conditional_multipurpose_map = []
_multi_modmaps = []

# multipurpose timeout
_timeout = 1
Expand All @@ -31,34 +30,39 @@
# needed for testing teardowns
def reset_configuration():
global _modmaps
global _multipurpose_map
global _conditional_multipurpose_map
global _multi_modmaps
global _toplevel_keymaps
global _timeout

_modmaps = []
_multipurpose_map = None
_conditional_multipurpose_map = []
_multi_modmaps = []
_toplevel_keymaps = []
_timeout = 1

# how transform hooks into the configuration
def get_configuration():
global _modmaps
global _multi_modmaps

# setup modmaps
conditional = [mm for mm in _modmaps if mm.conditional]
default = [mm for mm in _modmaps if not mm.conditional] or [Modmap("default", {})]
if len(default) > 1:
error(f"You may only have a single default (non-conditional modmap), you have {len(default)} currently.")
exit(0)

_modmaps = default + conditional

# setup multi-modmaps
conditional = [mm for mm in _multi_modmaps if mm.conditional]
default = [mm for mm in _multi_modmaps if not mm.conditional] or [MultiModmap("default", {})]
if len(default) > 1:
error(f"You may only have a single default (non-conditional multi-modmap), you have {len(default)} currently.")
exit(0)
_multi_modmaps = default + conditional

return (
_modmaps,
_multipurpose_map,
_conditional_multipurpose_map,
_multi_modmaps,
_toplevel_keymaps,
_timeout
)
Expand Down Expand Up @@ -197,6 +201,7 @@ def fn(ctx):

return condition_fn


def define_conditional_modmap(condition, mappings):
"""Defines conditional modmap (keycode translation)
Expand All @@ -217,7 +222,15 @@ def define_conditional_modmap(condition, mappings):
# _conditional_mod_map.append((condition, mod_remappings))


def define_multipurpose_modmap(multipurpose_remappings):
def multipurpose_modmap(name, mappings):
for key, value in mappings.items():
# TODO: why, we don't use this anywhere???
value.append(Action.RELEASE)
mmm = MultiModmap(name, mappings)
_multi_modmaps.append(mmm)
return mmm

def define_multipurpose_modmap(mappings):
"""Defines multipurpose modmap (multi-key translations)
Give a key two different meanings. One when pressed and released alone and
Expand All @@ -230,13 +243,10 @@ def define_multipurpose_modmap(multipurpose_remappings):
{Key.CAPSLOCK: [Key.ESC, Key.LEFT_CTRL]
})
"""
global _multipurpose_map
for key, value in multipurpose_remappings.items():
value.append(Action.RELEASE)
_multipurpose_map = multipurpose_remappings
return multipurpose_modmap("default", mappings)


def define_conditional_multipurpose_modmap(condition, multipurpose_remappings):
def define_conditional_multipurpose_modmap(condition, mappings):
"""Defines conditional multipurpose modmap (multi-key translation)
Example:
Expand All @@ -245,13 +255,12 @@ def define_conditional_multipurpose_modmap(condition, multipurpose_remappings):
{Key.CAPSLOCK: [Key.ESC, Key.LEFT_CTRL]
})
"""
if hasattr(condition, 'search'):
condition = condition.search
if not callable(condition):
condition_fn = old_style_condition_to_fn(condition)
if not callable(condition_fn):
raise ValueError('condition must be a function or compiled regexp')
for key, value in multipurpose_remappings.items():
value.append(Action.RELEASE)
_conditional_multipurpose_map.append((condition, multipurpose_remappings))

name = "anonymous multipurpose map (old API)"
return conditional(condition_fn, multipurpose_modmap(name, mappings))


# ============================================================ #
Expand Down Expand Up @@ -319,6 +328,6 @@ def define_keymap(condition, mappings, name="Anonymous keymap"):

timeout = define_timeout
conditional_modmap = define_conditional_modmap
multipurpose_modmap = define_multipurpose_modmap
conditional_multipurpose_modmap = define_conditional_multipurpose_modmap
# multipurpose_modmap = define_multipurpose_modmap
# conditional_multipurpose_modmap = define_conditional_multipurpose_modmap

15 changes: 15 additions & 0 deletions xkeysnail/lib/modmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,19 @@ def __contains__(self, key):

def __getitem__(self, item):
return self.mappings[item]

class MultiModmap:
def __init__(self, name, mappings):
self.name = name
self.mappings = mappings
self.conditional = None

def __contains__(self, key):
return key in self.mappings

def __getitem__(self, item):
return self.mappings[item]

def items(self):
return self.mappings.items()

46 changes: 26 additions & 20 deletions xkeysnail/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@

def boot_config():
global _modmaps
global _multipurpose_map
global _conditional_multipurpose_map
global _multi_modmaps
global _toplevel_keymaps
global _timeout
_modmaps, _multipurpose_map, \
_conditional_multipurpose_map, _toplevel_keymaps, _timeout = \
_modmaps, _multi_modmaps, _toplevel_keymaps, _timeout = \
get_configuration()


Expand Down Expand Up @@ -228,6 +226,23 @@ def apply_modmap(key, context):

return key


def apply_multi_modmap(key, action, context):
active_multi_modmap = _multi_modmaps[0]
conditional_multi_modmaps = _multi_modmaps[1:]
if conditional_multi_modmaps:
for modmap in conditional_multi_modmaps:
if modmap.conditional(context):
active_multi_modmap = modmap
break
if active_multi_modmap:
multipurpose_handler(active_multi_modmap, key, action, context)
if key in active_multi_modmap:
return True

return False


JUST_KEYS = []
JUST_KEYS.extend([Key[x] for x in "QWERTYUIOPASDFGHJKLZXCVBNM"])

Expand All @@ -247,22 +262,13 @@ def on_event(event, device_name, quiet):

context = KeyContext(device_name)
action = Action(event.value)
key = apply_modmap(Key(event.code), context)

active_multipurpose_map = _multipurpose_map
if _conditional_multipurpose_map:
for condition, mod_map in _conditional_multipurpose_map:
params = [context.wm_class]
if len(signature(condition).parameters) == 2:
params = [context.wm_class, context.device_name]

if condition(*params):
active_multipurpose_map = mod_map
break
if active_multipurpose_map:
multipurpose_handler(active_multipurpose_map, key, action, context)
if key in active_multipurpose_map:
return
key = Key(event.code)
key = apply_modmap(key, context)
# multipurpose modmaps fire their own on_key and do their own
# pressed key updating, so if a multi-modmap decides to apply
# then we stop here and do not proceed with normal processing
if apply_multi_modmap(key, action, context):
return

on_key(key, action, context, quiet=quiet)
update_pressed_keys(key, action)
Expand Down

0 comments on commit e219e45

Please sign in to comment.