Skip to content

Commit

Permalink
Implement secondary command to switch between languages
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisJeong committed Mar 6, 2022
1 parent 5be09b2 commit a3f396c
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 42 deletions.
8 changes: 7 additions & 1 deletion BetterCapsLock/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

import Cocoa

// TODO: Implement options view for choosing between two modes
// Temporary flag for choosing between two modes.
let debugUseCmd = true

class AppDelegate: NSObject, NSApplicationDelegate {
static var statusItem: NSStatusItem!

Expand All @@ -19,7 +23,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {

CapsLockManager.initialize()

ShortcutManager.initialize()
if !debugUseCmd || true {
ShortcutManager.initialize()
}
}

static func initStatusItem() {
Expand Down
121 changes: 81 additions & 40 deletions BetterCapsLock/CapsLockManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import Foundation
import Cocoa
import Carbon

let changeLanguageKey = KeyCodes.F13

class CapsLockManager {
static let instance = CapsLockManager()

Expand All @@ -35,39 +37,58 @@ class CapsLockManager {
}

static func initialize() {
// TODO: Temporarily using boolean flag. Implement GUI option later.
let useCmd = true
if useCmd {
if debugUseCmd {
KeyInterceptor.interceptEvents(eventTypes: [.keyDown, .keyUp, .flagsChanged], callback: handleSecondaryCommand)
instance.registerSecondaryCommandEventListener()
} else {
// Keep handling caps lock with same logic, since:
// 1. It also captures all mouse clicks, which might affect performance
// 1. I tried to capture caps lock event with:
// CGEventType(rawValue: NSEvent.EventType.systemDefined)
// , but It also captures all mouse clicks, which might affect performance
// 2. CapsLock isn't being consumed by returning nil, still activating actual caps lock feature
instance.registerEventListener()
instance.registerCapsLockEventListener()
}
}

func registerEventListener() {
func registerCapsLockEventListener() {
NSEvent.addGlobalMonitorForEvents(matching: [.keyDown, .systemDefined]) { (event) in
guard self.handleEvent(event: event) else {
return
}

let isCapsLockEnabled = self.getCapsLockState()
if isCapsLockEnabled {
self.setCapsLockState(false)
return
}

if self.sticky {
self.setCapsLockState(!isCapsLockEnabled)
} else {
self.changeLanguage()
}
self.handleCapsLockEvent(event: event)
}
}

func registerSecondaryCommandEventListener() {
NSEvent.addGlobalMonitorForEvents(matching: [.flagsChanged]) { (event) in
self.handleSecondaryCommandEvent(event: event)
}
}

func handleCapsLockEvent(event: NSEvent) {
guard self.filterCapsLockEvent(event: event) else {
return
}

let isCapsLockEnabled = self.getCapsLockState()
if isCapsLockEnabled {
self.setCapsLockState(false)
return
}

if self.sticky {
self.setCapsLockState(!isCapsLockEnabled)
} else {
self.changeLanguage()
}
}

func handleSecondaryCommandEvent(event: NSEvent) {
guard self.filterSecondaryCommandEvent(event: event) else {
return
}

self.changeLanguage()
}

func handleEvent(event: NSEvent) -> Bool {
func filterCapsLockEvent(event: NSEvent) -> Bool {
guard event.type == .systemDefined, event.subtype.rawValue == 211, event.data1 == 1 else {
return false
}
Expand All @@ -77,6 +98,14 @@ class CapsLockManager {
return true
}

func filterSecondaryCommandEvent(event: NSEvent) -> Bool {
guard event.keyCode == changeLanguageKey.rawValue else {
return false
}

return true
}

func changeLanguage() {
guard let selectPreviousShort = Shortcut.selectPreviousInputSource else {
return
Expand Down Expand Up @@ -117,24 +146,36 @@ func handleSecondaryCommand(proxy: CGEventTapProxy, type: CGEventType, event: CG
let primaryCommandFlag: UInt64 = 1 << 3
let secondaryCommandFlag: UInt64 = 1 << 4

let isCmdDown = event.flags.rawValue & CGEventFlags.maskCommand.rawValue > 0
let isPrimaryCmd = isCmdDown && (event.flags.rawValue & primaryCommandFlag) > 0
let isSecondaryCmd = isCmdDown && (event.flags.rawValue & secondaryCommandFlag) > 0

let shouldConsume = isSecondaryCmd
let shouldActivateMappedCommand = isCmdDown && isSecondaryCmd

if shouldActivateMappedCommand {
// FIX: Temporarily disabled due to quirky behavior.
// CapsLockManager.instance.changeLanguage()
let isCmdOn = event.flags.rawValue & CGEventFlags.maskCommand.rawValue > 0
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)

let isPrimaryCmdOn = isCmdOn && (event.flags.rawValue & primaryCommandFlag) > 0
let isSecondaryCmdOn = isCmdOn && (event.flags.rawValue & secondaryCommandFlag) > 0
let isSecondaryCmdDown = isSecondaryCmdOn && keyCode == KeyCodes.mod_secondary_command.rawValue

let secondaryCommandFlagMask = CGEventFlags.maskCommand.rawValue | secondaryCommandFlag | CGEventFlags.maskNonCoalesced.rawValue
let isExactlySecondaryCommand = event.flags.rawValue & ~secondaryCommandFlagMask == 0

let hasNonCoalescedFlag = event.flags.rawValue & CGEventFlags.maskNonCoalesced.rawValue > 0

if isSecondaryCmdDown && isExactlySecondaryCommand {
event.setIntegerValueField(.keyboardEventKeycode, value: changeLanguageKey.rawValue)
}
if isSecondaryCmd {
let filteredRawFlags = event.flags.rawValue &
(isPrimaryCmd
? ~secondaryCommandFlag
: ~(CGEventFlags.maskCommand.rawValue | secondaryCommandFlag))
event.flags = CGEventFlags(rawValue: filteredRawFlags)

var filteredRawFlags = event.flags.rawValue
if isPrimaryCmdOn && isSecondaryCmdOn {
filteredRawFlags &= ~secondaryCommandFlag
} else if isPrimaryCmdOn {
// no-op
} else if isSecondaryCmdOn {
filteredRawFlags &= ~secondaryCommandFlagMask
if hasNonCoalescedFlag {
filteredRawFlags |= CGEventFlags.maskNonCoalesced.rawValue
}
} else {
// no-op
}

return shouldConsume ? nil : Unmanaged.passRetained(event)
event.flags = CGEventFlags(rawValue: filteredRawFlags)

return Unmanaged.passRetained(event)
}
2 changes: 1 addition & 1 deletion BetterCapsLock/ShortcutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ enum KeyCodes: Int64, CaseIterable {
b = 11, q, w, e, r, y, t,
num_1, num_2, num_3, num_4, num_6, num_5, sym_equal, num_9, num_7, sym_minus, num_8, num_0,
sym_braket_close, o, u, sym_braket_open, i, p, cmd_return, l, j, sym_quote, k, sym_semicolon, sym_backslask, sym_comma, sym_slash, n, m, sym_period, cmd_tab, space, sym_backtick, cmd_backspace, cmd_enter, cmd_escape,
mod_command = 55, mod_shift, mod_alternate = 58, mod_control, mod_fn = 63
mod_secondary_command = 54, mod_command, mod_shift, mod_alternate = 58, mod_control, mod_fn = 63
case sym_numpad_period = 65,
sym_numpad_asterisk = 67,
sym_numpad_plus = 69,
Expand Down

0 comments on commit a3f396c

Please sign in to comment.