Skip to content

fix: tab detection#2020

Closed
decodism wants to merge 1 commit intolwouis:masterfrom
decodism:dev-6
Closed

fix: tab detection#2020
decodism wants to merge 1 commit intolwouis:masterfrom
decodism:dev-6

Conversation

@decodism
Copy link
Copy Markdown
Contributor

Tries to fix #2017.

@lwouis
Copy link
Copy Markdown
Owner

lwouis commented Oct 19, 2022

@decodism this seems like a draft. Is this WIP that you're sharing early?

@decodism
Copy link
Copy Markdown
Contributor Author

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.

Comment on lines +139 to +141
if let index = (Windows.list.firstIndex { $0.isEqualRobust(element, wid) }) {
let window = Windows.list[index]
DispatchQueue.main.async {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

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()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 kAXFocusedUIElementChangedNotification which 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

Comment on lines +119 to +150
guard !isTabbed else {
if let tabHoldingWindows = application.tabHoldingWindows[spaceId], tabHoldingWindows.count == 1 {
tabHoldingWindows.first!.minDemin()
}
return
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@decodism decodism force-pushed the dev-6 branch 4 times, most recently from 24cec46 to 9a3556a Compare November 1, 2022 17:29
@decodism
Copy link
Copy Markdown
Contributor Author

decodism commented Nov 1, 2022

The first approach being too flawed, I changed to a simpler one.

Edit: Also too flawed.

@decodism decodism marked this pull request as draft November 1, 2022 18:33
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 {
Copy link
Copy Markdown
Owner

@lwouis lwouis Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had not caught this exception. It can be partially mitigated by using the option only for minimized or hidden windows.

@decodism decodism changed the base branch from dev to master November 3, 2022 20:27
@decodism decodism marked this pull request as ready for review November 3, 2022 20:43
list.forEach {
if let cgWindowId = $0.cgWindowId {
$0.isTabbed = !$0.isMinimized && !$0.isHidden && !cgsWindowIds.contains(cgWindowId)
if #available(macOS 13, *), $0.isMinimized || $0.isHidden {
Copy link
Copy Markdown
Contributor Author

@decodism decodism Nov 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lwouis Maybe implement this check for #2136.

Edit: rebased

@decodism
Copy link
Copy Markdown
Contributor Author

@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)
Copy link
Copy Markdown
Owner

@lwouis lwouis Jun 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with lazy var

@lwouis
Copy link
Copy Markdown
Owner

lwouis commented Jun 26, 2023

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

@decodism
Copy link
Copy Markdown
Contributor Author

You should be able to virtualize macOS 12 with UTM and IPSW Downloads. If not, I can re-do it.

@lwouis
Copy link
Copy Markdown
Owner

lwouis commented Jun 26, 2023

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 🙇

@decodism
Copy link
Copy Markdown
Contributor Author

decodism commented Jul 5, 2023

I briefly re-tested it on macOS 12 and it seems fine.

@lwouis
Copy link
Copy Markdown
Owner

lwouis commented Jul 12, 2023

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!

@lwouis lwouis closed this Jul 12, 2023
@decodism
Copy link
Copy Markdown
Contributor Author

If I wasn't clear, this doesn't fix the main issue in #2017 but a regression of AltTab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AltTab always shows tabs as separate window, when the window is hidden/minimized, on Monterey

2 participants