Skip to content

Commit

Permalink
feat: add shortcut modifier side
Browse files Browse the repository at this point in the history
  • Loading branch information
decodism committed Nov 18, 2023
1 parent 9e03d5f commit 8a92433
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 11 deletions.
14 changes: 14 additions & 0 deletions src/api-wrappers/HelperExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,17 @@ extension Optional where Wrapped == String {
return (self ?? "").localizedStandardCompare(string ?? "")
}
}

extension NSEvent.ModifierFlags {
static let leftShift = Self(rawValue: UInt(NX_DEVICELSHIFTKEYMASK))
static let rightShift = Self(rawValue: UInt(NX_DEVICERSHIFTKEYMASK))

static let leftControl = Self(rawValue: UInt(NX_DEVICELCTLKEYMASK))
static let rightControl = Self(rawValue: UInt(NX_DEVICERCTLKEYMASK))

static let leftOption = Self(rawValue: UInt(NX_DEVICELALTKEYMASK))
static let rightOption = Self(rawValue: UInt(NX_DEVICERALTKEYMASK))

static let leftCommand = Self(rawValue: UInt(NX_DEVICELCMDKEYMASK))
static let rightCommand = Self(rawValue: UInt(NX_DEVICERCMDKEYMASK))
}
5 changes: 4 additions & 1 deletion src/logic/ATShortcut.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class ATShortcut {
self.index = index
}

func matches(_ id: EventHotKeyID?, _ shortcutState: ShortcutState?, _ keyCode: UInt32?, _ modifiers: UInt32?, _ isARepeat: Bool) -> Bool {
func matches(_ id: EventHotKeyID?, _ shortcutState: ShortcutState?, _ keyCode: UInt32?, _ modifiers: UInt32?, _ isARepeat: Bool, _ shortcutScope: ShortcutScope) -> Bool {
guard shortcutScope == scope else {
return false
}
if let id = id, let shortcutState = shortcutState {
let shortcutIndex = Int(id.id)
let shortcutId = Array(KeyboardEvents.globalShortcutsIds).first { $0.value == shortcutIndex }!.key
Expand Down
20 changes: 20 additions & 0 deletions src/logic/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ class Preferences {
"shortcutStyle3": ShortcutStylePreference.focusOnRelease.rawValue,
"shortcutStyle4": ShortcutStylePreference.focusOnRelease.rawValue,
"shortcutStyle5": ShortcutStylePreference.focusOnRelease.rawValue,
"shortcutModifierSide": ShortcutModifierSidePreference.any.rawValue,
"shortcutModifierSide2": ShortcutModifierSidePreference.any.rawValue,
"shortcutModifierSide3": ShortcutModifierSidePreference.any.rawValue,
"shortcutModifierSide4": ShortcutModifierSidePreference.any.rawValue,
"shortcutModifierSide5": ShortcutModifierSidePreference.any.rawValue,
"hideAppBadges": "false",
"hideWindowlessApps": "false",
"hideThumbnails": "false",
Expand Down Expand Up @@ -157,6 +162,7 @@ class Preferences {
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4", "showFullscreenWindows5"].map { defaults.macroPref($0, ShowHowPreference.allCases) } }
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4", "windowOrder5"].map { defaults.macroPref($0, WindowOrderPreference.allCases) } }
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4", "shortcutStyle5"].map { defaults.macroPref($0, ShortcutStylePreference.allCases) } }
static var shortcutModifierSide: [ShortcutModifierSidePreference] { ["shortcutModifierSide", "shortcutModifierSide2", "shortcutModifierSide3", "shortcutModifierSide4", "shortcutModifierSide5"].map { defaults.macroPref($0, ShortcutModifierSidePreference.allCases) } }
static var menubarIcon: MenubarIconPreference { defaults.macroPref("menubarIcon", MenubarIconPreference.allCases) }

// derived values
Expand Down Expand Up @@ -477,6 +483,20 @@ enum ShortcutStylePreference: String, CaseIterable, MacroPreference {
}
}

enum ShortcutModifierSidePreference: String, CaseIterable, MacroPreference {
case any = "0"
case left = "1"
case right = "2"

var localizedString: LocalizedString {
switch self {
case .any: return NSLocalizedString("Any", comment: "")
case .left: return NSLocalizedString("Left", comment: "")
case .right: return NSLocalizedString("Right", comment: "")
}
}
}

enum ShowHowPreference: String, CaseIterable, MacroPreference {
case show = "0"
case hide = "1"
Expand Down
65 changes: 55 additions & 10 deletions src/logic/events/KeyboardEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ class KeyboardEvents {
removeHandlerIfNeeded()
}

private static func unregisterHotKeyIfNeeded(_ controlId: String, _ shortcut: Shortcut) {
if shortcut.keyCode != .none {
static func unregisterHotKeyIfNeeded(_ controlId: String, _ shortcut: Shortcut) {
if shortcut.keyCode != .none, eventHotKeyRefs[controlId] != nil {
UnregisterEventHotKey(eventHotKeyRefs[controlId]!)
eventHotKeyRefs[controlId] = nil
}
}

static func registerHotKeyIfNeeded(_ controlId: String, _ shortcut: Shortcut) {
if shortcut.keyCode != .none {
if shortcut.keyCode != .none, eventHotKeyRefs[controlId] == nil {
let id = globalShortcutsIds[controlId]!
let hotkeyId = EventHotKeyID(signature: signature, id: UInt32(id))
let key = shortcut.carbonKeyCode
Expand Down Expand Up @@ -74,7 +74,7 @@ class KeyboardEvents {

private static func addLocalMonitorForKeyDownAndKeyUp() {
localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp]) { (event: NSEvent) in
let someShortcutTriggered = handleEvent(nil, nil, event.type == .keyDown ? UInt32(event.keyCode) : nil, cocoaToCarbonFlags(event.modifierFlags), event.type == .keyDown ? event.isARepeat : false)
let someShortcutTriggered = handleEvent(nil, nil, event.type == .keyDown ? UInt32(event.keyCode) : nil, cocoaToCarbonFlags(event.modifierFlags), event.type == .keyDown ? event.isARepeat : false, .local)
return someShortcutTriggered ? nil : event
}
}
Expand Down Expand Up @@ -104,7 +104,7 @@ class KeyboardEvents {
InstallEventHandler(shortcutEventTarget, { (_: EventHandlerCallRef?, event: EventRef?, _: UnsafeMutableRawPointer?) -> OSStatus in
var id = EventHotKeyID()
GetEventParameter(event, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID), nil, MemoryLayout<EventHotKeyID>.size, nil, &id)
handleEvent(id, .down, nil, nil, false)
handleEvent(id, .down, nil, nil, false, .global)
return noErr
}, eventTypes.count, &eventTypes, nil, &hotKeyPressedEventHandler)
}
Expand All @@ -113,7 +113,7 @@ class KeyboardEvents {
InstallEventHandler(shortcutEventTarget, { (_: EventHandlerCallRef?, event: EventRef?, _: UnsafeMutableRawPointer?) -> OSStatus in
var id = EventHotKeyID()
GetEventParameter(event, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID), nil, MemoryLayout<EventHotKeyID>.size, nil, &id)
handleEvent(id, .up, nil, nil, false)
handleEvent(id, .up, nil, nil, false, .global)
return noErr
}, eventTypes.count, &eventTypes, nil, &hotKeyReleasedEventHandler)
}
Expand All @@ -131,11 +131,55 @@ class KeyboardEvents {
}
}

fileprivate func handleShortcutModifierSide(_ modifiers: NSEvent.ModifierFlags) {
let sideModifiers: [(any: NSEvent.ModifierFlags, left: NSEvent.ModifierFlags, right: NSEvent.ModifierFlags)] = [
(.shift, .leftShift, .rightShift),
(.control, .leftControl, .rightControl),
(.option, .leftOption, .rightOption),
(.command, .leftCommand, .rightCommand)
]
for shortcutIndex in 0...4 {
let shortcutModifierSide = Preferences.shortcutModifierSide[shortcutIndex]
let shortcutIds = [
Preferences.indexToName("holdShortcut", shortcutIndex),
Preferences.indexToName("nextWindowShortcut", shortcutIndex)
]
var register = true
if shortcutModifierSide != .any {
let shortcutModifiers = shortcutIds.reduce(into: NSEvent.ModifierFlags()) {
guard let shortcut = ControlsTab.shortcuts[$1] else {
return
}
$0.formUnion(shortcut.shortcut.modifierFlags)
}
if
(sideModifiers.contains {
shortcutModifiers.contains($0.any) &&
!modifiers.contains(shortcutModifierSide == .left ? $0.left : $0.right) &&
modifiers.contains(shortcutModifierSide == .left ? $0.right : $0.left)
})
{
register = false
}
}
shortcutIds.forEach {
guard let shortcut = ControlsTab.shortcuts[$0] else {
return
}
if register {
KeyboardEvents.registerHotKeyIfNeeded($0, shortcut.shortcut)
} else {
KeyboardEvents.unregisterHotKeyIfNeeded($0, shortcut.shortcut)
}
}
}
}

@discardableResult
fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutState?, _ keyCode: UInt32?, _ modifiers: UInt32?, _ isARepeat: Bool) -> Bool {
fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutState?, _ keyCode: UInt32?, _ modifiers: UInt32?, _ isARepeat: Bool, _ shortcutScope: ShortcutScope) -> Bool {
var someShortcutTriggered = false
for shortcut in ControlsTab.shortcuts.values {
if shortcut.matches(id, shortcutState, keyCode, modifiers, isARepeat) && shortcut.shouldTrigger() {
if shortcut.matches(id, shortcutState, keyCode, modifiers, isARepeat, shortcutScope) && shortcut.shouldTrigger() {
shortcut.executeAction(isARepeat)
someShortcutTriggered = true
}
Expand All @@ -145,8 +189,9 @@ fileprivate func handleEvent(_ id: EventHotKeyID?, _ shortcutState: ShortcutStat

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)))
handleEvent(nil, nil, nil, modifiers, false)
let modifiers = NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue))
handleShortcutModifierSide(modifiers)
handleEvent(nil, nil, nil, cocoaToCarbonFlags(modifiers), false, .global)
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) {
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
Expand Down
2 changes: 2 additions & 0 deletions src/ui/preferences-window/tabs/ControlsTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class ControlsTab {
separator.boxType = .separator
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), Preferences.indexToName("nextWindowShortcut", index), Preferences.nextWindowShortcut[index], labelPosition: .right)
let shortcutStyle = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Then release:", comment: ""), Preferences.indexToName("shortcutStyle", index), ShortcutStylePreference.allCases)
let shortcutModifierSide = LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Modifier side:", comment: ""), Preferences.indexToName("shortcutModifierSide", index), ShortcutModifierSidePreference.allCases)
let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow], .vertical, false)
toShowDropdowns.spacing = TabView.padding
toShowDropdowns.fit()
Expand All @@ -132,6 +133,7 @@ class ControlsTab {
[separator],
[holdAndPress, StackView(nextWindowShortcut)],
shortcutStyle,
shortcutModifierSide,
], TabView.padding)
tab.column(at: 0).xPlacement = .trailing
tab.mergeCells(inHorizontalRange: NSRange(location: 0, length: 2), verticalRange: NSRange(location: 5, length: 1))
Expand Down

0 comments on commit 8a92433

Please sign in to comment.