Skip to content

Commit

Permalink
fix: ui would sometimes stay open (closes #588)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwouis committed Oct 10, 2020
1 parent 3019dd5 commit 8912c70
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 23 deletions.
5 changes: 0 additions & 5 deletions src/logic/ATShortcut.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ class ATShortcut {
state = state == .down ? .up : .down
if state == .up {
KeyRepeatTimer.timer?.invalidate()
} else {
// macOS bug: the app doesn't receive either local or global modifierChanged events during Space transition
// we force holdShortcut to be .down if nextWindowShortcut trigger during a transition, since we know it has to be down at that point
let suffix = shortcutId == "nextWindowShortcut" ? "" : "2"
ControlsTab.shortcuts["holdShortcut" + suffix]!.state = .down
}
if (triggerPhase == .down && shortcutState == .down) || (triggerPhase == .up && shortcutState == .up) {
return true
Expand Down
2 changes: 0 additions & 2 deletions src/logic/Spaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ class Spaces {
debugPrint("OS event", "activeSpaceDidChangeNotification")
idsAndIndexes = allIdsAndIndexes()
updateCurrentSpace()
guard App.app.appIsBeingUsed else { return }
App.app.reopenUi()
})
}

Expand Down
49 changes: 34 additions & 15 deletions src/logic/events/KeyboardEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class KeyboardEvents {
"holdShortcut2": 3,
]
static var eventHotKeyRefs = [String: EventHotKeyRef?]()
static var hotModifierEventHandler: EventHandlerRef?
static var hotKeyPressedEventHandler: EventHandlerRef?
static var hotKeyReleasedEventHandler: EventHandlerRef?
static var localMonitor: Any!
Expand Down Expand Up @@ -70,22 +69,33 @@ class KeyboardEvents {
}
}

static func addLocalEventHandler() {
localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp, .flagsChanged]) { event in
static func addEventHandlers() {
addLocalMonitorForKeyDownAndKeyUp()
addCgEventTapForModifierFlags()
}

private static func addLocalMonitorForKeyDownAndKeyUp() {
localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp]) { event in
return handleEvent(nil, nil, event.type == .keyDown ? UInt32(event.keyCode) : nil, cocoaToCarbonFlags(event.modifierFlags), event.type == .keyDown ? event.isARepeat : false) ? nil : event
}
}

private static func addCgEventTapForModifierFlags() {
let eventMask = [CGEventType.flagsChanged].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
// CGEvent.tapCreate returns null if ensureAccessibilityCheckboxIsChecked() didn't pass
// CGEvent.tapCreate is unaffected by SecureInput for .flagsChanged
eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: eventMask,
callback: cgEventFlagsChangedHandler,
userInfo: nil)
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
}

private static func addGlobalHandlerIfNeeded(_ shortcut: Shortcut) {
if shortcut.keyCode == .none && hotModifierEventHandler == nil {
var eventTypes = [EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: OSType(kEventRawKeyModifiersChanged))]
InstallEventHandler(GetEventMonitorTarget(), { (_: EventHandlerCallRef?, event: EventRef?, _: UnsafeMutableRawPointer?) -> OSStatus in
var modifiers = UInt32(0)
GetEventParameter(event, EventParamName(kEventParamKeyModifiers), EventParamType(typeUInt32), nil, MemoryLayout<UInt32>.size, nil, &modifiers)
handleEvent(nil, nil, nil, modifiers, false)
return noErr
}, eventTypes.count, &eventTypes, nil, &hotModifierEventHandler)
}
if shortcut.keyCode != .none && hotKeyPressedEventHandler == nil {
var eventTypes = [EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: OSType(kEventHotKeyPressed))]
InstallEventHandler(shortcutEventTarget, { (_: EventHandlerCallRef?, event: EventRef?, _: UnsafeMutableRawPointer?) -> OSStatus in
Expand Down Expand Up @@ -114,9 +124,6 @@ class KeyboardEvents {
hotKeyPressedEventHandler = nil
RemoveEventHandler(hotKeyReleasedEventHandler_)
hotKeyReleasedEventHandler = nil
} else if let hotModifierEventHandler_ = hotModifierEventHandler, (globalShortcuts.allSatisfy { $0.shortcut.keyCode != .none }) {
RemoveEventHandler(hotModifierEventHandler_)
hotModifierEventHandler = nil
}
}
}
Expand All @@ -132,3 +139,15 @@ fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutStat
}
return someShortcutTriggered
}

fileprivate func cgEventFlagsChangedHandler(proxy: CGEventTapProxy, type: CGEventType, cgEvent: CGEvent, userInfo: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
if type == .flagsChanged {
let modifiers = cocoaToCarbonFlags(NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)))
if handleEvent(nil, nil, nil, modifiers, false) {
return nil // focused app won't receive the event
}
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) {
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
return Unmanaged.passUnretained(cgEvent) // focused app will receive the event
}
2 changes: 1 addition & 1 deletion src/ui/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
Applications.initialDiscovery()
self.preferencesWindow = PreferencesWindow()
self.feedbackWindow = FeedbackWindow()
KeyboardEvents.addLocalEventHandler()
KeyboardEvents.addEventHandlers()
MouseEvents.observe()
// TODO: undeterministic; events in the queue may still be processing; good enough for now
DispatchQueue.main.async { () -> () in Windows.sortByLevel() }
Expand Down
2 changes: 2 additions & 0 deletions src/ui/main-window/ThumbnailsPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class ThumbnailsPanel: NSPanel {
contentView!.addSubview(thumbnailsView)
preservesContentDuringLiveResize = false
disableSnapshotRestoration()
// triggering AltTab before or during Space transition animation brings the window on the Space post-transition
collectionBehavior = .canJoinAllSpaces
// 2nd highest level possible; this allows the app to go on top of context menus
// highest level is .screenSaver but makes drag and drop on top the main window impossible
level = .popUpMenu
Expand Down

0 comments on commit 8912c70

Please sign in to comment.