Yabai: [Help Please] Cannot Open kitty Window on the Focused Display

Created on 16 Feb 2020  路  19Comments  路  Source: koekeishiya/yabai

yabai version: 2.3.0
macOS version: 10.15.3

I am using two external monitors. Then I open the first kitty window on monior-1 and say Safari on monitor-2. Then I make the mouse focus on safari (i.e., monitor-2). Then I hit cmd-enter to open another kitty window (in skhdrc, I set cmd - return : /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~ to open a kitty widow). What I expected is to open this window on monitor-2 next to the Safari window. However, it is open on monitor-1, even though the focus is not there.

How can I open a kitty window on a monitor where the focus is on, instead of the monitor where the first kitty window is created?

Thank you very much!

question

Most helpful comment

Thanks, @yanzhang0219 馃檪 I updated my own script to use yabai signals which seems to work well. A few things to note about the newly launched kitty window:

Configuring

If the new kitty window is opened from an _existing_ kitty window, my script will open the new window in the same working directory as the existing kitty window, much like what I'm used to with i3/urxvt on Linux.

Placing

Rather than moving the new window over to the focused display, I opted to:

  1. Check if the new window landed on the focused display and, if not,
  2. Re-insert ("warp") the new window at the previously focused window (i.e., the one I launched the new window from).

That way, it'll always split the previously focused window, instead of landing in a random location on the focused display.

Focusing

Regarding your comment above:

Actually I have tried [to focus the target window] already but it failed to focus the window.

That's because you need to escape the yabai window ID variable within the signal command鈥攊.e., \$YABAI_WINDOW_ID rather than $YABAI_WINDOW_ID鈥攕o that the variable's name is resolved when the _signal command_ executes, rather than when the script executes. If the variable name is resolved when the script executes, no value has been assigned to $YABAI_WINDOW_ID yet which simply causes the focus command to fail.

All 19 comments

If Kitty Is focusing its most recently focused window before creating a new one, there's nothing you can directly do about that.

What you can do instead, is save the index of the space you're on before running the command to open a new Kitty window, and then move the focused window to the space you were on originally right after.

For debugging, try taking a look at the log files that yabai creates when ran in verbose mode, and look at the events that happen when you open a new Kitty instance.

Alternatively, you could also have open force a new application instance, but that may have unexpected side effects, as most apps are not built with this option in mind. To do so, run open -n -a Kitty --args --single-instance -d ~, for more info on open -n see man 1 open. I wouldn't recommend doing this unless you have exhausted all other options.

Hi @dominiklohmann, I have been trying to implement this, i.e., opening a new kitty window in the current display instead of the initial display where the first kitty window was created. I think I implemented it successfully but I am not sure whether my implementation is efficient. I will demonstrate the process of my implementation below. Because I am kinda new to Yabai and have little experience. Could you please help me take a look at it and give me some suggestions? Thank you.

My general idea is that press Command + Return to open a new kitty window (it will be open in the initial display) and then move that window to the current display where I press the shortcut.

My First Idea:
I created a script ~/open-kitty.sh

#!/bin/bash

# get the index of the current display
index=$(yabai -m query --displays --display | jq .index)
# open a new kitty window
/Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~
# try to move the window to that display
yabai -m window --display $index

And then in .skhdrc, I bind cmd - return to this script.
It doesn't work! Through my testing, the first line is correct to obtain the index of the current display where I press Command + Enter; the second line also can be executed successfully to open a new kitty window in the initial display; However, the third line fails. Then I tried to run this script directly by ./open-kitty.sh in a terminal and I got "could not locate the window to act on!". So it seems that kitty cannot find a window to move, even though the newly-created kitty window is focused automatically. I have no idea why.

My Second Idea
In order to get the newly-created window, yabai signal came to my mind. Through the event window_created, we can get the newly-created window id by $YABAI_WINDOW_ID. Then in my .yabairc, I added:

yabai -m signal --add event=window_created action="~/open-kitty.sh $YABAI_WINDOW_ID" app="kitty"

And then I changed the script to:

#!/bin/bash

# get the index of the current display
index=$(yabai -m query --displays --display | jq .index)
# try to move the window to that display
yabai -m window $1 --display $index

Then in .skhdrc, I changed the binding cmd-return to /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~ to open a new kitty window.

This time, when I press Command + Enter, a kitty window will be firstly created in the initial display. Then the action of the signal will be triggered so that the script will be executed with an argument, the id of the newly-created window.

But it fails again! Because firstly the new kitty window will be open in the initial display instead of the current display, thus the index we get through the first line is the index of the initial display, not the current display.

In my first idea, the script is executed by a shortcut, so I can get the index of the current display where I press the shortcut. However, in my second idea, the script is executed by the signal action, so the index I get is not the current display and instead it is the initial display.

So in this method, even though we can get the newly-created window id, but we cannot get the index of the current display. Then a final idea I came up with was to combine them together.

My Third Idea:
In .skhdrc, I bind cmd - return to the script ~/open-kitty.sh.
Then I changed the script to:

#!/bin/bash

index=$(yabai -m query --displays --display | jq .index)
yabai -m signal --add event=window_created action=" \
  yabai -m window $YABAI_WINDOW_ID --display $index; \
  yabai -m display --focus $index; \
  yabai -m signal --remove 'openkitty'" \
app="kitty" label="openkitty"
/Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~

Bingo, it works!!!! The same as my first idea, because this script is executed by pressing the shortcut, so the index in the first line is the current display where I press the shortcut. Then I create a yabai signal. Because I put the index retrieving and signal creation together in a script file, thus, in the action part, I can use the index and the window id together. The action is to move the window to the current display, change the focus to that display and at the last remove this signal.

I am so glad I figured it out. A little shortcoming is that the process of a window created in the initial display and then moved to the current display is visible, even though it is very transient.

Could you please give me some suggestions about my method and how to improve it? Am I in the correct direction to solve this? Really appreciate it.

Best regards

Did you consider Kitty's instance groups?
```
--single-instance, -1
If specified only a single instance of kitty will run. New invocations
will instead create a new top-level window in the existing kitty
instance. This allows kitty to share a single sprite cache on the GPU
and also reduces startup time. You can also have separate groups of
kitty instances by using the --instance-group option

--instance-group=INSTANCE_GROUP
Used in combination with the --single-instance option. All kitty
invocations with the same --instance-group will result in new windows
being created in the first kitty instance within that group

Then you can use an instance group per display:

cmd + alt - return : index=$(yabai -m query --displays --display | jq .index) && \
/Applications/Kitty.app/Contents/MacOS/kitty --instance-group=$index --single-instance -d ~
```

@edward-mb Hi, thank you for your advice. I know in this way, i.e., one instance for each display, I can fulfill my needs, and actually this is what I am doing so far. I am just wondering, as my issue, whether I can use only one instance for all the displays and meanwhile I can open a new window in the focused display.

I know probably this requirement is kinda demanding. I just want to investigate the possibilities by means of yabai. At least it can be regarded as a good exercise to play with yabai. :)

You've got a timing issue: Immediately after spawning the application, yabai already knows that the focused window changed, but fails to act on it. You can work around this by simply retrying.

Here's a simple test case:

#! /usr/bin/env zsh

() {
  emulate -L zsh -o err_return -o no_unset -o pipefail
  local current_display
  current_display=$(yabai -m query --spaces --space | jq -er '.index')
  open -na Terminal
  while ! yabai -m window --display "${current_display}"; do
    sleep 0.05
  done
}

I think this can be considered a bug in yabai (cc @koekeishiya)

@dominiklohmann Okay, thank you.

You've got a timing issue

So you mean that my first idea encounters a timing issue, right? If I launch a new window, yabai already changes the focus to this newly-created window. The reason why I cannot operate this immediately-newly-created window is a bug of yabai, right?

Thanks a lot.

BTW, what do you think my final solution (my third idea)? Is that correct?

I think this can be considered a bug in yabai

I'm not sure if I agree here. The call that fails is https://github.com/koekeishiya/yabai/blob/master/src/message.c#L1435 which simply requests the focused window from macOS: https://github.com/koekeishiya/yabai/blob/master/src/window_manager.c#L900

When ran with debug_output on, what does the event history look like?

Edit:
I don't really see what yabai can do here to accomodate this.
I wonder if moving newly opened windows/applications to the currently active display (before opening said app/window) is omething that is worth adding to yabai as a config-setting anyway.
I find the default behaviour to be quite annoying myself when using multiple monitors. IIRC there was a setting back in the kwm days to do this, but with the display that had the cursor or something.

BTW, what do you think my final solution (my third idea)? Is that correct?

Sorry, I was strapped on time yesterday, I was only replying to your first answer.

Yeah it is. Making sure the signal only ever triggers once with the label is the correct way to handle this.

I'd only change a few things:

  • Remove the signal first
  • Check for exit codes
  • Focus the target window (instead of display) at the end

So this diff basically:

-  yabai -m window $YABAI_WINDOW_ID --display $index; \
-  yabai -m display --focus $index; \
-  yabai -m signal --remove 'openkitty'" \
+  yabai -m signal --remove 'openkitty'" &&
+  yabai -m window $YABAI_WINDOW_ID --display $index &&
+  yabai -m window --focus $YABAI_WINDOW_ID

Side note: You can use uuidgen to generate a good label for you for situations like this, to ensure that you can run this script twice without any issues.

@koekeishiya I think I'm going back on considering this faulty behavior. Signals already provide a good API for this, as shown above. The exit code clearly indicates failure, and people should just write more robust shell scripts instead of relying on commands to always work. E.g., it is not a valid assumption that a focused window exists at all times.

@dominiklohmann Wow, thank you so much for your reply and suggestions. I still have two confusions here:

Focus the target window (instead of display) at the end

Actually I have tried this way already but it failed to focus the window. That's why I use display --focus $display_index instead of window --focus $YABAI_WINDOW_ID. I have no idea about the reason why it fails. yabai -m window $YABAI_WINDOW_ID --display $index can successfully move this newly-created window from display-1 to display-2, but yabai -m window --focus $YABAI_WINDOW_ID fails to move the focus, which means the focus still stays at display-1. Any ideas?

Remove the signal first

What is the purpose to remove the signal first instead of removing it at the end? What is your consideration? Thanks.

Really appreciate all of your help.

Just a quick reply; don't have the time right now to investigate the first part. It _might_ be a bug.

What is the purpose to remove the signal first instead of removing it at the end? What is your consideration? Thanks.

In combination with the exit codes it makes it so the following code in the signal can never be run twice. Your version leaves a small window open for the signal to trigger more than just once.

Thank you for your time. I will cc @koekeishiya to take a look at the first part whether it should be considered as a bug.

@dominiklohmann I am still confused about "run twice". Sorry for my slow mind. My original version is

#!/bin/bash

01  index=$(yabai -m query --displays --display | jq .index)

02  yabai -m signal --add event=window_created action=" \
            yabai -m window $YABAI_WINDOW_ID --display $index; \
            yabai -m display --focus $index; \
            yabai -m signal --remove 'openkitty'" \
    app="kitty" label="openkitty"

03  /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~

What my understanding is that: After I press Command + Enter (which is a binding to run this script in my skhdrc), a signal is created by adding a filter app="kitty" (line 02). So this signal will only be triggered once when a kitty window is created. And when it is triggered (by line 03, i.e., open a new kitty window), on the action part, it first moves the window to the focused display, and then it changes the focus to the window, and at the end, it removes this signal.

So it guarantees that each time I press the shortcut keybindings, the entire process will be: a signal is created -> a kitty window is open -> trigger the signal -> run the action part -> remove this signal at the end of the action.

So I am confused what situation will make the code in the action part run twice??

Take your time dude. No rush to reply. Just please give me some instructions when you are free. Thank you very much!!!!

Best

Let's say that opening Kitty opens two windows. There exist applications for which this is not unusual.

Open Kitty. Now the first window will trigger the signal. Then, the second window will trigger the signal, if it still exists. Notice that it _may_ still exist.

The way I changed the code, the signal may still trigger twice, but the action does not continue past trying to remove the signal, because removing a signal fails if that signal does not exist.

Okay, I got it. Thank you.

Hi, @yanzhang0219. I ran into this issue last year and wrote a script to solve it (and I launch it from here). It works _pretty_ well, but kitty still occasionally lands on the wrong display. I sat down to try to troubleshoot it today and found this issue鈥lad to see I'm not the only one experiencing this.

Hi @noperator, try my script above please and it runs perfectly! Thanks.

Thanks, will do. Just to make sure I'm not missing anything鈥攂y the "script above," are you referring to the "Third Idea" in this comment? Have you updated that script at all since 20 Feb to incorporate any suggestions in this comment chain?

@noperator try to use the script below:

#!/bin/bash

# Used by yabai and skhd
# Open kitty window in the current display instead of the display where the
# first kitty window was created.
index=$(yabai -m query --displays --display | jq .index)
yabai -m signal --add event=window_created action=" \
  yabai -m signal --remove 'testkitty' &&
  yabai -m window $YABAI_WINDOW_ID --display $index &&
  yabai -m display --focus $index" \
app="kitty" label="testkitty"
/Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~

Thank you.

Thanks, @yanzhang0219 馃檪 I updated my own script to use yabai signals which seems to work well. A few things to note about the newly launched kitty window:

Configuring

If the new kitty window is opened from an _existing_ kitty window, my script will open the new window in the same working directory as the existing kitty window, much like what I'm used to with i3/urxvt on Linux.

Placing

Rather than moving the new window over to the focused display, I opted to:

  1. Check if the new window landed on the focused display and, if not,
  2. Re-insert ("warp") the new window at the previously focused window (i.e., the one I launched the new window from).

That way, it'll always split the previously focused window, instead of landing in a random location on the focused display.

Focusing

Regarding your comment above:

Actually I have tried [to focus the target window] already but it failed to focus the window.

That's because you need to escape the yabai window ID variable within the signal command鈥攊.e., \$YABAI_WINDOW_ID rather than $YABAI_WINDOW_ID鈥攕o that the variable's name is resolved when the _signal command_ executes, rather than when the script executes. If the variable name is resolved when the script executes, no value has been assigned to $YABAI_WINDOW_ID yet which simply causes the focus command to fail.

@noperator, thank you so much for sharing your amazing idea. It inspired me a lot.

you need to escape the yabai window ID variable within the signal command

Really appreciate that you figured this out. I didn't realize to use escaping. Thanks!!!!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

d-miketa picture d-miketa  路  3Comments

chris-kahn picture chris-kahn  路  3Comments

koekeishiya picture koekeishiya  路  4Comments

denisidoro picture denisidoro  路  4Comments

fuckbitchesgitmoney picture fuckbitchesgitmoney  路  4Comments