From f0fa02c4a2bfda2bf66bf4b6bcf53367681b4e10 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Thu, 27 Aug 2020 20:36:25 +0900 Subject: [PATCH] feat: show apps with no open window (closes #397) --- resources/SF-Pro-Text-Regular.otf | Bin 13392 -> 23041 bytes resources/l10n/Localizable.strings | 3 ++ resources/l10n/en.lproj/Localizable.strings | 3 ++ scripts/subset_font.sh | 2 +- src/logic/Application.swift | 41 ++++++++++++++++-- src/logic/Applications.swift | 10 ++--- src/logic/Preferences.swift | 2 + src/logic/Window.swift | 39 +++++++++++++---- src/logic/Windows.swift | 16 ++++--- src/logic/events/AccessibilityEvents.swift | 16 ++++--- .../main-window/ThumbnailFontIconView.swift | 11 +++++ src/ui/main-window/ThumbnailView.swift | 17 +++++++- .../tabs/AppearanceTab.swift | 1 + 13 files changed, 126 insertions(+), 35 deletions(-) diff --git a/resources/SF-Pro-Text-Regular.otf b/resources/SF-Pro-Text-Regular.otf index f8a76a751502a26bd310cd1707b3520207a5c56e..e6ae7f7b90a724d964d7ad63b8c00d72c3a8e0f8 100644 GIT binary patch literal 23041 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 literal 13392 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 beSAoIBK%L!y504qf0UQL5_|do`S*VS>N(vZ diff --git a/resources/l10n/Localizable.strings b/resources/l10n/Localizable.strings index 8e745a4c..fdba6e27 100644 --- a/resources/l10n/Localizable.strings +++ b/resources/l10n/Localizable.strings @@ -130,6 +130,9 @@ /* No comment provided by engineer. */ "Hide app badges:" = "Hide app badges:"; +/* No comment provided by engineer. */ +"Hide apps with no open window:" = "Hide apps with no open window:"; + /* No comment provided by engineer. */ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/resources/l10n/en.lproj/Localizable.strings b/resources/l10n/en.lproj/Localizable.strings index f5abaee4..8b911e66 100644 --- a/resources/l10n/en.lproj/Localizable.strings +++ b/resources/l10n/en.lproj/Localizable.strings @@ -131,6 +131,9 @@ /*No comment provided by engineer.*/ "Hide app badges:" = "Hide app badges:"; +/*No comment provided by engineer.*/ +"Hide apps with no open window:" = "Hide apps with no open window:"; + /*No comment provided by engineer.*/ "Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:"; diff --git a/scripts/subset_font.sh b/scripts/subset_font.sh index 6fdb5ed0..582c3aa9 100755 --- a/scripts/subset_font.sh +++ b/scripts/subset_font.sh @@ -4,4 +4,4 @@ set -exu "$(pipenv --venv)"/bin/pyftsubset resources/SF-Pro-Text-Regular-Full.otf \ --output-file=resources/SF-Pro-Text-Regular.otf \ - --text="􀀁􀁎􀁌􀕧􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀕬􀀹􀀻􀀽􀀿􀁁􀘘􀁃􀁅􀑲􀁇􀁉􀁋􀑴􀔔􀔕􀔖􀔗􀔘􀔙􀔚􀔛􀔜􀔝􀔞􀔟􀔠􀔡􀔢􀔣􀔤􀔥􀔦􀔧􀔨􀕭" + --text="􀥃􀀁􀁎􀁌􀕧􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀕬􀀹􀀻􀀽􀀿􀁁􀘘􀁃􀁅􀑲􀁇􀁉􀁋􀑴􀔔􀔕􀔖􀔗􀔘􀔙􀔚􀔛􀔜􀔝􀔞􀔟􀔠􀔡􀔢􀔣􀔤􀔥􀔦􀔧􀔨􀕭" diff --git a/src/logic/Application.swift b/src/logic/Application.swift index 39c2b848..50b246d5 100644 --- a/src/logic/Application.swift +++ b/src/logic/Application.swift @@ -10,6 +10,7 @@ class Application: NSObject { var isHidden: Bool! var icon: NSImage? var dockLabel: String? + var pid: pid_t { runningApplication.processIdentifier } static func notifications(_ app: NSRunningApplication) -> [String] { var n = [ @@ -36,8 +37,17 @@ class Application: NSObject { icon = runningApplication.icon addAndObserveWindows() kvObservers = [ - runningApplication.observe(\.isFinishedLaunching, options: [.new]) { [weak self] _, _ in self?.addAndObserveWindows() }, - runningApplication.observe(\.activationPolicy, options: [.new]) { [weak self] _, _ in self?.addAndObserveWindows() }, + runningApplication.observe(\.isFinishedLaunching, options: [.new]) { [weak self] _, _ in + guard let self = self else { return } + self.addAndObserveWindows() + }, + runningApplication.observe(\.activationPolicy, options: [.new]) { [weak self] _, _ in + guard let self = self else { return } + if self.runningApplication.activationPolicy != .regular { + self.removeWindowslessAppWindow() + } + self.addAndObserveWindows() + }, ] } @@ -45,8 +55,15 @@ class Application: NSObject { debugPrint("Deinit app", runningApplication.bundleIdentifier ?? runningApplication.bundleURL ?? "nil") } + func removeWindowslessAppWindow() { + if let windowlessAppWindow = Windows.list.firstIndex { $0.isWindowlessApp == true && $0.application.pid == pid } { + Windows.list.remove(at: windowlessAppWindow) + App.app.refreshOpenUi() + } + } + func addAndObserveWindows() { - if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited { + if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited && axUiElement == nil { axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier) AXObserverCreate(runningApplication.processIdentifier, axObserverCallback, &axObserver) debugPrint("Adding app", runningApplication.processIdentifier, runningApplication.bundleIdentifier ?? "nil") @@ -75,6 +92,14 @@ class Application: NSObject { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.addWindows(windows) + self.addWindowslessAppsIfNeeded() + App.app.refreshOpenUi() + } + } else { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.addWindowslessAppsIfNeeded() + App.app.refreshOpenUi() } } } @@ -92,7 +117,15 @@ class Application: NSObject { if App.app.appIsBeingUsed { Windows.cycleFocusedWindowIndex(windows.count) } - App.app.refreshOpenUi(windows) + } + + func addWindowslessAppsIfNeeded() { + if !Preferences.hideWindowlessApps && + runningApplication.activationPolicy == .regular && + !runningApplication.isTerminated && + (Windows.list.firstIndex { $0.application.pid == pid }) == nil { + Windows.list.insertAndScaleRecycledPool(Window(self), at: Windows.list.count) + } } private func observeEvents() { diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 1aebef0c..9b5c4b1c 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -10,7 +10,7 @@ class Applications { guard app.runningApplication.isFinishedLaunching else { continue } app.observeNewWindows(group) } - _ = group.wait(wallTimeout: .now() + .seconds(1)) + _ = group.wait(wallTimeout: .now() + .seconds(2)) } static func initialDiscovery() { @@ -49,13 +49,13 @@ class Applications { static func removeRunningApplications(_ runningApps: [NSRunningApplication]) { var windowsOnTheLeftOfFocusedWindow = 0 for runningApp in runningApps { - Applications.list.removeAll(where: { $0.runningApplication.isEqual(runningApp) }) + Applications.list.removeAll(where: { $0.pid == runningApp.processIdentifier }) Windows.list.enumerated().forEach { (index, window) in - if window.application.runningApplication.isEqual(runningApp) && index < Windows.focusedWindowIndex { + if window.application.pid == runningApp.processIdentifier && index < Windows.focusedWindowIndex { windowsOnTheLeftOfFocusedWindow += 1 } } - Windows.list.removeAll(where: { $0.application.runningApplication.isEqual(runningApp) }) + Windows.list.removeAll(where: { $0.application.pid == runningApp.processIdentifier }) } guard Windows.list.count > 0 else { App.app.hideUi(); return } if windowsOnTheLeftOfFocusedWindow > 0 { @@ -67,7 +67,7 @@ class Applications { static func refreshBadges() { let group = DispatchGroup() retryAxCallUntilTimeout(group) { - if let dockPid = (Applications.list.first { $0.runningApplication.bundleIdentifier == "com.apple.dock" }?.runningApplication.processIdentifier), + if let dockPid = (Applications.list.first { $0.runningApplication.bundleIdentifier == "com.apple.dock" }?.pid), let axList = (try AXUIElementCreateApplication(dockPid).children()?.first { try $0.role() == "AXList" }), let axAppDockItem = (try axList.children()?.filter { try $0.subrole() == "AXApplicationDockItem" && ($0.appIsRunning() ?? false) }) { try Applications.list.forEach { app in diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 927d07cc..65ef5e34 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -59,6 +59,7 @@ class Preferences { "maxCellsPerRow": defaultsDependingOnScreenRatio_["maxCellsPerRow"]!, "shortcutStyle": "0", "hideAppBadges": "false", + "hideWindowlessApps": "false", ] // constant values @@ -98,6 +99,7 @@ class Preferences { static var hideSpaceNumberLabels: Bool { defaults.bool("hideSpaceNumberLabels") } static var hideStatusIcons: Bool { defaults.bool("hideStatusIcons") } static var hideAppBadges: Bool { defaults.bool("hideAppBadges") } + static var hideWindowlessApps: Bool { defaults.bool("hideWindowlessApps") } static var startAtLogin: Bool { defaults.bool("startAtLogin") } static var dontShowBlacklist: [String] { blacklistStringToArray(defaults.string("dontShowBlacklist")) } static var disableShortcutsBlacklist: [String] { blacklistStringToArray(defaults.string("disableShortcutsBlacklist")) } diff --git a/src/logic/Window.swift b/src/logic/Window.swift index 54a20942..448f1f97 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -1,7 +1,7 @@ import Cocoa class Window { - var cgWindowId: CGWindowID + var cgWindowId = CGWindowID.max var title: String! var thumbnail: NSImage? var thumbnailFullSize: NSSize? @@ -10,13 +10,14 @@ class Window { var isTabbed: Bool = false var isHidden: Bool { get { application.isHidden } } var dockLabel: Int? { get { application.dockLabel.flatMap { Int($0) } } } - var isFullscreen: Bool - var isMinimized: Bool - var isOnAllSpaces: Bool + var isFullscreen = false + var isMinimized = false + var isOnAllSpaces = false + var isWindowlessApp = false var position: CGPoint? - var spaceId: CGSSpaceID - var spaceIndex: SpaceIndex - var axUiElement: AXUIElement + var spaceId = CGSSpaceID.max + var spaceIndex = SpaceIndex.max + var axUiElement: AXUIElement! var application: Application var axObserver: AXObserver? var row: Int? @@ -43,17 +44,27 @@ class Window { self.position = position self.title = bestEffortTitle(axTitle) self.isTabbed = false - refreshThumbnail() + if !Preferences.hideThumbnails { + refreshThumbnail() + } + application.removeWindowslessAppWindow() debugPrint("Adding window", cgWindowId, title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") observeEvents() } + init(_ application: Application) { + isWindowlessApp = true + self.application = application + self.title = application.runningApplication.localizedName + debugPrint("Adding app-window", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") + } + deinit { debugPrint("Deinit window", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil") } private func observeEvents() { - AXObserverCreate(application.runningApplication.processIdentifier, axObserverCallback, &axObserver) + AXObserverCreate(application.pid, axObserverCallback, &axObserver) guard let axObserver = axObserver else { return } for notification in Window.notifications { retryAxCallUntilTimeout { [weak self] in @@ -84,6 +95,7 @@ class Window { } func close() { + if isWindowlessApp { return } BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in guard let self = self else { return } if self.isFullscreen { @@ -96,6 +108,7 @@ class Window { } func minDemin() { + if isWindowlessApp { return } BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in guard let self = self else { return } if self.isFullscreen { @@ -112,6 +125,7 @@ class Window { } func toggleFullscreen() { + if isWindowlessApp { return } BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in guard let self = self else { return } self.axUiElement.setAttribute(kAXFullscreenAttribute, !self.isFullscreen) @@ -140,6 +154,12 @@ class Window { func focus() { if application.runningApplication.processIdentifier == ProcessInfo.processInfo.processIdentifier { App.app.showSecondaryWindow(App.app.window(withWindowNumber: Int(cgWindowId))) + } else if isWindowlessApp { + if let bundleID = application.runningApplication.bundleIdentifier { + NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleID, additionalEventParamDescriptor: nil, launchIdentifier: nil) + } else { + application.runningApplication.activate(options: .activateIgnoringOtherApps) + } } else { // macOS bug: when switching to a System Preferences window in another space, it switches to that space, // but quickly switches back to another window in that space @@ -190,3 +210,4 @@ class Window { return application.runningApplication.localizedName ?? "" } } + diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index a7921db2..d4636614 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -122,14 +122,16 @@ class Windows { static func refreshIfWindowShouldBeShownToTheUser(_ window: Window, _ screen: NSScreen) { window.shouldShowTheUser = - !(!Preferences.showFullscreenWindows[App.app.shortcutIndex] && window.isFullscreen) && - !(!Preferences.showMinimizedWindows[App.app.shortcutIndex] && window.isMinimized) && - !(!Preferences.showHiddenWindows[App.app.shortcutIndex] && window.isHidden) && + !(window.application.runningApplication.bundleIdentifier.flatMap { Preferences.dontShowBlacklist.contains($0) } ?? false) && !(Preferences.appsToShow[App.app.shortcutIndex] == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) && - !(Preferences.spacesToShow[App.app.shortcutIndex] == .active && window.spaceId != Spaces.currentSpaceId) && - !(Preferences.screensToShow[App.app.shortcutIndex] == .showingAltTab && !isOnScreen(window, screen)) && - (Preferences.showTabsAsWindows || !window.isTabbed) && - !(window.application.runningApplication.bundleIdentifier.flatMap { Preferences.dontShowBlacklist.contains($0) } ?? false) + !(!Preferences.showHiddenWindows[App.app.shortcutIndex] && window.isHidden) && + ((!Preferences.hideWindowlessApps && window.isWindowlessApp) || + !window.isWindowlessApp && + !(!Preferences.showFullscreenWindows[App.app.shortcutIndex] && window.isFullscreen) && + !(!Preferences.showMinimizedWindows[App.app.shortcutIndex] && window.isMinimized) && + !(Preferences.spacesToShow[App.app.shortcutIndex] == .active && window.spaceId != Spaces.currentSpaceId) && + !(Preferences.screensToShow[App.app.shortcutIndex] == .showingAltTab && !isOnScreen(window, screen)) && + (Preferences.showTabsAsWindows || !window.isTabbed)) } static func isOnScreen(_ window: Window, _ screen: NSScreen) -> Bool { diff --git a/src/logic/events/AccessibilityEvents.swift b/src/logic/events/AccessibilityEvents.swift index 7d9f448c..f8b9a305 100644 --- a/src/logic/events/AccessibilityEvents.swift +++ b/src/logic/events/AccessibilityEvents.swift @@ -28,10 +28,10 @@ func retryAxCallUntilTimeout_(_ group: DispatchGroup?, _ fn: @escaping () throws } func handleEvent(_ type: String, _ element: AXUIElement) throws { - debugPrint("Accessibility event", type, try element.title() ?? "nil") + debugPrint("Accessibility event", type, type != kAXFocusedUIElementChangedNotification ? (try element.title() ?? "nil") : "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() == "AXUnknown")) { + try (!(type == kAXWindowCreatedNotification && pid == ProcessInfo.processInfo.processIdentifier && element.subrole() == "AXUnknown")) { switch type { case kAXApplicationActivatedNotification: try applicationActivated(element) case kAXApplicationHiddenNotification, @@ -57,7 +57,7 @@ private func focusedUiElementChanged(_ element: AXUIElement, _ pid: pid_t) throw DispatchQueue.main.async { let windows = Windows.list.filter { w in // for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug - let isFromApp = w.application.runningApplication.processIdentifier == pid + let isFromApp = w.application.pid == pid if isFromApp { // this event is the only opportunity we have to check if a window became a tab, or a tab became a window let oldIsTabbed = w.isTabbed @@ -87,12 +87,12 @@ private func applicationActivated(_ element: AXUIElement) throws { private func applicationHiddenOrShown(_ element: AXUIElement, _ pid: pid_t, _ type: String) throws { DispatchQueue.main.async { - if let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) { + if let app = (Applications.list.first { $0.pid == pid }) { app.isHidden = type == kAXApplicationHiddenNotification } let windows = Windows.list.filter { // for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug - return $0.application.runningApplication.processIdentifier == pid + return $0.application.pid == pid } App.app.refreshOpenUi(windows) } @@ -112,7 +112,7 @@ private func windowCreated(_ element: AXUIElement, _ pid: pid_t) throws { if Windows.list.firstIndexThatMatches(element, wid) == nil, let runningApp = NSRunningApplication(processIdentifier: pid), element.isActualWindow(runningApp, wid, isOnNormalLevel, axTitle, subrole, role), - let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) { + let app = (Applications.list.first { $0.pid == pid }) { let window = Window(element, app, wid, axTitle, isFullscreen, isMinimized, position) Windows.list.insertAndScaleRecycledPool(window, at: 0) Windows.cycleFocusedWindowIndex(1) @@ -139,7 +139,7 @@ private func focusedWindowChanged(_ element: AXUIElement, _ pid: pid_t) throws { } } else if let runningApp = NSRunningApplication(processIdentifier: pid), element.isActualWindow(runningApp, wid, isOnNormalLevel, axTitle, subrole, role), - let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) { + let app = (Applications.list.first { $0.pid == pid }) { Windows.list.insertAndScaleRecycledPool(Window(element, app, wid, axTitle, isFullscreen, isMinimized, position), at: 0) App.app.refreshOpenUi([Windows.list[0]]) } @@ -152,7 +152,9 @@ private func windowDestroyed(_ element: AXUIElement) throws { let wid = try element.cgWindowId() DispatchQueue.main.async { guard let existingIndex = Windows.list.firstIndexThatMatches(element, wid) else { return } + let window = Windows.list[existingIndex] Windows.list.remove(at: existingIndex) + window.application.addWindowslessAppsIfNeeded() guard Windows.list.count > 0 else { App.app.hideUi(); return } Windows.moveFocusedWindowIndexAfterWindowDestroyedInBackground(existingIndex) App.app.refreshOpenUi() diff --git a/src/ui/main-window/ThumbnailFontIconView.swift b/src/ui/main-window/ThumbnailFontIconView.swift index 029f525f..862553c0 100644 --- a/src/ui/main-window/ThumbnailFontIconView.swift +++ b/src/ui/main-window/ThumbnailFontIconView.swift @@ -11,6 +11,17 @@ enum Symbols: String { case filledCircled = "􀀁" case filledCircledNumber0 = "􀀹" case filledCircledNumber10 = "􀔔" + case newWindow = "􀥃" +} + +class FontIcon: BaseLabel { + convenience init(_ symbol: Symbols, _ color: NSColor = .white) { + self.init(symbol.rawValue) + font = NSFont(name: "SF Pro Text", size: Preferences.fontHeight)! + textColor = color + alignment = .center + shadow = ThumbnailView.makeShadow(.darkGray) + } } // Font icon using SF Symbols from the SF Pro font from Apple diff --git a/src/ui/main-window/ThumbnailView.swift b/src/ui/main-window/ThumbnailView.swift index bb22ce94..6c4ace61 100644 --- a/src/ui/main-window/ThumbnailView.swift +++ b/src/ui/main-window/ThumbnailView.swift @@ -20,6 +20,7 @@ class ThumbnailView: NSStackView { var isHighlighted = false var shouldShowWindowControls = false var isShowingWindowControls = false + var windowlessIcon = FontIcon(.newWindow) convenience init() { self.init(frame: .zero) @@ -44,6 +45,7 @@ class ThumbnailView: NSStackView { setViews([hStackView, thumbnail], in: .leading) addWindowControls() addDockLabelIcon() + thumbnail.addSubview(windowlessIcon, positioned: .above, relativeTo: nil) } private func addDockLabelIcon() { @@ -68,7 +70,7 @@ class ThumbnailView: NSStackView { if let shouldShowWindowControls = shouldShowWindowControls_ { self.shouldShowWindowControls = shouldShowWindowControls } - let shouldShow = shouldShowWindowControls && isHighlighted && !Preferences.hideColoredCircles + let shouldShow = shouldShowWindowControls && isHighlighted && !Preferences.hideColoredCircles && !window_!.isWindowlessApp if isShowingWindowControls != shouldShow { isShowingWindowControls = shouldShow [closeIcon, minimizeIcon, maximizeIcon].forEach { $0.isHidden = !shouldShow } @@ -144,7 +146,18 @@ class ThumbnailView: NSStackView { assignIfDifferent(&frame.size.height, newHeight) let fontIconWidth = CGFloat([fullscreenIcon, minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding) assignIfDifferent(&label.textContainer!.size.width, frame.width - Preferences.iconSize - Preferences.intraCellPadding * 3 - fontIconWidth) - assignIfDifferent(&subviews.first!.frame.size, frame.size) + assignIfDifferent(&windowlessIcon.isHidden, !element.isWindowlessApp) + if element.isWindowlessApp { + let maxWidth = (ThumbnailView.widthMin(screen) - Preferences.intraCellPadding * 2).rounded() + let maxHeight = ((ThumbnailView.height(screen) - hStackView.fittingSize.height) - Preferences.intraCellPadding * 2).rounded() + // heuristic to determine font size based on bounding box + let fontSize = (min(maxWidth, maxHeight) * 0.6).rounded() + // 1.25 is a heuristic to fit the SF Symbol into the bounding box + windowlessIcon.frame.size = CGSize(width: maxWidth, height: (fontSize * 1.25).rounded()) + windowlessIcon.font = NSFont(name: windowlessIcon.font!.fontName, size: fontSize) + // 2.5 is a heuristic to have a _perceived_ vertical alignment on this particular icon + windowlessIcon.frame.origin = CGPoint(x: (-maxWidth / 2).rounded(), y: (-maxHeight / 2 - windowlessIcon.frame.size.height / 2.5).rounded()) + } self.mouseUpCallback = { () -> Void in App.app.focusSelectedWindow(element) } self.mouseMovedCallback = { () -> Void in Windows.updateFocusedWindowIndex(index) } // force a display to avoid flickering; see https://github.com/lwouis/alt-tab-macos/issues/197 diff --git a/src/ui/preferences-window/tabs/AppearanceTab.swift b/src/ui/preferences-window/tabs/AppearanceTab.swift index aed7b6df..8d8f49d1 100644 --- a/src/ui/preferences-window/tabs/AppearanceTab.swift +++ b/src/ui/preferences-window/tabs/AppearanceTab.swift @@ -20,6 +20,7 @@ class AppearanceTab { LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Show standard tabs as windows:", comment: ""), "showTabsAsWindows"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide colored circles on mouse hover:", comment: ""), "hideColoredCircles"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide app badges:", comment: ""), "hideAppBadges"), + LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide apps with no open window:", comment: ""), "hideWindowlessApps"), ]) grid.column(at: 0).xPlacement = .trailing grid.rowAlignment = .lastBaseline