Skip to content

Latest commit

 

History

History
193 lines (164 loc) · 8.91 KB

notes.org

File metadata and controls

193 lines (164 loc) · 8.91 KB

Notes

What I should work on next

  • Make the existing tests pass

What I should work on after that

  • Make sure we’re handling the case of two equivalent modifier aliases being pressed at the same time
  • Get the main loop of the app working

Thoughts on how to implement

Right now, the app is written to have a giant switch statement, in the form of a core.match expression. But I think I might be able to clean it up a lot. The key observation is that for modifier aliases, you don’t actually have to wait until all of the modifiers have been pressed. When a modifier alias goes down, it is undecided, but it can be decided as a modifier at the very next key press, even if the next press is a different modifier alias. And you can even send the aliased modifier down event right away.

So instead of the current behavior, which is this:

[:j :dn] => [] [:j :dn :k :dn] => [] [:j :dn :k :dn :x :dn] => [:rshift :dn :rcontrol :dn :x :dn]

You’d get this:

[:j :dn] => [] [:j :dn :k :dn] => [:rshift :dn] [:j :dn :k :dn :x :dn] => [:rshift :dn :rcontrol :dn :x :dn]

Further, I think you might be able to structure the code much better. I think you could define an IKeyHandler protocol that consists of self-down, self-up, other-down, and other-up functions. An instance of a record would be kept in an ordered list for every key that was down, in the order they were pressed. Every key pressed would be passed to all the handlers, in order, either to the self methods (if the handler was associated with the key that was pressed) or to the other methods (if not).

So initially we’d have two implementations of the protocol: RegularKeyHandler and ModifierAliasKeyHandler. RegularKeyHandler would have a pretty simple implementation, as it would simply pass through self events, and do nothing at all with other events. ModifierAliasKeyHandler would, at self-down, go into an undecided state. If an other-down happened before a self-up, then it could transition to a decided state, and send its aliased down event.

And after further thought, I wonder whether there might not be a better way. The responsibilities would be split between the app and key handlers. The app would be responsible for knowing which keys are down and which are up. These would be maintained in an ordered list, with the keys that went down first being first. Each key would have an associated handler (perhaps an implementation of the IKeyHandler protocol), a new instance of which would be created for each key that goes down. IKeyHandler/process would be called for each key press, including the one that corresponds to the creation of the handler. The method would accept the key event and would return a map. The map would have the following keys:

:handlera new handler object to represent the updated state of this handler. Returning nil would remove the handler from the handler list.
:continuefalse if no further handlers should be called; true to continue processing.
:eventsa vector of key events to generate. Events from all handlers will be concatenated.

So the application’s job would be to watch for key events. When a key goes from up to down, a new handler would be instantiated and added to the handler list. For all key events, process would be called and the result examined. The handler would be updated based on the value of :handler, events from :events would be accumulated, and processing would continue or not based on the value of :continue.

There are at least three implementations of IKeyHandler: one for regular keys, one for modifiers alias, and one for special action keys.

The regular key handler would be very simple. It would return a map whose :handler was itself unless the key was going up, in which case :handler would be nil. :continue would always be true. :events would simply be whatever event was being handled if it was for the self key, and nil otherwise.

The modifier alias handler would initially send no keys and continue processing. On the next non-self key down event it saw, it would send the modifier and enter a decided state, unless the next event was self up, in which case it would send self down, self up and remove itself from the handler list. Once decided, it would always send the modifier for self down events and remove itself on self up.

The special action handler would be for things like quitting the application or suspending key processing. For this we’d probably need to modify :events to be something more like :commands. Then it could be a sequence of tuples like [:keys [:a :dn :a :up] :suspend true :mouse [:left 200]].

How do we leverage multimethods or protocols to make all this work? It seems like we ought to be able to dispatch off of the key to look up the key handler, although perhaps all we need there is a record class, so we can call new on it, passing the key as an argument.

Maybe what we have is a map, like this:

{:j [->ModifierAliasHandler :rshift]
 :q [->SpecialActionHandler]}

That is, a map that associates keys with a tuple whose first element is a function that returns an implementation of IKeyHandler given a key and any other elements of the tuple. Failing an entry, an instance of DefaultHandler is used.

Questions

How to send events?

Maybe we’d pass a function to self/other-up/down that could be called to transmit events.

Do we need the ability to suppress further processing?

For example, if I add macros, I might need a way to prevent [:q :dn :z :dn] from having the :z event transmit anything. Or maybe this implies that the handler associated with a key can be changed as a result of some other key going down, so that holding down :q results in no other key transmitting itself.

Do we really need to have the self/other and up/down split?

Would it make more sense to just have one method on the protocol that handles everything? Or would we wind up doing having a bunch of conditional processing in every implementation?

JNA problems

I get consistent failures when trying to run the full project. Specifically, this code:

(require '[khordr.platform.common :as com])
(require '[khordr.platform :as p])

(let [p (p/initialize)
      evt (com/await-key-event p)]
  (println "received" evt)
  (com/cleanup p))

Results in one of two errors. Which one I get depends on where the JNA interop code is coming from. When I get it from the Java source code generated by JNAerator…

[Later]

Looks like it might be as simple as removing the dependency on net.java.dev.jna/jna. Maybe a conflict with the jnaerator-runtime dependency? Either way, it looks like I really need to figure out a way to have alternative setups in the project.clj, since I wind up switching back and forth a lot.

Bugs in current version

Note: bugs should be registered in the project tracker from here on out.

FIXED Typing “test” quickly does not work

FIXED We get trapped in the ModifierKeyAlias sometimes

FIXED We get weird state state exception from ModifierKeyAlias sometimes

INHERENT Key events are not received?

s-down, d-down, p-down, p-up, n-down: No events appear to arrive for n. Is this a problem at the hardware level? Somewhere else? Sure looks like it’s happening at the hardware level. :(

OK, yep. Most keyboards are limited on how many down events they can report. And USB keyboards are limited to six regardless. It appears to depend entirely on the keyboard, although they can all do at least two. The fix here is to by a keyboard that supports n-key rollover (NKRO). I’m using a dasKeyboard brand, and it seems to work just fine.

Limitations of ModifierAliasKeyHandler

False negatives

There are a fairly high number of false negatives. That it, it often happens that when I mean to use a home row key as a modifier, I instead roll over, resulting in two keys being typed. I’m not sure yet if that’s just a familiarity thing or if I’m just wired to roll over based on how the normal modifier keys get used.

Key force

I find that I sometimes tend to type the modifiers with more than usual force. I wonder whether that might be an ergonomic problem for some people.

No way to do solo modifier keys in training mode

When I engage training mode (turn off the normal modifier keys), there’s no way to register, for example, “control down”. That is occasionally needed. For example, to do a mouse wheel zoom. Of course, if I’m not in training mode, I can just use the regular control key.

Indecision

I occasionally press a modifier alias and then change my mind, which results in a spurious key press. It’s not really a big deal, but it would be great if there were a way to cancel modification. Maybe by hitting escape or something. Although that would have the problem that then there would be no way to modify whatever the cancellation key was. Perhaps backtick? We already use that for a bunch of other things, although it’s configured that way rather than hard-coded.