From ee6785f6ab382239efed4940241026ca482b4e10 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Wed, 22 Mar 2023 21:39:53 +0100 Subject: [PATCH] fix: drag-and-drop from a folder on the dock (closes #706) --- alt-tab-macos.xcodeproj/project.pbxproj | 4 ++++ src/api-wrappers/CGWindow.swift | 9 --------- src/api-wrappers/MissionControl.swift | 24 ++++++++++++++++++++++++ src/ui/App.swift | 11 ++++++----- 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 src/api-wrappers/MissionControl.swift diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index b4d57b8a..1ba2befb 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ BF0C823630E3DEAC576FAFE6 /* menubar-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C88BF67CF4D71002A60EB /* menubar-1@2x.png */; }; BF0C828D53D429C2B8B22391 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C883EBA8FDA9CB61C91FD /* Localizable.strings */; }; BF0C829583ED47E458528F3A /* accessibility@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C889529FF19620EDAC295 /* accessibility@2x.png */; }; + BF0C832C5BF4C8E9C95F7767 /* MissionControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C82E4DE5A28800F1DA6E6 /* MissionControl.swift */; }; BF0C8382435C0087E131BD83 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8A900DA32A7F62457CC2 /* InfoPlist.strings */; }; BF0C83AB0186CC48FADF66A7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C847B2D68EACFAC1B06D1 /* Localizable.strings */; }; BF0C83E902F75A97E8FD374A /* menubar-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D3333653ACAEF730DD8 /* menubar-2@2x.png */; }; @@ -184,6 +185,7 @@ BF0C828AF7BCC4F47E70B28F /* menubar-2.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = "menubar-2.svg"; sourceTree = ""; }; BF0C82C381FCBCF8109735AC /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = InfoPlist.strings; sourceTree = ""; }; BF0C82D44C2DFAA5D8F60394 /* sq */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sq; path = Localizable.strings; sourceTree = ""; }; + BF0C82E4DE5A28800F1DA6E6 /* MissionControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MissionControl.swift; sourceTree = ""; }; BF0C831AA76CF82EAC7B60B3 /* gl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = gl; path = Localizable.strings; sourceTree = ""; }; BF0C83258B2873D9CDC74EA5 /* preferences-controls.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "preferences-controls.jpg"; sourceTree = ""; }; BF0C833D471343CEA3DEFE56 /* icon_512x512@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_512x512@2x.png"; sourceTree = ""; }; @@ -946,6 +948,7 @@ D04BA03200F5A8FC0CD03607 /* CGWindowID.swift */, D04BA8DB8AA7E5570DAC568A /* Sysctl.swift */, D04BA514F1C4B475B3CA6EB6 /* Bash.swift */, + BF0C82E4DE5A28800F1DA6E6 /* MissionControl.swift */, ); path = "api-wrappers"; sourceTree = ""; @@ -1665,6 +1668,7 @@ BF0C8C7E96DB48120462DA00 /* TrafficLightButton.swift in Sources */, BF0C898511686611E4D7D81E /* Button.swift in Sources */, BF0C8E16F38203AEC71E062B /* TableView.swift in Sources */, + BF0C832C5BF4C8E9C95F7767 /* MissionControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/api-wrappers/CGWindow.swift b/src/api-wrappers/CGWindow.swift index ff71eae5..e0639d9f 100644 --- a/src/api-wrappers/CGWindow.swift +++ b/src/api-wrappers/CGWindow.swift @@ -9,15 +9,6 @@ extension CGWindow { return CGWindowListCopyWindowInfo([.excludeDesktopElements, option], kCGNullWindowID) as! [CGWindow] } - static func isMissionControlActive() -> Bool { - // when Mission Control is active, the Dock process spawns some windows. We observe this side-effect and infer - for window in windows(.optionOnScreenOnly) { - guard window.ownerName() == "Dock", window.title() == nil else { continue } - return true - } - return false - } - // workaround: filtering this criteria seems to remove non-windows UI elements func isNotMenubarOrOthers() -> Bool { return layer() == 0 diff --git a/src/api-wrappers/MissionControl.swift b/src/api-wrappers/MissionControl.swift new file mode 100644 index 00000000..cbdd47af --- /dev/null +++ b/src/api-wrappers/MissionControl.swift @@ -0,0 +1,24 @@ +import Foundation + +class MissionControl { + static func isActive() -> Bool { + // when Mission Control is active, the Dock process spawns some windows. We observe this side-effect and infer + var missionControlHint = false + for window in CGWindow.windows(.optionOnScreenOnly) { + // ownerName == "Dock" && title == nil is a sign that Mission Control may be active + if window.ownerName() == "Dock" && window.title() == nil + // layer == 500 can be a false positive when a user drags a file from a Dock folder + // see https://github.com/lwouis/alt-tab-macos/issues/706 + && window.layer() != 500 { + return true + } + } + return false + } +} + +enum MissionControlState { + case inactive + case expose + case showDesktop +} diff --git a/src/ui/App.swift b/src/ui/App.swift index 8c950c1c..bf4cf19d 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -202,10 +202,11 @@ class App: AppCenterApplication, NSApplicationDelegate { func focusSelectedWindow(_ selectedWindow: Window?) { hideUi() - guard let window = selectedWindow, !CGWindow.isMissionControlActive() else { return } - window.focus() - if Preferences.cursorFollowFocusEnabled { - moveCursorToSelectedWindow(window) + if let window = selectedWindow, !MissionControl.isActive() { + window.focus() + if Preferences.cursorFollowFocusEnabled { + moveCursorToSelectedWindow(window) + } } } @@ -258,7 +259,7 @@ class App: AppCenterApplication, NSApplicationDelegate { if isFirstSummon || shortcutIndex != self.shortcutIndex { debugPrint("showUiOrCycleSelection: isFirstSummon") isFirstSummon = false - if Windows.list.count == 0 || CGWindow.isMissionControlActive() { hideUi(); return } + if Windows.list.count == 0 || MissionControl.isActive() { hideUi(); return } // TODO: can the CGS call inside detectTabbedWindows introduce latency when WindowServer is busy? Windows.detectTabbedWindows() // TODO: find a way to update space info when spaces are changed, instead of on every trigger