feat: show apps with no open window (closes #397)
This commit is contained in:
1 parent
dc8b363e57
commit
f0fa02c4a2
13 files changed
+126
-35
No files matched your search
Binary file not shown.
@@ -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:";
|
||||
|
||||
|
||||
@@ -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:";
|
||||
|
||||
|
||||
@@ -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=""
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")) }
|
||||
|
||||
+30
-9
@@ -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 ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user