Skip to content

Commit

Permalink
feat: More modified-key mapping unicode options
Browse files Browse the repository at this point in the history
This commit lets the user choose between a left, right, or
side-ambivalent modifier using the following unicode syntax:

⌘   - command
‹⌘  - left_command
⌘›  - right_command

etc. for other mods, ⌘, ⌥, ⌃, ⇧. Also added is an alias that's valid for
hyper, ☆. e.g.

☆ | 8

would map to 'command+option+control+shift + 8'

This syntax was discussed in this issue in the GokuRakuJoudo repo:
yqrashawn/GokuRakuJoudo#205

so credit to the participants of that issue.

Addresses #2
  • Loading branch information
al-ce committed Apr 14, 2023
1 parent 6307267 commit 1c359f7
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 22 deletions.
22 changes: 17 additions & 5 deletions karaml/key_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
alias_modifiers = ["shift"]
ALIASES = {
"hyper": Alias("right_shift", ["right_command", "right_control", "right_option"]),
"☆": Alias("shift", ["command", "control", "option"]),
"ultra": Alias("right_shift", ["right_command", "right_control", "right_option", "fn"]),
"super": Alias("right_shift", ["right_option", "right_control"]),
"enter": Alias("return_or_enter", None),
Expand Down Expand Up @@ -121,12 +122,23 @@
"r": "control",
"a": "option",
"h": "shift",
}


# Unicode symbols for modifiers
"⌘": "command",
"⌥": "option",
"⌃": "control",
"⇧": "shift",
UNICODE_MODS = {
"⌘": "g",
"⌥": "a",
"⌃": "r",
"⇧": "h",
"‹⌘": "m",
"⌘›": "M",
"‹⌥": "o",
"⌥›": "O",
"‹⌃": "c",
"⌃›": "C",
"‹⇧": "s",
"⇧›": "S",
"☆": "garh",
}

KEY_CODE = [
Expand Down
49 changes: 41 additions & 8 deletions karaml/map_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
check_and_validate_str_as_dict,
)
from karaml.exceptions import invalidKey, invalidSoftFunct
from karaml.key_codes import KEY_CODE_REF_LISTS, MODIFIERS, PSEUDO_FUNCS
from karaml.key_codes import (KEY_CODE_REF_LISTS, MODIFIERS, PSEUDO_FUNCS,
UNICODE_MODS,
)

KeyStruct = namedtuple("KeyStruct", ["key_type", "key_code", "modifiers"])
ModifiedKey = namedtuple("ModifiedKey", ["modifiers", "key"])
Expand Down Expand Up @@ -361,10 +363,42 @@ def translate_if_valid_keycode(usr_key: str, usr_map: str) -> KeyStruct:


def parse_primary_key_and_mods(usr_key: str, usr_map) -> tuple[str, dict]:
if modded_key := is_modded_key(usr_key):
modifiers: dict = get_modifiers(modded_key.modifiers, usr_map)
return modded_key.key, modifiers
return usr_key, {}
modded_key = is_modded_key(usr_key)
if not modded_key:
return usr_key, {}

# The modifiers should either be ascii or unicode, but not a mix
if bool(set(UNICODE_MODS.keys()) & set(modded_key.modifiers)):
modifiers_string: str = translate_unicode_mods(modded_key.modifiers)
else:
modifiers_string: str = modded_key.modifiers
modifiers: dict = get_modifiers(modifiers_string, usr_map)
return modded_key.key, modifiers


def translate_unicode_mods(modifiers: str) -> str:
"""
Translate unicode modifiers to their corresponding ascii modifier aliases
and return the translated string.
Unlike the ascii modifiers, whose side is determined by whether they are
lower or upper case, unicode modifiers' side are determined by whether they
are prefixed or suffixed by a unicode 'arrow' character. This function
handles the three cases: prefix, suffix, and neither.
"""
translated_mods = ""
for i, char in enumerate(modifiers):
if char in ["‹", "›", " "]:
continue
if i > 0 and modifiers[i-1] == "‹":
translated_char = UNICODE_MODS["‹" + char]
elif i < len(modifiers) - 1 and modifiers[i + 1] == "›":
translated_char = UNICODE_MODS[char + "›"]
else:
translated_char = UNICODE_MODS[char]
translated_mods += translated_char

return translated_mods


def is_modded_key(mapping: str) -> ModifiedKey:
Expand All @@ -377,15 +411,14 @@ def is_modded_key(mapping: str) -> ModifiedKey:
a hyphen or a pipe). The modifiers may be enclosed in angle brackets.
"""

expression = r"<?([^|>-]+)[|-]([^>]+)>?"
expression = r"[<:]?([^|>-]+)[|-]([^>]+)>?"
if query := search(expression, mapping):
modifiers, key = query.groups()
modifiers = modifiers.replace(" ", "")
key = key.strip()
return ModifiedKey(modifiers, key)



def get_modifiers(usr_mods: str, usr_map: str) -> dict:
validate_mod_aliases(usr_mods)
mods = {}
Expand All @@ -409,7 +442,7 @@ def parse_chars_in_parens(string: str) -> tuple:
"""
in_parens: list = findall(r"\((.*?)\)", string)
validate_optional_mod_sets(string, in_parens)
not_in_parens: list = findall(r"[\w+⌘⌥⌃⇧]+(?![^()]*\))", string)
not_in_parens: list = findall(r"[\w+]+(?![^()]*\))", string)

in_parens = list(in_parens[0]) if in_parens else None
not_in_parens = list(not_in_parens[0]) if not_in_parens else None
Expand Down
25 changes: 16 additions & 9 deletions test/test_map_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@ def test_TranslatedMap():


def test_is_modded_key():
valid = "<mods-primary>"
invalid_modified = [
valid_modified_keys = [
"<mods-primary>",
"mods-primary",
"mods|primary",
"mods | primary",
"m o d s | primary",
]
invalid_modified = [
"mods:primary",
"<modsprimary>",
"<mods-primary",
"mods-primary>",
"mods |",
"| primary",
]

valid_ModifiedKey: ModifiedKey = mp.is_modded_key(valid)
assert valid_ModifiedKey
assert type(valid_ModifiedKey) == ModifiedKey
assert valid_ModifiedKey.modifiers == "mods"
assert valid_ModifiedKey.key == "primary"
for valid in valid_modified_keys:
valid_ModifiedKey: ModifiedKey = mp.is_modded_key(valid)
assert valid_ModifiedKey
assert type(valid_ModifiedKey) == ModifiedKey
assert valid_ModifiedKey.modifiers == "mods"
assert valid_ModifiedKey.key == "primary"

for inv_mod in invalid_modified:
assert not mp.is_modded_key(inv_mod)
Expand Down

0 comments on commit 1c359f7

Please sign in to comment.