Skip to content

Commit

Permalink
new modmap and conditional API
Browse files Browse the repository at this point in the history
  • Loading branch information
joshgoebel committed Jun 4, 2022
1 parent c4591f8 commit cae3135
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 39 deletions.
6 changes: 3 additions & 3 deletions tests/test_modmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def setup_function(module):
reset_configuration()

async def test_command_to_control():
modmap({
modmap("default", {
Key.LEFT_META: Key.LEFT_CTRL
})

Expand Down Expand Up @@ -58,7 +58,7 @@ async def test_command_to_control():
]

async def test_remapped_in_combo_with_unremapped():
modmap({
modmap("default",{
Key.LEFT_META: Key.LEFT_CTRL
})

Expand All @@ -81,7 +81,7 @@ async def test_remapped_in_combo_with_unremapped():
]

async def test_multiple_remapped():
modmap({
modmap("default", {
Key.LEFT_META: Key.LEFT_CTRL,
Key.LEFT_SHIFT: Key.RIGHT_ALT
})
Expand Down
2 changes: 1 addition & 1 deletion tests/test_modmap_cond.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def setup_function(module):
reset_configuration()

async def test_cond_modmap_wins_over_default_modmap():
modmap({
modmap("default", {
Key.RIGHT_CTRL: Key.LEFT_META,
})
conditional_modmap(re.compile(r'Emacs'), {
Expand Down
2 changes: 1 addition & 1 deletion tests/test_modmap_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def test_weird_abc_to_ctrl_alt_del():
Key.A: [Key.A, Key.LEFT_CTRL],
Key.B: [Key.B, Key.LEFT_ALT],
})
modmap(
modmap("default",
{Key.C : Key.DELETE}
)

Expand Down
82 changes: 66 additions & 16 deletions xkeysnail/config_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import itertools

from .key import Action, Combo, Key, Modifier
from .lib.modmap import Modmap
from sys import exit
from .logger import *

# GLOBALS

Expand All @@ -10,8 +13,7 @@

# keycode translation
# e.g., { Key.CAPSLOCK: Key.LEFT_CTRL }
_mod_map = {}
_conditional_mod_map = []
_modmaps = []

# multipurpose keys
# e.g, {Key.LEFT_CTRL: [Key.ESC, Key.LEFT_CTRL, Action.RELEASE]}
Expand All @@ -27,25 +29,33 @@

# needed for testing teardowns
def reset_configuration():
global _mod_map
global _conditional_mod_map
global _modmaps
global _multipurpose_map
global _conditional_multipurpose_map
global _toplevel_keymaps
global _timeout

_mod_map = {}
_conditional_mod_map = []
_modmaps = []
_multipurpose_map = None
_conditional_multipurpose_map = []
_toplevel_keymaps = []
_timeout = 1

# how transform hooks into the configuration
def get_configuration():
global _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

return (
_mod_map,
_conditional_mod_map,
_modmaps,
_multipurpose_map,
_conditional_multipurpose_map,
_toplevel_keymaps,
Expand Down Expand Up @@ -132,7 +142,19 @@ def define_timeout(seconds=1):
_timeout = seconds


def define_modmap(mod_remappings):
def conditional(fn, what):
# TODO: check that fn is a valid conditional
what.conditional = fn
return what


# old API, takes name as an optional param
def define_modmap(mappings, name = "unnamed"):
return modmap(name, mappings)


# new API, requires name
def modmap(name, mappings):
"""Defines modmap (keycode translation)
Example:
Expand All @@ -141,11 +163,13 @@ def define_modmap(mod_remappings):
Key.CAPSLOCK: Key.LEFT_CTRL
})
"""
global _mod_map
_mod_map = mod_remappings
global _modmaps
mm = Modmap(name, mappings)
_modmaps.append(mm)
return mm


def define_conditional_modmap(condition, mod_remappings):
def define_conditional_modmap(condition, mappings):
"""Defines conditional modmap (keycode translation)
Example:
Expand All @@ -154,11 +178,38 @@ def define_conditional_modmap(condition, mod_remappings):
Key.CAPSLOCK: Key.LEFT_CTRL
})
"""
condition_fn = None
def re_search(re):
def fn(ctx):
print("running re_search", ctx)
return re.search(ctx["wm_class"])
return fn

def wm_class(wm_class_fn):
def fn(ctx):
return wm_class_fn(ctx["wm_class"])
return fn

def wm_class_and_device(cond_fn):
def fn(ctx):
return cond_fn(ctx["wm_class"], ctx["device_name"])
return fn


name = "define_conditional_modmap (old API)"
if hasattr(condition, 'search'):
condition = condition.search
if not callable(condition):
condition_fn = re_search(condition)
elif callable(condition):
if len(signature(condition).parameters) == 1:
condition_fn = wm_class(condition)
elif len(signature(condition).parameters) == 2:
condition_fn = wm_class_and_device(condition)

if not callable(condition_fn):
raise ValueError('condition must be a function or compiled regexp')
_conditional_mod_map.append((condition, mod_remappings))

return conditional(condition_fn, modmap(name, mappings))
# _conditional_mod_map.append((condition, mod_remappings))


def define_multipurpose_modmap(multipurpose_remappings):
Expand Down Expand Up @@ -258,7 +309,6 @@ def expand(target):

timeout = define_timeout
keymap = define_keymap
modmap = define_modmap
conditional_modmap = define_conditional_modmap
multipurpose_modmap = define_multipurpose_modmap
conditional_multipurpose_modmap = define_conditional_multipurpose_modmap
Expand Down
12 changes: 12 additions & 0 deletions xkeysnail/lib/modmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Modmap:
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]

37 changes: 19 additions & 18 deletions xkeysnail/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
from .config_api import get_configuration,escape_next_key, pass_through_key, ignore_key

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

Expand Down Expand Up @@ -212,21 +211,23 @@ def maybe_press_modifiers(multipurpose_map):


# translate keycode (like xmodmap)
def apply_modmap(key):
wm_class = None
active_mod_map = _mod_map
if _conditional_mod_map:
wm_class = get_active_window_wm_class()
for condition, mod_map in _conditional_mod_map:
params = [wm_class]
if len(signature(condition).parameters) == 2:
params = [wm_class, device_name]

if condition(*params):
active_mod_map = mod_map
def apply_modmap(key, device_name):
# first modmap is always the default, unconditional
active_modmap = _modmaps[0]
print("active", active_modmap)
conditional_modmaps = _modmaps[1:]
print("conditionals", conditional_modmaps)
if conditional_modmaps:
ctx = {
"wm_class": get_active_window_wm_class(),
"device_name": device_name
}
for modmap in conditional_modmaps:
if modmap.conditional(ctx):
active_modmap = modmap
break
if active_mod_map and key in active_mod_map:
key = active_mod_map[key]
if active_modmap and key in active_modmap:
key = active_modmap[key]

return key

Expand All @@ -248,7 +249,7 @@ def on_event(event, device_name, quiet):
# return

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

wm_class = None
active_multipurpose_map = _multipurpose_map
Expand Down

0 comments on commit cae3135

Please sign in to comment.