Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Better window animations + Remove Hyperkey conflicts + Fix window snapping on external screens #360

Merged
merged 11 commits into from
May 24, 2024
2 changes: 1 addition & 1 deletion Loop/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
21 changes: 9 additions & 12 deletions Loop/Extensions/CGGeometry+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions Loop/Extensions/NSScreen+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions Loop/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,7 @@
}
},
"Screen recording access" : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
Expand All @@ -1741,7 +1742,7 @@
}
},
"Screen Recording Request: Content" : {
"extractionState" : "extracted_with_value",
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand All @@ -1758,7 +1759,7 @@
}
},
"Screen Recording Request: Title" : {
"extractionState" : "extracted_with_value",
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -1945,6 +1946,7 @@
}
},
"This is only needed to animate windows being resized." : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
Expand Down
76 changes: 37 additions & 39 deletions Loop/Managers/KeybindMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,46 @@ class KeybindMonitor {

func start() {
guard self.eventMonitor == nil,
PermissionsManager.Accessibility.getStatus() else {
AccessibilityManager.getStatus() else {
return
}

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
Expand All @@ -75,7 +79,7 @@ class KeybindMonitor {

self.performKeybind(event: event)
}
return Unmanaged.passRetained(cgEvent)
return Unmanaged.passUnretained(cgEvent)
}

self.eventMonitor!.start()
Expand All @@ -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

Expand All @@ -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) {
Expand Down