A Python script that works like Karabiner for Mac's VI mode (but for Windows, now). Press and hold "D", and use the right hand home row to emulate arrow keys, based on the VI mapping.
This software creates a System Tray icon, and can be enabled/disabled from that icon. Number lock must be off for the keypad to work/selection to work properly (see Known Issues).
This project was originally attempted in AHK (see the ahk-attempt
branch), but switched to Python for more flexibility. While it should be cross-platform, it has only beed tested on Windows.
In a previous version of Karabiner for Mac, this mode was triggered by pressing S+D. However, now it is triggered by only pressing D (as per the most recent version of the Mac software).
- Toshiro T. contributed significantly in fixing the Number Lock bug, which rendered the project useless for months.
- Keyboard icon by Icons8.
While holding D:
- H -> Left Arrow
- J -> Down Arrow
- K -> Up Arrow
- L -> Right Arrow
If you don't press any of those before releasing the D key, a "d" is typed (like normal).
Modifier keys (specifically, shift) are applied as pressed. This tool can be used to move around, or select text.
- Not all key up events are trigger properly for normal characters (ex: 'world'). This is speculative, but probably not actually an issue.
- 'D' presses now occur when 'd' is lifted (feels delayed). For video games (especially those that use WASD-keys), you'll likely want to disable this software.
- Some keys, when typed very fast before or after a 'd', may behaviour weirdly: their position may be switched with the 'd' key. Examples of this include the 'tab' character. This can be solved on a character-by-character basis by adding them to the
config["hookKeys"]
list.
- Typing sequences like 'Did' causes both the 'Di' to be both capital or both lower case. No known fix currently. "Discord" with 'D' typed with 'right shift' is an example.
Exact Sample of this bug:
shift down
d down
shift up
i down
d up obvious
i up obvious
If you come up with a fix, please make a pull request. If you notice a bug, please open an issue and/or make a pull request.
- Consider the following mappings:
- b -> go to start of word (ctrl+left)
- w -> go to end of word (ctrl+right)
- 0 -> go to start of line (ctrl+up?)
- Add a "disable for x amount of time" submenu in system tray icon.
- Add an "Features Enabled" submenu in the system tray icon.
There are even more VI mappings that can be added after. We'll start with these, though.
- Combine
gstate["viTriggeredYet"]
andgstate["dSentYet"]
into a singlegstate["stillSendD"]
variable, that records whether or not to send the 'd'. Resets (to True) on the start of a d press, and gets flagged (to False) when a VI key is pressed, or the first time a 'd' is sent.
The following sections are labelled with comments in the Python code. Each section is within the hookCallback(event)
function, and handles a certain type of key press/release occurrence.
By pressing the "END" key, the program is exited. Can be disabled in the config["enableQuickExit"]
. Useful for stopping the program if any bugs occur and the keyboard is blocked.
Updates the set() at gstate["down"]
with lowercase names of all keys currently pressed.
Passes through normal (non-VI) keys. This section is activated when 'd' is not held down, or when the key being received is not a VI key.
- If the key event_type is UP (key received is being released), a release is simply sent to the computer.
- If the key event_type is DOWN (key received is being pressed):
- We check to see if 'd' is pressed down currently, and see if it has been sent for this time it is pressed.
- If this is the case, as would be when typing "cards" very quickly, (known as the 'cards' bug) send a 'd' before the received event
- Finally, send the received event
- We check to see if 'd' is pressed down currently, and see if it has been sent for this time it is pressed.
Normally (neglecting fast consecutive presses), the 'd' key is sent on a key up event. However, it is not sent if either 1) a VI key was pressed since it was pressed down, or 2) it was already sent because of a fast consecutive press
- When you type the word "world" fast, the 'l' and 'd' cause a unique case that must be caught here.
- The
if any([...]) and <d pressed down right now>
statement checks to see if any VI keys are currently pressed, and checks to see if the current event is 'd DOWN'.
If the key is a mappable key, and 'd' is currently held down, send the appropriate arrowkey.
Prints debug info about the current event, and various states. In the future (or as needed), add a printout of gstate
to the end.
Sample of Debug Info: New Event: type(down) name(42 = shift) keysDown(space|shift))