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

Handle native tabs properly #68

Open
dominiklohmann opened this issue Jun 25, 2019 · 52 comments
Open

Handle native tabs properly #68

dominiklohmann opened this issue Jun 25, 2019 · 52 comments
Labels
bug Something isn't working help wanted Community help appreciated

Comments

@dominiklohmann
Copy link
Collaborator

dominiklohmann commented Jun 25, 2019

Issue

NSDocument-based applications send window created notification on tab creation.

This is a re-iteration of the same issue with chunkwm (https://github.com/koekeishiya/chunkwm/issues/235).

I have created another radar for this issue now that the Catalina beta is a thing. This time around, I got an answer: Works as expected.

iPad apps brought to the Mac using Catalyst have the same behaviour, so this should probably be fixed before the release of macOS 10.15 Catalina.

Proposed fix

Investigate how Amethyst works around this and replicate the behaviour in yabai.

Here's what they're doing from my understanding: When a kAXWindowCreated notification triggers, check whether the window has the same position and same size as another window of the same application. For applications like Terminal, some tolerance is needed because the window size may change ever so slightly when changing tabs.

@koekeishiya
Copy link
Owner

I don't think the implementation in Amethyst is complete, or fully robust. How does Amethyst handle minimization and deminization of such grouped windows? What about hiding and restoring an application that has a grouped window, and a "normal" window?

I'm also unsure if the same heuristic applied by Amethyst can be used here, because I believe that Amethyst behaves differently in terms of how windows are stored / tracked. IIRC they have some caching mechanism that is flushed and regenerated upon a space activation? I may be mistaken as the last time I looked at Amethyst was maybe 4years ago.

@koekeishiya
Copy link
Owner

koekeishiya commented Jun 30, 2019

So I just tried Amethyst to see how it behaves with tabbed windows.

  • Creating a new tab seems to work fine.
  • Closing a tab is fine.
  • Switching between tabs are ok.
  • Minimize and deminimize seem to work fine.

Hiding an application did not work for me at all, regardless of the application having tabbed windows or not, so I could not verify my assumption there.

I did take a quick glance at the code that is run upon a space change, and it does indeed drop and regenerate some sort of cache of windows that it consider to be current. This is not something that can be replicated in yabai, as we don't have this form of caching. In yabai, a window is tracked from the moment we learn about its existence until it is destroyed, regardless of which space or display or whatever we transition from/to, or consider active.

I'll probably spend some time to observe the behaviour in detail myself, and if I can come up with some solution that can integrate properly with yabai, I will support this. If I cannot do so, I'd rather state that tab integration is not supported. Implementing a partially working, but flawed solution, would be worse than ignoring it completely in my opinion.

@koekeishiya
Copy link
Owner

koekeishiya commented Jun 30, 2019

There is an additional case here that Amethyst also does not seem to tackle at this moment in time.

When a window is de-tabbed we somehow need to detect this and tile the window.

@koekeishiya koekeishiya added the help wanted Community help appreciated label Jun 30, 2019
@koekeishiya
Copy link
Owner

I spent a bit of time looking into this, and we can actually query information about the tabs using the accessibility API. That seems a lot better to me than having to try and differentiate based on which order we receive notifications in.

@koekeishiya koekeishiya removed the help wanted Community help appreciated label Jun 30, 2019
@koekeishiya
Copy link
Owner

koekeishiya commented Jul 1, 2019

Tested some more. The accessibility API is not reliable. All it really gives us is the fact that a tabgroup has been created, and the number of tabs. However, if you go from a non-tabbed window to a window with 3 or more tabs (spamming new tab hotkey). The creation of all but the last tab will not report as part of a tabgroup, as the tabgroup have yet to be created.

Looks like we may have to do some weird combination, or rely on notifications only, which I'm not sure will be good enough.

koekeishiya added a commit that referenced this issue Jul 1, 2019
@koekeishiya
Copy link
Owner

When a window is de-tabbed we only receive moved and resized notifications for said window.
There is no consistent way to know when these events correspond to a window being de-tabbed, or the window group itself being moved around.

@dominiklohmann
Copy link
Collaborator Author

When a window is de-tabbed we only receive moved and resized notifications for said window.

So when a tab bar is present in an NSDocument-based app, there's an element with a role of AXTabGroup, which has tab count + 1 children (the tabs themselves and the create tab button). The actual tab buttons have the role AXRadioButton.

When a window is de-tabbed, this count changes. To associate this with the corresponding window id, there'd need to be a mapping of tab button UI element identifier (does such a thing exist?) to tab window id.

@koekeishiya
Copy link
Owner

When a window is de-tabbed, this count changes. To associate this with the corresponding window id, there'd need to be a mapping of tab button UI element identifier (does such a thing exist?) to tab window id.

The problem here is that the detabbed window no longer has that tab-group as an accessibility element, so we are not able to know which group it came from. Tracking windows that belong to a group is not possible, at least I have not managed to do it reliably:

Tested some more. The accessibility API is not reliable. All it really gives us is the fact that a tabgroup has been created, and the number of tabs. However, if you go from a non-tabbed window to a window with 3 or more tabs (spamming new tab hotkey). The creation of all but the last tab will not report as part of a tabgroup, as the tabgroup have yet to be created.

@dominiklohmann
Copy link
Collaborator Author

dominiklohmann commented Jul 26, 2019

Couldn't you created a cache that maps window id -> tab group?

Now when a window is moved/resized, you check if the windows id belonged to a different tab group than it does now.

@koekeishiya
Copy link
Owner

Couldn't you created a cache that maps window id -> tab group?

I have not managed to do this no, as no API return the id of windows that are the inactive windows in a tab group. When trying to get the elements in a tab group, they all return the window id of the active window as well. I also tried to cache the AXUIElementRef itself, and use the appropriate function to compare them, but that also did not work.

This starts to become a problem when multiple tabs are opened in a short amount of time, as per my comment above.

@dominiklohmann
Copy link
Collaborator Author

The only way to access the tabs programmatically seems to be by using [NSWindow tabbedWindows], which can only be accessed by its window id from [NSApplication windowWithWindowNumber:], which is unavailable from other applications.

It really seems like a position based workaround is the only option: By default, macOS will never spawn a new window with the same coordinates as an existing window of the same application, except for when it's a tab.

@jerryqhyu
Copy link

Please handle this somehow, many people use tabs and this is a big inconvenience. How does yabai restore proper layout for tabs when switching windows but not when the tab is created in the first place? There's gotta be some way to fix it.

@goranmoomin
Copy link

@koekeishiya @dominiklohmann Sorry for the noise but is there any improvement in the area?

@dominiklohmann
Copy link
Collaborator Author

I agree that this is a big inconvenience, but as it is yabai would need to be restructured internally for some kind of stacking support (#203, so a tree node can hold multiple windows), and with that in place this change would be easier to make, although still based on workarounds (frame x/y stay the same, w/h may change slightly for some apps like Terminal). Some kind of caching may also need to be introduced, because macOS does not include window ids from background tab windows in query results.

It's just a tough issue to solve correctly and likely to cause the need for further workarounds in the future. ¯\_(ツ)_/¯

In general I think this should still be approached, especially with 10.15 bringing iPadOS apps to macOS with Catalyst, where all document-based apps are gonna have this problem.

@goranmoomin
Copy link

@dominiklohmann Thanks for your kindly explanation!

Sent with GitHawk

@jerryqhyu
Copy link

jerryqhyu commented Sep 8, 2019

@dominiklohmann One thing I've seen: open a random app and finder. Add a new tab in finder, now yabai thinks there are three window and splits finder. Close one tab and it restores to normal. Now try again, add a new tab, but instead of closing, switch to another desktop and switch back. Magically, Yabai recognizes that there are only two windows, despite multiple tabs being open. It seems like Yabai just dropped all but the last tab from the tree. If you close the last tab, finder becomes a floating window because the first tab is not part of the tree. I'm sure it's just accessibility API fucking up, but how does the tree just drop a node out of nowhere?

P.s. can you point me to the part where window tree is being managed? Thanks.

@koekeishiya
Copy link
Owner

koekeishiya commented Sep 9, 2019

I'm sure it's just accessibility API fucking up, but how does the tree just drop a node out of nowhere?

Every single API that macOS exposes (both public and private) stop reporting the window id of windows that are not the active tab, which is why yabai currently assumes that the window no longer exists, and releases said region.

P.s. can you point me to the part where window tree is being managed? Thanks.

Mostly from functions called from src/window_manager.c and src/space_manager.c. Handling of events (e.g: window created, window destroyed etc) can be found in src/event.c

@koekeishiya
Copy link
Owner

koekeishiya commented Sep 9, 2019

I'm sure most people will probably be displeased with this answer, but I don't consider this issue worth more of my time. I have spent probably more than 40+ hours messing around experimenting and looking for robust ways to fully support this throughout the features that yabai make available, and my conclusion is that it is simply not possible (at the level of quality I'd expect it to have).

I also never use this feature, and macOS is not my primary OS anymore. I'll happily remain a part of the discussion If other people are interested in trying to tackle this problem themselves.

@Xadoy
Copy link

Xadoy commented Sep 22, 2019

I also never use this feature, and macOS is not my primary OS anymore.

I am curious that which OS is now your primary OS? MacOS's WM is a bit frustrating

@koekeishiya koekeishiya added the help wanted Community help appreciated label Oct 9, 2019
@choco
Copy link

choco commented Oct 17, 2019

I think the best course of action would be to expose necessary functionality through rules and then let people write these per application. How to implement this would still be a big problem, maybe just adding a is-tab option to the rule with some kind of key path to where the tab group would be located in the accessibility hierarchy?

@bashu
Copy link

bashu commented Nov 20, 2022

to @Liverm0r strange, but tabs in iTerm2 (3.4.17+) works just fine.

However three years later tabs in Finder and Terminal (by Apple) still broken.

@D00mch
Copy link

D00mch commented Nov 20, 2022

I updated my list above, and personally I am using Wezterm now, as it was faster than hyper and the easiest to set up to follow OSX system dark mode (with this script).

@wdanilo
Copy link

wdanilo commented Jan 31, 2023

@koekeishiya first of all, I am so freaking thankful for creating yabai. You don't know how much UX you are improving for MANY macOS users. It's one of the greatest things ever done. Sorry for the above sentence in this thread, but I just wanted to express my big gratitude toward you.

Regarding this issue, do you see any possible solution for the future development of yabai? Or all options that you tested/considered are just not working?

@sevimuelli
Copy link

@leonardopennino I found a similar workaround, which doesn't rearange the windows. The problem with setting the layout to float and back to bsp is that it seams to always put the active window in the left top corner. But if you go to another space and come back, the layout is recalucated, as already mentioned above. What we need is a way to refresh the layout.

My temportary "solution" is this. It works pretty ok but of course there is a flicker, as one needs to focus on anoter space and back on the previous one to work. That's also the reason fo the delay. It's not pretty but it get's the job done:

# Refresh yabai if a tab in finder or terminal is created or moved to new window
yabai -m signal --add event=window_created app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_destroyed app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_moved app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_resized app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

In Amethyst there is an option "Force windows to be reevaluated". Would be nice if one has a similar option here as well. Like yabai -m refresh or something. Then one could use this signals to force a reload.

@koekeishiya Do you think it is simple to implement such a comand? You already gave a startingpoint where this is handeled.

I'm sure it's just accessibility API fucking up, but how does the tree just drop a node out of nowhere?

Every single API that macOS exposes (both public and private) stop reporting the window id of windows that are not the active tab, which is why yabai currently assumes that the window no longer exists, and releases said region.

P.s. can you point me to the part where window tree is being managed? Thanks.

Mostly from functions called from src/window_manager.c and src/space_manager.c. Handling of events (e.g: window created, window destroyed etc) can be found in src/event.c

@rootix
Copy link

rootix commented Jul 8, 2023

I found a workaround which works for me by accident. I wanted to have unresizable windows like some settings dialogs to float.

Found this issue: #1317

The last comment with the signal fixed the Finder tab behavior as a side effect.

EDIT: Just realized the signal lets the Finder window itself float.

@kerryj89
Copy link

@leonardopennino I found a similar workaround, which doesn't rearange the windows. The problem with setting the layout to float and back to bsp is that it seams to always put the active window in the left top corner. But if you go to another space and come back, the layout is recalucated, as already mentioned above. What we need is a way to refresh the layout.

My temportary "solution" is this. It works pretty ok but of course there is a flicker, as one needs to focus on anoter space and back on the previous one to work. That's also the reason fo the delay. It's not pretty but it get's the job done:

# Refresh yabai if a tab in finder or terminal is created or moved to new window
yabai -m signal --add event=window_created app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_destroyed app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_moved app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

yabai -m signal --add event=window_resized app="^Terminal$|^Finder$" \
    action="yabai -m space --focus next && sleep 0.01 && yabai -m space --focus recent"

In Amethyst there is an option "Force windows to be reevaluated". Would be nice if one has a similar option here as well. Like yabai -m refresh or something. Then one could use this signals to force a reload.

@koekeishiya Do you think it is simple to implement such a comand? You already gave a startingpoint where this is handeled.

I'm sure it's just accessibility API fucking up, but how does the tree just drop a node out of nowhere?

Every single API that macOS exposes (both public and private) stop reporting the window id of windows that are not the active tab, which is why yabai currently assumes that the window no longer exists, and releases said region.

P.s. can you point me to the part where window tree is being managed? Thanks.

Mostly from functions called from src/window_manager.c and src/space_manager.c. Handling of events (e.g: window created, window destroyed etc) can be found in src/event.c

This helped me with PhpStorm tabs. Thanks!

@phryneas
Copy link

phryneas commented Nov 6, 2023

Unfortunately, yabai -m space --focus next only works if you have the Scripting Additions enabled, which I cannot do (company laptop).
Did anyone discover a similar workaround for layout recalculation without this?

@michaelaye
Copy link

Not sure, if I'm missing a setting but iTerm2 tabs make the window too long here on macOS 14.2.1. The screenshot shows the bottom of the window, where due to its length, the last line is cut off a bit when the iTerm2 window has tabs.

image

@katogatogato
Copy link

katogatogato commented Jan 17, 2024

Unfortunately, yabai -m space --focus next only works if you have the Scripting Additions enabled, which I cannot do (company laptop). Did anyone discover a similar workaround for layout recalculation without this?

You can achieve the same thing using display instead of space without the Scripting Additions.

# Refresh yabai if a tab is created or moved to new window
yabai -m signal --add event=window_created app="^Terminal$" action="yabai -m display --focus prev && yabai -m display --focus next"
yabai -m signal --add event=window_destroyed app="^Terminal$" action="yabai -m display --focus prev && yabai -m display --focus next"
yabai -m signal --add event=window_moved app="^Terminal$" action="yabai -m display --focus prev && yabai -m display --focus next"
yabai -m signal --add event=window_resized app="^Terminal$" action="yabai -m display --focus prev && yabai -m display --focus next"

This works on my setup without SIP disabled.

@hrvstr
Copy link

hrvstr commented Jan 17, 2024

You probably need a second display for this workaround, right? I only have a single display.

❯ yabai -m display --focus prev && yabai -m display --focus next
could not locate the previous display.

hakusaro added a commit to hakusaro/yabai that referenced this issue Feb 19, 2024
The NSDocument tab issue is a pretty annoying issue and is easily surfaced with native / default apps like Finder and Terminal, which are built into the OS. To avoid users creating multiple issues to track the same issue, this lists the caveat on the caveats section and links to the associated issue.

See: koekeishiya#68
koekeishiya added a commit that referenced this issue Mar 12, 2024
@koekeishiya koekeishiya added bug Something isn't working and removed enhancement New feature or request labels Mar 12, 2024
@nazriel
Copy link

nazriel commented Apr 23, 2024

Looks like Amethyst somehow works around this issue since windows with native tabs stay balanced most of the time. In worst case scenario if I spam tabs too much it swaps places with other window. Other than that it works.

@meetmangukiya
Copy link

is it possible to ignore the tabs for particular program like alacritty?

@aayushsapkota9
Copy link

aayushsapkota9 commented Jul 22, 2024

Screenshot 2024-07-22 at 08 07 07 Screenshot 2024-07-22 at 08 10 07

I am getting this issue, the other terminal is not visible or it snaps into half when I do Ctrl + T is this the same issue being discussed here? or is there another workaround for this.
This issue is fixed when I mimimize and reopen the terminal.

@hakusaro
Copy link

@aayushsapkota9 correct, basically.

@meetmangukiya
Copy link

current workaround for me is after i have created a new tab, switch to another space and back, that seems to fix it

@ceigh
Copy link

ceigh commented Jul 22, 2024

I just started using ⌘ N for this 😉.

@luxuxl
Copy link

luxuxl commented Sep 27, 2024

is it possible to fix now

@nazriel
Copy link

nazriel commented Oct 6, 2024

I am playing around with this workaround for now while using Ghostty:

yabai -m signal --add app='^Ghostty$' event=window_created action='yabai -m space --layout bsp'
yabai -m signal --add app='^Ghostty$' event=window_destroyed action='yabai -m space --layout bsp'

Isn't perfected but works most of the time

Update 07/10/2024:

yabai -m signal --add app='^Ghostty$' event=window_created action='yabai -m space --focus next; sleep 0.01; yabai -m space --focus prev'
yabai -m signal --add app='^Ghostty$' event=window_destroyed action='yabai -m space --focus next; sleep 0.01; yabai -m space --focus prev'
yabai -m signal --add app='^Finder$' event=window_created action='yabai -m space --focus next; sleep 0.01; yabai -m space --focus prev'
yabai -m signal --add app='^Finder$' event=window_destroyed action='yabai -m space --focus next; sleep 0.01; yabai -m space --focus prev'

moving focus to next space and back to previous seems to work better - since it doesn't reset sizing of windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Community help appreciated
Projects
None yet
Development

No branches or pull requests