sezanzeb / input-remapper

🎮 ⌨ An easy to use tool to change the behaviour of your input devices.
GNU General Public License v3.0
3.86k stars 161 forks source link

Request: LED control #160

Open cybermaus opened 3 years ago

cybermaus commented 3 years ago

I would be nice if key-mapper could also control the keyboard LED's: Num/Caps/Scroll lock. Not very urgent though.

I checked, and evdev already supports it. But simply writing the codes with event function e(...) does not work, because the LED event needs to backfeed to the actual device rather then into the virtual keyboard.

I am sure people can find several uses for them, but simply emulating Num-lock & Caps-lock behaviour would be one of them. In other words, if Caps-lock is on, play different keys (typically uppercase).

In my use case, I use 3 keys (actual num-lock and the 2 next to it) to set 3 "device modes", each priming the keyboard to control a different device (camera's to be exact). When none of these 3 modes is active, the keyboard is 'dead' so no accidental keystrokes. The mode stays active for 10 seconds, so after 10 seconds the keyboard goes 'dead' again.

I have it like this:

{
    "mapping": {
        "1,69,1": "set(dev,cam01).w(10000).ifeq(dev,cam01,set(dev,none))",
        "1,98,1": "set(dev,cam02).w(10000).ifeq(dev,cam02,set(dev,none))",
        "1,55,1": "set(dev,mon01).w(10000).ifeq(dev,mon01,set(dev,none))",
    }
}

It would be very nice, if the Num-Lock LED would be on while the keyboard is primed for any device. So I imagine a function led(LED_NUML,1) (or shorter form l() )

allowing for

{
    "mapping": {
        "1,69,1": "set(dev,cam01).led(LED_NUML,1).w(10000).ifeq(dev,cam01,set(dev,none).led(LED_NUML,0))",
        "1,98,1": "set(dev,cam02).led(LED_NUML,1).w(10000).ifeq(dev,cam02,set(dev,none).led(LED_NUML,0))",
        "1,55,1": "set(dev,mon01).led(LED_NUML,1).w(10000).ifeq(dev,mon01,set(dev,none).led(LED_NUML,0))",
    }
}
sezanzeb commented 3 years ago

Some notes (for contributors or for myself if I find time to work on features again):

cybermaus commented 3 years ago

Out of interest, if I start working on this, which of the following would you feel is best:

Additional notes:

sezanzeb commented 3 years ago

Thanks for your ideas

If we detect a LED_xx is used, it automatically goes back into the device rather then onward?

Sounds unintuitive, e should probably always write to the uinput if not explicitly specified

macro's in short form are already rather opaque to read

If there was a nice editor available with lots of space then I would also be in favor of adding long names for all functions: https://github.com/sezanzeb/key-mapper/issues/109

New function led()

Would it be intuitive if key writes key events into the uinput, but led writes them into the InputDevice?

New function l()

In the long term there should be a macro editor, so I would rather not add more short names.


how about something that works like e but that writes into the InputDevice with the matching capability? What would be a readable name for something like that? toDevice?

cybermaus commented 3 years ago

I worked on this a little, got it to work. And dispite your misgivings on the "reuse e()" suggestion, I have to say it was extremely easy using that, looking at the actual code changes needed: commit 95240868a9e67f2b3f06c6ba7dc484b0df1b8743

That was not my first attempt, I first went into macro.py, replacing all mapping parameters with context parameters in the parse call stack, to make availible the new context.source (as well as context.mapping). However, then I figured out that the final actual e() working bit was a call-back into keycode_mapper() and I could merely do the same, with no need to insert the context into macro at all.

I think I would still write a led() that would be a shorthand wrapper to e()

And I guess we -could- make a parallel toDevice(), pretty much duplicating e(). But frankly, I do not think reusing e() is all that counter-intuitive. It is normal, expected even, and has always been, that key-events travel from keyboard to pc, and led-events travel in the opposite direction from pc to keyboard.

So I am arguing to leave the co-opting of e() as is, and merely add a led() shorthand wrapper.

sezanzeb commented 3 years ago

I left two comments there: https://github.com/sezanzeb/key-mapper/commit/95240868a9e67f2b3f06c6ba7dc484b0df1b8743#r55064065 https://github.com/sezanzeb/key-mapper/commit/95240868a9e67f2b3f06c6ba7dc484b0df1b8743#r55064130

sezanzeb commented 3 years ago

Since toDevice adds ultimate flexibility I would be strongly in favor of solving it with that.

It is normal, expected even, and has always been, that key-events travel from keyboard to pc, and led-events travel in the opposite direction from pc to keyboard.

but what if some rare use-case requires the unexpected

cybermaus commented 3 years ago

I left comments to your comments. How does that work, are those visible to you?

Anyway, comments aside (please do also look at those), I have an additional reason I may indeed want to duplicate to a toDevice() and that is.. to which device?

For my personal use case it is not critical, just use the mapping source. But when I test, putting the led() macro in a mouse button, the EV_LED event get's send to the mouse. Which is promptly ignoring it. Again, fine for me, but I suspect you prefer a more structured solution.

So making a separate function does allow us to put extra code in there without obfuscating the original e() code. As well as address the hypothetical "what if we need to send EV_LED forward rather then back" concern.

One obvious solution is to check capabilities. But there are some hooks and sharp edges attached to that:

I think I would go with the parameter, with <any> as omitable default, meaning "self preferred, but otherwise the first one we find with such capabilities"

PS: and I would still advocate for a led(code,value) shorthand for toDevice(EV_LED, code, value, "any")

sezanzeb commented 3 years ago

How does that work, are those visible to you?

I got an email for each one. I left comments on them, but lets continue here instead of the comments since they are not visible in the thread

Which is promptly ignoring it.

are you sure it got sent to the right devnode with the EV_LED capability? since there are multiple sources

If the mapping source has LED capabilities

there are multiple (I have added the explanation from my comment to development.md: https://github.com/sezanzeb/key-mapper/blob/main/readme/development.md#multiple-sources-single-uinput)

Otherwise, check all sources Should we require a target parameter? ; ; ;

you mean all devnodes of all connected input devices? For example if I apply a mapping to a "Razer Mouse" are you suggesting that "Logitech Keyboard" could be entered as parameter?

and I would still advocate for a led(code,value) shorthand for toDevice(EV_LED, code, value, "any")

we can probably do that

cybermaus commented 3 years ago

Ok, slowly progressing: commit 24e8a12dace518ea389b633ec2e1a4d553fcbe31 Maybe viewed better relative to your own main

I am not at this point done, nor asking for help. Just reporting on progress and if you have the time, comments.

sezanzeb commented 3 years ago

Nice! You could already open a pull request and set it to draft, it will make keeping track of comments easier

cybermaus commented 3 years ago

Ok, I am running into some difficulties that is the magic of asyncio, lambda functions, ensure_future. I am a noob at python, never wrote a single line till a few weeks ago.

I think I am starting to see some structure in above magic, but apart from me having trouble reciting those incantations, I am also realising splitting out normal event macro's from device event macro's is not so simple, because they are utterly mixed, and I would have to start to tag them with their 'type' because in the middle of that syncio stuff, it would need to suddenly go to a different handler function.

So I was wondering, just an idea: maybe we should simply rethink how we see event()

Now it """Write any event.""" Make it """Write any event to any device"""

Add a optional parameter: def event(self, ev_type, code, value, dev=None):

with dev=: -None : Send to uinput (current behaviour) -uinput : Send to uinput (current behaviour) -source : Send to current mapping source device with correct capabilities -any : send to any source (iow, real) device with correct capabilities (optional future) -"name" : send to device that has "name" as (partial) match. (optional future)