Code Monkey home page Code Monkey logo

Comments (15)

joshgoebel avatar joshgoebel commented on June 9, 2024 1

I tried to import the relevant class into key_context so that it could report the LED states there, but can't get the import reference to work.

I dunno what you mean... look at tranform.py lines 296-302... every time a key is hit we pass in the device name, and device_name is passed to KeyContext directly... if you need the whole device, just start passing the device to on_event instead of the device_name... (input.py line 115) then you'll need to switch to device.name in a few places... and you'll now have device right inside KeyContext where you want it.

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024 1

The other thing is that the OS does seem to keep the lock key states latched to be the same no matter which keyboard ... You can turn a lock key "on" using one device and then "off" using a different keyboard. I don't think these keys were ever intended to be used independently "per device".

Thanks for this research!

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024 1

@joshgoebel

Let me know if you want me to assign this one to you. :)

Now I can respond to this: No thanks! Hahaha 😆

Closing this as resolved by the merging of PR #76. 👍🏽 😝 🎉

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024

As I believe I stated in the other thread the first step here would be writing a utility function that simply gets the state of the numlock bit... then it should be easy to use that paired with existing conditional support. We may need to add device_status or something to the conditional context object. Or perhaps just individual attrs like is_numlock...

I'd be more than happy to review a PR exploring this a bit. I think you could get pretty far just passing device into KeyContext (though we don't want to export it) and adding a numlock helper there... then explore what some sample configs look like and whether those are sufficient to solve the problems at hand or not.

modmap("numpad (w/numlock)", {
  Key.KP1: Key.END
  }, when = lambda ctx: ctx.is_numlock
)

And one needs to clarity what happens with multiple keyboards and multiple numlock bits... but that could come after. Actually if the context is just based on the bit of the keyboard doing the input then this may just solve itself.

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024

Once you have the pieces in place it might be best to remap all the keys in one pass in a top-level modmap... mapping them correctly in one place, depending on the state of numlock.

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024

Let me know if you want me to assign this one to you. :)

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Just leaving a note here for future reference. I was able to get the current state of NumLock and CapsLock, but only at startup time, since the code is not in the right place to run constantly. It runs only when the keyboard device gets "grabbed". I tried to import the relevant class into key_context so that it could report the LED states there, but can't get the import reference to work.

I think there should be a much simpler way of obtaining just the raw device path, which is the main thing that is needed to reference the LED states. Much of what I had to do here was isolating the path from the whole device string, which contains a bunch of extraneous information the led() function doesn't want.

device /dev/input/event4, name "AT Translated Set 2 keyboard", phys "isa0060/serio0/input0"

The code below was added in devices.py in the DeviceRegistry class. All the commented-out bits are what I worked up to finally print out the state of the LEDs into the log. And it does work. But again, this just runs once at startup, as expected. It needs to be elsewhere so the LED state can be attached to each key press. I just have to figure out how to obtain the device path within a part of the app like key_context that runs for each key press.

    def grab(self, device):
        info(f"Grabbing {device.name} ({device.fn})", ctx="+K")

        # dev_path_list = []
        # print(f"###############################################   Device is: {device}", flush=True)
        # devStr = str(device)
        # start = int(devStr.index(' '))+1
        # end = int(devStr.index(','))
        # print(f"###############################################   Start: {start}, End: {end}")
        # dev_path = devStr[start:end]
        # print(f"###############################################   Device path is: '{dev_path}'", flush=True)
        # dev_path_list.append(dev_path)
        # print(f"###############################################   Device paths list is: '{dev_path_list}'", flush=True)
        # dev = InputDevice(dev_path)
        # print(f"####################################################   LED states are: {dev.leds()}", flush=True)
        # if 0 in dev.leds():
        #     numlock_is_on = True        # Int "zero" present in list means NumLock is ON
        # else:
        #     numlock_is_on = False       # Int "zero" NOT present in list means NumLock is OFF
        # print(f"####################################################   Numlock state is: {numlock_is_on}", flush=True)
        # if 1 in dev.leds():
        #     capslock_is_on = True       # Int "one" present in list means CapsLock is ON
        # else:
        #     capslock_is_on = False      # Int "one" NOT present in list means CapLock is OFF
        # print(f"####################################################   Capslock state is: {capslock_is_on}", flush=True)
        # print()

        self._loop.add_reader(device, self._input_cb, device)
        self._devices.append(device)
        try:
            device.grab()
        except IOError:
            error(
                "IOError grabbing keyboard. Maybe, another instance is running?"
            )
            raise DeviceGrabError()

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Some notes about how the LED states behave.

There is a strange disconnect in the LED state reported by evdev if you toggle the state of either key while keyzer is running. At least, with this version of the code that only runs once. And the physical LEDs on an external USB keyboard actually turn off when using the bailout key on keyszer, but the GNOME shell extension I use that shows the lock key states (laptop keyboard doesn't have any physical keyboard `LEDs) doesn't change until I actually press either lock key, which is accurate to the real state of the lock keys. So the external USB keyboard can temporarily be in a strange state where the physical LED lights don't accurately represent the state of the lock keys as far as the operating system is concerned. I would have to assume that this would also happen with physical lock key LEDs on a laptop that still has them.

Tapping one of the lock keys in this odd state will reset the state of the LEDs for both lock keys, getting the external USB keyboard back into sync with the "real" lock key states.

Don't know if this behavior of turning the lock key LEDs off when quitting was inherited from xkeysnail, or something specific to keyszer, or even specific to using the bailout key. In any case, this should probably be fixed at some point.

The other thing is that the OS does seem to keep the lock key states latched to be the same no matter which keyboard you're using. Toggling NumLock on the laptop will light up the LED on the external USB keyboard, and the same for CapsLock. And vice versa for everything. You can turn a lock key "on" using one device and then "off" using a different keyboard. I don't think these keys were ever intended to be used independently "per device".

The test log output from my addition above shows results for both keyboards if there is an external plugged in, and of course the lock key LED states are reported as being the same for both keyboards. It may actually be possible to catch (and block) the lock key events from a specific device and thereby get the OS to leave the lock state on other devices unchanged, but then I think you'd have to manually change the output from each individual key press originating from that device, until you catch another lock key event from it. Doesn't seem like it would be worth the trouble to try to isolate a specific device.

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Looks like in the right context the path can be easily obtained with device.path. Had a terrible time understanding where the dot-notation objects were actually pointing to. VSCode doesn't seem to know where they come from.

No doubt this is not what you had in mind, but just experimenting and was able to at least get it printing out the lock key states for each key press. This is extremely rough but doesn't appear to break anything. Adding an optional parameter with predefined value avoids having to change all the other places where on_event is called with only two parameters. Have to add import of InputDevice in transform.py.

OK, what the heck. Looks like all the examples online were leading me in the wrong direction. The .leds() method actually works with the entire device string, which is what you get from InputDevice(device.path). So it's actually redundant to worry about just getting the path. Complete waste of time. 🤦🏽 🤦🏽‍♂️ 🤦🏽‍♀️

I think it's fair to say I'll be messing with this for quite a while before anything useful comes from it.

input.py

def receive_input(device):
...
        on_event(event, device.name, device.path)

transform.py

from evdev import ecodes, InputDevice
...
def on_event(event, device_name, device_path=""):
    # we do not attempt to transform non-key events
    if event.type != ecodes.EV_KEY:
        _output.send_event(event)
        return

    context = KeyContext(device_name)
    action = Action(event.value)
    key = Key(event.code)

    if not device_path == "":
        led_dev = InputDevice(device_path)
        print(f"####################################################   LED states are: {led_dev.leds()}", flush=True)
        if 0 in led_dev.leds():
            numlock_is_on = True        # Int "zero" present in list means NumLock is ON
        else:
            numlock_is_on = False       # Int "zero" NOT present in list means NumLock is OFF
        print(f"####################################################   Numlock state is: {numlock_is_on}", flush=True)
        if 1 in led_dev.leds():
            capslock_is_on = True       # Int "one" present in list means CapsLock is ON
        else:
            capslock_is_on = False      # Int "one" NOT present in list means CapLock is OFF
        print(f"####################################################   Capslock state is: {capslock_is_on}", flush=True)
        print()
        print(f"########################  context: {context}", flush=True)
        print(f"########################  device_path: {device_path}", flush=True)
        print(f"########################  led_dev: {led_dev}", flush=True)
        print(f"########################  led_dev.leds(): {led_dev.leds()}", flush=True)
        print(f"########################  action: {action}", flush=True)
        print(f"########################  key: {key}", flush=True)
        print()

    ks = find_keystate_or_new(
        inkey=key,
        action=action
    )
...

Some output:

(II) in A (press)
(DD) on_key A press
(OO) press A
####################################################   LED states are: []
####################################################   Numlock state is: False
####################################################   Capslock state is: False

########################  context: <keyszer.lib.key_context.KeyContext object at 0x7fb41ac6fd60>
########################  device_path: /dev/input/event4
########################  led_dev: device /dev/input/event4, name "AT Translated Set 2 keyboard", phys "isa0060/serio0/input0"
########################  led_dev.leds(): []
########################  action: release
########################  key: A

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Simplified. Passing in the whole device as the optional parameter. Now to pass the relevant values into KeyContext, I guess? Which means this should be rearranged a bit. I'll try to mess with it more this weekend and see what can be accomplished.

def on_event(event, device_name, device=""):
    # we do not attempt to transform non-key events
    if event.type != ecodes.EV_KEY:
        _output.send_event(event)
        return

    context = KeyContext(device_name)
    action = Action(event.value)
    key = Key(event.code)

    capslock_is_on = False
    numlock_is_on = False

    if not device == "":
        leds_list = [device.leds()]
        print(f"####################################################   LED states are: {device.leds()}", flush=True)
        if 0 in leds_list: numlock_is_on = True
        print(f"####################################################   Numlock state is: {numlock_is_on}", flush=True)
        if 1 in leds_list: capslock_is_on = True
        print(f"####################################################   Capslock state is: {capslock_is_on}", flush=True)

    ks = find_keystate_or_new(
        inkey=key,
        action=action
    )
...

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024

You're getting there. Have you done any Exercism or other Python exercises lately? You just need to pass the whole device now:

def on_event(event, device):

We don't need a default - device is mandatory... then later if you need name or path you just get them from device... piece of code that need access to LEDs (KeyContext) you can pass down the full device object... piece of code that don't you just change to reference device.name instead of the older variable, etc....

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

I thought that I understood where device.name was coming from, but when I try to use it in key_context I keep getting:

AttributeError: 'str' object has no attribute 'name'

Even if I do:

from evdev import *

Or

from evdev import InputDevice, InputEvent, ecodes

Which is what is in input.py where device.name works fine.

These things seem to be defined in evdev InputDevice class, so I would have thought that importing InputDevice would be the only thing necessary.

Then again I guess I can just use InputDevice on the device_name parameter that's already getting passed in... Will have to try that.

from keyszer.

joshgoebel avatar joshgoebel commented on June 9, 2024

Make a PR of this and push it. That'd be easiest way to help you I think.

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Wanted to actually get something working all the way through to the config file before submitting anything. Still wasn't able to get past the problem with getting device.name to work within KeyContext, but was able to support using ctx.capslock_state and ctx.numlock_state in my config file. Works, but has issues, as outlined in the PR comments.

I think the issue with getting device.name working in KeyContext is that I used an optional parameter that's just a string passed in from on_event when the app starts up and the device hasn't been grabbed yet, and device.name doesn't like that. Maybe the issue could be bypassed with a try so it doesn't cause a crash when there is no valid device yet. Or just an if statement, similar to how I block using .leds() until there is a real device available.

#76

EDIT: Yes, looks like that is one solution, and now I can just pass in device all by itself everywhere. Just had to avoid using device.name prior to there being a valid device available. That doesn't happen until you press a key on the keyboard.

class KeyContext:
    # def __init__(self, device_name, device):
    def __init__(self, device):
        self._X_ctx = None

        # Must declare these here or app will crash if keyboard device hasn't been "grabbed" yet
        self._capslock_state = ""
        self._numlock_state = ""

        leds_list = []

        # Check for actual device name being present before using evdev's ".leds()" method
        if not device == "":
            self._device_name = device.name
            leds_list = device.leds()
            self._capslock_state = "ON" if 1 in leds_list else "OFF"
            self._numlock_state = "ON" if 0 in leds_list else "OFF"

from keyszer.

RedBearAK avatar RedBearAK commented on June 9, 2024

Updated the PR with this apparent solution. It no longer has an issue with device.name inside KeyContext.

from keyszer.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.