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

Stacking support #203

Closed
pat-s opened this issue Aug 8, 2019 · 84 comments
Closed

Stacking support #203

pat-s opened this issue Aug 8, 2019 · 84 comments
Labels
enhancement

Comments

@pat-s
Copy link

pat-s commented Aug 8, 2019

Wondering if you have thought about supporting stacking of windows (similar to i3 and sway).

I usually use this mode a lot in certain spaces in sway and it can be pretty handy.

Thanks for the great project!

@dominiklohmann
Copy link
Collaborator

dominiklohmann commented Aug 8, 2019

A flexible implementation of this has a lot to offer. Obviously this can already be done using a combination of signals, rules and queries—similar to my monocle mode implementation from #83—but I personally think that yabai has a lot to gain from this if implemented properly.

I'm imagining a syntax like this:

# stack source window on target window
yabai -m window [<source window selector>] --stack <target window selector>

# the next window gets inserted stacked on top of the source window
# the window command --insert currently supports <DIR_SEL>
yabai -m window [<source window selector>] --insert stack

Which would make the source window occupy the same frame and node as the target window. A window could be taken out of a stack through any means that reinserts it, e.g. a warp command.

What still needs to be discussed is how window selectors interact with stacked windows: Should directional selectors (west, south, north, east) ignore stacked windows? Should next and prev cycle through stacked windows in order? Is the focused window always moved to the top of its stack whenever its focused?

How should stacked windows display? Should there be an overlap, e.g. the lowest window of the stack using the full height of the stack, the 2nd lowest has its frame start a set amount of pixels lower, the 3rd lowest has its frame start twice the set amount of pixels lower and so on? That way, mouse support would not be compromised. I would suggest using 22pt here, which is the height of the menu bar.

Some form of stacking could also be the foundation for fixing #68 (would require the internals to be changed in a similar way anyways) and would allow a more robust implementation of a monocle mode, and many more custom ways to tile windows like suggested in #98.

Thoughts @koekeishiya?

@koekeishiya
Copy link
Owner

koekeishiya commented Aug 17, 2019

I do see the use case for this type of window management, but I am currently not interested in attempting to implement this. I would not be against a well thought out PR that integrates this nicely if anyone wants to give it a shot on their own.

@koekeishiya koekeishiya added help wanted suggestion labels Aug 17, 2019
@Krever
Copy link

Krever commented Apr 3, 2020

Just to share some explanation on why I think this is a very VERY useful feature (for me at least):
I typically work with several (2-8) instances of IDE (Intellij). What I got used to (on i3wm) is having one instance open as fullscreen and the other ones visible only as window titles. Then whenever I need to switch I use arrows to navigate through the windows titles. What's important is that I completely don't care about any visuals or interactivity of these other windows.

I'm trying to use amethyst right now but the closest I was able to get is a huge pain in comparison to i3. Mostly because it's very hard to identify which window I want to switch to. They all look almost the same and titles are not well exposed.

@koekeishiya
Copy link
Owner

koekeishiya commented Apr 3, 2020

To clarify, my main issue with this is not inherently the functionality by itself. It is the fact that macOS does not actually expose the functionality necessary for me to implement the various solutions that would make this a good experience, and so I'm reluctant to spend too much of my time dealing with their garbage.

I have this in the back of my mind and iterate on possible solutions that could work, but this is not as simple to solve as it would be with e.g the X windowing system.

@koekeishiya
Copy link
Owner

koekeishiya commented Jun 27, 2020

Implemented basic logic and structure necessary for stacking windows.

Directional selectors will ignore the additional stacked windows belonging to a node (meaning that they will target the currently focused window in the stack).

Stacked windows are not cascaded; their region overlap as is. If a stacked window activates parent zoom or fullscreen zoom, that operation will simultaneously apply to all windows in the same stack.

To make a window share node/region with some managed window, use the following command:

# stack target_window_sel onto source_window_sel
yabai -m window [<source_window_sel>] --stack <target_window_sel>

# next window is inserted onto source_window_sel
yabai -m window [<source_window_sel>] --insert stack

# focus the prev window in a stack
yabai -m window --focus stack.prev

# focus the next window in a stack
yabai -m window --focus stack.next

# focus the first window in a stack
yabai -m window --focus stack.first

# focus the last window in a stack
yabai -m window --focus stack.last

# focus the most recently focused window in a stack
yabai -m window --focus stack.recent

<target_window_sel> will take on the node/region of <source_window_sel>. The reasoning behind the arguments working in this order is that I find it more intuitive due to the way that you can omit the <source_window_sel> to implicitly place an arbitrary window onto the currently focused window.

To un-stack a window, simply perform a warp operation using window --warp or using mouse integration, with the stacked window as the source operand. When a stacked window is the source operand of a mouse action it is not eligible for a swap operation, and it will always perform a warp following the regular rules, in which the end result is that the window is split into its own separate node. If a regular managed window (non-stack) is the source operand of a warp/mouse operation it will follow all the regular rules.

With the exception of mouse integration, swap operations should work as it always has.

If a stacked window is moved to a different space/display, enters fullscreen, or any operation that would make this window become unmanaged, that operation will only affect that window and it will be removed from the stack.


Remaining things that need to be implemented/solved:

  1. How to focus stacked windows? New command/selectors or make prev/next cycle through stacked windows in order? Implemented.
    • Is the focused window always moved to the top of its stack whenever its focused?
    • Properly track the focused window in a node when it has multiple windows (if it is not moved to the top of its stack). Implemented.
  2. Support stacking through window --insert. Implemented.

@koekeishiya koekeishiya added wip enhancement and removed help wanted suggestion labels Jun 27, 2020
@kvcrawford
Copy link

kvcrawford commented Jun 27, 2020

I would like to be able to perform operations (warp, swap, resize) on a stack, but I’m not sure how to indicate that the stack is what’s selected, not a window inside of it.

I suppose resize is no issue—just let the stack inherit sizing from the top (focused) window.

How to focus stacked windows? New command/selecotrs or should next and prev cycle through stacked windows in order?

Cycling through the stack with prev/next sounds good to me.

Is the focused window always moved to the top of its stack whenever its focused?

I believe so, yes. Would be odd to be able to focus a window underneath that’s not visible.

Moving focused window to the top is also consistent with mouse UI behavior in floated or un-managed windows that layer over one another.

But I think what you may be getting at is how to represent the behavior in the tree. Do you want to do so by index in the array of stacked windows (meaning you have to modify that array every time a different window is focused), or track it another way?

I’m not sure which way would be best, but I’m happy to discuss and think about it.

@koekeishiya
Copy link
Owner

koekeishiya commented Jun 27, 2020

I would like to be able to perform operations (warp, swap, resize) on a stack, but I’m not sure how to indicate that the stack is what’s selected, not a window inside of it.

With the current implementation all operations that affect the node tree will apply to the entire stack, except for warp operations where the stacked window is the source operand. You can still warp a non-stacked window onto a stack and it will split the stack using the elected parameters. The window --insert will support stack as an option for whenever situations like this arise, where you want the warp operation to insert that window into the stack instead of splitting it.

But I think what you may be getting at is how to represent the behavior in the tree. Do you want to do so by index in the array of stacked windows (meaning you have to modify that array every time a different window is focused), or track it another way?

That's pretty much it, yeah. Visually, the window will be at the top of the stack of course.
I guess this comes down to whether we want to persist window ordering completely, or only move the focused window in a stack into index zero or something.

@kvcrawford
Copy link

kvcrawford commented Jun 27, 2020

I think maintaining order separately from focus will allow you to have an API that is also compatible for managing a tabbed UI.

@jonatan-branting
Copy link

jonatan-branting commented Jun 28, 2020

  1. How to focus stacked windows? New command/selectors or make prev/next cycle through stacked windows in order?

I would say as a new command/selector, as this would allow the user to define something like this:

yabai -m window —focus upwards || yabai-m window —focus north

Which would make selecting stacked windows similar to that of the i3 behaviour.

On the same topic, I’d say that an important aspect of making this feature easy to use, would be to give the user visual cues for what effects the navigating the stack will have. This could be achieved by introducing cascading of stacked windows, allowing the user to specify a window region offset defined in pixels. This would allow the user to see the titlebars (or as many pixels of each window as they would like), of each window in the stack, removing a lot of guesswork.

This might introduce complexities regarding zooming stacked windows, however.

@koekeishiya
Copy link
Owner

koekeishiya commented Jun 28, 2020

This could be achieved by introducing cascading of stacked windows, allowing the user to specify a window region offset defined in pixels.

Cascading is not really something that I consider a good solution because a lot of windows on macOS have size constraints, and this kind of feature will fall apart quickly when more than a couple of windows get stacked.

I don't think upwards/downwards are intuitive names. Sure, it is a stack (maybe group would be a better term), but if you are on window 1 out of 3, and go "downwards", window 2 gets focus and is moved to the top (visually), and then intuitively "downwards" feels like it would re-focus window 1, because it is now below window 2 and above window 3.

@jonatan-branting
Copy link

jonatan-branting commented Jun 28, 2020

Agreed, downwards/upwards certainly aren’t the best descriptors. Dedicated descriptors is probably the way to go though, I believe.

On the topic of cascading. My use case with yabai, is that I only tile whitelisted applications. I do this because most windows simply cannot be tiled properly. Either they’re too large or too small. If I whitelist only the applications I want to have tiled, and which I know can be resized, I can have an experience that feels less hacky.

I’m sure I’m in the minority in regards to this. But with such a use case, cascading the windows certainly makes sense.

Now I’m getting a bit off topic, but is this use case something you might be interested in supporting more fully in the future (There are some things which doesn’t align well with this idea right now)? If so, I’d be happy to work on that, including optionally adding cascading support to stacked windows.

@dominiklohmann
Copy link
Collaborator

dominiklohmann commented Jun 28, 2020

One thing I'd like to see for this is mouse support à la #142. Maybe there should be an option to have the center drag be stack instead of swap now.

@koekeishiya koekeishiya removed the addressed not released label Sep 3, 2020
@koekeishiya
Copy link
Owner

koekeishiya commented Sep 3, 2020

stacked windows cannot be reordered currently.

@dnordberg
Copy link

dnordberg commented Sep 3, 2020

Sorry, its a long thread and didn't have time to quite go through all of it.

Is it now possible to have two stacks on a screen and use hotkeys to navigate each?

@babygau
Copy link

babygau commented Sep 3, 2020

yes, you can do it 👍

@peppy
Copy link

peppy commented Sep 4, 2020

Thanks for this new mode!

Is there a way to make this work? I can't seem to find the right combination or ordering (allow cycling through both BSP and stack mode):

cmd - tab : yabai -m window --focus next || yabai -m window --focus stack.next || yabai -m window --focus first || yabai -m window --focus stack.first

@koekeishiya
Copy link
Owner

koekeishiya commented Sep 16, 2020

@peppy Is this what you are trying to do?

# focus the next stacked window if possible; otherwise focus the next window or go back to the first window
cmd - tab : yabai -m window --focus stack.next || yabai -m window --focus next || yabai -m window --focus first

It's not possible to have a single bind that can both navigate between windows and also cycle within a stack (that's what your sample above looks like it wants to do).

@peppy
Copy link

peppy commented Sep 18, 2020

I'm trying to do the thing you say is not possible, yeah. Since I was trialling having some layouts BSP and some stacking.

Thanks for confirming, I guess I'll need to maintain two binds for now.

@danielfalbo
Copy link

danielfalbo commented Sep 22, 2020

every node in the bsp layout can be turned into a stack, so you can have as many stacks side by side, vertically, horizontally, as deeply nested as you want.

What's the command to do this?

@babygau
Copy link

babygau commented Sep 22, 2020

@danielfalbo

yabai -m window --stack north|east|south|west

@danielfalbo
Copy link

danielfalbo commented Sep 22, 2020

Thank you!

@babygau
Copy link

babygau commented Sep 29, 2020

@koekeishiya, do you mind explaining to me? I don't know but cycling through windows in bsp and stack suddenly works for me with just one keybinding.

bf66d7b0d8a5a41fb2adc0bf2d319206

In the short clip above, I have:

  • 4 windows in a BSP space
  • 3 stack on the left (2 Firefox windows, 1 Finder window)
  • 1 Firefox window on the right.
  • hyper-; bound to @dominiklohmann snippet.

@dominiklohmann
Copy link
Collaborator

dominiklohmann commented Sep 29, 2020

Sorry for not being aware of this thread before the mention, this really should've gone into a new issue asking how to have a single keybind for cycling instead of repurposing this thread.

Here's how I do it:

# forward
yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.x, .id) | reverse | nth(index(map(select(.focused == 1))) - 1).id" \
  | xargs -I{} yabai -m window --focus {}

# backward
yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.y, .id) | nth(index(map(select(.focused == 1))) - 1).id" \
  | xargs -I{} yabai -m window --focus {}

This is a slight deviation from my snippet someone posted earlier with a fix for minimized windows. I've been using this myself and it works as expected.

The reason why this works is because it doesn't try to focus the visible window on a stack, but rather performs a stable sort on all windows of the space and just focuses the next entry in the sorted list (with wraparound).

@babygau
Copy link

babygau commented Sep 29, 2020

@dominiklohmann, I just tried it, and it worked perfectly. This snippet deserve to be listed on Wiki Page Tips and Tricks.

@stefandeml
Copy link

stefandeml commented Oct 11, 2020

Shout-out! - Very nice contribution.

Is there a way to unstack only a single window?
wrap works if there already is another peer node already, but often I end up with a single stack node and want to pop-up one window?
Right now my work-around is to set yabai -m space --layout bsp, but then the entire tree gets unstacked.
Thanks for any help!

@danielfalbo
Copy link

danielfalbo commented Oct 11, 2020

Shout-out! - Very nice contribution.

Is there a way to unstack only a single window?
wrap works if there already is another peer node already, but often I end up with a single stack node and want to pop-up one window?
Right now my work-around is to set yabai -m space --layout bsp, but then the entire tree gets unstacked.
Thanks for any help!

@stefandeml what about yabai -m window --toggle float ? If your space layout is bsp, doing it two times should do the job

@stefandeml
Copy link

stefandeml commented Oct 11, 2020

Shout-out! - Very nice contribution.
Is there a way to unstack only a single window?
wrap works if there already is another peer node already, but often I end up with a single stack node and want to pop-up one window?
Right now my work-around is to set yabai -m space --layout bsp, but then the entire tree gets unstacked.
Thanks for any help!

@stefandeml what about yabai -m window --toggle float ? If your space layout is bsp, doing it two times should do the job

sweet - indeed, that does the job. Thanks

sendhil added a commit to sendhil/dotfiles-mac that referenced this issue May 2, 2021
Found
koekeishiya/yabai#203 (comment)
which will cycle through the current windows instead of just stopping.
kiprasmel added a commit to kiprasmel/yabai-config that referenced this issue Oct 6, 2021
bliss

koekeishiya/yabai#203 (comment)

Signed-off-by: Kipras Melnikovas <kipras@kipras.org>
kiprasmel added a commit to kiprasmel/yabai-config that referenced this issue Oct 6, 2021
bliss

koekeishiya/yabai#203 (comment)

Signed-off-by: Kipras Melnikovas <kipras@kipras.org>
@kola-web
Copy link

kola-web commented Dec 1, 2021

Apply to yabai yabai-v4.0.0

# alt-k
alt + shift - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --swap {}

# alt-j
alt + shift - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --swap {}

# alt-k
alt - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --focus {}

# alt-j
alt - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --focus {}

Amar1729 added a commit to Amar1729/yabai-zsh-completions that referenced this issue Dec 31, 2021
@zt1983811
Copy link

zt1983811 commented Jan 27, 2022

Apply to yabai yabai-v4.0.0

# alt-k
alt + shift - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --swap {}

# alt-j
alt + shift - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --swap {}

# alt-k
alt - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --focus {}

# alt-j
alt - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --focus {}

Hi @kola-web
This is working perfectly thanks. I am wondering the reason you create this is because in Yabai 4

alt - h : yabai -m window --focus west

Not working?

@kola-web
Copy link

kola-web commented Apr 1, 2022

适用于yabai yabai-v4.0.0

# alt-k
alt + shift - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --swap {}

# alt-j
alt + shift - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --swap {}

# alt-k
alt - k : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $has_index > 0 then nth($has_index - 1).id else nth($array_length - 1).id end'  \
  | xargs -I{} yabai -m window --focus {}

# alt-j
alt - j : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | . as $array | length as $array_length | index(map(select(."has-focus"==true))) as $has_index | if $array_length - 1 > $has_index then nth($has_index + 1).id else nth(0).id end' \
  | xargs -I{} yabai -m window --focus {}

@kola-web 这工作得很好,谢谢。我想知道你创建这个的原因是因为在 Yabai 4

alt - h : yabai -m window --focus west

不工作?
It should be because I think it is more convenient for me to use the second phase that takes up less shortcut keys.

@nilsolofsson
Copy link

nilsolofsson commented Apr 5, 2022

I ended up going with this alternative (yabai 4.0):

cmd + shift - k : if [ "$(yabai -m query --spaces --space | jq -r '.type')" = "stack" ]; then (yabai -m window --focus stack.next || yabai -m window --focus stack.first); else yabai -m window --focus next || yabai -m window --focus first; fi
cmd + shift - j : if [ "$(yabai -m query --spaces --space | jq -r '.type')" = "stack" ]; then (yabai -m window --focus stack.prev || yabai -m window --focus stack.last); else yabai -m window --focus prev || yabai -m window --focus last; fi

The alternative suggested above didn't have the behavior I expected. With alt-j it would only toggle between the last two windows instead of circulating back through the windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement
Projects
None yet
Development

No branches or pull requests