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 · 97 comments
Closed

Stacking support #203

pat-s opened this issue Aug 8, 2019 · 97 comments
Labels
enhancement New feature or request

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

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 Community help appreciated suggestion Request for new feature or some form of enhancement 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

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 Work in progress enhancement New feature or request and removed help wanted Community help appreciated suggestion Request for new feature or some form of enhancement labels Jun 27, 2020
@kcrwfrd
Copy link

kcrwfrd 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.

@kcrwfrd
Copy link

kcrwfrd 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

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

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

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.

@danielfalbo
Copy link

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

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

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

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.

@bangedorrunt
Copy link

@nilsolofsson I found that yours is not working with windows hidden in stack, @kola-web solution is working seamlessly:

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)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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 = yabai -m query --spaces --space |
  jq -re ".index" |
  xargs -I{} yabai -m query --windows --space {} |
  jq -sre 'add | map(select(."is-minimized"==false)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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 {}

@williamhCode
Copy link

@bangedorrunt thx this works better
here's it properly formatted for .skhrc

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)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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 : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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 {}

@boedy
Copy link

boedy commented Apr 17, 2023

Is it currently possible to stack windows with the mouse (using a modifier key)?

@Jared0430
Copy link

I was also looking to do this with the mouse, was hoping for something like

yabai -m config mouse_action1 stack

in my yabairc file

@koekeishiya
Copy link
Owner

koekeishiya commented Apr 19, 2023

yabai -m config mouse_drop_action stack

Everything is described here: https://github.com/koekeishiya/yabai/blob/master/doc/yabai.asciidoc although it may be a bit terse.

rriski added a commit to rriski/dotfiles.fish that referenced this issue Jul 20, 2023
@JustSaX
Copy link

JustSaX commented Jan 26, 2024

The following solution doesn't work for me. Do I need to have SIP disabled to have it work?

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)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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 : yabai -m query --spaces --space \
  | jq -re ".index" \
  | xargs -I{} yabai -m query --windows --space {} \
  | jq -sre 'add | map(select(."is-minimized"==false)) | sort_by(.display, .frame.y, .frame.x, .id) | . 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
Copy link

I'm disabled

@JustSaX
Copy link

JustSaX commented Jan 30, 2024

@kola-web thx for your quick reply. I had a mistake in my config.

The solution above from @bangedorrunt stays always on the same display. How could I switch the display when the last window of the stack or the furthermost window of the split is selected? E.g. focus display east if the if there is no window more east or the last window of the stack is selected.

@kola-web
Copy link

@JustSaX Sorry, my English is not good and I don’t quite understand what you mean. You can try my yabai configuration to see if it can solve your problem.

tjex added a commit to tjex/dotfiles that referenced this issue Mar 17, 2024
@jcelaya775
Copy link

jcelaya775 commented May 22, 2024

Is there support for focusing the nth window in a stack? For example, if you have 7 windows in a stack, then you could focus the 4th window in that stack with one keystroke instead of having to cycle through multiple windows. This would allow for faster navigation, especially if you're constantly navigating between windows in a stack. I was thinking of something like this:

yabai -m window --focus stack.1
yabai -m window --focus stack.2
.
.
.
yabai -m window --focus stack.9

@danielo515
Copy link

None of my windows has a property focused is that only available when you have them stacked?

@jcelaya775
Copy link

jcelaya775 commented Jul 4, 2024

None of my windows has a property focused is that only available when you have them stacked?

I created a PR to address the issue I was talking about: #2295

@koekeishiya
Copy link
Owner

Implemented on master 84a41c9 #2342

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

No branches or pull requests