fix: various issues with the app freezing or being slowly
This commit is contained in:
1 parent
8b52dc8acb
commit
121515cbc7
10 files changed
+80
-81
No files matched your search
@@ -54,7 +54,7 @@ class Application: NSObject {
|
||||
func removeWindowslessAppWindow() {
|
||||
if let windowlessAppWindow = (Windows.list.firstIndex { $0.isWindowlessApp == true && $0.application.pid == pid }) {
|
||||
Windows.list.remove(at: windowlessAppWindow)
|
||||
App.app.refreshOpenUi([])
|
||||
App.app.refreshOpenUi([], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,11 @@ class Application: NSObject {
|
||||
retryAxCallUntilTimeout(group, 5) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
var atLeastOneActualWindow = false
|
||||
if let axWindows_ = try self.axUiElement!.windows(), axWindows_.count > 0 {
|
||||
if let axWindows_ = try self.axUiElement!.windows(), !axWindows_.isEmpty {
|
||||
// bug in macOS: sometimes the OS returns multiple duplicate windows (e.g. Mail.app starting at login)
|
||||
try Array(Set(axWindows_)).forEach { axWindow in
|
||||
let uniqueWindows = Array(Set(axWindows_))
|
||||
if uniqueWindows.isEmpty { return }
|
||||
for axWindow in uniqueWindows {
|
||||
if let wid = try axWindow.cgWindowId() {
|
||||
let title = try axWindow.title()
|
||||
let subrole = try axWindow.subrole()
|
||||
@@ -96,7 +98,7 @@ class Application: NSObject {
|
||||
window.position = position
|
||||
} else {
|
||||
let window = self.addWindow(axWindow, wid, title, isFullscreen, isMinimized, position, size)
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +109,7 @@ class Application: NSObject {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.addWindowlessWindowIfNeeded() != nil {
|
||||
App.app.refreshOpenUi([])
|
||||
App.app.refreshOpenUi([], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
// workaround: some apps launch but take a while to create their window(s)
|
||||
|
||||
@@ -83,7 +83,7 @@ class Applications {
|
||||
Windows.cycleFocusedWindowIndex(-windowsOnTheLeftOfFocusedWindow)
|
||||
}
|
||||
if !existingWindowstoRemove.isEmpty {
|
||||
App.app.refreshOpenUi([])
|
||||
App.app.refreshOpenUi([], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,18 @@ class BackgroundWork {
|
||||
static var repeatingKeyThread: BackgroundThreadWithRunLoop!
|
||||
static var missionControlThread: BackgroundThreadWithRunLoop!
|
||||
static var cliEventsThread: BackgroundThreadWithRunLoop!
|
||||
static let screenshotsDispatchGroup = DispatchGroup()
|
||||
|
||||
// swift static variables are lazy; we artificially force the threads to init
|
||||
static func start() {
|
||||
// screenshots are taken off the main thread, concurrently
|
||||
screenshotsQueue = DispatchQueue.globalConcurrent("screenshotsQueue", .userInteractive)
|
||||
// screenshots are taken on a serial DispatchQueue. They used to be taken on the .global() concurrent queue.
|
||||
// it could hand the app the screenshot OS calls are slow. It would hang or crash with this error:
|
||||
// >Processes reached dispatch thread soft limit (64)
|
||||
screenshotsQueue = DispatchQueue.queue("screenshotsQueue", .userInteractive, false)
|
||||
// calls to act on windows (e.g. AXUIElementSetAttributeValue, AXUIElementPerformAction) are done off the main thread
|
||||
accessibilityCommandsQueue = DispatchQueue.globalConcurrent("accessibilityCommandsQueue", .userInteractive)
|
||||
// calls to the AX APIs are blocking. We dispatch those on a globalConcurrent queue
|
||||
axCallsQueue = DispatchQueue.globalConcurrent("axCallsQueue", .userInteractive)
|
||||
accessibilityCommandsQueue = DispatchQueue.queue("accessibilityCommandsQueue", .userInteractive, false)
|
||||
// calls to the AX APIs can block for a long time (e.g. if an app is unresponsive)
|
||||
// We can't use a serial queue. We use the global concurrent queue
|
||||
axCallsQueue = DispatchQueue.queue("axCallsQueue", .userInteractive, true)
|
||||
// we observe app and windows notifications. They arrive on this thread, and are handled off the main thread initially
|
||||
accessibilityEventsThread = BackgroundThreadWithRunLoop("accessibilityEventsThread", .userInteractive)
|
||||
// we listen to as any keyboard events as possible on a background thread, as it's more available/reliable than the main thread
|
||||
@@ -37,7 +39,7 @@ class BackgroundWork {
|
||||
static func startCrashReportsQueue() {
|
||||
if crashReportsQueue == nil {
|
||||
// crash reports can be sent off the main thread
|
||||
crashReportsQueue = DispatchQueue.globalConcurrent("crashReportsQueue", .utility)
|
||||
crashReportsQueue = DispatchQueue.queue("crashReportsQueue", .utility, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,27 +81,14 @@ class BackgroundWork {
|
||||
}
|
||||
}
|
||||
|
||||
// we cap concurrent tasks to .processorCount to avoid thread explosion on the .global queue
|
||||
let backgroundWorkGlobalSemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.processorCount)
|
||||
|
||||
extension DispatchQueue {
|
||||
static func globalConcurrent(_ label: String, _ qos: DispatchQoS) -> DispatchQueue {
|
||||
// label is not reflected in Instruments because the target is .global
|
||||
// if we want to see our custom labels, we need our private queue.
|
||||
// However, we want to be efficient and use the OS thread pool, so we use .global
|
||||
DispatchQueue(label: label, attributes: .concurrent, target: .global(qos: qos.qosClass))
|
||||
}
|
||||
|
||||
func asyncWithCap(_ deadline: DispatchTime? = nil, _ fn: @escaping () -> Void) {
|
||||
let block = {
|
||||
fn()
|
||||
backgroundWorkGlobalSemaphore.signal()
|
||||
}
|
||||
backgroundWorkGlobalSemaphore.wait()
|
||||
if let deadline = deadline {
|
||||
asyncAfter(deadline: deadline, execute: block)
|
||||
} else {
|
||||
async(execute: block)
|
||||
static func queue(_ label: String, _ qos: DispatchQoS, _ globalParallel: Bool) -> DispatchQueue {
|
||||
if globalParallel {
|
||||
// label is not reflected in Instruments because the target is .global
|
||||
// if we want to see our custom labels, we need our private queue.
|
||||
// However, we want to be efficient and use the OS thread pool, so we use .global
|
||||
return DispatchQueue(label: label, attributes: [.concurrent], target: .global(qos: qos.qosClass))
|
||||
}
|
||||
return DispatchQueue(label: label, qos: qos)
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -97,18 +97,17 @@ class Window {
|
||||
CFRunLoopAddSource(BackgroundWork.accessibilityEventsThread.runLoop, AXObserverGetRunLoopSource(axObserver), .commonModes)
|
||||
}
|
||||
|
||||
func refreshThumbnail(_ screenshot: NSImage?) {
|
||||
func refreshThumbnail(_ screenshot: NSImage) {
|
||||
thumbnail = screenshot
|
||||
thumbnailFullSize = screenshot?.size
|
||||
if App.app.appIsBeingUsed && shouldShowTheUser {
|
||||
if let index = (Windows.list.firstIndex { $0.cgWindowId == cgWindowId }) {
|
||||
let view = ThumbnailsView.recycledViews[index]
|
||||
if !view.thumbnail.isHidden {
|
||||
view.thumbnail.image = thumbnail?.copyToSeparateContexts()
|
||||
let thumbnailSize = ThumbnailView.thumbnailSize(thumbnail, false)
|
||||
view.thumbnail.setSize(thumbnailSize)
|
||||
}
|
||||
thumbnailFullSize = screenshot.size
|
||||
if !App.app.appIsBeingUsed || !shouldShowTheUser { return }
|
||||
if let view = (ThumbnailsView.recycledViews.first { $0.window_?.cgWindowId == cgWindowId }) {
|
||||
if !view.thumbnail.isHidden {
|
||||
view.thumbnail.image = thumbnail?.copyToSeparateContexts()
|
||||
let thumbnailSize = ThumbnailView.thumbnailSize(thumbnail, false)
|
||||
view.thumbnail.setSize(thumbnailSize)
|
||||
}
|
||||
App.app.previewPanel.updateImageIfShowing(cgWindowId, screenshot, screenshot.size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +120,7 @@ class Window {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in
|
||||
BackgroundWork.accessibilityCommandsQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.isFullscreen {
|
||||
self.axUiElement.setAttribute(kAXFullscreenAttribute, false)
|
||||
@@ -141,12 +140,12 @@ class Window {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in
|
||||
BackgroundWork.accessibilityCommandsQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.isFullscreen {
|
||||
self.axUiElement.setAttribute(kAXFullscreenAttribute, false)
|
||||
// minimizing is ignored if sent immediatly; we wait for the de-fullscreen animation to be over
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncWithCap(.now() + .seconds(1)) { [weak self] in
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.axUiElement.setAttribute(kAXMinimizedAttribute, true)
|
||||
}
|
||||
@@ -161,7 +160,7 @@ class Window {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in
|
||||
BackgroundWork.accessibilityCommandsQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.axUiElement.setAttribute(kAXFullscreenAttribute, !self.isFullscreen)
|
||||
}
|
||||
@@ -186,7 +185,7 @@ class Window {
|
||||
// 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
|
||||
// You can reproduce this buggy behaviour by clicking on the dock icon, proving it's an OS bug
|
||||
BackgroundWork.accessibilityCommandsQueue.asyncWithCap { [weak self] in
|
||||
BackgroundWork.accessibilityCommandsQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
var psn = ProcessSerialNumber()
|
||||
GetProcessForPID(self.application.pid, &psn)
|
||||
|
||||
+7
-16
@@ -273,7 +273,6 @@ class Windows {
|
||||
lazy var cgsWindowIds = Spaces.windowsInSpaces(spaceIdsAndIndexes)
|
||||
lazy var visibleCgsWindowIds = Spaces.windowsInSpaces(spaceIdsAndIndexes, false)
|
||||
for window in list {
|
||||
// TODO: can the CGS call inside detectTabbedWindows introduce latency when WindowServer is busy?
|
||||
detectTabbedWindows(window, cgsWindowIds, visibleCgsWindowIds)
|
||||
updatesWindowSpace(window)
|
||||
refreshIfWindowShouldBeShownToTheUser(window)
|
||||
@@ -303,7 +302,7 @@ class Windows {
|
||||
}
|
||||
|
||||
// dispatch screenshot requests off the main-thread, then wait for completion
|
||||
static func refreshThumbnails(_ windows: [Window], _ onlyUpdateScreenshots: Bool) {
|
||||
static func refreshThumbnails(_ windows: [Window], _ source: RefreshCausedBy) {
|
||||
var eligibleWindows = [Window]()
|
||||
for window in windows {
|
||||
if !window.isWindowlessApp, let cgWindowId = window.cgWindowId, cgWindowId != CGWindowID(bitPattern: -1) {
|
||||
@@ -311,30 +310,22 @@ class Windows {
|
||||
}
|
||||
}
|
||||
if eligibleWindows.isEmpty { return }
|
||||
screenshotEligibleWindowsAndRefreshUi(eligibleWindows, onlyUpdateScreenshots)
|
||||
screenshotEligibleWindowsAndRefreshUi(eligibleWindows, source)
|
||||
}
|
||||
|
||||
private static func screenshotEligibleWindowsAndRefreshUi(_ eligibleWindows: [Window], _ onlyUpdateScreenshots: Bool) {
|
||||
eligibleWindows.forEach { _ in BackgroundWork.screenshotsDispatchGroup.enter() }
|
||||
private static func screenshotEligibleWindowsAndRefreshUi(_ eligibleWindows: [Window], _ source: RefreshCausedBy) {
|
||||
for window in eligibleWindows {
|
||||
BackgroundWork.screenshotsQueue.async { [weak window] in
|
||||
backgroundWorkGlobalSemaphore.wait()
|
||||
defer {
|
||||
backgroundWorkGlobalSemaphore.signal()
|
||||
BackgroundWork.screenshotsDispatchGroup.leave()
|
||||
}
|
||||
if let cgImage = window?.cgWindowId?.screenshot() {
|
||||
if source == .refreshOnlyThumbnailsAfterShowUi && !App.app.appIsBeingUsed { return }
|
||||
if let wid = window?.cgWindowId, let cgImage = wid.screenshot() {
|
||||
if source == .refreshOnlyThumbnailsAfterShowUi && !App.app.appIsBeingUsed { return }
|
||||
DispatchQueue.main.async { [weak window] in
|
||||
if source == .refreshOnlyThumbnailsAfterShowUi && !App.app.appIsBeingUsed { return }
|
||||
window?.refreshThumbnail(NSImage.fromCgImage(cgImage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !onlyUpdateScreenshots {
|
||||
BackgroundWork.screenshotsDispatchGroup.notify(queue: DispatchQueue.main) {
|
||||
App.app.refreshOpenUi([])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func refreshWhichWindowsToShowTheUser() {
|
||||
|
||||
@@ -42,7 +42,7 @@ fileprivate func applicationActivated(_ element: AXUIElement, _ pid: pid_t) thro
|
||||
let window = (appFocusedWindow != nil && wid != nil) ? Windows.updateLastFocus(appFocusedWindow!, wid!)?.first : nil
|
||||
app.focusedWindow = window
|
||||
App.app.checkIfShortcutsShouldBeDisabled(window, app.runningApplication)
|
||||
App.app.refreshOpenUi(window != nil ? [window!] : [])
|
||||
App.app.refreshOpenUi(window != nil ? [window!] : [], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,11 @@ fileprivate func applicationHiddenOrShown(_ pid: pid_t, _ type: String) throws {
|
||||
// for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug
|
||||
return $0.application.pid == pid
|
||||
}
|
||||
App.app.refreshOpenUi(windows)
|
||||
// if we process the "shown" event too fast, the window won't be listed by CGSCopyWindowsWithOptionsAndTags
|
||||
// it will thus be detected as isTabbed. We add a delay to work around this scenario
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) {
|
||||
App.app.refreshOpenUi(windows, .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +81,7 @@ fileprivate func windowCreated(_ element: AXUIElement, _ pid: pid_t) throws {
|
||||
let window = Window(element, app, wid, axTitle, isFullscreen, isMinimized, position, size)
|
||||
Windows.appendAndUpdateFocus(window)
|
||||
Windows.cycleFocusedWindowIndex(1)
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,11 +110,11 @@ fileprivate func focusedWindowChanged(_ element: AXUIElement, _ pid: pid_t) thro
|
||||
app.focusedWindow = w
|
||||
}
|
||||
if let windows = Windows.updateLastFocus(element, wid) {
|
||||
App.app.refreshOpenUi(windows)
|
||||
App.app.refreshOpenUi(windows, .refreshUiAfterExternalEvent)
|
||||
} else if AXUIElement.isActualWindow(app, wid, level, axTitle, subrole, role, size) {
|
||||
let window = Window(element, app, wid, axTitle, isFullscreen, isMinimized, position, size)
|
||||
Windows.appendAndUpdateFocus(window)
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +146,7 @@ fileprivate func windowDestroyed(_ element: AXUIElement, _ pid: pid_t) throws {
|
||||
}
|
||||
if Windows.list.count > 0 {
|
||||
Windows.moveFocusedWindowIndexAfterWindowDestroyedInBackground(index)
|
||||
App.app.refreshOpenUi([])
|
||||
App.app.refreshOpenUi([], .refreshUiAfterExternalEvent)
|
||||
} else {
|
||||
App.app.hideUi()
|
||||
}
|
||||
@@ -155,7 +159,7 @@ fileprivate func windowMiniaturizedOrDeminiaturized(_ element: AXUIElement, _ ty
|
||||
DispatchQueue.main.async {
|
||||
if let window = (Windows.list.first { $0.isEqualRobust(element, wid) }) {
|
||||
window.isMinimized = type == kAXWindowMiniaturizedNotification
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +171,7 @@ fileprivate func windowTitleChanged(_ element: AXUIElement) throws {
|
||||
DispatchQueue.main.async {
|
||||
if let window = (Windows.list.first { $0.isEqualRobust(element, wid) }), newTitle != window.title {
|
||||
window.title = window.bestEffortTitle(newTitle)
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +192,7 @@ fileprivate func windowResizedOrMoved(_ element: AXUIElement) throws {
|
||||
window.isFullscreen = isFullscreen
|
||||
App.app.checkIfShortcutsShouldBeDisabled(window, nil)
|
||||
}
|
||||
App.app.refreshOpenUi([window])
|
||||
App.app.refreshOpenUi([window], .refreshUiAfterExternalEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class ScreensEvents {
|
||||
@objc private static func handleEvent(_ notification: Notification) {
|
||||
Logger.debug(notification.name.rawValue)
|
||||
// a screen added or removed can shuffle windows around Spaces; we refresh them
|
||||
App.app.refreshOpenUi(Windows.list)
|
||||
App.app.refreshOpenUi(Windows.list, .refreshUiAfterExternalEvent)
|
||||
Logger.info("screens", NSScreen.screens.map { ($0.uuid() ?? "nil" as CFString, $0.frame) })
|
||||
Logger.info("spaces", Spaces.screenSpacesMap)
|
||||
Logger.info("current space", Spaces.currentSpaceIndex, Spaces.currentSpaceId)
|
||||
|
||||
@@ -8,7 +8,7 @@ class SpacesEvents {
|
||||
@objc private static func handleEvent(_ notification: Notification) {
|
||||
Logger.debug(notification.name.rawValue)
|
||||
// if UI was kept open during Space transition, the Spaces may be obsolete; we refresh them
|
||||
App.app.refreshOpenUi(Windows.list)
|
||||
App.app.refreshOpenUi(Windows.list, .refreshUiAfterExternalEvent)
|
||||
Logger.info("current space", Spaces.currentSpaceIndex, Spaces.currentSpaceId)
|
||||
// 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.
|
||||
|
||||
+13
-6
@@ -197,15 +197,15 @@ class App: AppCenterApplication {
|
||||
CGWarpMouseCursorPosition(point)
|
||||
}
|
||||
|
||||
func refreshOpenUi(_ windowsToScreenshot: [Window], skipUpdatesBeforeShowing: Bool = false, onlyUpdateScreenshots: Bool = false) {
|
||||
func refreshOpenUi(_ windowsToScreenshot: [Window], _ source: RefreshCausedBy) {
|
||||
if !windowsToScreenshot.isEmpty && SystemPermissions.screenRecordingPermission == .granted
|
||||
&& !Preferences.onlyShowApplications()
|
||||
&& (!Appearance.hideThumbnails || Preferences.previewFocusedWindow) {
|
||||
Windows.refreshThumbnails(windowsToScreenshot, onlyUpdateScreenshots)
|
||||
return
|
||||
Windows.refreshThumbnails(windowsToScreenshot, source)
|
||||
if source == .refreshOnlyThumbnailsAfterShowUi { return }
|
||||
}
|
||||
guard appIsBeingUsed else { return }
|
||||
if !skipUpdatesBeforeShowing {
|
||||
if source == .refreshUiAfterExternalEvent {
|
||||
if !Windows.updatesBeforeShowing() { hideUi(); return }
|
||||
}
|
||||
guard appIsBeingUsed else { return }
|
||||
@@ -262,10 +262,10 @@ class App: AppCenterApplication {
|
||||
thumbnailsPanel.makeKeyAndOrderFront(nil) // workaround: without this, switching between 2 screens make thumbnailPanel invisible
|
||||
KeyRepeatTimer.toggleRepeatingKeyNextWindow()
|
||||
guard appIsBeingUsed else { return }
|
||||
refreshOpenUi([], skipUpdatesBeforeShowing: true) // first display with the data we have
|
||||
refreshOpenUi([], .showUi)
|
||||
guard appIsBeingUsed else { return }
|
||||
thumbnailsPanel.show()
|
||||
refreshOpenUi(Windows.list, skipUpdatesBeforeShowing: true, onlyUpdateScreenshots: true) // second refresh with fresh thumbnails
|
||||
refreshOpenUi(Windows.list, .refreshOnlyThumbnailsAfterShowUi)
|
||||
}
|
||||
|
||||
func checkIfShortcutsShouldBeDisabled(_ activeWindow: Window?, _ activeApp: NSRunningApplication?) {
|
||||
@@ -338,3 +338,10 @@ extension App: NSApplicationDelegate {
|
||||
setNativeCommandTabEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
enum RefreshCausedBy {
|
||||
case showUi
|
||||
case refreshOnlyThumbnailsAfterShowUi
|
||||
case refreshUiAfterThumbnailsHaveBeenRefreshed
|
||||
case refreshUiAfterExternalEvent
|
||||
}
|
||||
@@ -27,6 +27,13 @@ class PreviewPanel: NSPanel {
|
||||
setAccessibilitySubrole(.unknown)
|
||||
}
|
||||
|
||||
func updateImageIfShowing(_ id: CGWindowID?, _ preview: NSImage, _ size: CGSize) {
|
||||
if id == currentId && previewView.image != preview {
|
||||
previewView.image = preview.copyToSeparateContexts()
|
||||
previewView.image!.size = size
|
||||
}
|
||||
}
|
||||
|
||||
func show(_ id: CGWindowID, _ preview: NSImage, _ position: CGPoint, _ size: CGSize) {
|
||||
if previewView.image != preview {
|
||||
previewView.image = preview.copyToSeparateContexts()
|
||||
|
||||
Reference in new issue
Block a user