cajhin / capsicain

Powerful low-level keyboard remapping tool for Windows
361 stars 18 forks source link

Features for momentary and toggle layers, properly held keys, more combo triggers, etc #101

Open Dregu opened 10 months ago

Dregu commented 10 months ago

TL;DR: This PR really just fixes a ton of gripes I have with my own keyboard, but also adds many requested features like momentary/toggleable layers, holding keys properly, many new trigger types and the list goes on.

I've been using capsicain to emulate the Fn layer on various budget 65% keebs with completely different Fn layers, which was super annoying. Another annoying thing these boards do with Fn is, when you release Fn before the other key, like a quick Fn+(F)2 to rename a file, it switches the output too early and renames the file "2". Capsicain didn't help with that issue much, cause key() is behaving the same way, only after repeat delay.

This fixes #39, fixes #56, fixes #83, fixes #85, fixes #98, fixes #102.

The release is updated on my fork if you want to test how hold() is superior to key().

# This is not a complete working ini, it just has random examples of the
# new features in https://github.com/cajhin/capsicain/pull/101

# Forward some Esc keys to combos or OS, but also do the default Esc action, unless disabled
GLOBAL forwardEscKey A

# Disable some Esc actions you don't need and don't want to end up in some weird mode accidentally
# Disabled keys can also be forwarded to OS
GLOBAL disableEscKey Q W E Y U P A S H J K L ; Z B , .

# Repeat all held keys in combos, instead of the last key only.
# Standard keyboard behavior is to only repeat the last key pressed.
#OPTION holdRepeatsAllKeys

# Set default formatter to wrap around combo parameters when no function is specified
# This is the default default already
#OPTION defaultFunction key(%s, m)

# Modstring is also optional, so this is the simplest form for combos
DOWN A > LSHF+B       # Equivalent to: COMBO A [] > key(LSHF+B, m)

REWIRE RCTRL MOD9
REWIRE LWIN MOD11 LWIN

# You can rewire the Esc key and use it in combos if the combo key is included in forwardEscKey
REWIRE ESC MOD12 ESC
DOWN   A      [&^^^ ^^^^ ^^^^] > B

# Simple example with RCTRL emulating the Fn layer on a 65%
# with no physical INS,HOME,END keys and a horrible real Fn layer
COMBO  DEL    [...& .... ....] > hold(INS)
COMBO  PGUP   [...& .... ....] > hold(HOME)
COMBO  PGDOWN [...& .... ....] > hold(END)

# More confusing examples with real modifiers
COMBO Z [.&] > hold(LSHF + A)       # Shift+Z will hold Shift+A until Z is released and also block physical Shift release
COMBO Z [&.] > hold(LSHF + A)       # Ctrl+Z will hold Shift+A until Z is released, but not touch Ctrl
COMBO X [&.] > moddedhold(LSHF + A) # Ctrl+X will release Ctrl and hold Shift+A until X is released
# After pressing MOD11+SPACE, MOD11 holds LWIN, SPACE holds SPACE, cause language switcher demands it
COMBO SPACE [^&^^ ^^^^ ^^^^] > holdmods(LWIN + SPACE)

# This is just weird, but still works
COMBO C [...& .... ....] > hold(A + ..&& + LWIN + X) # RCtrl+C will hold Shift+Ctrl+A+Win+X until C is released

# key(combo, type) can be used as an alias for different functions, where type is any of
# 42: repeat combo 42 times, can be combined with r but not h or m
#  r: release all keys (temporarily, unless combined with hold) before the combo
#  h: hold all keys in the combo until triggering main physical key is released
#  m: hold modifiers with physical modifier and other keys with other keys separately

# key(LSHF+X)       == combo(LSHF+X)
# key(LSHF+X, r)    == moddedkey(X + ...&)
# key(LSHF+X, 10)   == combontimes(LSHF+X, 10)
# key(LSHF+X, h)    == hold(LSHF+X)
# key(LSHF+X, m)    == holdmods(LSHF+X)
# key(LSHF+X, hr)   == moddedhold(LSHF+X)
# key(LSHF+X, rm)   == moddedholdmods(LSHF+X)

# h is great for simple one-to-one momentary Fn layer keys that should be held
# Example: MOD11+DEL will hold INS until DEL is released
COMBO    DEL    [...& .... ....] > key(INS, h)
# Example: MOD11+UP will hold LCTRL+UP until UP is released
COMBO    UP     [...& .... ....] > key(LCTRL+UP, h)

# use m when pressing MOD+X opens and cycles something, but releasing MOD closes it
# perfect for the language switcher or cycling windows of pinned taskbar items
# Example: when MOD11+. is pressed, send LWIN + SPACE,
#          hold LWIN until MOD11 is released,
#          hold SPACE until . is released
COMBO    .      [^&^^ ^^^^ ^^^^] > key(LWIN + SPACE, m)

# Use r with real modifiers that shouldn't be included in the resulting combo
# Example: when LSHF+A is pressed, release LSHF, but
#          hold LCTRL until physical LSHF is released,
#          hold A until A is released
COMBO    A      [^^^^ ^^^^ ^^^&] > key(LCTRL + A, rm)

# Example: LCTRL+UP will release LCTRL, press UP 10 times and press LCTRL again
COMBO    UP     [^^^^ ^^^^ ^^&^] > key(UP, 10r)

# Support multiple functions per combo, and some new functions to go with that
COMBO    1      [^^^^ ^^^^ ^^&^] > release() altchar(0169) key(SPACE) sequence(&LSHF_d_^LSHF_r_e_g_u)
COMBO    2      [^^^^ ^^^^ ^^&^] > release() delay(33) sequence(h_e_l_l_o_SPACE_delay:0_w_o_r_l_d)

# Tap OR Hold MOD9+4 to F4, without adding two combos for & and t
# DOWN == COMBO, I aliased it when I added UP to make it clear which event triggers it
DOWN    4      [.../ .... ....] > hold(F4)
# TapAndHold Alt+4 to Alt+F4
DOWN    4      [.... .... *...] > key(F4)
# Press B after releasing A
# Note that you have to specifically release keys with release() or up(key) if you hijack their UP/TAP/SLOW events
UP       A      [.... .... ....] > up(A) key(B)

# Set MOD9 down with slow tap (one long enough to trigger repeat)
# Modifiers are forced down even if pressed and released physically, you have to specifically up() them
# Normal keys will pop back up when pressed once
SLOW     MOD9   [.... ^^^^ ^^^^] > down(MOD9)
# Set MOD9 up with tap if it's forced down, but also mark it untapped,
# cause we don't want the tap to trigger on the next key, as this tap was to disable the mod
TAP      MOD9   [...& ^^^^ ^^^^] > up(MOD9) untap(MOD9)

# Add executables to run with exe(num) function
# The params are passed to ShellExecute(lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd) as is
EXE      1      open,wt,,,10
EXE      2      open,WindowsTerminal.exe
# Launch terminal with MOD11+RET
DOWN     RET    [^&^^ ^^^^ ^^^^] > exe(1)
# Kill previously launched program if we still have a valid handle to it, or any process matching the filename
# kill(1) wouldn't work here, cause wt is not the name of the process, nor is the handle from ShellExecute valid any more
DOWN     RET    [^&^^ ^^^| ^^^|] > kill(2)

# "Mod-Tap", send small m when tapping fast, and big M when tapping slow enough to trigger key repeat
# The repeat delay depends on your Windows keyboard settings and can't be changed here
DOWN     M > nop()
TAP      M > key(M)
SLOW     M > key(LSHF+M)

# Add {&deviceid} on combos to trigger only on matching devices
DOWN A {&003d} [&.] > B
# Add {^deviceid} on combos to exclude all matching devices
DOWN A {^003d} [&.] > C

# Enable experimental mouse support for a config, otherwise mouse is ignored
# Mouse has some limitations atm, like skipping events if you release two buttons at exactly the same time...
# M->KB and KB->M combos are sent through the last seen device of the other kind
OPTION enableMouse
# Ctrl + left click releases Ctrl and sends right click
DOWN     MOUSE1 [&.] > key(MOUSE2, r)
# PGDOWN sends wheel down
DOWN     PGDOWN [] > MWDOWN
# Middle mouse sends Return
DOWN     MOUSE3 [] > RET
# Toggle thumb buttons to keys with MOD9+M,
# cause it's *checks calendar* 2024(!) and games still don't support thumb buttons
DOWN     M      [^^.^ ^^^& ^^^^ ^^^^] > toggle(MOD14)
DOWN     MOUSE4 [^^&^ ^^^^ .... ....] > PGDOWN
DOWN     MOUSE5 [^^&^ ^^^^ .... ....] > PGUP
# Or you could just do this and switch configs I guess
#REWIRE MOUSE4 PGDOWN
#REWIRE MOUSE5 PGUP

# Load AutoHotkey_H 2.0+ as DLL and call AHK functions directly (or use cryptic hotkeys if you really want to)
# Get AutoHotkey64.dll from https://github.com/thqby/AutoHotkey_H and put next to capsicain.exe
GLOBAL startAHK
# Run some AHK functions
DOWN     T      [^^^& ^^^^ ^^^^] > ahk(test, 42, 27)
DOWN     X      [^^^& ^^^^ ^^^^] > ahk(maximize)

# Everything after [ahk] will be loaded as an AHK script and reloaded on Esc+A/R
# This has to be the last section in the ini
[ahk]

; Remember to add Persistent if you don't have real hotkeys in here, AHK won't run without hotkeys by default
Persistent

test(a, b) {
    c := a + b
    MsgBox "Quick maths: " . a . " + " . b . " = " . c
}

maximize() {
    If (WinGetMinMax("A")) {
      WinRestore "A"
    } else {
      WinMaximize "A"
    }
}
cajhin commented 10 months ago

just fyi, I'm tracking your activity with some interest, could be a useful feature and solve the issue of auto-repeating keys being translated from "vvv^" to "v^v^v^". I've attempted it once, but didn't find an easy solution. I don't have the time now to really dig into it and understand what exactly you're doing and how. Can't make any promises if I will ever merge this. Generally speaking, it would help if you make the whole thing easier, rather than more complex (I need to stay in control of what's happening in the code), and if you don't add specific functions for special cases where one general function could cover it.

Dregu commented 10 months ago

Yeah well just like you probably, I'm mainly doing this for me, but putting it out there cause I know it will help someone else too.

The latest commits should definitely make the whole thing more simple by unifying the combo formats and removing restrictions, but there are also cases where key() behavior is still preferred to hold(), so I'm not going to simplify it too much by getting rid of all the choices. Also tried not to make any breaking changes to old configs, but that said I think most of these simple one-to-one mappings using key() should definitely switch to hold(), it just acts more natural and doesn't glitch with key repeat.

Dregu commented 10 months ago

Heh, this PR got out of hand really fast, but I kept finding more things that weren't possible. I completely understand if you never want to merge this mess, but maybe it gives you some ideas of what kind of functionality is missing. And people can still use this fork if they need these things too.

Dregu commented 10 months ago

Looks like I really set out to fix every single thing that annoys me with my keyboard/shortcuts. I know adding stuff like exec() might be outside the scope of this project, but it's really the only solution to all these games eating inputs that they have no business eating, even from autohotkey.

Not even recommending this to be merged any more, everything is very untested and undocumented, and I keep finding little problems here and there, but I'll also keep fixing those problems.

Do what you want, but I'd keep this PR up for the people.