Conversation
|
@decodism this seems like a draft. Is this WIP that you're sharing early? |
|
It wasn't but I've reworked it to include an improvement of #2021. It is now possible to minimize and fullscreen tabs if the app only has one tab group in the space. |
| if let index = (Windows.list.firstIndex { $0.isEqualRobust(element, wid) }) { | ||
| let window = Windows.list[index] | ||
| DispatchQueue.main.async { |
There was a problem hiding this comment.
I think it's dangerous to call objects like Windows.list from the AX background thread. That's why all the functions in this file have a pattern of doing a few lines of AX calls (all the trys), then doing work inside a DispatchQueue.main block.
src/ui/App.swift
Outdated
| if Windows.list.count == 0 || CGWindow.isMissionControlActive() { hideUi(); return } | ||
| // TODO: can the CGS call inside detectTabbedWindows introduce latency when WindowServer is busy? | ||
| Windows.detectTabbedWindows() | ||
| //Windows.detectTabbedWindows() |
There was a problem hiding this comment.
You replaced this unconditional call, before showing the UI, with a similar call, but only when a window gets deleted. I think it will miss many cases where tabs are reconfigured. I was originally tracking tabs in the events (window resized, created, deleted, etc). Then I faced many subtle issues where tabs wouldn't properly be noticed.
I ended up putting an unconditional call upfront before showing the UI, and it proved way more reliable. I'm worried here that you are going back to the old approach, and it will not pick up the right state. The user can combine tabs with the mouse, with keyboard shortcuts, by closing windows, by creating windows, etc.
There was a problem hiding this comment.
The issue with this new method compared to the old one is that it only works for the current space. So calling it when the UI is displayed would not be sufficient. It is called for the application when a window of the application is focused or deleted. The only case that is not covered that I can think of is when AltTab closes a window that is part of a group of tabs in another space.
There was a problem hiding this comment.
I'll give you an example:
- Create 2 Finder windows
- Click in the menubar at the top:
Window > Merge All Windows - This will not trigger any accessibility events (except
kAXFocusedUIElementChangedNotificationwhich we used to listen to, but no longer do) - It will thus never trigger
detectTabsInCurrentSpace() - AltTab will not be aware of this change in tabs/windows
src/logic/Window.swift
Outdated
| guard !isTabbed else { | ||
| if let tabHoldingWindows = application.tabHoldingWindows[spaceId], tabHoldingWindows.count == 1 { | ||
| tabHoldingWindows.first!.minDemin() | ||
| } | ||
| return | ||
| } |
There was a problem hiding this comment.
I think it would be best to hide the buttons, and not try to do things that the OS itself doesn't allow the user to do. This is in line with our previous discussions here: #2021 (comment)
There was a problem hiding this comment.
It seems to me that AltTab already does things not allowed by the OS, depending on your definition. My issue with this though is that it only covers one case.
There was a problem hiding this comment.
Yes, I meant that if we can't have great support for some approach, it's probably better to give up on it, and it shouldn't be too missed by users since they are used to not having such capabilities in the first place, from the native OS UI.
24cec46 to
9a3556a
Compare
|
The first approach being too flawed, I changed to a simpler one. Edit: Also too flawed. |
src/logic/Windows.swift
Outdated
| list.forEach { | ||
| if let cgWindowId = $0.cgWindowId { | ||
| $0.isTabbed = !$0.isMinimized && !$0.isHidden && !cgsWindowIds.contains(cgWindowId) | ||
| if let cgWindowId = $0.cgWindowId, !$0.isMinimized && !$0.isHidden { |
There was a problem hiding this comment.
I think one scenario that this would struggle with is when a window with tabs has been minimized/hidden before AltTab runs. AltTab would then shows those tabs as windows since their isTabbed would be at default value false
A great upside of this approach, is that it will probably fix #2017 since tabs arrangements can't be changed when a window is minimized/hidden, as far as I know. So detections done when the window is normal would remain true later.
It's overall probably an improvement on the current situation. I need to test it a bit though, because it's all theoretical for now, on my side at least.
There was a problem hiding this comment.
The bigger flaw is that it does not take into account the creation/deletion of a group of tabs and its minimizing/hiding between two invocations of AltTab.
There was a problem hiding this comment.
I've experienced with detecting tabs better.
I found that there is no need to check for minimized/hidden. If we use CGSCopyWindowsWithOptionsAndTags with the .minimizedAndTabbed option (and actually use the option in the method).
It seems to handle every case I tested.
Except one of course. When you make 2 windows of 2 tabs each, then use the menubar action Window > Merge All Windows. Then, even though there is now only 1 window with 4 tabs, CGSCopyWindowsWithOptionsAndTags still returns an array containing the 2 tabs that were the visible tabs of the previous 2 windows. Even though now there is only 1 active tab in the 4-tabs window, the API still returns 2 tabs.
I experimented with using axUiElement.children(), and checking if there is a child with role() == kAXTabGroupRole. First of all, using AX calls is really bad when the user invokes AltTab. We want to avoid it, as these calls can resolve seconds later sometimes. That's why we normally process them on a background thread. But even if I ignore this technical issue, it's still not enough because it doesn't let us distinguish a window with no tabs (has no tabs-bar), from a non-visible tab (has no tabs-bar).
I also re-read, and advise you do the same, this ticket were I explained many approaches I tried in the past: #1540
My conclusion for now is that there is no perfect way still. All approaches have issues.
I think I'll release a variation of this commit, with the minimized/hidden checks removed, and instead adding .minimizedAndTabbed to CGSCopyWindowsWithOptionsAndTags. That seems to me like the most robust/comprehensive check we can do for the time being.
There was a problem hiding this comment.
I had not caught this exception. It can be partially mitigated by using the option only for minimized or hidden windows.
src/logic/Windows.swift
Outdated
| list.forEach { | ||
| if let cgWindowId = $0.cgWindowId { | ||
| $0.isTabbed = !$0.isMinimized && !$0.isHidden && !cgsWindowIds.contains(cgWindowId) | ||
| if #available(macOS 13, *), $0.isMinimized || $0.isHidden { |
796a3c3 to
a67d123
Compare
|
@lwouis Could you look at the latest iteration of this PR? If I remember correctly, the detection of minimized / hidden tabs had broken the general detection of tabs on macOS 12. This PR reverts to the old behavior before macOS 13 and improves the case of merging visible tabs on macOS 13. |
| $0.isTabbed = false | ||
| } | ||
| } else { | ||
| $0.isTabbed = !visibleCgsWindowIds.contains(cgWindowId) |
There was a problem hiding this comment.
Maybe here we could only declare visibleCgsWindowIds at the top, then do this to avoid making the call if not needed?
if let visibleCgsWindowIds = visibleCgsWindowIds {
$0.isTabbed = !visibleCgsWindowIds.contains(cgWindowId)
else {
visibleCgsWindowIds = Spaces.windowsInSpaces(Spaces.idsAndIndexes.map { $0.0 }, false)
}There was a problem hiding this comment.
Done with lazy var
|
Hi @decodism, I've taken a look at the latest code. The code is all heuristics and workarounds. There is no way for me to tell if it works just by reading it. I have to actually QA it. An issue I have is that since moving to an M2 macbook and upgrading to Ventura, I can no longer run previous macOS releases in a VM. It's not supported. So I can QA on Ventura, but I can't make sure that the change doesn't bring things on macOS 12. Do you have access to a macOS 12 machine? Could you test the changes there? I'm a bit worried to release a fix like this without test, because changes in that area can brick the app by mis-detecting tabs. Thank you |
|
You should be able to virtualize macOS 12 with UTM and IPSW Downloads. If not, I can re-do it. |
|
I remember exploring this. I had the setup working but something was not working well. Maybe the performance was horrible? I forgot. I remember concluding that it's not viable for me to use to retro-test AltTab, then I removed all the UTM VMs from my machine. If it's not too much of a bother, could you please test it on your side? Thank you 🙇 |
|
I briefly re-tested it on macOS 12 and it seems fine. |
|
I did some tests on macOS 13.4, and it seems to not have broken things. I'll ship this in the next release. Closing this MR. Thank you very much for you work, as usual, @decodism! |
|
If I wasn't clear, this doesn't fix the main issue in #2017 but a regression of AltTab. |
Tries to fix #2017.