From 66d16262200b0e3ed24bcf844587d2d86aa3c9a1 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Tue, 21 May 2024 18:11:40 -0600 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=90=9B=20#314=20Try=20and=20reduce?= =?UTF-8?q?=20conflicts=20with=20hyperkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/LoopManager.swift | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index 80f8338b..b3da3fe5 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -299,10 +299,24 @@ class LoopManager: ObservableObject { } private func handleLoopKeypress(_ event: NSEvent) { - self.triggerDelayTimer = nil + triggerDelayTimer = nil + + let previousModifiers = currentlyPressedModifiers processModifiers(event) - if Defaults[.triggerKey].isSubset(of: self.currentlyPressedModifiers) { + let triggerKey = Defaults[.triggerKey] + let wasKeyDown = (event.type == .keyDown || currentlyPressedModifiers.count > previousModifiers.count) + + if wasKeyDown, + triggerKey.isSubset(of: currentlyPressedModifiers) { + + guard + !isLoopActive, + currentlyPressedModifiers.count <= triggerKey.count + else { + return + } + let useTriggerDelay = Defaults[.triggerDelay] > 0.1 let useDoubleClickTrigger = Defaults[.doubleClickToTrigger] @@ -310,14 +324,14 @@ class LoopManager: ObservableObject { guard currentlyPressedModifiers.sorted() == Defaults[.triggerKey].sorted() else { return } handleDoubleClickToTrigger(useTriggerDelay) } else if useTriggerDelay { - self.handleTriggerDelay() + handleTriggerDelay() } else { - self.openLoop() + openLoop() } self.lastTriggerKeyClick = Date.now } else { - if self.isLoopActive { - self.closeLoop() + if isLoopActive { + closeLoop() } } } @@ -337,6 +351,10 @@ class LoopManager: ObservableObject { for key in flags where CGKeyCode.keyToImage.contains(where: { $0.key == key }) { if !self.currentlyPressedModifiers.map({ $0.baseModifier }).contains(key) { self.currentlyPressedModifiers.insert(key) + +// if key.isOnRightSide { +// print("\(key) is on right side") +// } } } } From 14506fad3a25db51d7880886b3151af78e920260 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Tue, 21 May 2024 21:02:53 -0600 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=90=9B=20#314=20Make=20more=20relia?= =?UTF-8?q?ble?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/LoopManager.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index b3da3fe5..e301155d 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -8,6 +8,7 @@ import SwiftUI import Defaults +// swiftlint:disable:next type_body_length class LoopManager: ObservableObject { // Size Adjustment static var sidesToAdjust: Edge.Set? @@ -306,10 +307,14 @@ class LoopManager: ObservableObject { let triggerKey = Defaults[.triggerKey] let wasKeyDown = (event.type == .keyDown || currentlyPressedModifiers.count > previousModifiers.count) + let keybindIsValid = ( + Defaults[.triggerKey].contains(event.keyCode) || + Defaults[.keybinds].contains { + $0.keybind.contains(event.keyCode) + } + ) - if wasKeyDown, - triggerKey.isSubset(of: currentlyPressedModifiers) { - + if wasKeyDown, keybindIsValid, triggerKey.isSubset(of: currentlyPressedModifiers) { guard !isLoopActive, currentlyPressedModifiers.count <= triggerKey.count @@ -351,10 +356,6 @@ class LoopManager: ObservableObject { for key in flags where CGKeyCode.keyToImage.contains(where: { $0.key == key }) { if !self.currentlyPressedModifiers.map({ $0.baseModifier }).contains(key) { self.currentlyPressedModifiers.insert(key) - -// if key.isOnRightSide { -// print("\(key) is on right side") -// } } } } @@ -399,8 +400,6 @@ class LoopManager: ObservableObject { self.keybindMonitor.stop() self.mouseMovedEventMonitor!.stop() - self.currentlyPressedModifiers = [] - if self.targetWindow != nil, self.screenToResizeOn != nil, forceClose == false, From 1bf79b7a22bbe77f6f47747a1ccd3f533200605e Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 22 May 2024 16:43:31 -0600 Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=A8=20Basic=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Window Management/Window.swift | 62 ++++--------------- Loop/Window Management/WindowEngine.swift | 49 +++++---------- .../WindowTransformAnimation.swift | 57 ++++++++++++++--- 3 files changed, 76 insertions(+), 92 deletions(-) diff --git a/Loop/Window Management/Window.swift b/Loop/Window Management/Window.swift index 866fabba..15d42752 100644 --- a/Loop/Window Management/Window.swift +++ b/Loop/Window Management/Window.swift @@ -194,7 +194,9 @@ class Window { func setFrame( _ rect: CGRect, animate: Bool = false, - sizeFirst: Bool = false + sizeFirst: Bool = false, + bounds: CGRect = .zero, // Only does something when window animations are on + completionHandler: @escaping (() -> Void) = {} ) { let enhancedUI = self.enhancedUserInterface ?? false @@ -205,7 +207,12 @@ class Window { } if animate { - let animation = WindowTransformAnimation(rect, window: self) + let animation = WindowTransformAnimation( + rect, + window: self, + bounds: bounds, + completionHandler: completionHandler + ) animation.startInBackground() } else { if sizeFirst { @@ -213,59 +220,12 @@ class Window { } self.setPosition(rect.origin) self.setSize(rect.size) + + completionHandler() } if enhancedUI { self.enhancedUserInterface = true } } - - /// MacOS doesn't provide us a way to find the minimum size of a window from the accessibility API. - /// So we deliberately force-resize the window to 0x0 and see how small it goes, take note of the frame, - /// then we restore the original window size. However, this does have one big consequence. The user - /// can see a single frame when the window is being resized to 0x0, then restored. So to counteract this, - /// we take a screenshot of the screen, overlay it, and get the minimum size then close the overlay window. - /// - Parameters: - /// - screen: The screen the window is on - /// - completion: What to do with the minimum size - func getMinSize(screen: NSScreen, completion: @escaping (CGSize) -> Void) { - // Take screenshot of screen - guard let displayID = screen.displayID else { return } - let imageRef = CGDisplayCreateImage(displayID) - let image = NSImage(cgImage: imageRef!, size: .zero) - - // Initialize the overlay NSPanel - let panel = NSPanel( - contentRect: .zero, - styleMask: [.borderless, .nonactivatingPanel], - backing: .buffered, - defer: false - ) - panel.hasShadow = false - panel.backgroundColor = NSColor.white.withAlphaComponent(0.00001) - panel.level = .screenSaver - panel.ignoresMouseEvents = true - panel.setFrame(screen.frame, display: false) - panel.contentView = NSImageView(image: image) - panel.orderFrontRegardless() - - var minSize: CGSize = .zero - DispatchQueue.main.async { - // Force-resize the window to 0x0 - let startingSize = self.size - self.setSize(CGSize(width: 0, height: 0)) - - // Take note of the minimum size - minSize = self.size - - // Restore original window size - self.setSize(startingSize) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.005) { - // Close window, then activate completion handler - panel.close() - completion(minSize) - } - } - } } diff --git a/Loop/Window Management/WindowEngine.swift b/Loop/Window Management/WindowEngine.swift index ff6b5aff..07f56c82 100644 --- a/Loop/Window Management/WindowEngine.swift +++ b/Loop/Window Management/WindowEngine.swift @@ -52,7 +52,7 @@ struct WindowEngine { return } - var targetWindowFrame = action.getFrame(window: window, bounds: screen.safeScreenFrame) + let targetWindowFrame = action.getFrame(window: window, bounds: screen.safeScreenFrame) if action.direction == .undo { WindowRecords.removeLastAction(for: window) @@ -61,42 +61,27 @@ struct WindowEngine { print("Target window frame: \(targetWindowFrame)") let enhancedUI = window.enhancedUserInterface ?? false - var animate = (!suppressAnimations && Defaults[.animateWindowResizes] && !enhancedUI) - - WindowRecords.record(window, action) - - if animate { - if PermissionsManager.ScreenRecording.getStatus() == false { - PermissionsManager.ScreenRecording.requestAccess() - animate = false - return - } - - // Calculate the window's minimum window size and change the target accordingly - window.getMinSize(screen: screen) { minSize in - let nsScreenFrame = screen.safeScreenFrame.flipY! - - if (targetWindowFrame.minX + minSize.width) > nsScreenFrame.maxX { - targetWindowFrame.origin.x = nsScreenFrame.maxX - minSize.width - Defaults[.padding].right + let animate = (!suppressAnimations && Defaults[.animateWindowResizes] && !enhancedUI) + + window.setFrame( + targetWindowFrame, + animate: animate, + sizeFirst: willChangeScreens, + bounds: screen.safeScreenFrame + ) { + // If animations are disabled, check if the window needs extra resizing + if !animate { + // Fixes an issue where window isn't resized correctly on multi-monitor setups (only happens when ) + if !window.frame.approximatelyEqual(to: targetWindowFrame) { + print("Backup resizing...") + window.setFrame(targetWindowFrame) } - - if (targetWindowFrame.minY + minSize.height) > nsScreenFrame.maxY { - targetWindowFrame.origin.y = nsScreenFrame.maxY - minSize.height - Defaults[.padding].bottom - } - - window.setFrame(targetWindowFrame, animate: true) - } - } else { - window.setFrame(targetWindowFrame, sizeFirst: willChangeScreens) - - // Fixes an issue where window isn't resized correctly on multi-monitor setups - if !window.frame.approximatelyEqual(to: targetWindowFrame) { - print("Backup resizing...") - window.setFrame(targetWindowFrame) } WindowEngine.handleSizeConstrainedWindow(window: window, screenFrame: screen.safeScreenFrame) } + + WindowRecords.record(window, action) } static func getTargetWindow() -> Window? { diff --git a/Loop/Window Management/WindowTransformAnimation.swift b/Loop/Window Management/WindowTransformAnimation.swift index 89312b90..b36fb285 100644 --- a/Loop/Window Management/WindowTransformAnimation.swift +++ b/Loop/Window Management/WindowTransformAnimation.swift @@ -10,16 +10,26 @@ import SwiftUI // Animate a window's resize! class WindowTransformAnimation: NSAnimation { private var targetFrame: CGRect - private let oldFrame: CGRect + private let originalFrame: CGRect private let window: Window + private let bounds: CGRect + private let completionHandler: () -> Void - init(_ newRect: CGRect, window: Window) { + private var lastWindowFrame: CGRect = .zero + + private var didHitMinWidth = false + private var didHitMinHeight = false + + init(_ newRect: CGRect, window: Window, bounds: CGRect, completionHandler: @escaping () -> Void) { self.targetFrame = newRect - self.oldFrame = window.frame + self.originalFrame = window.frame self.window = window - super.init(duration: 0.3, animationCurve: .linear) + self.bounds = bounds + self.completionHandler = completionHandler + super.init(duration: 0.3, animationCurve: .easeOut) self.frameRate = 60.0 self.animationBlockingMode = .nonblocking + self.lastWindowFrame = originalFrame } required init?(coder: NSCoder) { @@ -36,15 +46,44 @@ class WindowTransformAnimation: NSAnimation { override public var currentProgress: NSAnimation.Progress { didSet { let value = CGFloat(1.0 - pow(1.0 - self.currentValue, 3)) - let newFrame = CGRect( - x: oldFrame.origin.x + value * (targetFrame.origin.x - oldFrame.origin.x), - y: oldFrame.origin.y + value * (targetFrame.origin.y - oldFrame.origin.y), - width: oldFrame.size.width + value * (targetFrame.size.width - oldFrame.size.width), - height: oldFrame.size.height + value * (targetFrame.size.height - oldFrame.size.height) + + var newFrame = CGRect( + x: originalFrame.origin.x + value * (targetFrame.origin.x - originalFrame.origin.x), + y: originalFrame.origin.y + value * (targetFrame.origin.y - originalFrame.origin.y), + width: originalFrame.size.width + value * (targetFrame.size.width - originalFrame.size.width), + height: originalFrame.size.height + value * (targetFrame.size.height - originalFrame.size.height) ) + if didHitMinWidth, + newFrame.minX + lastWindowFrame.width > bounds.maxX { + newFrame.origin.x = bounds.maxX - lastWindowFrame.width + } + + if didHitMinHeight, + newFrame.minY + lastWindowFrame.height > bounds.maxY { + newFrame.origin.y = bounds.maxY - lastWindowFrame.height + } + window.setPosition(newFrame.origin) window.setSize(newFrame.size) + + let currentFrame = window.frame + + if !didHitMinWidth, + currentFrame.width.approximatelyEquals(to: lastWindowFrame.width, tolerance: 2) { + didHitMinWidth = true + } + + if !didHitMinHeight, + currentFrame.height.approximatelyEquals(to: lastWindowFrame.height, tolerance: 2) { + didHitMinHeight = true + } + + lastWindowFrame = currentFrame + + if currentProgress >= 1.0 { + completionHandler() + } } } } From 856137bbcebb249def4fb171ef21938a35ad8634 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 22 May 2024 16:47:07 -0600 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9A=A1=20Optimizations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WindowTransformAnimation.swift | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/Loop/Window Management/WindowTransformAnimation.swift b/Loop/Window Management/WindowTransformAnimation.swift index b36fb285..2411f80e 100644 --- a/Loop/Window Management/WindowTransformAnimation.swift +++ b/Loop/Window Management/WindowTransformAnimation.swift @@ -17,9 +17,6 @@ class WindowTransformAnimation: NSAnimation { private var lastWindowFrame: CGRect = .zero - private var didHitMinWidth = false - private var didHitMinHeight = false - init(_ newRect: CGRect, window: Window, bounds: CGRect, completionHandler: @escaping () -> Void) { self.targetFrame = newRect self.originalFrame = window.frame @@ -54,32 +51,17 @@ class WindowTransformAnimation: NSAnimation { height: originalFrame.size.height + value * (targetFrame.size.height - originalFrame.size.height) ) - if didHitMinWidth, - newFrame.minX + lastWindowFrame.width > bounds.maxX { + // Keep the window inside the bounds + if newFrame.maxX + (lastWindowFrame.width - newFrame.width) > bounds.maxX { newFrame.origin.x = bounds.maxX - lastWindowFrame.width } - - if didHitMinHeight, - newFrame.minY + lastWindowFrame.height > bounds.maxY { + if newFrame.maxY + (lastWindowFrame.height - newFrame.height) > bounds.maxY { newFrame.origin.y = bounds.maxY - lastWindowFrame.height } window.setPosition(newFrame.origin) window.setSize(newFrame.size) - - let currentFrame = window.frame - - if !didHitMinWidth, - currentFrame.width.approximatelyEquals(to: lastWindowFrame.width, tolerance: 2) { - didHitMinWidth = true - } - - if !didHitMinHeight, - currentFrame.height.approximatelyEquals(to: lastWindowFrame.height, tolerance: 2) { - didHitMinHeight = true - } - - lastWindowFrame = currentFrame + lastWindowFrame = window.frame if currentProgress >= 1.0 { completionHandler() From f440547f620f5b02d0d351eb73f0419dd5c80767 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 22 May 2024 16:52:12 -0600 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=94=A5=20Remove=20all=20`Permission?= =?UTF-8?q?sManager.ScreenRecording`=20references?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/AppDelegate.swift | 2 +- Loop/Localizable.xcstrings | 6 +- Loop/Managers/KeybindMonitor.swift | 2 +- Loop/Managers/LoopManager.swift | 3 +- Loop/Managers/PermissionsManager.swift | 90 ++++++++------------------ Loop/Settings/MoreSettingsView.swift | 42 ++---------- 6 files changed, 38 insertions(+), 107 deletions(-) diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index a799ef45..c44b5d2f 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -24,7 +24,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele NSApp.setActivationPolicy(.accessory) // Check & ask for accessibility access - PermissionsManager.Accessibility.requestAccess() + AccessibilityManager.requestAccess() UNUserNotificationCenter.current().delegate = self AppDelegate.requestNotificationAuthorization() diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index dd00e5b5..557ccf92 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -1731,6 +1731,7 @@ } }, "Screen recording access" : { + "extractionState" : "stale", "localizations" : { "zh-Hans" : { "stringUnit" : { @@ -1741,7 +1742,7 @@ } }, "Screen Recording Request: Content" : { - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1758,7 +1759,7 @@ } }, "Screen Recording Request: Title" : { - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1945,6 +1946,7 @@ } }, "This is only needed to animate windows being resized." : { + "extractionState" : "stale", "localizations" : { "zh-Hans" : { "stringUnit" : { diff --git a/Loop/Managers/KeybindMonitor.swift b/Loop/Managers/KeybindMonitor.swift index 939e0da5..9fa82ae1 100644 --- a/Loop/Managers/KeybindMonitor.swift +++ b/Loop/Managers/KeybindMonitor.swift @@ -26,7 +26,7 @@ class KeybindMonitor { func start() { guard self.eventMonitor == nil, - PermissionsManager.Accessibility.getStatus() else { + AccessibilityManager.getStatus() else { return } diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index e301155d..71561a34 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -15,7 +15,6 @@ class LoopManager: ObservableObject { static var lastTargetFrame: CGRect = .zero static var canAdjustSize: Bool = true - private let accessibilityAccessManager = PermissionsManager() private let keybindMonitor = KeybindMonitor.shared private let radialMenuController = RadialMenuController() @@ -368,7 +367,7 @@ class LoopManager: ObservableObject { self.targetWindow = nil // Ensure accessibility access - guard PermissionsManager.Accessibility.getStatus() else { return } + guard AccessibilityManager.getStatus() else { return } self.targetWindow = WindowEngine.getTargetWindow() guard self.targetWindow?.isAppExcluded != true else { return } diff --git a/Loop/Managers/PermissionsManager.swift b/Loop/Managers/PermissionsManager.swift index 12efea6c..15f894cd 100644 --- a/Loop/Managers/PermissionsManager.swift +++ b/Loop/Managers/PermissionsManager.swift @@ -8,76 +8,38 @@ import SwiftUI import Defaults -class PermissionsManager { - static func requestAccess() { - PermissionsManager.Accessibility.requestAccess() - PermissionsManager.ScreenRecording.requestAccess() - } - - class Accessibility { - static func getStatus() -> Bool { - // Get current state for accessibility access - let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: false] - let status = AXIsProcessTrustedWithOptions(options) - - return status - } - - @discardableResult - static func requestAccess() -> Bool { - if PermissionsManager.Accessibility.getStatus() { - return true - } - let alert = NSAlert() - alert.messageText = .init( - localized: .init( - "Accessibility Request: Title", - defaultValue: "\(Bundle.main.appName) Needs Accessibility Permissions" - ) - ) - alert.informativeText = .init( - localized: .init( - "Accessibility Request: Content", - defaultValue: "Please grant access to be able to resize windows." - ) - ) - alert.runModal() +class AccessibilityManager { + static func getStatus() -> Bool { + // Get current state for accessibility access + let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: false] + let status = AXIsProcessTrustedWithOptions(options) - let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] - let status = AXIsProcessTrustedWithOptions(options) - - return status - } + return status } - class ScreenRecording { - static func getStatus() -> Bool { - return CGPreflightScreenCaptureAccess() + @discardableResult + static func requestAccess() -> Bool { + if AccessibilityManager.getStatus() { + return true } - - static func requestAccess() { - if PermissionsManager.ScreenRecording.getStatus() { - return - } - - let alert = NSAlert() - alert.messageText = .init( - localized: .init( - "Screen Recording Request: Title", - defaultValue: "\(Bundle.main.appName) Needs Screen Recording Permissions" - ) + let alert = NSAlert() + alert.messageText = .init( + localized: .init( + "Accessibility Request: Title", + defaultValue: "\(Bundle.main.appName) Needs Accessibility Permissions" ) - alert.informativeText = .init( - localized: .init( - "Screen Recording Request: Content", - defaultValue: """ -Screen recording permissions are required to animate windows being resized. \(Bundle.main.appName) may need to be relaunched to reflect these changes. -""" - ) + ) + alert.informativeText = .init( + localized: .init( + "Accessibility Request: Content", + defaultValue: "Please grant access to be able to resize windows." ) - alert.runModal() + ) + alert.runModal() - CGRequestScreenCaptureAccess() - } + let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] + let status = AXIsProcessTrustedWithOptions(options) + + return status } } diff --git a/Loop/Settings/MoreSettingsView.swift b/Loop/Settings/MoreSettingsView.swift index 492d09c1..97771e03 100644 --- a/Loop/Settings/MoreSettingsView.swift +++ b/Loop/Settings/MoreSettingsView.swift @@ -21,7 +21,6 @@ struct MoreSettingsView: View { @Default(.animateWindowResizes) var animateWindowResizes @Default(.includeDevelopmentVersions) var includeDevelopmentVersions @State var isAccessibilityAccessGranted = false - @State var isScreenRecordingAccessGranted = false var body: some View { Form { @@ -82,11 +81,6 @@ struct MoreSettingsView: View { UnstableIndicator(.init(localized: .init("ALPHA", defaultValue: "ALPHA")), color: .orange) } } - .onChange(of: animateWindowResizes) { _ in - if animateWindowResizes == true { - PermissionsManager.ScreenRecording.requestAccess() - } - } Toggle("Hide menu until direction is chosen", isOn: $hideUntilDirectionIsChosen) @@ -123,26 +117,6 @@ struct MoreSettingsView: View { .foregroundColor(isAccessibilityAccessGranted ? .green : .red) .shadow(color: isAccessibilityAccessGranted ? .green : .red, radius: 8) } - - VStack(alignment: .leading) { - HStack { - Text("Screen recording access") - Spacer() - Text( - isAccessibilityAccessGranted - ? .init(localized: .init("Granted", defaultValue: "Granted")) - : .init(localized: .init("Not granted", defaultValue: "Not granted")) - ) - Circle() - .frame(width: 8, height: 8) - .padding(.trailing, 5) - .foregroundColor(isScreenRecordingAccessGranted ? .green : .red) - .shadow(color: isScreenRecordingAccessGranted ? .green : .red, radius: 8) - } - Text("This is only needed to animate windows being resized.") - .font(.caption) - .foregroundColor(.secondary) - } }, header: { HStack { Text("Permissions") @@ -150,21 +124,15 @@ struct MoreSettingsView: View { Spacer() Button("Request Access…") { - PermissionsManager.requestAccess() - self.isAccessibilityAccessGranted = PermissionsManager.Accessibility.getStatus() - self.isScreenRecordingAccessGranted = PermissionsManager.ScreenRecording.getStatus() + AccessibilityManager.requestAccess() + self.isAccessibilityAccessGranted = AccessibilityManager.getStatus() } .buttonStyle(.link) .foregroundStyle(Color.accentColor) - .disabled(isAccessibilityAccessGranted && isScreenRecordingAccessGranted) - .opacity(isAccessibilityAccessGranted ? isScreenRecordingAccessGranted ? 0.6 : 1 : 1) + .disabled(isAccessibilityAccessGranted) + .opacity(isAccessibilityAccessGranted ? 0.6 : 1) .onAppear { - self.isAccessibilityAccessGranted = PermissionsManager.Accessibility.getStatus() - self.isScreenRecordingAccessGranted = PermissionsManager.ScreenRecording.getStatus() - - if !isScreenRecordingAccessGranted { - self.animateWindowResizes = false - } + self.isAccessibilityAccessGranted = AccessibilityManager.getStatus() } } }) From e2df593344c4ea0cf5911c9d212dd15272abd2c3 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 22 May 2024 17:09:48 -0600 Subject: [PATCH 06/11] =?UTF-8?q?=E2=9C=A8=20#298=20Animate=20windows=20ev?= =?UTF-8?q?en=20when=20=E2=80=9CAnimate=20windows=20being=20resized?= =?UTF-8?q?=E2=80=9D=20is=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/LoopManager.swift | 6 ++---- Loop/Window Management/WindowEngine.swift | 5 ++--- .../Window Management/WindowTransformAnimation.swift | 12 ++++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index 71561a34..fb92e403 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -227,8 +227,7 @@ class LoopManager: ObservableObject { WindowEngine.resize( self.targetWindow!, to: self.currentAction, - on: screenToResizeOn, - suppressAnimations: true + on: screenToResizeOn ) } } @@ -254,8 +253,7 @@ class LoopManager: ObservableObject { WindowEngine.resize( self.targetWindow!, to: self.currentAction, - on: screenToResizeOn, - suppressAnimations: true + on: screenToResizeOn ) } } diff --git a/Loop/Window Management/WindowEngine.swift b/Loop/Window Management/WindowEngine.swift index 07f56c82..dd653824 100644 --- a/Loop/Window Management/WindowEngine.swift +++ b/Loop/Window Management/WindowEngine.swift @@ -17,8 +17,7 @@ struct WindowEngine { static func resize( _ window: Window, to action: WindowAction, - on screen: NSScreen, - suppressAnimations: Bool = false + on screen: NSScreen ) { guard action.direction != .noAction else { return } let willChangeScreens = ScreenManager.screenContaining(window) != screen @@ -61,7 +60,7 @@ struct WindowEngine { print("Target window frame: \(targetWindowFrame)") let enhancedUI = window.enhancedUserInterface ?? false - let animate = (!suppressAnimations && Defaults[.animateWindowResizes] && !enhancedUI) + let animate = Defaults[.animateWindowResizes] && !enhancedUI window.setFrame( targetWindowFrame, diff --git a/Loop/Window Management/WindowTransformAnimation.swift b/Loop/Window Management/WindowTransformAnimation.swift index 2411f80e..b387e9f2 100644 --- a/Loop/Window Management/WindowTransformAnimation.swift +++ b/Loop/Window Management/WindowTransformAnimation.swift @@ -17,6 +17,10 @@ class WindowTransformAnimation: NSAnimation { private var lastWindowFrame: CGRect = .zero + // Using ids for each ongoing animation, we can cancel as a new window animation is started for that specific window + private var id: UUID = UUID() + static var currentAnimations: [CGWindowID: UUID] = [:] + init(_ newRect: CGRect, window: Window, bounds: CGRect, completionHandler: @escaping () -> Void) { self.targetFrame = newRect self.originalFrame = window.frame @@ -27,6 +31,8 @@ class WindowTransformAnimation: NSAnimation { self.frameRate = 60.0 self.animationBlockingMode = .nonblocking self.lastWindowFrame = originalFrame + + WindowTransformAnimation.currentAnimations[window.cgWindowID] = self.id } required init?(coder: NSCoder) { @@ -42,6 +48,11 @@ class WindowTransformAnimation: NSAnimation { override public var currentProgress: NSAnimation.Progress { didSet { + guard WindowTransformAnimation.currentAnimations.contains(where: { $0.value == self.id }) else { + stop() + return + } + let value = CGFloat(1.0 - pow(1.0 - self.currentValue, 3)) var newFrame = CGRect( @@ -64,6 +75,7 @@ class WindowTransformAnimation: NSAnimation { lastWindowFrame = window.frame if currentProgress >= 1.0 { + WindowTransformAnimation.currentAnimations[window.cgWindowID] = nil completionHandler() } } From b1c9eedeb5ec08f5f9ae8c8ed22f3f53c71b86c3 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Wed, 22 May 2024 17:29:31 -0600 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=92=A1=20Update=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Window Management/Window.swift | 2 +- Loop/Window Management/WindowEngine.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Loop/Window Management/Window.swift b/Loop/Window Management/Window.swift index 15d42752..83cebe15 100644 --- a/Loop/Window Management/Window.swift +++ b/Loop/Window Management/Window.swift @@ -194,7 +194,7 @@ class Window { func setFrame( _ rect: CGRect, animate: Bool = false, - sizeFirst: Bool = false, + sizeFirst: Bool = false, // Only does something when window animations are off bounds: CGRect = .zero, // Only does something when window animations are on completionHandler: @escaping (() -> Void) = {} ) { diff --git a/Loop/Window Management/WindowEngine.swift b/Loop/Window Management/WindowEngine.swift index dd653824..217420be 100644 --- a/Loop/Window Management/WindowEngine.swift +++ b/Loop/Window Management/WindowEngine.swift @@ -70,7 +70,7 @@ struct WindowEngine { ) { // If animations are disabled, check if the window needs extra resizing if !animate { - // Fixes an issue where window isn't resized correctly on multi-monitor setups (only happens when ) + // Fixes an issue where window isn't resized correctly on multi-monitor setups if !window.frame.approximatelyEqual(to: targetWindowFrame) { print("Backup resizing...") window.setFrame(targetWindowFrame) From 6258d9bc57802d189bb75571e88afa1ffeb55f5e Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Thu, 23 May 2024 17:48:47 -0600 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=90=9B=20#350=20Fix=20bug=20in=20`f?= =?UTF-8?q?lipY`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Extensions/CGGeometry+Extensions.swift | 21 +++++++++------------ Loop/Extensions/NSScreen+Extensions.swift | 4 ++-- Loop/Managers/WindowDragManager.swift | 15 +++++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Loop/Extensions/CGGeometry+Extensions.swift b/Loop/Extensions/CGGeometry+Extensions.swift index fed482dc..38a37562 100644 --- a/Loop/Extensions/CGGeometry+Extensions.swift +++ b/Loop/Extensions/CGGeometry+Extensions.swift @@ -30,9 +30,12 @@ extension CGPoint { * (from.y - comparisonPoint.y) } - var flipY: CGPoint? { - guard let screen = NSScreen.main else { return nil } - return CGPoint(x: self.x, y: screen.frame.maxY - self.y) + func flipY(maxY: CGFloat) -> CGPoint { + CGPoint(x: self.x, y: maxY - self.y) + } + + func flipY(screen: NSScreen) -> CGPoint { + return flipY(maxY: screen.frame.maxY) } func approximatelyEqual(to point: CGPoint, tolerance: CGFloat = 10) -> Bool { @@ -52,18 +55,12 @@ extension CGSize { } extension CGRect { - var flipY: CGRect? { - guard let screen = NSScreen.main else { return nil } - return CGRect( - x: self.minX, - y: screen.frame.maxY - self.maxY, - width: self.width, - height: self.height - ) + func flipY(screen: NSScreen) -> CGRect { + return flipY(maxY: screen.frame.maxY) } func flipY(maxY: CGFloat) -> CGRect { - return CGRect( + CGRect( x: self.minX, y: maxY - self.maxY, width: self.width, diff --git a/Loop/Extensions/NSScreen+Extensions.swift b/Loop/Extensions/NSScreen+Extensions.swift index 16a8415c..a2fbd1d3 100644 --- a/Loop/Extensions/NSScreen+Extensions.swift +++ b/Loop/Extensions/NSScreen+Extensions.swift @@ -29,7 +29,7 @@ extension NSScreen { let displayID = self.displayID else { print("ERROR: Failed to get NSScreen.displayID in NSScreen.safeScreenFrame") - return self.frame.flipY! + return self.frame.flipY(screen: self) } let screenFrame = CGDisplayBounds(displayID) @@ -68,7 +68,7 @@ extension NSScreen { let displayID = self.displayID else { print("ERROR: Failed to get NSScreen.displayID in NSScreen.displayBounds") - return self.frame.flipY! + return self.frame.flipY(screen: self) } return CGDisplayBounds(displayID) diff --git a/Loop/Managers/WindowDragManager.swift b/Loop/Managers/WindowDragManager.swift index e56a8530..88e5dd3a 100644 --- a/Loop/Managers/WindowDragManager.swift +++ b/Loop/Managers/WindowDragManager.swift @@ -68,8 +68,13 @@ class WindowDragManager { } private func setCurrentDraggingWindow() { + guard let screen = NSScreen.screenWithMouse else { + return + } + + let mousePosition = NSEvent.mouseLocation.flipY(screen: screen) + guard - let mousePosition = NSEvent.mouseLocation.flipY, let draggingWindow = WindowEngine.windowAtPosition(mousePosition), !draggingWindow.isAppExcluded else { @@ -121,13 +126,11 @@ class WindowDragManager { } private func getWindowSnapDirection() { - guard - let mousePosition = NSEvent.mouseLocation.flipY, - let screen = NSScreen.screenWithMouse, - let screenFrame = screen.frame.flipY - else { + guard let screen = NSScreen.screenWithMouse else { return } + let mousePosition = NSEvent.mouseLocation.flipY(maxY: screen.frame.maxY) + let screenFrame = screen.frame.flipY(maxY: screen.frame.maxY) self.previewController.setScreen(to: screen) let ignoredFrame = screenFrame.insetBy(dx: 2, dy: 2) From 6e8ad0bb1b0a45e1a73b4ba5384a4e46f3a51caf Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Thu, 23 May 2024 18:35:32 -0600 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9A=A1=20#314=20Improve=20performance?= =?UTF-8?q?=20even=20more?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/LoopManager.swift | 156 +++++++++--------- .../Keybind Recorder/TriggerKeycorder.swift | 2 +- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index fb92e403..8fd277b1 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -29,7 +29,7 @@ class LoopManager: ObservableObject { private var mouseMovedEventMonitor: EventMonitor? private var keyDownEventMonitor: EventMonitor? private var middleClickMonitor: EventMonitor? - private var lastTriggerKeyClick: Date = Date.now + private var lastTriggerKeyClick: Date = .now @Published var currentAction: WindowAction = .init(.noAction) private var initialMousePosition: CGPoint = CGPoint() @@ -43,24 +43,24 @@ class LoopManager: ObservableObject { } func startObservingKeys() { - self.flagsChangedEventMonitor = NSEventMonitor( + flagsChangedEventMonitor = NSEventMonitor( scope: .global, eventMask: .flagsChanged, handler: handleLoopKeypress(_:) ) - self.mouseMovedEventMonitor = NSEventMonitor( + mouseMovedEventMonitor = NSEventMonitor( scope: .global, eventMask: [.mouseMoved, .otherMouseDragged], handler: mouseMoved(_:) ) - self.middleClickMonitor = CGEventMonitor( + middleClickMonitor = CGEventMonitor( eventMask: [.otherMouseDragged, .otherMouseUp], callback: handleMiddleClick(cgEvent:) ) - self.keyDownEventMonitor = NSEventMonitor( + keyDownEventMonitor = NSEventMonitor( scope: .global, eventMask: .keyDown ) { _ in @@ -80,13 +80,13 @@ class LoopManager: ObservableObject { } } - self.flagsChangedEventMonitor!.start() - self.middleClickMonitor!.start() - self.keyDownEventMonitor!.start() + flagsChangedEventMonitor!.start() + middleClickMonitor!.start() + keyDownEventMonitor!.start() } private func mouseMoved(_ event: NSEvent) { - guard self.isLoopActive else { return } + guard isLoopActive else { return } keybindMonitor.canPassthroughSpecialEvents = false let noActionDistance: CGFloat = 10 @@ -101,8 +101,8 @@ class LoopManager: ObservableObject { } // Get angle & distance to mouse - self.angleToMouse = mouseAngle - self.distanceToMouse = mouseDistance + angleToMouse = mouseAngle + distanceToMouse = mouseDistance var resizeDirection: WindowDirection = .noAction @@ -134,7 +134,7 @@ class LoopManager: ObservableObject { resizeDirection = .maximize } - if resizeDirection != self.currentAction.direction { + if resizeDirection != currentAction.direction { changeAction(.init(resizeDirection)) } } @@ -163,10 +163,10 @@ class LoopManager: ObservableObject { } else if currentAction.direction == .custom { // We need to check if *all* the characteristics of the action are the same - nextIndex = (cycle.firstIndex(of: self.currentAction) ?? -1) + 1 + nextIndex = (cycle.firstIndex(of: currentAction) ?? -1) + 1 } else { // Only check the direction, since the rest of the info is insignificant - nextIndex = (cycle.firstIndex { $0.direction == self.currentAction.direction } ?? -1) + 1 + nextIndex = (cycle.firstIndex { $0.direction == currentAction.direction } ?? -1) + 1 } if nextIndex >= cycle.count { @@ -178,9 +178,9 @@ class LoopManager: ObservableObject { private func changeAction(_ action: WindowAction) { guard - self.currentAction != action || action.willManipulateCurrentWindowSize, - self.isLoopActive, - let currentScreen = self.screenToResizeOn + currentAction != action || action.willManipulateCurrentWindowSize, + isLoopActive, + let currentScreen = screenToResizeOn else { return } @@ -204,12 +204,12 @@ class LoopManager: ObservableObject { newScreen = previousScreen } - if self.currentAction.direction == .noAction { - self.currentAction = .init(.center) + if currentAction.direction == .noAction { + currentAction = .init(.center) } - self.screenToResizeOn = newScreen - self.previewController.setScreen(to: newScreen) + screenToResizeOn = newScreen + previewController.setScreen(to: newScreen) // This is only needed because if preview window is moved // onto a new screen, it needs to receive a window action @@ -218,15 +218,15 @@ class LoopManager: ObservableObject { } if action.direction == .cycle { - self.currentAction = newAction - self.changeAction(action) + currentAction = newAction + changeAction(action) } else { - if let screenToResizeOn = self.screenToResizeOn, + if let screenToResizeOn = screenToResizeOn, !Defaults[.previewVisibility] { performHapticFeedback() WindowEngine.resize( - self.targetWindow!, - to: self.currentAction, + targetWindow!, + to: currentAction, on: screenToResizeOn ) } @@ -240,10 +240,10 @@ class LoopManager: ObservableObject { performHapticFeedback() if newAction != currentAction || newAction.willManipulateCurrentWindowSize { - self.currentAction = newAction + currentAction = newAction if Defaults[.hideUntilDirectionIsChosen] { - self.openWindows() + openWindows() } DispatchQueue.main.async { Notification.Name.updateUIDirection.post(userInfo: ["action": self.currentAction]) @@ -258,26 +258,26 @@ class LoopManager: ObservableObject { } } - print("Window action changed: \(self.currentAction.direction)") + print("Window action changed: \(currentAction.direction)") } } func handleMiddleClick(cgEvent: CGEvent) -> Unmanaged? { if let event = NSEvent(cgEvent: cgEvent), event.buttonNumber == 2, Defaults[.middleClickTriggersLoop] { - if event.type == .otherMouseDragged && !self.isLoopActive { - self.openLoop() + if event.type == .otherMouseDragged && !isLoopActive { + openLoop() } - if event.type == .otherMouseUp && self.isLoopActive { - self.closeLoop() + if event.type == .otherMouseUp && isLoopActive { + closeLoop() } } return Unmanaged.passRetained(cgEvent) } private func handleTriggerDelay() { - if self.triggerDelayTimer == nil { - self.triggerDelayTimer = Timer.scheduledTimer( + if triggerDelayTimer == nil { + triggerDelayTimer = Timer.scheduledTimer( withTimeInterval: Double(Defaults[.triggerDelay]), repeats: false ) { _ in @@ -287,11 +287,11 @@ class LoopManager: ObservableObject { } private func handleDoubleClickToTrigger(_ useTriggerDelay: Bool) { - if abs(self.lastTriggerKeyClick.timeIntervalSinceNow) < NSEvent.doubleClickInterval { + if abs(lastTriggerKeyClick.timeIntervalSinceNow) < NSEvent.doubleClickInterval { if useTriggerDelay { - self.handleTriggerDelay() + handleTriggerDelay() } else { - self.openLoop() + openLoop() } } } @@ -330,7 +330,7 @@ class LoopManager: ObservableObject { } else { openLoop() } - self.lastTriggerKeyClick = Date.now + lastTriggerKeyClick = .now } else { if isLoopActive { closeLoop() @@ -339,75 +339,77 @@ class LoopManager: ObservableObject { } private func processModifiers(_ event: NSEvent) { - if self.currentlyPressedModifiers.contains(event.keyCode) { - self.currentlyPressedModifiers.remove(event.keyCode) - } else if event.modifierFlags.wasKeyUp { - self.currentlyPressedModifiers = [] + if event.modifierFlags.wasKeyUp { + currentlyPressedModifiers = [] + } else if currentlyPressedModifiers.contains(event.keyCode) { + currentlyPressedModifiers.remove(event.keyCode) } else { - self.currentlyPressedModifiers.insert(event.keyCode) + currentlyPressedModifiers.insert(event.keyCode) } // Backup system in case keys are pressed at the exact same time let flags = event.modifierFlags.convertToCGKeyCode() - if flags.count > 1 && !self.currentlyPressedModifiers.contains(flags) { + if flags.count != currentlyPressedModifiers.count { for key in flags where CGKeyCode.keyToImage.contains(where: { $0.key == key }) { - if !self.currentlyPressedModifiers.map({ $0.baseModifier }).contains(key) { - self.currentlyPressedModifiers.insert(key) + if !currentlyPressedModifiers.map({ $0.baseModifier }).contains(key) { + currentlyPressedModifiers.insert(key) } } } } private func openLoop() { - guard self.isLoopActive == false else { return } + guard isLoopActive == false else { return } - self.currentAction = .init(.noAction) - self.targetWindow = nil + currentAction = .init(.noAction) + targetWindow = nil // Ensure accessibility access guard AccessibilityManager.getStatus() else { return } - self.targetWindow = WindowEngine.getTargetWindow() - guard self.targetWindow?.isAppExcluded != true else { return } + targetWindow = WindowEngine.getTargetWindow() + guard targetWindow?.isAppExcluded != true else { return } - self.initialMousePosition = NSEvent.mouseLocation - self.screenToResizeOn = NSScreen.main + initialMousePosition = NSEvent.mouseLocation + screenToResizeOn = NSScreen.main if !Defaults[.disableCursorInteraction] { - self.mouseMovedEventMonitor!.start() + mouseMovedEventMonitor!.start() } if !Defaults[.hideUntilDirectionIsChosen] { - self.openWindows() + openWindows() } - self.keybindMonitor.start() + keybindMonitor.start() isLoopActive = true - if let window = self.targetWindow { + if let window = targetWindow { LoopManager.lastTargetFrame = window.frame } } private func closeLoop(forceClose: Bool = false) { - self.triggerDelayTimer = nil - self.closeWindows() + triggerDelayTimer = nil + closeWindows() + + keybindMonitor.stop() + mouseMovedEventMonitor!.stop() - self.keybindMonitor.stop() - self.mouseMovedEventMonitor!.stop() + currentlyPressedModifiers = [] - if self.targetWindow != nil, - self.screenToResizeOn != nil, + if targetWindow != nil, + screenToResizeOn != nil, forceClose == false, - self.currentAction.direction != .noAction, - self.isLoopActive { - if let screenToResizeOn = self.screenToResizeOn, + currentAction.direction != .noAction, + isLoopActive { + if let screenToResizeOn = screenToResizeOn, Defaults[.previewVisibility] { LoopManager.canAdjustSize = false WindowEngine.resize( - self.targetWindow!, - to: self.currentAction, + targetWindow!, + to: currentAction, on: screenToResizeOn ) } @@ -419,7 +421,7 @@ class LoopManager: ObservableObject { Defaults[.timesLooped] += 1 IconManager.checkIfUnlockedNewIcon() } else { - if self.targetWindow == nil && isLoopActive { + if targetWindow == nil && isLoopActive { NSSound.beep() } } @@ -431,20 +433,20 @@ class LoopManager: ObservableObject { } private func openWindows() { - if Defaults[.previewVisibility] && self.targetWindow != nil { - self.previewController.open(screen: self.screenToResizeOn!, window: targetWindow) + if Defaults[.previewVisibility] && targetWindow != nil { + previewController.open(screen: screenToResizeOn!, window: targetWindow) } if Defaults[.radialMenuVisibility] { - self.radialMenuController.open( - position: self.initialMousePosition, - frontmostWindow: self.targetWindow + radialMenuController.open( + position: initialMousePosition, + frontmostWindow: targetWindow ) } } private func closeWindows() { - self.radialMenuController.close() - self.previewController.close() + radialMenuController.close() + previewController.close() } } diff --git a/Loop/Settings/Keybindings/Keybind Recorder/TriggerKeycorder.swift b/Loop/Settings/Keybindings/Keybind Recorder/TriggerKeycorder.swift index 573ed3cb..7d495e3d 100644 --- a/Loop/Settings/Keybindings/Keybind Recorder/TriggerKeycorder.swift +++ b/Loop/Settings/Keybindings/Keybind Recorder/TriggerKeycorder.swift @@ -113,7 +113,7 @@ struct TriggerKeycorder: View { // Backup system in case keys are pressed at the exact same time let flags = event.modifierFlags.convertToCGKeyCode() - if flags.count > 1 && !self.selectionKey.contains(flags) { + if flags.count != selectionKey.count { for key in flags where CGKeyCode.keyToImage.contains(where: { $0.key == key }) { if !self.selectionKey.map({ $0.baseModifier }).contains(key) { self.selectionKey.insert(key) From a0f88c89d101d3cff9866e61a21bd11c478398dc Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Thu, 23 May 2024 20:16:35 -0600 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8=20#314=20Improve=20key=20passth?= =?UTF-8?q?rough?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/KeybindMonitor.swift | 74 +++++++++++++-------------- Loop/Managers/LoopManager.swift | 15 ++---- Loop/Managers/WindowDragManager.swift | 2 +- 3 files changed, 40 insertions(+), 51 deletions(-) diff --git a/Loop/Managers/KeybindMonitor.swift b/Loop/Managers/KeybindMonitor.swift index 9fa82ae1..86b639e5 100644 --- a/Loop/Managers/KeybindMonitor.swift +++ b/Loop/Managers/KeybindMonitor.swift @@ -31,37 +31,41 @@ class KeybindMonitor { } self.eventMonitor = CGEventMonitor(eventMask: [.keyDown, .keyUp]) { cgEvent in - if cgEvent.type == .keyDown || cgEvent.type == .keyUp, - let event = NSEvent(cgEvent: cgEvent) { - if event.type == .keyUp { - KeybindMonitor.shared.pressedKeys.remove(event.keyCode.baseKey) - } else if event.type == .keyDown { - KeybindMonitor.shared.pressedKeys.insert(event.keyCode.baseKey) - } + guard + cgEvent.type == .keyDown || cgEvent.type == .keyUp, + let event = NSEvent(cgEvent: cgEvent) + else { + return Unmanaged.passUnretained(cgEvent) + } - // Special events such as the emoji key - if self.specialEvents.contains(event.keyCode.baseKey) { - if self.canPassthroughSpecialEvents { - return Unmanaged.passRetained(cgEvent) - } - return nil - } + if event.type == .keyUp { + KeybindMonitor.shared.pressedKeys.remove(event.keyCode.baseKey) + } else if event.type == .keyDown { + KeybindMonitor.shared.pressedKeys.insert(event.keyCode.baseKey) + } - // If this is a valid event, don't passthrough - if self.performKeybind(event: event) { - return nil + // Special events such as the emoji key + if self.specialEvents.contains(event.keyCode.baseKey) { + if self.canPassthroughSpecialEvents { + return Unmanaged.passUnretained(cgEvent) } + return nil + } - // If this wasn't, check if it was a system keybind (ex. screenshot), and - // in that case, passthrough and foce-close Loop - if CGKeyCode.systemKeybinds.contains(self.pressedKeys) { - Notification.Name.forceCloseLoop.post() - print("Detected system keybind, closing!") - return Unmanaged.passRetained(cgEvent) - } + // If this is a valid event, don't passthrough + if self.performKeybind(event: event) { + return nil } - return Unmanaged.passRetained(cgEvent) + // If this wasn't, check if it was a system keybind (ex. screenshot), and + // in that case, passthrough and foce-close Loop + if CGKeyCode.systemKeybinds.contains(self.pressedKeys) { + Notification.Name.forceCloseLoop.post() + print("Detected system keybind, closing!") + return Unmanaged.passUnretained(cgEvent) + } + + return Unmanaged.passUnretained(cgEvent) } self.flagsEventMonitor = CGEventMonitor(eventMask: .flagsChanged) { cgEvent in @@ -75,7 +79,7 @@ class KeybindMonitor { self.performKeybind(event: event) } - return Unmanaged.passRetained(cgEvent) + return Unmanaged.passUnretained(cgEvent) } self.eventMonitor!.start() @@ -86,11 +90,6 @@ class KeybindMonitor { self.resetPressedKeys() self.canPassthroughSpecialEvents = true - guard self.eventMonitor != nil && - self.flagsEventMonitor != nil else { - return - } - self.eventMonitor?.stop() self.eventMonitor = nil @@ -100,17 +99,16 @@ class KeybindMonitor { @discardableResult private func performKeybind(event: NSEvent) -> Bool { - // If the current key up event is within 100 ms of the last key up event, return. - // This is used when the user is pressing 2+ keys so that it doesn't switch back - // to the one key direction when they're letting go of the keys. - if event.type == .keyUp || - (event.type == .flagsChanged && - !event.modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.shift)) { - if (abs(lastKeyReleaseTime.timeIntervalSinceNow)) > 0.1 { + if event.type == .keyUp { + // If the current key up event is within 100 ms of the last key up event, return. + // This is used when the user is pressing 2+ keys so that it doesn't switch back + // to the one key direction when they're letting go of the keys. + if abs(lastKeyReleaseTime.timeIntervalSinceNow) < 0.1 { print("performKeybind: returning true due to key release") return true } lastKeyReleaseTime = Date.now + return false } if pressedKeys.contains(.kVK_Escape) { diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index 8fd277b1..721873df 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -272,7 +272,7 @@ class LoopManager: ObservableObject { closeLoop() } } - return Unmanaged.passRetained(cgEvent) + return Unmanaged.passUnretained(cgEvent) } private func handleTriggerDelay() { @@ -304,18 +304,9 @@ class LoopManager: ObservableObject { let triggerKey = Defaults[.triggerKey] let wasKeyDown = (event.type == .keyDown || currentlyPressedModifiers.count > previousModifiers.count) - let keybindIsValid = ( - Defaults[.triggerKey].contains(event.keyCode) || - Defaults[.keybinds].contains { - $0.keybind.contains(event.keyCode) - } - ) - if wasKeyDown, keybindIsValid, triggerKey.isSubset(of: currentlyPressedModifiers) { - guard - !isLoopActive, - currentlyPressedModifiers.count <= triggerKey.count - else { + if wasKeyDown, triggerKey.isSubset(of: currentlyPressedModifiers) { + guard !isLoopActive else { return } diff --git a/Loop/Managers/WindowDragManager.swift b/Loop/Managers/WindowDragManager.swift index 88e5dd3a..1fce60bc 100644 --- a/Loop/Managers/WindowDragManager.swift +++ b/Loop/Managers/WindowDragManager.swift @@ -46,7 +46,7 @@ class WindowDragManager { } } - return Unmanaged.passRetained(cgEvent) + return Unmanaged.passUnretained(cgEvent) } self.leftMouseUpMonitor = NSEventMonitor(scope: .global, eventMask: .leftMouseUp) { _ in From 5c038502a941297ef95adda8eac5d0178a2f32cd Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Thu, 23 May 2024 20:32:52 -0600 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=90=9B=20#314=20Fix=20small=20regre?= =?UTF-8?q?ssion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop/Managers/LoopManager.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Loop/Managers/LoopManager.swift b/Loop/Managers/LoopManager.swift index 721873df..79f280ad 100644 --- a/Loop/Managers/LoopManager.swift +++ b/Loop/Managers/LoopManager.swift @@ -303,10 +303,15 @@ class LoopManager: ObservableObject { processModifiers(event) let triggerKey = Defaults[.triggerKey] - let wasKeyDown = (event.type == .keyDown || currentlyPressedModifiers.count > previousModifiers.count) + let wasKeyDown = event.type == .keyDown || currentlyPressedModifiers.count > previousModifiers.count if wasKeyDown, triggerKey.isSubset(of: currentlyPressedModifiers) { - guard !isLoopActive else { + guard + !isLoopActive, + + // This makes sure that the amount of keys being pressed is not more than the actual trigger key + currentlyPressedModifiers.count <= triggerKey.count + else { return } @@ -323,9 +328,7 @@ class LoopManager: ObservableObject { } lastTriggerKeyClick = .now } else { - if isLoopActive { - closeLoop() - } + closeLoop() } } @@ -382,6 +385,8 @@ class LoopManager: ObservableObject { } private func closeLoop(forceClose: Bool = false) { + guard isLoopActive == true else { return } + triggerDelayTimer = nil closeWindows()