diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 85545cb4..8f1e6089 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -22,7 +22,7 @@ 1C96132D9BFCC988B0013509 /* app_icons_show_running_applications_windows_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C961334F059C39979BEDF19 /* app_icons_show_running_applications_windows_light@2x.jpg */; }; 1C961383737C00845E881EA6 /* thumbnails_show_app_badges_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C9617A90991E963760F7475 /* thumbnails_show_app_badges_light@2x.jpg */; }; 1C9614261DF293C650675A6C /* titles_hide_tabs_as_windows_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C9619DEB8B7905DBF9A6030 /* titles_hide_tabs_as_windows_light@2x.jpg */; }; - 1C96143D5AD69C894A4D8375 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C96110EB5B28C814DA35465 /* LogManager.swift */; }; + 1C96143D5AD69C894A4D8375 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C96110EB5B28C814DA35465 /* Logger.swift */; }; 1C96149CFE6DB491DCAEA943 /* titles_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C961B26B70C826BBD762AC5 /* titles_light@2x.jpg */; }; 1C9614C924F2EEB592314C67 /* app_icons_show_running_windows_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C96127120349FE18C5C39DA /* app_icons_show_running_windows_light@2x.jpg */; }; 1C9614D96191FACFACD38055 /* thumbnails_align_thumbnails_center_light@2x.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1C9610071E9F3434C893DC95 /* thumbnails_align_thumbnails_center_light@2x.jpg */; }; @@ -74,6 +74,7 @@ 5F706EC32BA448D8004D542D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5F706EC12BA448D8004D542D /* InfoPlist.strings */; }; 76D02BB22BFE7C9E0056008D /* Pods_alt_tab_macos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */; }; AA974BA929B7D84C0099A29E /* PreviewPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA974BA829B7D84C0099A29E /* PreviewPanel.swift */; }; + BF0C80484AA63306D0DB4DB2 /* SpacesEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8DF008F1EB5F23291A13 /* SpacesEvents.swift */; }; BF0C8052AE41B1B10E42BFCE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C875C983226CB16DBD90F /* InfoPlist.strings */; }; BF0C8053A58DFB383A3E3351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E237569FA2A8FE20707 /* InfoPlist.strings */; }; BF0C807D26E465F467B066F7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C849EBA5A547912BD8BB9 /* Localizable.strings */; }; @@ -84,6 +85,7 @@ BF0C81A79128CF877FE895CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8A48BBB3BF649864DB08 /* InfoPlist.strings */; }; BF0C81B64A1205F066AC796A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C80404D3455D496C02F29 /* Localizable.strings */; }; BF0C81D836A8A4CA393B135F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C82E55127D38ABF969CA0 /* InfoPlist.strings */; }; + BF0C8203D0EFEDEED0F908E8 /* ScreensEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */; }; BF0C820C6E89C2D90FA30A4B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8F67ABD271F26FA0EDEF /* Localizable.strings */; }; BF0C8224CFD1ED511F3F0063 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D94228D90FE99D4A3C8 /* Localizable.strings */; }; BF0C823630E3DEAC576FAFE6 /* menubar-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C88BF67CF4D71002A60EB /* menubar-1@2x.png */; }; @@ -120,6 +122,7 @@ BF0C898511686611E4D7D81E /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8B2C057099EDABE46779 /* Button.swift */; }; BF0C89C49CFBAAB4646BB8CB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C82A8848224E76B880A83 /* InfoPlist.strings */; }; BF0C89F0632E0A2EC52D9168 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C87749F1A64E2D3305181 /* InfoPlist.strings */; }; + BF0C8A3F32B2177AF407DC7E /* DockEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C870C14C20C936BA4AF40 /* DockEvents.swift */; }; BF0C8A739A74695E60F16369 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8062E78677398A4A217B /* InfoPlist.strings */; }; BF0C8A95AAD9DADC95887A2D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C895A9AF79A869EE6B108 /* Localizable.strings */; }; BF0C8AC57CE5E9BBEEE2442A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C885D34CDCE2AFC5A94F7 /* InfoPlist.strings */; }; @@ -160,7 +163,7 @@ D04BA15A1B0C4871EA7CB899 /* GeneralTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACE22DC907F03D193075 /* GeneralTab.swift */; }; D04BA1621718FF26F8D5E75D /* CustomRecorderControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE333F7170E87C5AC0EF /* CustomRecorderControl.swift */; }; D04BA1637E125D38546C26E2 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8AC77465E1B2BC947CF /* StackView.swift */; }; - D04BA1766FBCB2E941D081A5 /* WorkspaceEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA76AEE37D6656EB80126 /* WorkspaceEvents.swift */; }; + D04BA1766FBCB2E941D081A5 /* RunningApplicationsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA76AEE37D6656EB80126 /* RunningApplicationsEvents.swift */; }; D04BA1B133D53572D7B312C2 /* ThumbnailFontIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA1DF8CAB2FAB7FE9244B /* ThumbnailFontIconView.swift */; }; D04BA1C85041FB043FD43E65 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA05006D56C7AA011C0CE /* Localizable.strings */; }; D04BA1CEC6B9C8945FEC8740 /* ThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA258B56193958D60978A /* ThumbnailView.swift */; }; @@ -254,7 +257,7 @@ 1C961093FB75180759EC6972 /* app_icons_hide_app_badges_light@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "app_icons_hide_app_badges_light@2x.jpg"; sourceTree = ""; }; 1C9610B2E76F1E429C8D3660 /* titles_show_running_windows_light@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "titles_show_running_windows_light@2x.jpg"; sourceTree = ""; }; 1C9611039BDBE0C84F349BA1 /* titles_show_status_icons_light@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "titles_show_status_icons_light@2x.jpg"; sourceTree = ""; }; - 1C96110EB5B28C814DA35465 /* LogManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = ""; }; + 1C96110EB5B28C814DA35465 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 1C96112432E87D36F90AFBED /* translate_i18n_tools.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = translate_i18n_tools.py; sourceTree = ""; }; 1C9611334B667767A4514B57 /* thumbnails_hide_space_number_labels_light@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "thumbnails_hide_space_number_labels_light@2x.jpg"; sourceTree = ""; }; 1C961139B16ADE6E0B8A33A6 /* thumbnails_show_running_applications_windows_light@2x.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "thumbnails_show_running_applications_windows_light@2x.jpg"; sourceTree = ""; }; @@ -333,6 +336,7 @@ BF0C816F38751995E4F49BB0 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = InfoPlist.strings; sourceTree = ""; }; BF0C817E063D1405392D6C5B /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = Localizable.strings; sourceTree = ""; }; BF0C81D602ED642A48D50B78 /* notarytool */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notarytool; sourceTree = ""; }; + BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreensEvents.swift; sourceTree = ""; }; 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 = ""; }; @@ -360,6 +364,7 @@ BF0C864F182B6155A66D241D /* KeyRepeatTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyRepeatTimer.swift; sourceTree = ""; }; BF0C86C2BB70A8E05D2103CA /* cs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = cs; path = Localizable.strings; sourceTree = ""; }; BF0C86E06A44AC151FEA4903 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = InfoPlist.strings; sourceTree = ""; }; + BF0C870C14C20C936BA4AF40 /* DockEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DockEvents.swift; sourceTree = ""; }; BF0C87320241EAE01E38EF53 /* .periphery.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = .periphery.yml; sourceTree = ""; }; BF0C87897C24638DDE4A9889 /* app-icons-style.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "app-icons-style.jpg"; sourceTree = ""; }; BF0C87B913292EE53EDE4220 /* menubar-1.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = "menubar-1.svg"; sourceTree = ""; }; @@ -411,6 +416,7 @@ BF0C8D7DE89E594FEE6DA692 /* ta */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ta; path = Localizable.strings; sourceTree = ""; }; BF0C8D87C20B7308B16498B6 /* et */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = et; path = InfoPlist.strings; sourceTree = ""; }; BF0C8D9E024876AD9A92BE8D /* sr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sr; path = Localizable.strings; sourceTree = ""; }; + BF0C8DF008F1EB5F23291A13 /* SpacesEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpacesEvents.swift; sourceTree = ""; }; BF0C8DF400A1AF64EB0CD0B3 /* TrafficLightButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrafficLightButton.swift; sourceTree = ""; }; BF0C8E1FBCBC7B64C69D933C /* titles-style-full.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "titles-style-full.jpg"; sourceTree = ""; }; BF0C8E25373671B528AD448D /* screen-recording@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "screen-recording@2x.png"; sourceTree = ""; }; @@ -509,7 +515,7 @@ D04BA72AC9B57A1673C28B2E /* android-chrome-256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "android-chrome-256x256.png"; sourceTree = ""; }; D04BA7457E30F608E139B695 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = Localizable.strings; sourceTree = ""; }; D04BA765138B5E1C512245BE /* Thanks.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Thanks.md; sourceTree = ""; }; - D04BA76AEE37D6656EB80126 /* WorkspaceEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkspaceEvents.swift; sourceTree = ""; }; + D04BA76AEE37D6656EB80126 /* RunningApplicationsEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunningApplicationsEvents.swift; sourceTree = ""; }; D04BA788228BA86D9EFBD1ED /* AcknowledgmentsTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcknowledgmentsTab.swift; sourceTree = ""; }; D04BA7957E5E02FB09BCE10C /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = de; path = InfoPlist.strings; sourceTree = ""; }; D04BA7A758026814667273DC /* convert_iconset_to_icns.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = convert_iconset_to_icns.sh; sourceTree = ""; }; @@ -1171,7 +1177,7 @@ D04BA1C271F0DA573345B860 /* permission-window */, D04BAAF79737A0673502FEB5 /* AppCenterApplication.h */, D04BA48BC82D3060C3A1AB11 /* AppCenterApplication.m */, - 1C96110EB5B28C814DA35465 /* LogManager.swift */, + 1C96110EB5B28C814DA35465 /* Logger.swift */, ); path = ui; sourceTree = ""; @@ -1643,9 +1649,12 @@ children = ( D04BA8FDCF137D892114F5F3 /* AccessibilityEvents.swift */, D04BA4A621A8A15660F973C1 /* KeyboardEvents.swift */, - D04BA76AEE37D6656EB80126 /* WorkspaceEvents.swift */, + D04BA76AEE37D6656EB80126 /* RunningApplicationsEvents.swift */, D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */, D04BAF320F4D43F0DDFE063E /* MouseEvents.swift */, + BF0C870C14C20C936BA4AF40 /* DockEvents.swift */, + BF0C8DF008F1EB5F23291A13 /* SpacesEvents.swift */, + BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */, ); path = events; sourceTree = ""; @@ -2102,7 +2111,7 @@ 5F21C9E02C6E94240091F72F /* ShortcutsWhenActiveSheet.swift in Sources */, D04BAE8B16A06A10E2FA94DE /* AccessibilityEvents.swift in Sources */, D04BAAAF5CFA991D3B078DB8 /* KeyboardEvents.swift in Sources */, - D04BA1766FBCB2E941D081A5 /* WorkspaceEvents.swift in Sources */, + D04BA1766FBCB2E941D081A5 /* RunningApplicationsEvents.swift in Sources */, D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */, D04BA4A11F821548EE3C5E95 /* Bash.swift in Sources */, 5F21C9E62C6E94920091F72F /* AnimationsSheet.swift in Sources */, @@ -2126,8 +2135,11 @@ BF0C83578F7292F307E30751 /* PopupButtonLikeSystemSettings.swift in Sources */, 1C961F9CF4F1BEDF04ACAEE0 /* Switch.swift in Sources */, 1C96182D2ECA735740ED474E /* ImageTextButtonView.swift in Sources */, - 1C96143D5AD69C894A4D8375 /* LogManager.swift in Sources */, + 1C96143D5AD69C894A4D8375 /* Logger.swift in Sources */, BF0C860F1779DDE8C0171EA0 /* WindowlessAppIndicator.swift in Sources */, + BF0C8A3F32B2177AF407DC7E /* DockEvents.swift in Sources */, + BF0C80484AA63306D0DB4DB2 /* SpacesEvents.swift in Sources */, + BF0C8203D0EFEDEED0F908E8 /* ScreensEvents.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/alt-tab-macos.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme b/alt-tab-macos.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme index 58be5454..18e607a5 100644 --- a/alt-tab-macos.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme +++ b/alt-tab-macos.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme @@ -55,6 +55,12 @@ identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier" referenceType = "1"> + + + + MissionControlState { if #available(macOS 12.0, *) { @@ -32,29 +30,4 @@ class MissionControl { } return false } - - static func observe(_ dockPid: pid_t) { - if #available(macOS 12.0, *) { - axUiElement = AXUIElementCreateApplication(dockPid) - AXObserverCreate(dockPid, axObserverCallbackDock, &axObserver) - // are we sure we always get a non-nil axObserver? - for notification in MissionControlState.allCases { - retryAxCallUntilTimeout { - try axUiElement!.subscribeToNotification(axObserver!, notification.rawValue, nil) - } - } - CFRunLoopAddSource(BackgroundWork.missionControlThread.runLoop, AXObserverGetRunLoopSource(axObserver!), .defaultMode) - } else { - // we could handle macOS < 12 here like yabai does. However, they poll with ax calls until they notice Mission Control stops - // this takes up ressources when Mission Control is open. If the user keeps it open for a few hours, this would accelerate battery usage - // SLSRegisterConnectionNotifyProc(g_connection, connection_handler, 1204, NULL); - // then listen every 0.1f * NSEC_PER_SEC for layer == 18 and owner = "Dock" - // when found, mission control is not active anymore - } - } -} - -fileprivate let axObserverCallbackDock: AXObserverCallback = { _, element, notificationName, _ in - logger.d(notificationName, element) - MissionControl.setState(MissionControlState(rawValue: notificationName as String)!) } diff --git a/src/logic/Application.swift b/src/logic/Application.swift index 571915e5..965afd36 100644 --- a/src/logic/Application.swift +++ b/src/logic/Application.swift @@ -40,7 +40,7 @@ class Application: NSObject { } deinit { - logger.i("Deinit app", runningApplication.bundleIdentifier ?? runningApplication.bundleURL ?? "nil") + logger.d("Deinit app", runningApplication.bundleIdentifier ?? runningApplication.bundleURL ?? "nil") } override var description: String { @@ -73,7 +73,7 @@ class Application: NSObject { if runningApplication.activationPolicy != .prohibited && axUiElement == nil { axUiElement = AXUIElementCreateApplication(pid) AXObserverCreate(pid, axObserverCallback, &axObserver) - logger.i("Adding app", pid ?? "nil", runningApplication.bundleIdentifier ?? "nil") + logger.d("Adding app", pid ?? "nil", runningApplication.bundleIdentifier ?? "nil") observeEvents() } } diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index cf29cfc6..06e1a133 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -21,7 +21,7 @@ class Applications { static func initialDiscovery() { addInitialRunningApplications() addInitialRunningApplicationsWindows() - WorkspaceEvents.observeRunningApplications() + RunningApplicationsEvents.observe() } static func addInitialRunningApplications() { @@ -48,7 +48,7 @@ class Applications { static func addRunningApplications(_ runningApps: [NSRunningApplication]) { runningApps.forEach { if $0.bundleIdentifier == "com.apple.dock" { - MissionControl.observe($0.processIdentifier) + DockEvents.observe($0.processIdentifier) } if isActualApplication($0) { Applications.list.append(Application($0)) @@ -68,7 +68,7 @@ class Applications { } Windows.list.removeAll { $0.application.runningApplication.isEqual(runningApp) } } - guard Windows.list.count > 0 else { App.app.hideUi(); return } + if Windows.list.count == 0 { App.app.hideUi(); return } if windowsOnTheLeftOfFocusedWindow > 0 { Windows.cycleFocusedWindowIndex(-windowsOnTheLeftOfFocusedWindow) } diff --git a/src/logic/Spaces.swift b/src/logic/Spaces.swift index 6a42ef6a..83ab85a4 100644 --- a/src/logic/Spaces.swift +++ b/src/logic/Spaces.swift @@ -7,25 +7,6 @@ class Spaces { static var screenSpacesMap = [ScreenUuid: [CGSSpaceID]]() static var idsAndIndexes = [(CGSSpaceID, SpaceIndex)]() - static func observeSpaceChanges() { - NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.activeSpaceDidChangeNotification, object: nil, queue: nil, using: { _ in - logger.i("OS event", "activeSpaceDidChangeNotification") - refreshAllIdsAndIndexes() - updateCurrentSpace() - // if UI was kept open during Space transition, the Spaces may be obsolete; we refresh them - Windows.list.forEachAsync { $0.updatesWindowSpace() } - // from macos 12.2 beta onwards, we can't get other-space windows; grabbing windows when switching spaces mitigates the issue - // also, updating windows on Space transition works around an issue with Safari where its fullscreen windows spawn not in fullscreen. - // resize/move events happen and the window is still not fullscreen. AltTab doesn't get informed that the window is later fullscreen. - // updating on Space change helps correct the window to being fullscreen - Applications.manuallyUpdateWindows() - }) - NSWorkspace.shared.notificationCenter.addObserver(forName: NSApplication.didChangeScreenParametersNotification, object: nil, queue: nil, using: { _ in - logger.i("OS event", "didChangeScreenParametersNotification") - refreshAllIdsAndIndexes() - }) - } - static func refreshCurrentSpaceId() { // it seems that in some rare scenarios, some of these values are nil; we wrap to avoid crashing if let mainScreen = NSScreen.main, @@ -34,10 +15,9 @@ class Spaces { } } - static func initialDiscovery() { + static func initialize() { refreshAllIdsAndIndexes() updateCurrentSpace() - observeSpaceChanges() } static func updateCurrentSpace() { @@ -45,7 +25,7 @@ class Spaces { currentSpaceIndex = idsAndIndexes.first { (spaceId: CGSSpaceID, _) -> Bool in spaceId == currentSpaceId }?.1 ?? SpaceIndex(1) - logger.i("Current Space", currentSpaceId) + logger.i(currentSpaceIndex, currentSpaceId) } static func refreshAllIdsAndIndexes() -> Void { diff --git a/src/logic/Window.swift b/src/logic/Window.swift index cedb7cbb..7d4024f7 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -54,7 +54,7 @@ class Window: CustomStringConvertible { } application.removeWindowslessAppWindow() checkIfFocused(application, wid) - logger.i("Adding window", cgWindowId ?? "nil", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") + logger.d("Adding window", cgWindowId ?? "nil", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") observeEvents() } @@ -64,11 +64,11 @@ class Window: CustomStringConvertible { self.title = application.runningApplication.localizedName Window.globalCreationCounter += 1 self.creationOrder = Window.globalCreationCounter - logger.i("Adding app-window", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") + logger.d(title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") } deinit { - logger.i("Deinit window", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") + logger.d(title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") } var description: String { diff --git a/src/logic/events/AccessibilityEvents.swift b/src/logic/events/AccessibilityEvents.swift index 3689c297..4e17d609 100644 --- a/src/logic/events/AccessibilityEvents.swift +++ b/src/logic/events/AccessibilityEvents.swift @@ -8,10 +8,10 @@ let axObserverCallback: AXObserverCallback = { _, element, notificationName, _ i } fileprivate func handleEvent(_ type: String, _ element: AXUIElement) throws { - logger.i("Accessibility event", type, try element.title() ?? "nil") // events are handled concurrently, thus we check that the app is still running if let pid = try element.pid(), try pid != ProcessInfo.processInfo.processIdentifier || (element.subrole() != kAXUnknownSubrole) { + logger.d(type, pid, try element.title() ?? "nil") switch type { case kAXApplicationActivatedNotification: try applicationActivated(element, pid) case kAXApplicationHiddenNotification, diff --git a/src/logic/events/DockEvents.swift b/src/logic/events/DockEvents.swift new file mode 100644 index 00000000..460ebc23 --- /dev/null +++ b/src/logic/events/DockEvents.swift @@ -0,0 +1,31 @@ +import Foundation + +class DockEvents { + private static var axObserver: AXObserver? + private static var axUiElement: AXUIElement? + + static func observe(_ dockPid: pid_t) { + if #available(macOS 12.0, *) { + axUiElement = AXUIElementCreateApplication(dockPid) + AXObserverCreate(dockPid, handleEvent, &axObserver) + // are we sure we always get a non-nil axObserver? + for notification in MissionControlState.allCases { + retryAxCallUntilTimeout { + try axUiElement!.subscribeToNotification(axObserver!, notification.rawValue, nil) + } + } + CFRunLoopAddSource(BackgroundWork.missionControlThread.runLoop, AXObserverGetRunLoopSource(axObserver!), .defaultMode) + } else { + // we could handle macOS < 12 here like yabai does. However, they poll with ax calls until they notice Mission Control stops + // this takes up ressources when Mission Control is open. If the user keeps it open for a few hours, this would accelerate battery usage + // SLSRegisterConnectionNotifyProc(g_connection, connection_handler, 1204, NULL); + // then listen every 0.1f * NSEC_PER_SEC for layer == 18 and owner = "Dock" + // when found, mission control is not active anymore + } + } +} + +fileprivate let handleEvent: AXObserverCallback = { _, element, notificationName, _ in + logger.d(notificationName, element) + MissionControl.setState(MissionControlState(rawValue: notificationName as String)!) +} diff --git a/src/logic/events/MouseEvents.swift b/src/logic/events/MouseEvents.swift index 11df02e7..5f8e50ff 100644 --- a/src/logic/events/MouseEvents.swift +++ b/src/logic/events/MouseEvents.swift @@ -26,7 +26,7 @@ private func observe_() { place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: eventMask, - callback: mouseHandler, + callback: handleEvent, userInfo: nil) if let eventTap = eventTap { MouseEvents.toggle(false) @@ -37,7 +37,7 @@ private func observe_() { } } -private let mouseHandler: CGEventTapCallBack = {_, type, cgEvent, _ in +private let handleEvent: CGEventTapCallBack = { _, type, cgEvent, _ in if type == .leftMouseDown { isPointerInsideUi_() if !isPointerInsideUi { diff --git a/src/logic/events/WorkspaceEvents.swift b/src/logic/events/RunningApplicationsEvents.swift similarity index 64% rename from src/logic/events/WorkspaceEvents.swift rename to src/logic/events/RunningApplicationsEvents.swift index 3a80bd68..bc183d50 100644 --- a/src/logic/events/WorkspaceEvents.swift +++ b/src/logic/events/RunningApplicationsEvents.swift @@ -1,23 +1,22 @@ import Cocoa -class WorkspaceEvents { +class RunningApplicationsEvents { private static var appsObserver: NSKeyValueObservation! private static var previousValueOfRunningApps: Set! - static func observeRunningApplications() { + static func observe() { previousValueOfRunningApps = Set(NSWorkspace.shared.runningApplications) - appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: observerCallback) + appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: handleEvent) } - static func observerCallback(_: NSWorkspace, _ change: NSKeyValueObservedChange) { + static func handleEvent(_: NSWorkspace, _ change: NSKeyValueObservedChange) { let workspaceApps = Set(NSWorkspace.shared.runningApplications) // TODO: symmetricDifference has bad performance let diff = Array(workspaceApps.symmetricDifference(previousValueOfRunningApps)) + logger.d(change.kind, diff.map { ($0.processIdentifier, $0.bundleIdentifier) }) if change.kind == .insertion { - logger.i("OS event", "apps launched", diff.map { ($0.processIdentifier, $0.bundleIdentifier) }) Applications.addRunningApplications(diff) } else if change.kind == .removal { - logger.i("OS event", "apps quit", diff.map { ($0.processIdentifier, $0.bundleIdentifier) }) Applications.removeRunningApplications(diff) } previousValueOfRunningApps = workspaceApps diff --git a/src/logic/events/ScreensEvents.swift b/src/logic/events/ScreensEvents.swift new file mode 100644 index 00000000..97ffecd7 --- /dev/null +++ b/src/logic/events/ScreensEvents.swift @@ -0,0 +1,10 @@ +import Cocoa + +class ScreensEvents { + static func observe() { + NSWorkspace.shared.notificationCenter.addObserver(forName: NSApplication.didChangeScreenParametersNotification, object: nil, queue: nil, using: { notification in + logger.i(notification.name, NSScreen.screens.map { ($0.uuid(), $0.frame) }) + Spaces.refreshAllIdsAndIndexes() + }) + } +} diff --git a/src/logic/events/SpacesEvents.swift b/src/logic/events/SpacesEvents.swift new file mode 100644 index 00000000..c65c9ba3 --- /dev/null +++ b/src/logic/events/SpacesEvents.swift @@ -0,0 +1,18 @@ +import Cocoa + +class SpacesEvents { + static func observe() { + NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.activeSpaceDidChangeNotification, object: nil, queue: nil, using: { notification in + logger.d(notification.name) + Spaces.refreshAllIdsAndIndexes() + Spaces.updateCurrentSpace() + // if UI was kept open during Space transition, the Spaces may be obsolete; we refresh them + Windows.list.forEachAsync { $0.updatesWindowSpace() } + // from macos 12.2 beta onwards, we can't get other-space windows; grabbing windows when switching spaces mitigates the issue + // also, updating windows on Space transition works around an issue with Safari where its fullscreen windows spawn not in fullscreen. + // resize/move events happen and the window is still not fullscreen. AltTab doesn't get informed that the window is later fullscreen. + // updating on Space change helps correct the window to being fullscreen + Applications.manuallyUpdateWindows() + }) + } +} diff --git a/src/logic/events/UserDefaultsEvents.swift b/src/logic/events/UserDefaultsEvents.swift index ca23fa64..d556935d 100644 --- a/src/logic/events/UserDefaultsEvents.swift +++ b/src/logic/events/UserDefaultsEvents.swift @@ -10,6 +10,11 @@ class UserDefaultsEvents: NSObject { } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + handleEvent(keyPath) + } + + private func handleEvent(_ keyPath: String?) { + logger.d(keyPath, PoliciesTab.policyLock, Preferences.updatePolicy) guard !PoliciesTab.policyLock else { return } let buttons = PoliciesTab.updatesPolicyDropdown! let id = buttonIdToUpdate() diff --git a/src/ui/App.swift b/src/ui/App.swift index bb579b7b..1a25334b 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -50,7 +50,7 @@ class App: AppCenterApplication, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { appCenterDelegate = AppCenterCrash() App.shared.disableRelaunchOnLogin() - LogManager.setup() + Logger.initialize() #if DEBUG UserDefaults.standard.set(true, forKey: "NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints") #endif @@ -69,7 +69,9 @@ class App: AppCenterApplication, NSApplicationDelegate { self.loadMainMenuXib() self.thumbnailsPanel = ThumbnailsPanel() self.previewPanel = PreviewPanel() - Spaces.initialDiscovery() + Spaces.initialize() + SpacesEvents.observe() + ScreensEvents.observe() Applications.initialDiscovery() self.preferencesWindow = PreferencesWindow() self.feedbackWindow = FeedbackWindow() @@ -122,7 +124,8 @@ class App: AppCenterApplication, NSApplicationDelegate { } func hideUi(_ keepPreview: Bool = false) { - logger.i("hideUi") + logger.i() + if appIsBeingUsed == false { return } // already hidden appIsBeingUsed = false isFirstSummon = true MouseEvents.toggle(false) @@ -171,8 +174,9 @@ class App: AppCenterApplication, NSApplicationDelegate { } func focusTarget() { - logger.i("focusTarget") - focusSelectedWindow(Windows.focusedWindow()) + let focusedWindow = Windows.focusedWindow() + logger.i(focusedWindow ?? "nil") + focusSelectedWindow(focusedWindow) } @objc func checkForUpdatesNow(_ sender: NSMenuItem) { diff --git a/src/ui/LogManager.swift b/src/ui/Logger.swift similarity index 58% rename from src/ui/LogManager.swift rename to src/ui/Logger.swift index 97088743..098da92e 100644 --- a/src/ui/LogManager.swift +++ b/src/ui/Logger.swift @@ -3,68 +3,38 @@ import Foundation let logger = SwiftyBeaver.self -class LogManager { - private static let maxDays = 1 // Set maximum log retention days - private static var timer: Timer? +class Logger { + static let flag = "--logs=" - static func setup() { - setupLogger() - if ((logger.destinations.first { $0 is FileDestination } as? FileDestination)?.logFileURL != nil) { - startLogFileCleanupTimer() - } - } - - private static func setupLogger() { - let format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c $N.swift:$l $F - $M" + static func initialize() { let console = ConsoleDestination() - console.format = format - let file = FileDestination() - file.format = format - - #if DEBUG - console.minLevel = .verbose - file.minLevel = .verbose + console.useTerminalColors = true + console.levelString.verbose = "VERB" + console.levelString.debug = "DEBG" + console.levelString.info = "INFO" + console.levelString.warning = "WARN" + console.levelString.error = "ERRO" + console.format = "$C$DHH:mm:ss$d $L$c $N.swift:$l $F $M" + console.minLevel = decideLevel() logger.addDestination(console) - logger.addDestination(file) - #else - console.minLevel = .error - file.minLevel = .error -// logger.addDestination(console) -// logger.addDestination(file) - #endif - - if let logFileURL = file.logFileURL { - logger.info("Default log file path: \(logFileURL.path)") - } } - private static func startLogFileCleanupTimer() { - // Schedule a timer to run every 24 hours to clean up log files - timer = Timer.scheduledTimer(timeInterval: 86400, target: self, selector: #selector(cleanupLogFiles), userInfo: nil, repeats: true) - // Set tolerance to save battery - timer?.tolerance = 3600 - } - - @objc private static func cleanupLogFiles() { - guard let fileDestination = logger.destinations.first(where: { $0 is FileDestination }) as? FileDestination, - let logFileURL = fileDestination.logFileURL else { return } - - let fileManager = FileManager.default - // Get the file attributes to check the creation date - if let attributes = try? fileManager.attributesOfItem(atPath: logFileURL.path), - let creationDate = attributes[.creationDate] as? Date { - // Calculate the expiration date - let expirationDate = Calendar.current.date(byAdding: .day, value: -maxDays, to: Date()) - // If the log file is older than the specified retention period, delete it - if creationDate < expirationDate ?? Date() { - fileDestination.deleteLogFile() + static func decideLevel() -> SwiftyBeaver.Level { + if let level = CommandLine.arguments.first { $0.starts(with: flag) }?.substring(from: flag.endIndex) { + switch level { + case "verbose": return .verbose + case "debug": return .debug + case "info": return .info + case "warning": return .warning + case "error": return .error + default: break } } + return .error } } extension SwiftyBeaver { - class func isVerboseEnabled() -> Bool { return isEnabled(level: .verbose) } diff --git a/src/ui/permission-window/PermissionsWindow.swift b/src/ui/permission-window/PermissionsWindow.swift index 16da42ac..0f828c07 100644 --- a/src/ui/permission-window/PermissionsWindow.swift +++ b/src/ui/permission-window/PermissionsWindow.swift @@ -25,7 +25,7 @@ class PermissionsWindow: NSWindow, NSWindowDelegate { func windowWillClose(_ notification: Notification) { - logger.d("PermissionsWindow windowWillClose", SystemPermissions.preStartupPermissionsPassed) + logger.d(SystemPermissions.preStartupPermissionsPassed) if !SystemPermissions.preStartupPermissionsPassed { if SystemPermissions.accessibilityIsGranted() == .notGranted || SystemPermissions.screenRecordingIsGranted() == .notGranted { logger.e("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Accessibility.",