yabai icon indicating copy to clipboard operation
yabai copied to clipboard

Handle native tabs properly

Open dominiklohmann opened this issue 5 years ago • 44 comments

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.

dominiklohmann avatar Jun 25 '19 06:06 dominiklohmann

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 avatar Jun 25 '19 10:06 koekeishiya

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 avatar Jun 30 '19 14:06 koekeishiya

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 avatar Jun 30 '19 17:06 koekeishiya

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 avatar Jun 30 '19 18:06 koekeishiya

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 avatar Jul 01 '19 21:07 koekeishiya

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.

koekeishiya avatar Jul 08 '19 12:07 koekeishiya

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.

dominiklohmann avatar Jul 26 '19 07:07 dominiklohmann

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.

koekeishiya avatar Jul 26 '19 08:07 koekeishiya

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.

dominiklohmann avatar Jul 26 '19 08:07 dominiklohmann

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.

koekeishiya avatar Jul 26 '19 09:07 koekeishiya

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.

dominiklohmann avatar Jul 26 '19 09:07 dominiklohmann

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.

jerryqhyu avatar Sep 01 '19 14:09 jerryqhyu

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

goranmoomin avatar Sep 07 '19 16:09 goranmoomin

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.

dominiklohmann avatar Sep 08 '19 15:09 dominiklohmann

@dominiklohmann Thanks for your kindly explanation!

Sent with GitHawk

goranmoomin avatar Sep 08 '19 22:09 goranmoomin

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

jerryqhyu avatar Sep 08 '19 22:09 jerryqhyu

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 avatar Sep 09 '19 07:09 koekeishiya

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.

koekeishiya avatar Sep 09 '19 07:09 koekeishiya

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

Xadoy avatar Sep 22 '19 04:09 Xadoy

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?

choco avatar Oct 17 '19 20:10 choco

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

I feel your pain! I have invested I think roughly 20 hours into this rabbit hole, and there just ain't no clear approach to deal with these damn tabs. Also this applies only to AppKit/native tabs. Apps that draw their own UI such as web browsers have completely custom behavior that can't be generalized. they are one the biggest use-case for tabs as well.

lwouis avatar May 06 '20 07:05 lwouis

Figured I would mention that this specific kind of Window tabbing can be disabled globally using the below command:

defaults write NSGlobalDomain NSWindowTabbingEnabled -bool false

Logout and back in after changing this value.

Edit: Only appears to affect some applications, e.g Finder

koekeishiya avatar Jul 10 '20 19:07 koekeishiya

Now that stacking is a thing I wonder if this can be worked around by having new windows that exactly match the frame of another window from the same application on the same space stack by default (instead of split). At least until the windows are moved or the tree is modified.

dominiklohmann avatar Jul 11 '20 12:07 dominiklohmann

I've noticed that Amethyst also fails to tile tabbed windows when in the binary space partition layout, but does not fail in any other layout.

iam-cult avatar Feb 04 '21 16:02 iam-cult

I saw that people created issues which connected to this issue from time to time.

It might make sense to maintain a list of known issues to prevent people from reporting this issue repetitively.

junwei-wang avatar Mar 04 '21 11:03 junwei-wang

A list of known issues would be the issues here at github.

koekeishiya avatar Mar 04 '21 13:03 koekeishiya

I mean a minimal list of general issues, which is known exist but lack of elegant solution for this moment. Here github issues could be e.g., a feature request or documentation request.

junwei-wang avatar Mar 04 '21 16:03 junwei-wang

I work in bsp mode. When I create a new terminal tab (I use macOS default terminal app btw) the tab is added to the terminal and the terminal is halved leaving a "hole" to the desktop. Any idea why this is happens and if related to this issue? I'm on Monterey.

pberto avatar Mar 30 '22 12:03 pberto

@pberto What you mention is exactly what this issue is about. It has not been solved yet

bob-ortiz avatar Apr 19 '22 17:04 bob-ortiz

I moved to a new implementation on AltTab (see https://github.com/lwouis/alt-tab-macos/issues/1540#issuecomment-1138579049).

I'm thinking that for yabai, it doesn't solve the situation since it would be best to have a notification-driven approach, since yabai is always on, unlike AltTab.

That being said, it may be acceptable to add a manual task to detect tabs like in AltTab. Users could run it manually or on a schedule, or even with complex scripting. At least they would have a command to run that detects tabs.

Maybe it's already available though, I'm not sure.

Also, another downside of this method is that it doesn't help figure out with tabs are in which tab group, which would be useful for yabai.

lwouis avatar May 26 '22 17:05 lwouis