Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

We need a global keymap #1685

Closed
blaggacao opened this issue Dec 29, 2020 · 11 comments
Closed

We need a global keymap #1685

blaggacao opened this issue Dec 29, 2020 · 11 comments

Comments

@blaggacao
Copy link
Contributor

blaggacao commented Dec 29, 2020

Issue description

That is what — in principle — we need (here encoded as sway config):

# key layout
set $LPinky        a
set $LRing         s
set $LRingUp       d
set $LMiddleDown   m
set $LMiddle       h
set $LMiddleUp     r
set $LPoint        t
set $LPointDown    c
set $LThumbO       Delete
set $LThumbM       Tab
set $LThumbI       Esc
# mirror
set $RThumbI       Space
set $RThumbM       Return
set $RThumbO       Backspace
set $RPointDown    k
set $RPoint        n
set $RMiddleUp     u
set $RMiddle       e
set $RMiddleDown   l
set $RRingUp       p
set $RRing         o
set $RPinky        i

in configs throughout we would then write:

{
      down = mkOption {
        # keymap type so renderKey can implement app specific key rendering over keyboards & layouts
        type = types.keymap;
        default = cofig.muscleMem.RMiddle;
        description = "Home row direction key for moving down.";
      };
      # renderKey iterates over keyboards and implements key map representation per application
      # then `renderKey down` produces a datastructure which configuration should iterate over for 
      # specific keyboard layouts / physical keyboard inputs
}

Meta

Maintainer CC

Technical details

evdev keycodes are invariants across the entire software stack and are the source representation most suitable to map into a coordinate based location reference. Alternatively, we might want to use xkb scancodes as utilized in cat /usr/share/X11/xkb/keycodes/evdev which are catched through evdev config on input events of interest and offset by 8 vs evdev codes. KBX does map those codes to internal symbols representation which resemble location identifiers for most keys (eg <AE01>), but keys that have a special shape on traditional keyboards (eg <SPACE>). Hence kbx internal symbols are not a reliable location identifier, for example on an Atreus.

{
  # so that configurations can discover boards and layouts
  boards =  {
    # see output below of cat /proc/bus/input/devices
    main = { 
      bus="0003";  # might not be needed
      vendor="045e"; 
      product="07b2"; 
      version="0111";
      # xkb style strings to pull in appropriate invariant metadata from xkb database?
      layouts = [ "us(dvorak)" "es(coleman)" "custom"];
    };
    atreus = {
      bus="0003"; 
      vendor="1209"; 
      product="2303"; 
      version="0101";
      layouts = [ "what to put here? - it's custom firmware" ];
    };
    # it should be possible in principle to work backwards from here in configurations towards their
    # implementation of keyboard identifiers, where they are supported (like in sway).
    # Else we need to extend this data structure accordingly until we reach the smallest common denominator.
  };
  LPinky = {
    # evdev_scancodes = (kbx_scancodes - 8)
    # see: https://cgit.freedesktop.org/xorg/driver/xf86-input-evdev/tree/src/evdev.c#n72
    # `cat /usr/include/linux/input-event-codes.h | grep KEY_A` --> 30
    # `xev -event keyboard` + press 'a'                         --> 38
    main = "0x001e"; # decimal: 30, evdev: KEY_A, xkb_scancode: 38, 
    atreus = "0x001e"; # indeed the same for my firmware version
    # from evdev scancodes with sufficient (invariant) metadata, we are absolutely guaranteed 
    # to be able to work backwards to encode the actual shortcut for each keyboard and layout
  };
  LPinkyUp = { };
  LPinkyUpUp = { };
  LPinkyDown = { };
  RPointUpUP = { };
  RPointLeftUPUp = { };
}

how sway/i3 identify them keyboards:

set $atreus        '4617:8963:Keyboardio_Atreus'  # can this be construed without the text?
set $msarc         '1118:1970:Microsoft_Microsoft®_Nano_Transceiver_v1.0'
  • Applications prefer key codes if supported; and if not depend on keyboard layout, which configuration needs to accommodate for.

  • All applications: same goes with one degree of freedom less for firmware remaps (atreus example).

cat /proc/bus/input/devices
$ 
  # see output below of cat /proc/bus/input/devices
I: Bus=0003 Vendor=045e Product=07b2 Version=0111
N: Name="Microsoft Microsoft® Nano Transceiver v1.0"
P: Phys=usb-0000:00:14.0-2/input0
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/0003:045E:07B2.0001/input/input4
U: Uniq=
H: Handlers=sysrq kbd event4 leds
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff800000000007ff febeffdff3cfffff fffffffffffffffe
B: MSC=10
B: LED=7

I: Bus=0003 Vendor=045e Product=07b2 Version=0111
N: Name="Microsoft Microsoft® Nano Transceiver v1.0 Mouse"
P: Phys=usb-0000:00:14.0-2/input1
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1/0003:045E:07B2.0002/input/input5
U: Uniq=
H: Handlers=mouse0 event5
B: PROP=0
B: EV=17
B: KEY=1f0000 0 0 0 0
B: REL=1943
B: MSC=10

I: Bus=0003 Vendor=045e Product=07b2 Version=0111
N: Name="Microsoft Microsoft® Nano Transceiver v1.0 Consumer Control"
P: Phys=usb-0000:00:14.0-2/input1
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.1/0003:045E:07B2.0002/input/input6
U: Uniq=
H: Handlers=event6
B: PROP=0
B: EV=5
B: REL=1040

I: Bus=0003 Vendor=045e Product=07b2 Version=0111
N: Name="Microsoft Microsoft® Nano Transceiver v1.0 Consumer Control"
P: Phys=usb-0000:00:14.0-2/input2
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.2/0003:045E:07B2.0003/input/input7
U: Uniq=
H: Handlers=sysrq kbd event7
B: PROP=0
B: EV=10001f
B: KEY=3f000301ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17c007 ffff7bfad9415fff ffbeffdfffefffff fffffffffffffffe
B: REL=1040
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=045e Product=07b2 Version=0111
N: Name="Microsoft Microsoft® Nano Transceiver v1.0 System Control"
P: Phys=usb-0000:00:14.0-2/input2
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.2/0003:045E:07B2.0003/input/input9
U: Uniq=
H: Handlers=kbd event8
B: PROP=0
B: EV=1b
B: KEY=40000001000000 1200000000 0 800000000 40000010cc00 10168000000000 0
B: ABS=10000000000
B: MSC=10

[ ... ]

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus"
P: Phys=usb-0000:00:14.0-1/input2
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.2/0003:1209:2303.0008/input/input25
U: Uniq=CatreusE
H: Handlers=mouse2 event18 js0
B: PROP=0
B: EV=1f
B: KEY=ff0000 0 0 0 0
B: REL=900
B: ABS=3
B: MSC=10

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus System Control"
P: Phys=usb-0000:00:14.0-1/input3
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:1209:2303.0009/input/input26
U: Uniq=CatreusE
H: Handlers=kbd event19
B: PROP=0
B: EV=1b
B: KEY=40000001000000 1200000000 0 800000000 40000010cc00 10168000000000 0
B: ABS=10000000000
B: MSC=10

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus Keyboard"
P: Phys=usb-0000:00:14.0-1/input3
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:1209:2303.0009/input/input27
U: Uniq=CatreusE
H: Handlers=sysrq kbd event20 leds
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff980000000007ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus Consumer Control"
P: Phys=usb-0000:00:14.0-1/input3
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:1209:2303.0009/input/input28
U: Uniq=CatreusE
H: Handlers=kbd event21
B: PROP=0
B: EV=1f
B: KEY=3f000301ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17c000 677bfad9415fed 19ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus Mouse"
P: Phys=usb-0000:00:14.0-1/input3
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:1209:2303.0009/input/input29
U: Uniq=CatreusE
H: Handlers=mouse3 event22
B: PROP=0
B: EV=17
B: KEY=ff0000 0 0 0 0
B: REL=1943
B: MSC=10

I: Bus=0003 Vendor=1209 Product=2303 Version=0101
N: Name="Keyboardio Atreus"
P: Phys=usb-0000:00:14.0-1/input4
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.4/0003:1209:2303.000A/input/input30
U: Uniq=CatreusE
H: Handlers=sysrq kbd event23 leds
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f
@blaggacao blaggacao changed the title We We need a global keymap Dec 29, 2020
@blaggacao
Copy link
Contributor Author

blaggacao commented Dec 30, 2020

An improved variant encodes keyboard layer information:

{
  LPinky = {
    main = "0x001e"; # has no layers -- damn, needs solution. emulate layers with a layer modifier combo?
    atreus = {
      layer1 = "0x001e"; # KEY_A
      layer2 = "0x0006"; # KEY_5
      layer3 = "0x0066"; # KEY_HOME
    };
  };
}

There is a problem, though: layers and modifiers are potential substitutes on keyboards that support layers. If a keyboard supports layers, one would probably always want to prefer layers over modifiers since those can piggyback on the layer muscle memory already trained for actual key symbol output in a text input situation.

To accommodate for this circumstance, and to stay true to the ideals of muscle memory identifiers, we would have to define a layer emulation mechanism for non-layer keyboards, potentially waving modifier keys all-together (or at least certain combinations to be used for emulated layer switching) — if you really mindful of preventing Repetitive Strain Injuries when traveling with your laptop without your custom keyboard or working with your environment on a foreign machine, you could map them to a foot pedal switches which you would still have to carry. (But then I guess, we would have to settle on not relying on kxb but work with evdev events directly... hmmm)

{
  board = {
    main = { 
      layermods = {
        layer1 = [ ];
        layer2 = [ "0x007d" ]; # KEY_LEFTMETA
        layer3 = [ "0x007d" "0x0038" ]; # KEY_LEFTMETA + KEY_LEFTALT
      };
    };
  };
}

If that was to work, people would possibly opt for using two layers (firmware implemented or emulated through this proposal) + classical modifiers (implemented in the application) to get acceptable performance in a mixed keyboard scenario.

@berbiche
Copy link
Member

Here's my understanding of your proposal:

What problem would this alleviate?

  • Provide a central source to define keys and keybindings
  • Provide a generic method to define keybindings
  • Reduce repetition
  • Use this central source as default keybindings in existing modules

Questions

How does this compose with the varying existing software working with keybindings?

  • sxhkd (simple X hotkey daemon)
  • i3/Sway
  • various mail clients
  • etc.
    How would you support both a sequence of keys and multiple keys pressed together?

Technically, how do you propose this should work?

Implementing something with evdev seems completely out of scope for Home Manager (and sounds like another key daemon IMO).

@blaggacao
Copy link
Contributor Author

blaggacao commented Dec 31, 2020

How does this compose with the varying existing software working with keybindings?

If there exists f (x [type.keymap], y layer, z [mod]) -> (g keychordString), then application module shall implement f. The resulting key chord string variable g holds an application specific string value that expresses the desired keychord.

Example:

{
    # assume sway-ish
    config = ''
    bindcodes ${f([LPinky Rpinky],2,[])} command
    ''
    # TODO: add sugar to the syntax
    # if this is an illegal keychord in app,
    # which it is in sway, then f shall alert out
}

So far so good. But this assumes global statefulness of the selected keyboard for the current generation. And most applications cannot bind to keycodes directly, so global keylayout state is conclusively assumed, too.

I suppose, application support migh be so poor in some instances, that the only workaround that could reliably produce acceptable outcomes is deduplicating configs for keylayouts*keyboards and invoking applications from a wrapper that selects config based on:

  • detecting xkb key layout
  • use helper switch for keyboard (people with multiple keyboards a rarer and a home-manager-provided switch wouldn't be too bad)

Of course, if an application supports adaptive behaviour per keyboard / keylayout, then a conditional configuration should be crafted by means of discovering keyboards and keylayouts from the configuration.

Seems like trouble. But pain is temporary, glory is forever. And this broken system (mnemonics 2 muscle memory 2 command) ought to be fixed in favor of true ergonomics (muscle memory 2 action).

How would you support both a sequence of keys and multiple keys pressed together?

I think, while I could give a relatively good answer for chording, a solution for sequences is a little more involved. First, hotkeys are always chorded. It is only a modal keyboard driven command language that requires for sequenced input. In principle, the same shortcutring of mental load should apply (muscle memor 2 action VS mnemonic 2 muscle memory 2 action).

An implementation of sequencs would be very much application specific, but at first glance f' with the same signature as f could do the job, where this behaviour is desired. Note that [type.keymap] already is a (ordered) slice.

We might rather think of ways to treat [mods] in the case of sequence rendering. But probably we can simplify our assumptions and it would be safe to assume that both layer and [mods] should be regarded as invariantes across the sequence by f'.

Imagine:

  • Mod1+a -> b -> Shift C — effectifely equivalent to sequential chording like on a steno typer.
  • Mod1(a -> b > c) — mod finger at rest

On the other hand, after this initial thought, it occurs to me that the more probable use case might be that a modal command mode is initiated by simple chording (Mod1+a) and then each subsequent command is implemented by "no-op" chording as well (b), so we could use plain f and omit the requirement for f'. Sounds good?

@blaggacao
Copy link
Contributor Author

blaggacao commented Dec 31, 2020

It occurs to me that in addition to a location identifier (LPinkyUpUp), we would also want to encode abstract (reusable) semantics in terms of location identifiers (up, down, left, right, del, yank, cut, etc.).

Those can have alternate bindings such as:

{
  up = [
    LRing
    RRing
  ];
}

so f'' must implement them as returning [g, g]. Wether that means newline seperated or a json array (or similar) in the context of a specific config format is an f'' implementation detail.

@blaggacao
Copy link
Contributor Author

blaggacao commented Dec 31, 2020

Please also note, if we attempt a solution as a community, we start to generate upstream backpressure so the problem gets a fair chance of a genuine fix throughout. RSI is a relative recent medical condition: I prospect at an overall level input ergonomics are still in its infant state.

Why does it matter? Only a global (location identified!) keymap will unlock the next level (of practically configurable) input ergonomics. ("as first seen on nix" TM)

@blaggacao
Copy link
Contributor Author

blaggacao commented Dec 31, 2020

I just so noticed that f (x [type.keymap] ... might be actually illegal chording in just avout any application as I doubt any application observes keypress and keyrelease events meticulously enough (if at all). Please teach me if xkb has a solution for this and can transmit normal key chording to applications.

In the absence of a solution, the signature would have to be f (x type.keymap ... instead, thereby forgoing any immediate prospect of regular key chording (which would be crazy awesome, if possible!! ... playing command chords like a piano master)

@Melkor333
Copy link

Do i understand this correctly?:

  • You want to 'rename' the keys on the keyboard and give them names relative to the position of the finger intended to press them. e.g. "transform" the english keyboard from asdf...qwer to lpinky,lring,lmid,lpoint...lpinkyup,lringup,lmidup,lpointup etc.
  • Change programs to use these abstracts for shortcuts instead of what the definition beforehand was. E.g. vim uses in the normal mode for the "down" movement the key rpoint instead of j.

If this is right, we get the problem that for every existing keybinding we have to define a new "default" keybinding. e.g. meta+f to toggle fullscreen in i3 would become ringdowndown (or lthumbleft...) + lpoint. Every Person who uses Colemak will have to relearn this shortcut since for them it was lmidup before. Letters like z and y are swapped on many keyboards and forcing a lot of NixOS users to either manually change the bindings or learn that ctrl+z now is ctrl+y is kind of a huge thing...

I love thinking about alternative keyboards and stuff like that but in my experience the general user absolutely hates if any shortcut changes for no matter what reason...

@blaggacao
Copy link
Contributor Author

blaggacao commented Jan 4, 2021

@Melkor333 Your interpretation is correct. But with a little twist: A new user would globally across home-manager "train" its layout onto the position key names (lmidup). For the more common layouts, defaults might be provided. The whole point of this idea that we can provide a consisted experience across keyboard layout switches and thereby lower the currently tremendous cost to adopt a more ergonomic keyboard layout (like colemak, workman or halmak). There never ought to be any relearning of muscle memory, which is the real command primitive versus the current mnemonic memory. Think of copy is always just lpinkydowndwon + lmiddown regardless of your layout or keyboard (coming from qwerty CTRL + C). Note that this muscle memory chord probably should ultimately also be a copy in vim/kakoune navigation mode.

If lpinkydowndwon + lmiddown is bad ergonomics for you, then anything else goes. To ease such mass reconfigurations I suggested to encode such abstractable semantics in a central place.

Change programs to use these abstracts for shortcuts instead of what the definition beforehand was.

f implemented by the program's config would translate back into current program configuration language. This translation is context aware on physical keyboard and layout. This context awareness is a legacy cost and is not easy to solve in all cases.

@stale
Copy link

stale bot commented Apr 28, 2021

Thank you for your contribution! I marked this issue as stale due to inactivity. If this remains inactive for another 7 days, I will close this issue. Please read the relevant sections below before commenting.

If you are the original author of the issue

  • If this is resolved, please consider closing it so that the maintainers know not to focus on this.
  • If this might still be an issue, but you are not interested in promoting its resolution, please consider closing it while encouraging others to take over and reopen an issue if they care enough.
  • If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.

If you are not the original author of the issue

  • If you are also experiencing this issue, please add details of your situation to help with the debugging process.
  • If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.

Memorandum on closing issues

If you have nothing of substance to add, please refrain from commenting and allow the bot close the issue. Also, don't be afraid to manually close an issue, even if it holds valuable information.

Closed issues stay in the system for people to search, read, cross-reference, or even reopen--nothing is lost! Closing obsolete issues is an important way to help maintainers focus their time and effort.

@blaggacao
Copy link
Contributor Author

I desist from promoting this, but encourage anyone to take this as inspiration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants