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

[feature request] Ignore key binds for specific apps/windows #412

Open
jakenvac opened this issue Aug 14, 2024 · 9 comments
Open

[feature request] Ignore key binds for specific apps/windows #412

jakenvac opened this issue Aug 14, 2024 · 9 comments
Labels
feature-proposal A well defined feature proposal triaged The issue makes sense to maintainers

Comments

@jakenvac
Copy link
Contributor

Apologies if this has been requested before - I couldn't find anything similar.

I would like to request a feature that allows us to ignore key binds if a specific app/window is focused and instead allow the keys to be sent to the application.

My use case is that I would like to integrate my focus keybinds with my terminal/multiplexer (Wezterm in this case). I was able to do this with yabai/skhd by leveraging Wezterms scriptable config. If it was focused, wezterm would handle the keypress and decide if it should navigate within its panes, based on the current panes siblings or if it should execute the yabai focus command.

I have achieved something similar to this by executing a shell script on my focus keybinds and writing custom user vars to wezterm for it to handle. However this has introduced latency when switching focus even outside of wezterm. My setup for this is shared at the bottom if anyone else wants to achieve similar behaviour in the meantime.

Proposal

TOML isn't the greatest format for dynamic config, so I wonder if this can wait until #278 is implemented. Maybe there could be a feature to propagate key presses under arbitrary conditions.

Otherwise, I would draw inspiration from the callback syntax.

[[node.main.binding.conditional]]
if.app-id.not = 'com.apple.systempreferences'
alt-h = 'focus left'

# etc.

I would be happy to have a go at implementing this if it's a feature that aligns with the projects goals. Thanks for considering.


My config

My keybinds

alt-h = 'exec-and-forget /Users/jakenvac/.config/aerospace/focus.sh left'
alt-j = 'exec-and-forget /Users/jakenvac/.config/aerospace/focus.sh down'
alt-k = 'exec-and-forget /Users/jakenvac/.config/aerospace/focus.sh up'
alt-l = 'exec-and-forget /Users/jakenvac/.config/aerospace/focus.sh right'

focus.sh

#!/bin/bash
DIR=$1

wezpane=-1
weztty=-1

if aerospace list-windows --focused | grep -q "WezTerm"; then
  wezpane=$(wezterm cli list-clients --format json | jq '.[0].focused_pane_id' 2>/dev/null) || wezpane=-1
  weztty=`wezterm cli list --format json | jq -r ".[] | select(.pane_id == $wezpane) | .tty_name" 2>/dev/null` || weztty=-1
fi
  
# If wezterm is focused and we have its tty
if [[ $wezpane != -1 ]] && [[ $weztty != -1 ]]; then
  base64dir=$(printf "%s" "$DIR" | base64)
  # this emits an event in wezterm that can be handled with lua scripts
  wezcmd=`printf "\033]1337;SetUserVar=%s=%s\007" ActivatePaneFromAerospace $base64dir`
  printf "%s" "$wezcmd" > "$weztty";
else
  aerospace focus $DIR
fi

wezterm user-var-changed handler (this is a stripped down version, I also have it set up to manage navigating within neovim windows)

local aerospace_to_wezterm_map = {
	left = "Left",
	down = "Down",
	up = "Up",
	right = "Right",
}

local function CustomActivatePaneDirection(window, pane, direction)
	local wezterm_direction = aerospace_to_wezterm_map[direction]

	local tab = pane:tab()
	local sibling = tab:get_pane_direction(direction)

	if sibling ~= nil then
		window:perform_action(act.ActivatePaneDirection(wezterm_direction), pane)
	else
		wezterm.run_child_process({ "/opt/homebrew/bin/aerospace", "focus", direction })
	end
end

wezterm.on("user-var-changed", function(window, pane, name, value)
	if name == "ActivatePaneFromAerospace" then
		CustomActivatePaneDirection(window, pane, value)
	end
end)
@jordevorstenbosch
Copy link

Well isn't that wild, I literally went here to request this feature as I myself was having the issue when trying to use terminal navigation within Wezterm. (alt+b/f for jumping words)

Happy to see a much more advanced version of what I was intending to ask already being proposed. In this case just an hour earlier.

I second this feature request in case that has any bearing.

@nikitabobko
Copy link
Owner

nikitabobko commented Aug 19, 2024

The request makes sense. One other use case that I wanted to cover is to disable macOS "hide application" cmd-h hotkey but keep it enabled for apps that remap cmd-h to something else (I personally remap cmd-h to alt-h in Alacritty. A muscle memory from Linux, but at the same time I want to disable cmd-h for everything else)

Alternative 1. A different syntax:

[main.mode.binding]
alt-h = { if.app-bundle-id.not = 'com.github.wez.wezterm', run = 'focus left' }

Unfortunately we have to use TOML and we can't reuse #278, because by the time the command inside the binding runs, the key event is already intercepted.

Alternative 2. Teach aerospace trigger-binding to trigger key press events that could be sent to macOS. IDK how easy it is. But in this case, #278 can be reused.

[main.mode.binding]
alt-h = '''
    if test --app-bundle-id com.github.wez.wezterm do
        trigger-binding alt-h --send-to-macos # fallthrough
    else
        focus left
    end
'''

And it unlocks other use cases. For example keys can be remapped this way. (partially covers karabiner elements use cases)

[mode.main.binding]
ctrl-left = 'trigger-binding --send-to-macos alt-left' # Bring Linux muscle memory to macOS
ctrl-right = 'trigger-binding --send-to-macos alt-right' # Bring Linux muscle memory to macOS

@nikitabobko nikitabobko added the feature-proposal A well defined feature proposal label Aug 19, 2024
@jakenvac
Copy link
Contributor Author

I like your second suggestion the most. It's something that can provide value in the meantime as in your final example (also in shell scripts) and would eventually tie in nicely with the embedded scripting language.

Would you be open to a PR for this? I'd like to have a go.

As a real world use case example, it would simplify my focus.sh example in the original post to be:

#!/bin/bash
DIR=$1
# just an example here. I'd have to map the direction to a key or change the script input
KEY='somekey' 
if aerospace list-windows --focused | grep -q "WezTerm"; then
 aerospace trigger-binding --send-to-macos alt-$KEY
else
  aerospace focus $DIR
fi

@nikitabobko
Copy link
Owner

Go ahead, thanks

I'm not sure if aerospace trigger-binding --send-to-macos is the right interface, but it's a minor issue. It's more important if it's even possible to send keycodes to macOS

@jakenvac
Copy link
Contributor Author

I'll see what's possible and we can go from there. I'm not precious about my code so I'm happy just to prove it's doable and throw it away for a future PR if needs be 😃

@nikitabobko
Copy link
Owner

nikitabobko commented Aug 20, 2024

keyDownHandler exposed by HotKey doesn't allow to return the result from the lambda, but aparently macOS Carbon API EventHandlerUPP allows to return eventNotHandledErr to send the event to other handles (we can fork HotKey lib and patch it)

If it works the alternative 3 could be:

[main.mode.binding]
alt-h = '''
    if test --app-bundle-id com.github.wez.wezterm do
        fallthrough-key-event # Draft. We need to properly think about the API we expose to users
    else
        focus left
    end
'''

@nikitabobko nikitabobko added the triaged The issue makes sense to maintainers label Oct 27, 2024
eljobe added a commit to eljobe/dotfiles that referenced this issue Nov 2, 2024
There are just too many bindings I really want to have in emacs.
The binding mode is also switched back to main when any other app's
window is in front.

This is my work-around for nikitabobko/AeroSpace#412
@eljobe
Copy link

eljobe commented Nov 2, 2024

Hey folks, I didn't mean to make all those changes in my emacs.d/init.el in the same commit with the aerospace hackery.
But, eljobe/dotfiles@0707319 does include an executable_aero-focus.sh and modifications to my aerospace.toml file that show how I'm dealing with this use-case. In case others find this useful or educational.

Essentially, I set up a special mode.emacs keybinding mode that just has a single command that I'm very unlikely to type (but, it also doesn't have any effect as it only sets the mode to the mode aerospace is already in.)

Then, I listen for on-focus-changed events and execute-and-forget my aero-focus.sh script which just asks aerospace if the currently focused window is emacs. If it is, I switch to mode emacs and if it's not I switch to mode main.

Works like a charm.

@abdullah-kasim
Copy link

abdullah-kasim commented Nov 4, 2024

If anyone needs something now, my workaround which is a bit based on @\eljobe, is to utilize skhd's skhd -k "<key-combination>" to do a bunch of key combination.

I added this to my aerospace.toml:

[mode.passthrough.binding]

# this one does nothing, here just so that we have passthrough
ctrl-cmd-alt-shift-9 = 'close'

I then have a script to do the passthrough magic:

#!/usr/bin/env bash

# ~/aerospace/remap-passthrough.sh

# adds flags --key, --only, --except
while [[ "$#" -gt 0 ]]; do
    case $1 in
        --key) key="$2"; shift ;;
        --only) only="$2"; shift ;;
        --except) except="$2"; shift ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

if [ -z "$key" ]; then
    echo "Key not provided"
    exit 1
fi

# --only handling: comma separated keys of apps for this keybind to act on
if [ -n "$only" ]; then
    IFS=',' read -r -a only_array <<< "$only"
    found=false
    for item in "${only_array[@]}"; do
        if aerospace list-windows --focused --format "%{app-name} | %{app-bundle-id}" | grep -q "$item"; then
            found=true
            break
        fi
    done
    if [ "$found" = false ]; then
        exit 0
    fi
fi

# --except handling: comma separated keys of apps for this keybind to NOT act on.
if [ -n "$except" ]; then
    IFS=',' read -r -a except_array <<< "$except"
    for item in "${except_array[@]}"; do
        if aerospace list-windows --focused --format "%{app-name} | %{app-bundle-id}" | grep -q "$item"; then
            exit 0
        fi
    done
fi

aerospace mode passthrough
skhd -k "$key"
aerospace mode main

So at the end, we can then bind something like this:

# linux muscle memory
ctrl-a = 'exec-and-forget ~/aerospace/remap-passthrough.sh --key cmd-a --except "Edge,Firefox"'

There's a slight delay as we're basically executing a bash script in the background each time, but it's good enough for me.

@jakenvac
Copy link
Contributor Author

jakenvac commented Nov 6, 2024

If you're going to the effort of integrating SKHD, you may as well just use it to manage all of your Aerospace keybinds as it supports passthrough out of the box.

Here is an example from my old skhdrc to ignore my yabai focus commands when wezterm is focused:

alt - h [ 
    * : yabai -m window --focus west
    "WezTerm" ~
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-proposal A well defined feature proposal triaged The issue makes sense to maintainers
Projects
None yet
Development

No branches or pull requests

5 participants