feat: support showing applications or windows

This commit is contained in:
allsochen committed 2024-07-29 01:09:35 +08:00
1 parent ad6d9614e2
commit a25ad22beb
5 files changed
+117 -8

No files matched your search

+19
View File
@@ -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)
+4 -4
View File
@@ -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
View File
@@ -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
}
}
+43
View File
@@ -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
}