feat: support showing applications or windows
This commit is contained in:
1 parent
ad6d9614e2
commit
a25ad22beb
5 files changed
+117
-8
No files matched your search
@@ -43,6 +43,25 @@ class Application: NSObject {
|
||||
debugPrint("Deinit app", runningApplication.bundleIdentifier ?? runningApplication.bundleURL ?? "nil")
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
let s = """
|
||||
Application(kvObservers: \(kvObservers)
|
||||
, runningApplication.bundleIdentifier: \(runningApplication.bundleIdentifier)
|
||||
, runningApplication.title: \(runningApplication.localizedName)
|
||||
, runningApplication.isActive: \(runningApplication.isActive)
|
||||
, axUiElement: \(axUiElement)
|
||||
, axObserver: \(axObserver)
|
||||
, isReallyFinishedLaunching: \(isReallyFinishedLaunching)
|
||||
, isHidden: \(isHidden)
|
||||
, hasBeenActiveOnce: \(hasBeenActiveOnce)
|
||||
, dockLabel: \(dockLabel)
|
||||
, pid: \(pid)
|
||||
, focusedWindow.cgWindowId: \(focusedWindow?.cgWindowId)
|
||||
, alreadyRequestedToQuit: \(alreadyRequestedToQuit))
|
||||
"""
|
||||
return s.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: " ")
|
||||
}
|
||||
|
||||
func removeWindowslessAppWindow() {
|
||||
if let windowlessAppWindow = (Windows.list.firstIndex { $0.isWindowlessApp == true && $0.application.pid == pid }) {
|
||||
Windows.list.remove(at: windowlessAppWindow)
|
||||
|
||||
@@ -751,24 +751,24 @@ enum TitleTruncationPreference: String, CaseIterable, MacroPreference {
|
||||
}
|
||||
|
||||
enum ShowAppsWindowsPreference: String, CaseIterable, MacroPreference {
|
||||
case apps = "0"
|
||||
case applications = "0"
|
||||
case windows = "1"
|
||||
|
||||
var localizedString: LocalizedString {
|
||||
switch self {
|
||||
case .apps: return NSLocalizedString("Apps", comment: "")
|
||||
case .applications: return NSLocalizedString("Applications", comment: "")
|
||||
case .windows: return NSLocalizedString("Windows", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ShowAppNamesWindowTitlesPreference: String, CaseIterable, MacroPreference {
|
||||
case appNames = "0"
|
||||
case applicationNames = "0"
|
||||
case windowTitles = "1"
|
||||
|
||||
var localizedString: LocalizedString {
|
||||
switch self {
|
||||
case .appNames: return NSLocalizedString("App Names", comment: "")
|
||||
case .applicationNames: return NSLocalizedString("Application Names", comment: "")
|
||||
case .windowTitles: return NSLocalizedString("Window Titles", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
+47
-2
@@ -1,6 +1,6 @@
|
||||
import Cocoa
|
||||
|
||||
class Window {
|
||||
class Window: CustomStringConvertible {
|
||||
static var globalCreationCounter = Int.zero
|
||||
var cgWindowId: CGWindowID?
|
||||
var lastFocusOrder = Int.zero
|
||||
@@ -71,6 +71,31 @@ class Window {
|
||||
debugPrint("Deinit window", title ?? "nil", application.runningApplication.bundleIdentifier ?? "nil")
|
||||
}
|
||||
|
||||
var description: String {
|
||||
let s = """
|
||||
Window(cgWindowId: \(cgWindowId)
|
||||
, title: \(title)
|
||||
, thumbnailFullSize: \(thumbnailFullSize)
|
||||
, shouldShowTheUser: \(shouldShowTheUser)
|
||||
, isTabbed: \(isTabbed)
|
||||
, isFullscreen: \(isFullscreen)
|
||||
, isMinimized: \(isMinimized)
|
||||
, isOnAllSpaces: \(isOnAllSpaces)
|
||||
, isWindowlessApp: \(isWindowlessApp)
|
||||
, position: \(position)
|
||||
, size: \(size)
|
||||
, spaceId: \(spaceId)
|
||||
, spaceIndex: \(spaceIndex)
|
||||
, axUiElement: \(axUiElement)
|
||||
, application: \(application)
|
||||
, axObserver: \(axObserver)
|
||||
, row: \(row)
|
||||
, lastFocusOrder: \(lastFocusOrder)
|
||||
, creationOrder: \(creationOrder))
|
||||
"""
|
||||
return s.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: " ")
|
||||
}
|
||||
|
||||
/// some apps will not trigger AXApplicationActivated, where we usually update application.focusedWindow
|
||||
/// workaround: we check and possibly do it here
|
||||
func checkIfFocused(_ application: Application, _ wid: CGWindowID) {
|
||||
@@ -278,5 +303,25 @@ class Window {
|
||||
// window-group, instead of picking the focused one
|
||||
return isTabbed ? application.focusedWindow : self
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if this window is the main application window
|
||||
func isAppMainWindow() -> Bool {
|
||||
if let element = application.axUiElement {
|
||||
var mainWindow: AnyObject?
|
||||
if AXUIElementCopyAttributeValue(element, kAXMainWindowAttribute as CFString, &mainWindow) == .success {
|
||||
if let mainWin = mainWindow as! AXUIElement? {
|
||||
do {
|
||||
let w1 = try mainWin.cgWindowId()
|
||||
let w2 = try self.axUiElement.cgWindowId()
|
||||
if w1 == w2 {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -295,6 +295,20 @@ class Windows {
|
||||
Windows.list.forEach { (window: Window) in
|
||||
refreshIfWindowShouldBeShownToTheUser(window, screen)
|
||||
}
|
||||
|
||||
if (Preferences.showAppsWindows == .applications && Preferences.appearanceModel != .thumbnails) {
|
||||
// Group windows by application and select the optimal main window
|
||||
let windowsGroupedByApp = Dictionary(grouping: Windows.list) { $0.application.pid }
|
||||
windowsGroupedByApp.forEach { (app, windows) in
|
||||
if windows.count > 1, let mainWindow = selectMainWindow(from: windows) {
|
||||
windows.forEach { window in
|
||||
if window.cgWindowId != mainWindow.cgWindowId {
|
||||
window.shouldShowTheUser = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func refreshIfWindowShouldBeShownToTheUser(_ window: Window, _ screen: NSScreen) {
|
||||
@@ -315,6 +329,35 @@ class Windows {
|
||||
!(Preferences.screensToShow[App.app.shortcutIndex] == .showingAltTab && !window.isOnScreen(screen)) &&
|
||||
(Preferences.showTabsAsWindows || !window.isTabbed))
|
||||
}
|
||||
|
||||
/// Selects the most appropriate main window from a given list of windows.
|
||||
///
|
||||
/// The selection criteria are as follows:
|
||||
/// 1. Prefer the focused window if it exists.
|
||||
/// 2. Prefer the main window of the application if the focused window is not found.
|
||||
///
|
||||
/// - Parameter windows: An array of `Window` objects to select from.
|
||||
/// - Returns: The most appropriate `Window` object based on the selection criteria, or `nil` if the array is empty.
|
||||
static func selectMainWindow(from windows: [Window]) -> Window? {
|
||||
let sortedWindows = windows.sorted { (window1, window2) -> Bool in
|
||||
// Prefer the focus window
|
||||
if window1.application.focusedWindow?.cgWindowId == window1.cgWindowId {
|
||||
return true
|
||||
} else if window2.application.focusedWindow?.cgWindowId == window2.cgWindowId {
|
||||
return false
|
||||
}
|
||||
|
||||
// Prefer the main window
|
||||
if window1.isAppMainWindow() && !window2.isAppMainWindow() {
|
||||
return true
|
||||
} else if !window1.isAppMainWindow() && window2.isAppMainWindow() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return sortedWindows.first
|
||||
}
|
||||
}
|
||||
|
||||
func sortByAppNameThenWindowTitle(_ w1: Window, _ w2: Window) -> ComparisonResult {
|
||||
|
||||
@@ -330,11 +330,11 @@ class ModelAdvancedSettingsWindow: NSWindow, NSTabViewDelegate {
|
||||
"alignThumbnails", AlignThumbnailsPreference.allCases)
|
||||
titleTruncation = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Window title truncation:", comment: ""),
|
||||
"titleTruncation", TitleTruncationPreference.allCases)
|
||||
showAppsWindows = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show:", comment: ""),
|
||||
showAppsWindows = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show running:", comment: ""),
|
||||
"showAppsWindows", ShowAppsWindowsPreference.allCases, extraAction: { _ in
|
||||
self.toggleAppNamesWindowTitles()
|
||||
})
|
||||
showAppNamesWindowTitles = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show:", comment: ""),
|
||||
showAppNamesWindowTitles = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show titles:", comment: ""),
|
||||
"showAppNamesWindowTitles", ShowAppNamesWindowTitlesPreference.allCases)
|
||||
|
||||
doneButton = NSButton(title: NSLocalizedString("Done", comment: ""), target: self, action: #selector(onClicked(_:)))
|
||||
@@ -429,6 +429,7 @@ class ModelAdvancedSettingsWindow: NSWindow, NSTabViewDelegate {
|
||||
// view.column(at: 1).width = 150
|
||||
view.column(at: 0).xPlacement = .trailing
|
||||
view.column(at: 1).xPlacement = .leading
|
||||
toggleAppNamesWindowTitles()
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -444,6 +445,7 @@ class ModelAdvancedSettingsWindow: NSWindow, NSTabViewDelegate {
|
||||
// view.column(at: 1).width = 200
|
||||
view.column(at: 0).xPlacement = .trailing
|
||||
view.column(at: 1).xPlacement = .leading
|
||||
toggleAppNamesWindowTitles()
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user