At what points can plugins act?

I’m mentally designing a plugin that would be a lot like ShapeShifter, but with the ability to also have the output of shift+key to be an unshifted character. For example, shift+9=.

My idea would be to look at the status of the whole keyboard, or at least the report, after the report has been compiled but before it gets sent. While the scan is happening, just don’t insert anything for the key in question, then, once the scan is finished, if there isn’t anything in the report that would be messed up by flipping the shift modifier bits, do that and insert the replacement keycode (or if the modifier bits are okay already, just insert the replacement keycode.

It may be that I just don’t understand Kaleidoscope well enough yet, and this all makes no sense with the existing architecture, but both of the plugins that I desire most would depend on checking the status of other keys in order to avoid introducing overlap errors, and that means that I would need access to the report after it’s ready, but before it’s sent, not just while it’s being compiled.

This is only half thought out so far, but I wanted to get it down and see if it makes sense before I get lost in the weeds again and forget the idea. Can anyone tell me if this is a feasible approach?

1 Like

Can you open a Kaleidoscope task tagged Bug + Doc with “Document all Kaleidoscope hooks, with their inputs, outputs and hook points”?

I expect @algernon has actually already written about this somewhere, but this is “important core doc” that should be somewhere easy to find.

2 Likes

This is something along the lines of what I wanted to do, to fix some of the plugins that currently suffer from scan order issues. There are also some plugins that call sendReport() themselves… But I’m getting ahead of myself.

At the moment, there are four places a plugin can hook into Kaleidoscope, and do some of their own stuff:

  • Their .begin() method runs when Kaleidoscope.use() is called, and can be used to register the other hooks.
  • They can have event handler hooks, which run every cycle, for every key, in the order they are registered. These hooks get the mapped key, its position, and the key state as arguments.
  • They can have a loop hook, which runs twice: once right after scanning the matrix and going through the event handler hooks, before sending the report; and a second time after sending and clearing the report.

For what you want, the first run of the loop hook is the appropriate place.

But there are a few gotchas with the method you outlined…

There are cases where you’d want to insert multiple things, or perform a series of events like key presses, releases, and so on. Think macros. At the moment, these will send a report themselves, kinda undermining your idea. It would be great if we could split up the functionality so that the eventHandler decides what to do, but the loop hook injects the keys, because that has access to the full report at that point. This isn’t as trivial as it sounds.

I’m not sure plugins have access to the report right now. This might change, but I’d feel bad about exposing such low level details. Especially since that’s tied to USB, and if Kaleidoscope ever starts supporting Bluetooth keyboards, I believe the report there looks different.

It is a great idea. I’m not sure about its feasibility, but I can’t rule it out either. This is something that will need quite a bit of pondering to figure out. To see if it is possible to do at all, in a way that isn’t a colossal, fragile hack.

1 Like

Either I don’t have the bits required to tag issues in that repository, or Github’s interface has defeated me.

I haven’t thought through all the implications of macros and the like yet, but I was thinking of having this particular plugin not insert anything into the report until it gets to the loopHooks, then only do it if it’s safe, possibly by paying attention to which keys were pressed during the event handler pass. There’s still some order dependence, though: not everyone can be last…

2 Likes

My naïve thought is that there should be an initial scan pass that just determines which keys are pressed, and then have all of that information available to the plugin event handlers when they actually start doing stuff. Would that not be as helpful as I imagine it would be?

I’ll get some time to really think this all through properly sometime soon, I hope…

It would probably make more sense to have it available in the loopHook and not the event handler hook - the event handler hook fires during scan for individual keys, while the loopHook fires after scan is complete. The only thing missing, really, is an API to let plugin loopHooks access reliable information from the USB report - as @algernon said above, it’s perhaps undesirable to just give plugins raw access, but maybe access through some type of intermediate layer could be doable. Then again, maybe that would be an undesirable amount of code bloat.

If it can’t access (and modify) the report after all the keys have been scanned, it won’t be able to do what I have in mind. And it’s (probably) got to act both in the event handler hook and the loop hook, because it needs to remove the keycode for that key from the report before anything else uses it and sends what would be the wrong code to the host, which could happen during the event handler hook, or before this plugin acts in the loop hook.

One way or another, to do this properly, I need to see the state of all the keys to make sure flipping the shift modifiers’ state(s) won’t result in an incorrect keycode being sent if there’s overlap, and likewise, I need to be able to stop a keycode from the key in question being sent if I can’t safely change that modifier. I’m pretty sure I can’t do what I want without read and write access to the report during the loop hook’s first pass.

1 Like

Well, there’s KeyboardHardware.leftHandState and KeyboardHardware.rightHandState, which is pretty much that. The downside is that these are hardware-specific things, and I found no good way to abstract them. Partly because different hardware may store the scan results differently. For example, if a half has more than 32 keys, the Model01 way will be insufficient.

It is used by the MagicCombo plugin despite this, though, because it is available by the time hooks run.

Mind you, this is read-only, and is not the same as the USB report. It just allows you to have a look at the full state of the scan. You’ll need to combine this with the USB report one way or the other.

This sounds like a good idea. And isn’t dead code (functions not called) eliminated by the linker, anyway? If so, additional interfaces such as the described interface layer would not affect any users that don’t use it.

Of course, this only works if there are no other mechanisms involved, such as some sort of caching of pressed keys, that would require to be called in other places of the KS-core and could therefore not be automatically detected as unwanted by the linker. In such a case, enabling the additional feature via pre-processor macros might be the only solution to avoid unwanted bloat.

I was looking at that plugin before going to sleep last night, and immediately thought, “What if there are more than 64 keys? That’s awfully hardware specific.” I’ll do some more delving into the code. Maybe I’ll even try writing something to experiment with…

1 Like

Over the course of my career, I have learned that building the thing I want in specific first and generalizing it later has lead to much better designed systems then starting by trying to build the platform.

we are definitely going to end up with plug-ins that only makes sense on keyboards with certain constraints. heck, the entirety of the LED system is that way. As we get a better feel for this, we will probably be able to figure out where to make the right trade-offs

4 Likes

Certainly! I didn’t mean to indicate disapproval of the current stuff, and you’re surely correct. Alas, I’m an inveterate perfectionist, so I have a very hard time taking that approach with things that I build — which is why I never get very much done.

2 Likes

Don’t worry. I have plenty of disapproval of my own code to go around :wink:

1 Like