Yabai: Ability to move between displays using direction?

Created on 30 Aug 2019  ·  17Comments  ·  Source: koekeishiya/yabai

I'm not a macOS expert, so please just let me know if I'm missing obvious things. =]

It seems like the arrangement indices aren't actually shown in the system preferences any more? But you can deduce them from yabai commands. It seems like macOS just assigns them in a fixed sequence based on the display devices. Rearranging the displays doesn't seem to alter the indices in any way.

This makes it hard to have key bindings to move between displays if at different times you use different arrangements.

My ideal feature would be to provide north/east/south/west style navigation based on the arrangement itself. I understand that it would be possible to have ambiguity (two displays "south" of another), but I'd be fine simply picking the first display in the requested direction of the current display. This would still make navigation key bindings much more intuitive to me.

As an alternative if the above isn't possible for some reason (maybe there is no API to discover the arrangement), simply allowing remapping of display arrangement indices would be similarly useful. I could then toggle between several arrangement mappings depending on how I have displays connected.

Thoughts? Is this doable in any way?

(Also, thanks so much for yabai! It's really great!)

enhancement

Most helpful comment

As this issue boards self-proclaimed shell script magician I took it upon me to implement something like this properly.

The below script calculates the angle between all other displays center and the focused displays center coordinate, and gives you the display that matches the target angle best. The target angle is $1 * pi/2, so you can use integers from -2 to 2 for the directions.

# save this to a file somewhere and make it executable
# call with argument from -2 to 2 depending on the direction you want to go in
jq -nr \
    --argjson displays "$(yabai -m query --displays)" \
    --argjson focused "$(yabai -m query --displays --display)" \
    --argjson direction "${1}" \
    '$displays
        | map(select(.id != $focused.id))
        | sort_by((1 | atan * 2 * $direction) 
            - (   (.frame.y + .frame.h / 2 - $focused.frame.y - $focused.frame.h / 2) 
                / (.frame.x + .frame.w / 2 - $focused.frame.x - $focused.frame.w / 2) 
                | atan)
            | fabs)
        | first.index // empty' \
    | xargs yabai -m display --focus

All 17 comments

Changing the order of macOS display arrangement indices comes with many issues because mission-control indices are ongoing across displays.

The query system can help you, though. Best thing you can do is using jq to sort displays queries by coordinates and grab the index of the next one in that sort order (the reverse | nth(... - 1) hack enables wraparound).

# get the index of the next display in a custom sort order 
# the output of display queries has no focused attribute, 
# which is why we need a second query to get the focused display
jq -nr \
    --argjson display "$(yabai -m query --displays --display)" \
    --argjson displays "$(yabai -m query --displays)" \
     '$displays | sort_by(.frame.x + .frame.w / 2, .frame.y + .frame.h / 2) 
                | reverse 
                | nth(index(map(select(.id == $display.id))) - 1)
                .index'

In a similar fashion, you could define a custom arrangement.

# get the index of the 2nd display by center coordinate sort
yabai -m query --displays \
     | jq -r 'sort_by(.frame.x + .frame.w / 2, .frame.y + .frame.h / 2)[1].index'

These indices can the be used in combination with yabai -m display --focus <display selector>.

Display queries carry very little information compared to window queries and space queries, but you can still build many custom commands with them.

I'm not particularly well versed in shell scripting, so I'm not sure if the approach mentioned by @dominiklohmann can be used to move between displays using directions as you proposed.

While I'm not aware of any API that will give us the arrangement in a usable way, it would be fairly trivial to construct a spatial mapping using display coordinates that can be used for this purpose.

As this issue boards self-proclaimed shell script magician I took it upon me to implement something like this properly.

The below script calculates the angle between all other displays center and the focused displays center coordinate, and gives you the display that matches the target angle best. The target angle is $1 * pi/2, so you can use integers from -2 to 2 for the directions.

# save this to a file somewhere and make it executable
# call with argument from -2 to 2 depending on the direction you want to go in
jq -nr \
    --argjson displays "$(yabai -m query --displays)" \
    --argjson focused "$(yabai -m query --displays --display)" \
    --argjson direction "${1}" \
    '$displays
        | map(select(.id != $focused.id))
        | sort_by((1 | atan * 2 * $direction) 
            - (   (.frame.y + .frame.h / 2 - $focused.frame.y - $focused.frame.h / 2) 
                / (.frame.x + .frame.w / 2 - $focused.frame.x - $focused.frame.w / 2) 
                | atan)
            | fabs)
        | first.index // empty' \
    | xargs yabai -m display --focus

That is a magnificent shell script approach.

I do suspect that it might be a tad cleaner to add this directly to the commands, but I'm delighted to have this workaround in the interim. Trying it out now.

There's still some weirdness around the 2*pi to 0 border, but I don't think this is worth fixing when @koekeishiya considers this worth implementing natively.

While I have different layouts I care about, they happen to all be linear along the x-axis and so I made a simpler script to give me my linearized "next" and "prev". It also handles saturating at the end and just displays the display index so that I can feed it into any command i want in my key bindings.

The result:

#!/bin/sh

case "${1}" in
  next)
    step=1
    ;;
  prev)
    step=-1
    ;;
  *)
    echo >&2 "ERROR: must provide an argument 'next' or 'prev'!"
    exit 1
    ;;
esac

jq -nr \
  --argjson displays "$(yabai -m query --displays)" \
  --argjson focused "$(yabai -m query --displays --display)" \
  --argjson step "$step" \
  '$displays
    | sort_by(.frame.x)
    | .[index($focused) + $step].index // $focused.index'

But yeah, having this work internally seems quite nice.

Also, thanks for all the help!

@chandlerc I believe there's a bug in your script, because jq makes the array index -1 access the last element, so your custom prev does wraparound while your next does not.

@chandlerc I believe there's a bug in your script, because jq makes the array index -1 access the last element, so your custom prev does wraparound while your next does not.

Doh, of course. Fixed. In case others are looking for it:

#!/bin/sh

case "${1}" in
  next)
    step=1
    ;;
  prev)
    step=-1
    ;;
  *)
    echo >&2 "ERROR: must provide an argument 'next' or 'prev'!"
    exit 1
    ;;
esac

jq -nr \
  --argjson displays "$(yabai -m query --displays)" \
  --argjson focused "$(yabai -m query --displays --display)" \
  --argjson step "$step" \
  '$displays
    | sort_by(.frame.x)
    | .[index($focused) + if (index($focused) + $step) < 0 then 0 else $step end].index // $focused.index'

Pretty goofy way to clamp, but 🤷🏻‍♂️

Hey, so I'm a former i3 user trying out yabai and I came across this issue. Does this include having the ability to have the focus span across multiple displays like on i3? For example, if I have 2 monitors side by side, I expect that by using the commands below, I'd be able to go from monitor 1 to monitor 2 by hitting "alt + l" if I am on the right most window on monitor 1, and monitor 2 was positioned on the right of monitor 1 in the system. However, right now the focus instead wraps back around to the leftmost window of monitor 1. Is this related to this issue, or should I open a new issue, or did I just miss something in the docs?

# navigate windows and spaces
alt - h : yabai -m window --focus west
alt - j : yabai -m window --focus south
alt - k : yabai -m window --focus north
alt - l : yabai -m window --focus east

@michaelsong-hz Window focus does currently not wrap around, and this is unlikely to ever change. Instead, you can rely on the exit code and queries to do wraparound in whatever way fits your setup best.

For example, I have these two keys setup for window focus with wraparound across displays.

alt - tab : yabai -m window --focus next || yabai -m window --focus "$((yabai -m query --spaces --display next || yabai -m query --spaces --display first) | jq -re '.[] | select(.visible == 1)."first-window"')" || yabai -m display --focus next || yabai -m display --focus first
shift + alt - tab : yabai -m window --focus prev || yabai -m window --focus "$((yabai -m query --spaces --display prev || yabai -m query --spaces --display last) | jq -re '.[] | select(.visible == 1)."last-window"')" || yabai -m display --focus prev || yabai -m display --focus last

I think I should just read the issue threads in this repo to learn from the jq wizardry of @dominiklohmann

Love the scripting solution and I it would be easy to swap in east and west instead of next and prev and it was great! For all of five minutes before I found that when you've got a fullscreen window with other windows beneath, it keeps toggling between the fullscreen window and another window and never goes to another monitor. Not sure how to express that to create a new issue though.

Thanks @dominiklohmann!

@ramblingenzyme Do you mind sharing how you adapted the scripts to use west and east instead of prev and next? I didn't manage to have it focus the correct window on the other screen or wrap around from the last screen to the first correctly.

@danijar I didn't, the issue that Dominik links to means it will be fixed in the next release, so I'm just putting up with prev and next for the moment.

Implemented on master.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tsujp picture tsujp  ·  3Comments

fuckbitchesgitmoney picture fuckbitchesgitmoney  ·  4Comments

danijar picture danijar  ·  4Comments

imjma picture imjma  ·  3Comments

d-miketa picture d-miketa  ·  3Comments