Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 47 commits into
base: master
Choose a base branch
from

Conversation

Dregu
Copy link

@Dregu Dregu commented Jan 26, 2024

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.

  • hold(key) function will keep key held for as long as the original key is held, without the repeat v^v^ flicker that key() has
  • If the modifier is released, the combo will still be held until the main key is released
  • moddedhold() will release all keys before holding the combo, like moddedkey() (but not re-press them on release)
  • holdmods() will hold modifiers separately with physical modifier, and other keys with physical key
  • Combos are pressed down in label order, released in reverse order, but modstrings always come first
  • If physical keys that are held by other keys are tapped, or moddedhold() tries to release them, the release is blocked
  • hold() will only translate repeat events to the last key in the combo, as is standard behavior. This can be changed with OPTION HoldRepeatsAllKeys
  • key(combo, type) can be used as an alias for different functions, cause these function names are getting stupid, see example
  • Unified (key/combo/moddedkey/hold/moddedhold/combontimes) functions to use any format and any amount of keys, like A + ..&& + LWIN + X
  • Modstring * will trigger on TapAndHold, but doesn't need a TapAndHold rewire
  • Modstring / will trigger on tap OR hold, but doesn't need two separate COMBOs for t and &
  • COMBOs can have multiple functions separated with space
  • Some new convenient functions to go with that, see example
  • DOWN, UP, TAP, SLOW combo triggers, down(), up(), toggle(), tap(), uptap() functions allow toggleable layers
  • exe() launches programs without AHK, when it would be blocked, or you don't want to send any keys to userspace to do that
  • Support up to 32 modifiers
  • Options to disable specific Esc keys, or forward specific Esc keys to combos
  • Load AutoHotkey_H as a DLL and call functions directly without forwarding weird hotkeys
  • Filter combos by deviceid to hook macropad, but keep normal numpad
  • Experimental mouse support
  • The list goes on, just check the example config

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
Copy link
Owner

cajhin commented Jan 27, 2024

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
Copy link
Author

Dregu commented Jan 27, 2024

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
Copy link
Author

Dregu commented Jan 28, 2024

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
Copy link
Author

Dregu commented Jan 29, 2024

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.

@Dregu Dregu changed the title Add hold() function for momentary layer effect and properly held keys Features for momentary and toggle layers, properly held keys, more combo triggers, etc Jan 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants