Skip to content

Commit

Permalink
add support for non-qwerty layout
Browse files Browse the repository at this point in the history
The support is done via a class that consist of a mapping from KC to
others.

Such mapping are similar to what is done in QMK therefore a script is
provided to bootstrap their definition.

Existing doc on how to add internationla keycode is removed as those
keycode are now all integrated in the base KC definition.
  • Loading branch information
crazyiop committed Jun 9, 2022
1 parent 80b44b5 commit e498c38
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 29 deletions.
66 changes: 37 additions & 29 deletions docs/international.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
# International Keycodes
International extension adds keys for non US layouts. It can simply be added to
the extensions list.
# Non QWERTY layout
If your computer is not configured (at the OS level) to use the QWERTY layout, you will have a hard time making your layers as `KC.Q` will for example output an `a` on an azerty layout.

To not have to deal yourself with all those translation we provide the `KeyMapConverter` class. It is intented to be subclassed for your layout, you then only have to define your mapping.

The mapping is a relation:
- from what you want to use in the keymap (= what you see on your keyboard key)
- to what it should send as qwerty (= what your computer will print for this key if you set it up to use a QWERTY layout)


## Example use
For a french AZERTY layout you would have something like:

file: /kmk/extensions/keymap_extras/keymap_french.py
```python
from kmk.extensions.international import International
keyboard.extensions.append(International())
from kmk.keys import KC
from kmk.extensions.keymap_extras.base import KeyMapConverter

class AZERTY(KeyMapConverter):
MAPPING = {
'A': KC.Q,
'Z': KC.W,
'E': KC.E,
'R': KC.R,
'T': KC.T,
'Y': KC.Y,
...
}
```

## Keycodes

|Key |Aliases |Description |
|-----------------------|--------------------|-----------------------------------------------|
|`KC.NONUS_HASH` |`KC.NUHS` |Non-US `#` and `~` |
|`KC.NONUS_BSLASH` |`KC.NUBS` |Non-US `\` and <code>&#124;</code> |
|`KC.INT1` |`KC.RO` |JIS `\` and <code>&#124;</code> |
|`KC.INT2` |`KC.KANA` |JIS Katakana/Hiragana |
|`KC.INT3` |`KC.JYEN` |JIS `¥` |
|`KC.INT4` |`KC.HENK` |JIS Henkan |
|`KC.INT5` |`KC.MHEN` |JIS Muhenkan |
|`KC.INT6` | |JIS Numpad `,` |
|`KC.INT7` | |International 7 |
|`KC.INT8` | |International 8 |
|`KC.INT9` | |International 9 |
|`KC.LANG1` |`KC.HAEN` |Hangul/English |
|`KC.LANG2` |`KC.HANJ` |Hanja |
|`KC.LANG3` | |JIS Katakana |
|`KC.LANG4` | |JIS Hiragana |
|`KC.LANG5` | |JIS Zenkaku/Hankaku |
|`KC.LANG6` | |Language 6 |
|`KC.LANG7` | |Language 7 |
|`KC.LANG8` | |Language 8 |
|`KC.LANG9` | |Language 9 |
file: /code.py
```python
from kmk.extensions.keymap_extras.keymap_french import AZERTY

FR = AZERTY()
keyboard.keymap = [[FR.A, FR.Z, FR.E, FR.R, FR.T, FR.Y, ...]]
```

## Generating a missing layout
Making such mapping is very tedious. The script in `/tools/mk_keymap.py` will generate such mapping from the existing one from the qmk repo.
For the above example, you would start with the file generated by `/tools/mk_keymap.py french`

Warning: The generated layout **need to be tested** and might need to be refined but that should save you quite some work.
11 changes: 11 additions & 0 deletions kmk/extensions/keymap_extras/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from kmk.keys import KC


class KeyMapConverter:
''' Class to allow easy definition of alternative layout '''

def __getattr__(self, key):
try:
return self.MAPPING[key]
except KeyError:
return getattr(KC, key)
134 changes: 134 additions & 0 deletions tools/mk_keymap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python3

import re
import requests
import sys
from pathlib import Path

try:
from git import Repo

repo = Repo('.', search_parent_directories=True)
root = Path(repo.working_tree_dir)
except ImportError:
print(
'could not import git (pip install gitpython). Assuming this is run from the top level of the repository'
)
root = Path('.')

URL = 'https://github.com/qmk/qmk_firmware/file-list/master/quantum/keymap_extras'
key_regexp = re.compile(
r'^#define\s*(?P<slug>..)_(?P<new_keycode>\S*)\s*(?P<kc_keycode>\S*)(\s*//\s*(?P<comment>.*))?'
)
sep_regexp = re.compile(r'^/(/|\*)\s*(?P<comment>.*)')


def list_qmk_keymap():
r = requests.get(URL)
return set(re.findall(r'keymap_[^\./]*\.h', r.text))


def generate(keymap):
print(f'using reference: {keymap}')
r = requests.get(
f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/quantum/keymap_extras/{keymap}'
)
slug = ''
content = []
seen = {}
for line in r.text.split('\n'):
key = re.search(key_regexp, line)
sep = re.search(sep_regexp, line)
if key:
slug = key['slug']
kc = key['kc_keycode']
for i in range(10):
kc = kc.replace(f'KC_{i}', f'KC.N{i}')
kc = kc.replace('KC_', 'KC.')
kc = kc.replace('S(', 'KC.LSFT(')
kc = kc.replace('ALGR(', 'KC.RALT(')

# expand self ref to previously defined keycode
for m in re.finditer(slug + '_\w*', kc):
try:
kc = kc.replace(m.group(), seen[m.group()])
except KeyError:
pass

new = key['new_keycode']
if new.isdigit():
new = 'N' + new

s = f" '{new}': {kc},"
seen[slug + '_' + key['new_keycode']] = kc

try:
s += f" # {key['comment']}"
except IndexError:
pass
content.append(s)

elif sep and sep['comment']:
if 'Copyright' in line or 'clang' in line:
continue
s = f" # {sep['comment']}"
content.append(s)

print(f'using slug: {slug}')
name = re.search(r'[^_]*_(.*)\.h', keymap).group(1).upper()
print(
f"default class name: {name} (You might want to change it before submitting your generated file)"
)

layout = """from kmk.keys import KC
from kmk.extensions.keymap_extras.base import KeyMapConverter
class {}(KeyMapConverter):
# Generated mapping from:
# https://github.com/qmk/qmk_firmware/blob/master/quantum/keymap_extras/{}
# might need some tweaking...
MAPPING = {{
{}
}}
""".format(
name, keymap, '\n'.join(content)
)

with open(
root / 'kmk' / 'extensions' / 'keymap_extras' / keymap.replace('.h', '.py'), 'w'
) as fp:
fp.write(layout)


if __name__ == '__main__':
lang = sys.argv[1] if len(sys.argv) == 2 else ''

if not lang:
print(
'You should pass an argument with a search term (probably your language abreviation), and if necessary refine it to select only one keymap.'
)
print(
f'You can also consult {URL} to check the list and the files content first...'
)
sys.exit(1)

choices = sorted(keymap for keymap in list_qmk_keymap() if lang in keymap)
if not choices:
print('Your search term is too restrictif. No keymap match it...')
sys.exit(2)

if lang + '.h' in choices:
keymap = lang + '.h'
elif 'keymap_' + lang + '.h' in choices:
keymap = 'keymap_' + lang + '.h'
elif len(choices) > 1:
print('The following keymap match your given name:')
print(choices),
print('Please refine your search term.')
sys.exit(0)
else:
keymap = choices[0]

generate(keymap)

0 comments on commit e498c38

Please sign in to comment.