Skip to content

Commit

Permalink
feat: more flexible shortcuts (closes #72)
Browse files Browse the repository at this point in the history
closes #50, closes #125, closes #133
  • Loading branch information
lwouis committed Apr 1, 2020
1 parent b575682 commit f6e4078
Show file tree
Hide file tree
Showing 19 changed files with 400 additions and 239 deletions.
2 changes: 1 addition & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ target 'alt-tab-macos' do
use_frameworks!
pod 'LetsMove', '1.24'
pod 'Sparkle', '1.23.0'
pod 'ShortcutRecorder', :git => 'https://github.com/Kentzo/ShortcutRecorder.git', :branch => 'issue-114'
end

15 changes: 14 additions & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
PODS:
- LetsMove (1.24)
- ShortcutRecorder (3.1)
- Sparkle (1.23.0)

DEPENDENCIES:
- LetsMove (= 1.24)
- ShortcutRecorder (from `https://github.com/Kentzo/ShortcutRecorder.git`, branch `issue-114`)
- Sparkle (= 1.23.0)

SPEC REPOS:
trunk:
- LetsMove
- Sparkle

EXTERNAL SOURCES:
ShortcutRecorder:
:branch: issue-114
:git: https://github.com/Kentzo/ShortcutRecorder.git

CHECKOUT OPTIONS:
ShortcutRecorder:
:commit: 4aebe01e1ec9eebb520ef50e42bc321be473c580
:git: https://github.com/Kentzo/ShortcutRecorder.git

SPEC CHECKSUMS:
LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5
ShortcutRecorder: fdf620aca34101b0cba3b10fca815e0459254189
Sparkle: 55b1a87ba69d56913375a281546b7c82dec95bb0

PODFILE CHECKSUM: 465451026269525f0f1d2dc7053cf0b789a35421
PODFILE CHECKSUM: 7a88a7e0e87002ed5ea9c531fdea213ac6657496

COCOAPODS: 1.8.4
22 changes: 14 additions & 8 deletions alt-tab-macos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
D04BA11E56383D082D7BE5A5 /* ThumbnailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA998119CAA8B70A2B67 /* ThumbnailsView.swift */; };
D04BA14D93726795A6937832 /* LabelAndControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2526DC6726E0F7ACF7C /* LabelAndControl.swift */; };
D04BA15A1B0C4871EA7CB899 /* GeneralTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACE22DC907F03D193075 /* GeneralTab.swift */; };
D04BA1621718FF26F8D5E75D /* UnclearableRecorderControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE333F7170E87C5AC0EF /* UnclearableRecorderControl.swift */; };
D04BA1637E125D38546C26E2 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8AC77465E1B2BC947CF /* StackView.swift */; };
D04BA1B133D53572D7B312C2 /* ThumbnailFontIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA1DF8CAB2FAB7FE9244B /* ThumbnailFontIconView.swift */; };
D04BA1CEC6B9C8945FEC8740 /* ThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA258B56193958D60978A /* ThumbnailView.swift */; };
D04BA26A691D56031FCCF00C /* Sysctl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8DB8AA7E5570DAC568A /* Sysctl.swift */; };
Expand All @@ -27,14 +29,14 @@
D04BA3CF766857381519B892 /* DispatchQueues.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAB74451B79FE18B8BEDF /* DispatchQueues.swift */; };
D04BA48B00B4211A465C7337 /* DebugProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACABD048E62EBE4576CC /* DebugProfile.swift */; };
D04BA570E7806F28741B1472 /* SF-Pro-Text-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = D04BA0CDCFF1F0B1A77E3E86 /* SF-Pro-Text-Regular.otf */; };
D04BA5F99B45DC13B9E9DD91 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8276B3D3905E80B1739 /* Keyboard.swift */; };
D04BA6187A91A847844B6ABB /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA015A45DE7AFDC9794FE /* Window.swift */; };
D04BA691CB6082A3C39CBC89 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE757BB2B605234FBF58 /* TabViewController.swift */; };
D04BA69D47B5E60A6AD9CBD9 /* ThumbnailTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD1297730B191E96E7FE /* ThumbnailTitleView.swift */; };
D04BA737008AA2CD4E230A21 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA10777505D8A67ABD186 /* Application.swift */; };
D04BA73E90EFEF8247A5105D /* CGWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAC34CFD42A7F6F1F01C0 /* CGWindow.swift */; };
D04BA76A74267B1346D23687 /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA6D57A1456C07318B8EA /* GridView.swift */; };
D04BA775CF3F8D9394A1E256 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA68C2561D9EE4FD851B8 /* Screen.swift */; };
D04BA78B5D288BF072050C43 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA857B1AB33FBCC59A86 /* Keyboard.swift */; };
D04BA7BE7F3DD24D58ACE942 /* AppearanceTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA64F1F344007EA13BA05 /* AppearanceTab.swift */; };
D04BA7F86F1926FBE31F44BF /* BaseLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA53992F116E5E704CAB3 /* BaseLabel.swift */; };
D04BA8092885B40CE3527370 /* UpdatesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD60C97E609A759E721E /* UpdatesTab.swift */; };
Expand All @@ -58,7 +60,6 @@
D04BADCDA9F9A6C3D6499877 /* SystemPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA7C6F2519091717F4B4E /* SystemPermissions.swift */; };
D04BAEE31B6FFCDC779E6C17 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D04BAC2FF99F629CD4ED20FC /* MainMenu.xib */; };
D04BAF12DF5D15B9D7D316A4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA61693F710CD7BD054D7 /* InfoPlist.strings */; };
D04BAF25E67A5B31CF7676DB /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA44F7B5E58A08416706B /* TextField.swift */; };
D04BAFB973C3D28718FAEB87 /* Windows.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACD976030676FD0761D5 /* Windows.swift */; };
D04BAFBC862BA5FE0294EA7A /* AXUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA6F823BC0EDA9AA4B80A /* AXUIElement.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -96,7 +97,6 @@
D04BA32F25860B686DFE818A /* 3 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line.jpg"; sourceTree = "<group>"; };
D04BA399F1DF2C61FC2C9599 /* menubar-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-icon@2x.png"; sourceTree = "<group>"; };
D04BA4336B6004A0A99849AD /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = package.json; sourceTree = "<group>"; };
D04BA44F7B5E58A08416706B /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
D04BA459034C1885CA43A807 /* LICENCE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENCE.md; sourceTree = "<group>"; };
D04BA47FF1B7838CF4814538 /* PreferencesWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindow.swift; sourceTree = "<group>"; };
D04BA49E45BFFF3D9FC60E43 /* HyperlinkLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HyperlinkLabel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -124,12 +124,13 @@
D04BA7C836A8CE8C0B8D128B /* TextArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextArea.swift; sourceTree = "<group>"; };
D04BA7CF9C2D1BEC7C05AB24 /* Spaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spaces.swift; sourceTree = "<group>"; };
D04BA7ECCE728582D9ECA613 /* determine_version_and_changelog.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = determine_version_and_changelog.sh; sourceTree = "<group>"; };
D04BA8276B3D3905E80B1739 /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = "<group>"; };
D04BA82F792DF53958D92572 /* AltTab.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltTab.app; sourceTree = BUILT_PRODUCTS_DIR; };
D04BA8AC77465E1B2BC947CF /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
D04BA8DB8AA7E5570DAC568A /* Sysctl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sysctl.swift; sourceTree = "<group>"; };
D04BA9B93823398A542FF7A0 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
D04BA9EF65B2E7AF9E3ADCA3 /* 2 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2 windows - 1 line.jpg"; sourceTree = "<group>"; };
D04BAA34E0CB00DED7C04B4F /* 2-rows.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2-rows.jpg"; sourceTree = "<group>"; };
D04BAA857B1AB33FBCC59A86 /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = "<group>"; };
D04BAA998119CAA8B70A2B67 /* ThumbnailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailsView.swift; sourceTree = "<group>"; };
D04BAA9E0539EE620D08F63F /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fr; path = InfoPlist.strings; sourceTree = "<group>"; };
D04BAAB92261FC04854FDDE9 /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -161,6 +162,7 @@
D04BADB20AB31BF83593E0BE /* greetings.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = greetings.yml; sourceTree = "<group>"; };
D04BAE1243C9B4BE3ED1B524 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = "<group>"; };
D04BAE2DC036FD84446E1AE6 /* menubar-icon.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = "menubar-icon.svg"; sourceTree = "<group>"; };
D04BAE333F7170E87C5AC0EF /* UnclearableRecorderControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnclearableRecorderControl.swift; sourceTree = "<group>"; };
D04BAE5D665680CB4B13CA26 /* app-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "app-icon.icns"; sourceTree = "<group>"; };
D04BAE757BB2B605234FBF58 /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; };
D04BAE93A5854C501639C640 /* update_homebrew_cask.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = update_homebrew_cask.sh; sourceTree = "<group>"; };
Expand Down Expand Up @@ -205,7 +207,6 @@
D04BA0D80B24E72B9B981A1D /* logic */ = {
isa = PBXGroup;
children = (
D04BA8276B3D3905E80B1739 /* Keyboard.swift */,
D04BACD976030676FD0761D5 /* Windows.swift */,
D04BA9B93823398A542FF7A0 /* Preferences.swift */,
D04BA68C2561D9EE4FD851B8 /* Screen.swift */,
Expand All @@ -216,6 +217,7 @@
D04BA282BB16C1554595A968 /* Applications.swift */,
D04BAB74451B79FE18B8BEDF /* DispatchQueues.swift */,
D04BACABD048E62EBE4576CC /* DebugProfile.swift */,
D04BAA857B1AB33FBCC59A86 /* Keyboard.swift */,
);
path = logic;
sourceTree = "<group>";
Expand Down Expand Up @@ -300,6 +302,8 @@
children = (
D04BA6D57A1456C07318B8EA /* GridView.swift */,
D04BAA41B9A74189B065D856 /* text */,
D04BAE333F7170E87C5AC0EF /* UnclearableRecorderControl.swift */,
D04BA8AC77465E1B2BC947CF /* StackView.swift */,
);
path = "generic-components";
sourceTree = "<group>";
Expand Down Expand Up @@ -454,7 +458,6 @@
children = (
D04BACEE8D430B8CAAD8C4CD /* BoldLabel.swift */,
D04BA49E45BFFF3D9FC60E43 /* HyperlinkLabel.swift */,
D04BA44F7B5E58A08416706B /* TextField.swift */,
D04BA7C836A8CE8C0B8D128B /* TextArea.swift */,
D04BA53992F116E5E704CAB3 /* BaseLabel.swift */,
);
Expand Down Expand Up @@ -634,12 +637,14 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-alt-tab-macos/Pods-alt-tab-macos-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/LetsMove/LetsMove.framework",
"${BUILT_PRODUCTS_DIR}/ShortcutRecorder/ShortcutRecorder.framework",
"${PODS_ROOT}/Sparkle/Sparkle.framework",
"${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LetsMove.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ShortcutRecorder.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework",
"${DWARF_DSYM_FOLDER_PATH}/Sparkle.framework.dSYM",
);
Expand All @@ -662,7 +667,6 @@
D04BABED81800E18732912CC /* CGWindowID.swift in Sources */,
D04BA26A691D56031FCCF00C /* Sysctl.swift in Sources */,
D04BA8480A8FF466CA89DA5B /* main.swift in Sources */,
D04BA5F99B45DC13B9E9DD91 /* Keyboard.swift in Sources */,
D04BAFB973C3D28718FAEB87 /* Windows.swift in Sources */,
D04BAC011A71E0418154F8CD /* Preferences.swift in Sources */,
D04BA775CF3F8D9394A1E256 /* Screen.swift in Sources */,
Expand Down Expand Up @@ -691,10 +695,12 @@
D04BA76A74267B1346D23687 /* GridView.swift in Sources */,
D04BAB4EB890853B5B9B2C61 /* BoldLabel.swift in Sources */,
D04BAABE804F3769CE22BEB6 /* HyperlinkLabel.swift in Sources */,
D04BAF25E67A5B31CF7676DB /* TextField.swift in Sources */,
D04BAD2A7F2E8BF64EE982E9 /* TextArea.swift in Sources */,
D04BA7F86F1926FBE31F44BF /* BaseLabel.swift in Sources */,
D04BA11E56383D082D7BE5A5 /* ThumbnailsView.swift in Sources */,
D04BA1621718FF26F8D5E75D /* UnclearableRecorderControl.swift in Sources */,
D04BA78B5D288BF072050C43 /* Keyboard.swift in Sources */,
D04BA1637E125D38546C26E2 /* StackView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
59 changes: 16 additions & 43 deletions src/logic/Keyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import Cocoa
import Carbon.HIToolbox.Events

class Keyboard {
static func listenToGlobalEvents(_ delegate: App) {
listenToGlobalKeyboardEvents(delegate)
static func listenToGlobalEvents() {
listenToGlobalKeyboardEvents()
}
}

var eventTap: CFMachPort?

func listenToGlobalKeyboardEvents(_ app: App) {
func listenToGlobalKeyboardEvents() {
DispatchQueues.keyboardEvents.async {
let eventMask = [CGEventType.keyDown, CGEventType.keyUp, CGEventType.flagsChanged].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
// CGEvent.tapCreate returns null if ensureAccessibilityCheckboxIsChecked() didn't pass
Expand All @@ -19,57 +19,30 @@ func listenToGlobalKeyboardEvents(_ app: App) {
options: .defaultTap,
eventsOfInterest: eventMask,
callback: keyboardHandler,
userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(app).toOpaque()))
userInfo: nil)
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CGEvent.tapEnable(tap: eventTap!, enable: true)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CFRunLoopRun()
}
}

func dispatchWork(_ fn: @escaping () -> Void) -> Unmanaged<CGEvent>? {
(App.shared as! App).uiWorkShouldBeDone = true
DispatchQueue.main.async {
fn()
}
return nil // previously focused app should not receive keys
}

func dispatchWorkCurrentThread(_ fn: @escaping () -> Void) -> Unmanaged<CGEvent>? {
(App.shared as! App).uiWorkShouldBeDone = false
fn()
return nil // previously focused app should not receive keys
}

func keyboardHandler(proxy: CGEventTapProxy, type: CGEventType, event_: CGEvent, appPointer: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
let app = Unmanaged<App>.fromOpaque(appPointer!).takeUnretainedValue()
func keyboardHandler(proxy: CGEventTapProxy, type: CGEventType, cgEvent: CGEvent, userInfo: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
if type == .keyDown || type == .keyUp || type == .flagsChanged {
if let event = NSEvent(cgEvent: event_) {
let isTab = event.keyCode == Preferences.tabKeyCode
let isMetaChanged = Preferences.metaKeyCodes.contains(event.keyCode)
let isMetaDown = event.modifierFlags.contains(Preferences.metaModifierFlag)
let isRightArrow = event.keyCode == kVK_RightArrow
let isLeftArrow = event.keyCode == kVK_LeftArrow
let isEscape = event.keyCode == kVK_Escape
if type == .keyDown && isEscape && app.appIsBeingUsed {
return dispatchWorkCurrentThread { app.hideUi() }
} else if isMetaDown && type == .keyDown {
if isTab && event.modifierFlags.contains(.shift) {
return dispatchWork { app.showUiOrCycleSelection(-1) }
} else if isTab {
return dispatchWork { app.showUiOrCycleSelection(1) }
} else if isRightArrow && app.appIsBeingUsed {
return dispatchWork { app.cycleSelection(1) }
} else if isLeftArrow && app.appIsBeingUsed {
return dispatchWork { app.cycleSelection(-1) }
}
} else if isMetaChanged && !isMetaDown {
return dispatchWorkCurrentThread { app.focusTarget() }
if let event_ = NSEvent(cgEvent: cgEvent),
// workaround: NSEvent.characters is not safe outside of the main thread; this is not documented by Apple
// see https://github.com/Kentzo/ShortcutRecorder/issues/114#issuecomment-606465340
let event = NSEvent.keyEvent(with: event_.type, location: event_.locationInWindow, modifierFlags: event_.modifierFlags,
timestamp: event_.timestamp, windowNumber: event_.windowNumber, context: event_.context, characters: "",
charactersIgnoringModifiers: "", isARepeat: type == .flagsChanged ? false : event_.isARepeat, keyCode: event_.keyCode) {
let appWasBeingUsed = App.app.appIsBeingUsed
App.shortcutMonitor.handle(event, withTarget: nil)
if appWasBeingUsed || App.app.appIsBeingUsed {
return nil // focused app won't receive the event
}
}
} else if type == .tapDisabledByUserInput || type == .tapDisabledByTimeout {
CGEvent.tapEnable(tap: eventTap!, enable: true)
}
// focused app will receive the event
return Unmanaged.passRetained(event_)
return Unmanaged.passRetained(cgEvent) // focused app will receive the event
}
Loading

0 comments on commit f6e4078

Please sign in to comment.