├── .gitignore ├── Touch-Tab ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── AppIcon_16x16.png │ │ ├── AppIcon_32x32.png │ │ ├── AppIcon_64x64.png │ │ ├── AppIcon_128x128.png │ │ ├── AppIcon_256x256.png │ │ ├── AppIcon_512x512.png │ │ ├── AppIcon_1024x1024.png │ │ └── Contents.json │ ├── StatusIcon.imageset │ │ ├── StatusIcon_16x16.png │ │ ├── StatusIcon_32x32.png │ │ └── Contents.json │ ├── MenuItem-Warning.imageset │ │ ├── MenuItem-Warning_16x16.png │ │ ├── MenuItem-Warning_32x32.png │ │ ├── MenuItem-Warning_48x48.png │ │ └── Contents.json │ └── StatusIcon-Warning.imageset │ │ ├── StatusIcon-Warning_22x22.png │ │ ├── StatusIcon-Warning_44x44.png │ │ └── Contents.json ├── main.swift ├── Touch-Tab.entitlements ├── BundleInfo.swift ├── AboutView.swift ├── AppSwitcher.swift ├── PrivacyHelper.swift ├── AppDelegate.swift └── SwipeManager.swift ├── README.md └── Touch-Tab.xcodeproj └── project.pbxproj /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | *.xcuserstate 6 | project.xcworkspace/ 7 | xcuserdata/ 8 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_16x16.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_32x32.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_64x64.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_128x128.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_256x256.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_512x512.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/AppIcon.appiconset/AppIcon_1024x1024.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon.imageset/StatusIcon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/StatusIcon.imageset/StatusIcon_16x16.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon.imageset/StatusIcon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/StatusIcon.imageset/StatusIcon_32x32.png -------------------------------------------------------------------------------- /Touch-Tab/main.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | let delegate = AppDelegate() 4 | NSApplication.shared.delegate = delegate 5 | 6 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 7 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_16x16.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_32x32.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/MenuItem-Warning_48x48.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon-Warning.imageset/StatusIcon-Warning_22x22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/StatusIcon-Warning.imageset/StatusIcon-Warning_22x22.png -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon-Warning.imageset/StatusIcon-Warning_44x44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris58h/Touch-Tab/HEAD/Touch-Tab/Assets.xcassets/StatusIcon-Warning.imageset/StatusIcon-Warning_44x44.png -------------------------------------------------------------------------------- /Touch-Tab/Touch-Tab.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "StatusIcon_16x16.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "StatusIcon_32x32.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/StatusIcon-Warning.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "StatusIcon-Warning_22x22.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "StatusIcon-Warning_44x44.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/MenuItem-Warning.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MenuItem-Warning_16x16.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "MenuItem-Warning_32x32.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "MenuItem-Warning_48x48.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Touch-Tab/BundleInfo.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class BundleInfo { 4 | private static func bundleInfo(_ key: String) -> String { 5 | return Bundle.main.infoDictionary?[key] as? String ?? "" 6 | } 7 | 8 | static func iconName() -> String { 9 | return bundleInfo("CFBundleIconName") 10 | } 11 | 12 | static func displayName() -> String { 13 | return bundleInfo("CFBundleDisplayName") 14 | } 15 | 16 | static func version() -> String { 17 | return bundleInfo("CFBundleShortVersionString") 18 | } 19 | 20 | static func build() -> String { 21 | return bundleInfo("CFBundleVersion") 22 | } 23 | 24 | static func copyright() -> String { 25 | return bundleInfo("NSHumanReadableCopyright") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Touch-Tab/AboutView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct AboutView: View { 4 | var body: some View { 5 | VStack(alignment: .center, spacing: 16) { 6 | Image(nsImage: NSImage(named: BundleInfo.iconName()) ?? NSImage()) 7 | .resizable() 8 | .frame(width: 64, height: 64) 9 | Text(BundleInfo.displayName()) 10 | .font(.system(size: 16, weight: .bold)) 11 | Text("Version \(BundleInfo.version()) (\(BundleInfo.build()))") 12 | .font(.system(size: 12)) 13 | Text("Copyright \(BundleInfo.copyright())") 14 | .font(.system(size: 12)) 15 | } 16 | .padding(.horizontal, 32) 17 | .padding(.vertical, 16) 18 | } 19 | } 20 | 21 | struct AboutView_Previews: PreviewProvider { 22 | static var previews: some View { 23 | AboutView() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Touch-Tab/AppSwitcher.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class AppSwitcher { 4 | private static let keyboardEventSource = CGEventSource(stateID: CGEventSourceStateID.hidSystemState) 5 | private static let tabKey = CGKeyCode(0x30); 6 | private static let leftCommandKey = CGKeyCode(0x37); 7 | 8 | static func selectInAppSwitcher() { 9 | postKeyEvent(key: leftCommandKey, down: false) 10 | } 11 | 12 | static func cmdTab() { 13 | postKeyEvent(key: tabKey, down: true, flags: .maskCommand) 14 | postKeyEvent(key: tabKey, down: false, flags: .maskCommand) 15 | } 16 | 17 | static func cmdShiftTab() { 18 | postKeyEvent(key: tabKey, down: true, flags: [.maskCommand, .maskShift]) 19 | postKeyEvent(key: tabKey, down: false, flags: [.maskCommand, .maskShift]) 20 | } 21 | 22 | private static func postKeyEvent(key: CGKeyCode, down: Bool, flags: CGEventFlags = []) { 23 | let event = CGEvent(keyboardEventSource: keyboardEventSource, virtualKey: key, keyDown: down) 24 | event?.flags = flags 25 | event?.post(tap: CGEventTapLocation.cghidEventTap) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Touch-Tab/PrivacyHelper.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class PrivacyHelper { 4 | static func isProcessTrustedWithPrompt() -> Bool { 5 | //TODO: Investigation required. Calling AXIsProcessTrustedWithOptions in sandboxed app doesn't prompt user (at least in Ventura 13.4.1) but creating CGEventTap does it. 6 | let isAccessibilityPermissionGranted = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : true] as CFDictionary) 7 | if isAccessibilityPermissionGranted { 8 | return true 9 | } else { 10 | PrivacyHelper.promptForAccessibilityPermissionFromSandbox() 11 | return false 12 | } 13 | } 14 | 15 | private static func promptForAccessibilityPermissionFromSandbox() { 16 | _ = CGEvent.tapCreate( 17 | tap: .cghidEventTap, 18 | place: .headInsertEventTap, 19 | options: .defaultTap, 20 | eventsOfInterest: NSEvent.EventTypeMask.gesture.rawValue, 21 | callback: dummyEventHandler, 22 | userInfo: nil 23 | ) 24 | } 25 | } 26 | 27 | fileprivate func dummyEventHandler(proxy: CGEventTapProxy, eventType: CGEventType, cgEvent: CGEvent, userInfo: UnsafeMutableRawPointer?) -> Unmanaged? { 28 | debugPrint("Should never happen!") 29 | return Unmanaged.passUnretained(cgEvent) 30 | } 31 | -------------------------------------------------------------------------------- /Touch-Tab/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "AppIcon_32x32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "AppIcon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "AppIcon_64x64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "AppIcon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "AppIcon_256x256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "AppIcon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "AppIcon_512x512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "AppIcon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "AppIcon_1024x1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Touch-Tab 2 | 3 | ![Touch-Tab AppSwitcher](https://user-images.githubusercontent.com/511242/185958284-e0f962aa-3f88-4d95-9176-3f3fe49a24c8.gif) 4 | 5 | Switch apps with trackpad on macOS. 6 | Use 3-fingers swipe right or 3-fingers swipe left to switch between apps. 7 | Hold after the swipe or swipe slowly to show App Switcher UI. 8 | 9 | Want to support? [Buy me a coffee](https://www.buymeacoffee.com/ris58h). 10 | 11 | ## Installation 12 | 1. Download the [latest](https://github.com/ris58h/Touch-Tab/releases/latest/download/Touch-Tab.zip) `Touch-Tab.zip` from [Releases](https://github.com/ris58h/Touch-Tab/releases) page. 13 | 2. Unzip the archive and move `Touch-Tab.app` into the `Applications` folder. 14 | 3. The app is ad-hoc signed so when you run the app macOS will warn you: `"Touch-Tab" can’t be opened because Apple cannot check it for malicious software`. Right-click the app and click `Open`, a 15 | pop-up will appear, click `Open` again. 16 | 4. The app needs access to global trackpad events. Allow Touch-Tab to control your computer in `System Settings > Privacy & Security > Accesibility`. If you had Touch-Tab installed before you may need to remove Touch-Tab from the `Accessibility` list first and add it again. 17 | 5. Disable 3-finger swipe between full-screen apps or make it 4-finger in `System Settings > Trackpad > More Gestures > Swipe between full-screen apps`. 18 | 19 | ## Usage 20 | - Use 3-fingers swipe right or 3-fingers swipe left to switch between apps. 21 | - Hold after the swipe or swipe slowly to show App Switcher UI. Pro tip: you can use 2-fingers scroll to switch apps in App Switcher faster. 22 | 23 | ### Hide Status Bar Item 24 | Holding ⌘ drag the item away from the status bar until you see ✖️ (cross icon) then let it go. To recover the item just open the app one more time. 25 | 26 | ## Troubleshooting 27 | ### "Touch-Tab" can’t be opened because Apple cannot check it for malicious software 28 | Right-click the app and click `Open`, a pop-up will appear, click `Open` again. 29 | ### It's running but doesn't work 30 | - Check that Touch-Tab is allowed to control your computer in `System Settings > Privacy & Security > Accesibility`. If you had Touch-Tab installed before you may need to remove Touch-Tab from the `Accessibility` list first and add it again. 31 | - Check that 3-finger swipe is disabled in `System Settings > Trackpad > More Gestures > Swipe between full-screen apps`. 32 | ### 3-finger swipe scrolls a content 33 | It's a [known issue](https://github.com/ris58h/Touch-Tab/issues/1). The workaround is to setup `Mission Control` or `App Expose` to use 3-finger swipe in `System Settings > Trackpad > More Gestures`. 34 | ### It still doesn't work 35 | Please create an [issue](https://github.com/ris58h/Touch-Tab/issues). 36 | -------------------------------------------------------------------------------- /Touch-Tab/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import SwiftUI 3 | 4 | class AppDelegate: NSObject, NSApplicationDelegate { 5 | private static let statusIcon = templateImage(named: "StatusIcon") 6 | private static let statusIconWarning = templateImage(named: "StatusIcon-Warning") 7 | 8 | private var statusBarItem: NSStatusItem! 9 | private var aboutWindow: NSWindow! 10 | 11 | private static func templateImage(named: String) -> NSImage? { 12 | let image = NSImage(named: named) 13 | image?.isTemplate = true 14 | return image 15 | } 16 | 17 | func applicationDidFinishLaunching(_ aNotification: Notification) { 18 | #if DEBUG 19 | if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { 20 | return 21 | } 22 | #endif 23 | 24 | createStatusBarItem() 25 | requestAccessibilityPermission() { 26 | SwipeManager.start() 27 | } 28 | } 29 | 30 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 31 | statusBarItem.isVisible=true 32 | return true 33 | } 34 | 35 | private func requestAccessibilityPermission(completion: @escaping ()->()) { 36 | let isAccessibilityPermissionGranted = PrivacyHelper.isProcessTrustedWithPrompt() 37 | debugPrint("Accessibility permission", isAccessibilityPermissionGranted) 38 | if isAccessibilityPermissionGranted { 39 | completion() 40 | } else { 41 | addAccessibilityWarning() 42 | Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] timer in 43 | if AXIsProcessTrusted() { 44 | debugPrint("Accessibility permission granted") 45 | removeAccessibilityWarning() 46 | timer.invalidate() 47 | completion() 48 | } 49 | } 50 | } 51 | } 52 | 53 | private func createStatusBarItem() { 54 | statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) 55 | statusBarItem.button?.image = AppDelegate.statusIcon 56 | statusBarItem.button?.toolTip = BundleInfo.displayName() 57 | statusBarItem.behavior = .removalAllowed 58 | 59 | statusBarItem.menu = NSMenu() 60 | statusBarItem.menu?.addItem( 61 | withTitle: "About \(BundleInfo.displayName())", 62 | action: #selector(AppDelegate.showAbout), 63 | keyEquivalent: "") 64 | statusBarItem.menu?.addItem( 65 | withTitle: "Quit", 66 | action: #selector(AppDelegate.quit), 67 | keyEquivalent: "") 68 | } 69 | 70 | private func addAccessibilityWarning() { 71 | statusBarItem.button?.image = AppDelegate.statusIconWarning 72 | let warningDescriptionMenuItem = NSMenuItem(title: "No Accessibility Access", action: nil, keyEquivalent: "") 73 | warningDescriptionMenuItem.image = AppDelegate.templateImage(named: "MenuItem-Warning") 74 | warningDescriptionMenuItem.toolTip = "Grant access to this application in Privacy & Security settings, located in System Settings" 75 | warningDescriptionMenuItem.isEnabled = false 76 | let openPrivacyAccessibilityMenuItem = NSMenuItem(title: "Authorize...", action: #selector(openPrivacyAccessibility), keyEquivalent: "") 77 | statusBarItem.menu?.insertItem(warningDescriptionMenuItem, at: 0) 78 | statusBarItem.menu?.insertItem(openPrivacyAccessibilityMenuItem, at: 1) 79 | statusBarItem.menu?.insertItem(NSMenuItem.separator(), at: 2) 80 | } 81 | 82 | private func removeAccessibilityWarning() { 83 | statusBarItem.button?.image = AppDelegate.statusIcon 84 | statusBarItem.menu?.removeItem(at: 2) 85 | statusBarItem.menu?.removeItem(at: 1) 86 | statusBarItem.menu?.removeItem(at: 0) 87 | } 88 | 89 | @objc private func openPrivacyAccessibility() { 90 | let privacyAccessibilityURL = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")! 91 | NSWorkspace.shared.open(privacyAccessibilityURL) 92 | } 93 | 94 | @objc private func quit() { 95 | NSApplication.shared.terminate(self) 96 | } 97 | 98 | @objc private func showAbout() { 99 | if aboutWindow == nil { 100 | aboutWindow = NSWindow(contentViewController: NSHostingController(rootView: AboutView().fixedSize())) 101 | aboutWindow.styleMask = [.closable, .titled] 102 | aboutWindow.title = "" 103 | } 104 | aboutWindow.center() 105 | aboutWindow.makeKeyAndOrderFront(nil) 106 | NSApp.activate(ignoringOtherApps: true) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Touch-Tab/SwipeManager.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class SwipeManager { 4 | private static let accVelXThreshold: Float = 0.07 5 | // TODO: figure out the real value of the delay. 6 | private static let appSwitcherUIDelay: Double = 0.2 7 | 8 | private static var eventTap: CFMachPort? = nil 9 | // Event state. 10 | private static var accVelX: Float = 0 11 | private static var prevTouchPositions: [String: NSPoint] = [:] 12 | // Gesture state. Gesture may consists of multiple events. 13 | private static var startTime: Date? = nil 14 | 15 | //TODO: move it somewhere else? 16 | private static func listener(_ eventType: EventType) { 17 | switch eventType { 18 | case .startOrContinue(.left): 19 | AppSwitcher.cmdShiftTab() 20 | case .startOrContinue(.right): 21 | AppSwitcher.cmdTab() 22 | case .end: 23 | AppSwitcher.selectInAppSwitcher() 24 | } 25 | } 26 | 27 | static func start() { 28 | if eventTap != nil { 29 | debugPrint("SwipeManager is already started") 30 | return 31 | } 32 | debugPrint("SwipeManager start") 33 | eventTap = CGEvent.tapCreate( 34 | tap: .cghidEventTap, 35 | place: .headInsertEventTap, 36 | options: .defaultTap, 37 | eventsOfInterest: NSEvent.EventTypeMask.gesture.rawValue, 38 | callback: { proxy, type, cgEvent, userInfo in 39 | return SwipeManager.eventHandler(proxy: proxy, eventType: type, cgEvent: cgEvent, userInfo: userInfo) 40 | }, 41 | userInfo: nil 42 | ) 43 | if eventTap == nil { 44 | debugPrint("SwipeManager couldn't create event tap") 45 | return 46 | } 47 | 48 | let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0) 49 | CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes) 50 | CGEvent.tapEnable(tap: eventTap!, enable: true) 51 | } 52 | 53 | private static func eventHandler(proxy: CGEventTapProxy, eventType: CGEventType, cgEvent: CGEvent, userInfo: UnsafeMutableRawPointer?) -> Unmanaged? { 54 | if eventType.rawValue == NSEvent.EventType.gesture.rawValue, let nsEvent = NSEvent(cgEvent: cgEvent) { 55 | touchEventHandler(nsEvent) 56 | } else if (eventType == .tapDisabledByUserInput || eventType == .tapDisabledByTimeout) { 57 | debugPrint("SwipeManager tap disabled", eventType.rawValue) 58 | CGEvent.tapEnable(tap: eventTap!, enable: true) 59 | } 60 | return Unmanaged.passUnretained(cgEvent) 61 | } 62 | 63 | private static func touchEventHandler(_ nsEvent: NSEvent) { 64 | let touches = nsEvent.allTouches() 65 | 66 | // Sometimes there are empty touch events that we have to skip. There are no empty touch events if Mission Control or App Expose use 3-finger swipes though. 67 | if touches.isEmpty { 68 | return 69 | } 70 | let touchesCount = touches.allSatisfy({ $0.phase == .ended }) ? 0 : touches.count 71 | 72 | switch touchesCount { 73 | case 2: processTwoFingers() 74 | case 3: processThreeFingers(touches: touches) 75 | default: processOtherFingers() 76 | } 77 | } 78 | 79 | private static func processTwoFingers() { 80 | // Two fingers scrolling in App Switcher is OK but we shouldn't accumulate gesture velocity here. 81 | clearEventState() 82 | } 83 | 84 | private static func processThreeFingers(touches: Set) { 85 | let velX = SwipeManager.horizontalSwipeVelocity(touches: touches) 86 | // We don't care about non-horizontal swipes. 87 | if velX == nil { 88 | return 89 | } 90 | 91 | accVelX += velX! 92 | // Not enough swiping. 93 | if abs(accVelX) < accVelXThreshold { 94 | return 95 | } 96 | 97 | if startTime == nil { 98 | startTime = Date() 99 | } else { 100 | let interval = startTime!.timeIntervalSinceNow 101 | if -interval < appSwitcherUIDelay { 102 | // We skip subsequent events until App Switcher UI is shown. 103 | clearEventState() 104 | return 105 | } 106 | } 107 | 108 | startOrContinueGesture() 109 | clearEventState() 110 | } 111 | 112 | private static func processOtherFingers() { 113 | if startTime != nil { 114 | endGesture() 115 | clearEventState() 116 | startTime = nil 117 | } 118 | } 119 | 120 | private static func clearEventState() { 121 | accVelX = 0 122 | prevTouchPositions.removeAll() 123 | } 124 | 125 | private static func startOrContinueGesture() { 126 | let direction: EventType.Direction = accVelX < 0 ? .left : .right 127 | listener(.startOrContinue(direction: direction)) 128 | } 129 | 130 | private static func endGesture() { 131 | listener(.end) 132 | } 133 | 134 | private static func horizontalSwipeVelocity(touches: Set) -> Float? { 135 | var allRight = true 136 | var allLeft = true 137 | var sumVelX = Float(0) 138 | var sumVelY = Float(0) 139 | for touch in touches { 140 | let (velX, velY) = touchVelocity(touch) 141 | allRight = allRight && velX >= 0 142 | allLeft = allLeft && velX <= 0 143 | sumVelX += velX 144 | sumVelY += velY 145 | 146 | if touch.phase == .ended { 147 | prevTouchPositions.removeValue(forKey: "\(touch.identity)") 148 | } else { 149 | prevTouchPositions["\(touch.identity)"] = touch.normalizedPosition 150 | } 151 | } 152 | // All fingers should move in the same direction. 153 | if !allRight && !allLeft { 154 | return nil 155 | } 156 | 157 | let velX = sumVelX / Float(touches.count) 158 | let velY = sumVelY / Float(touches.count) 159 | // Only horizontal swipes are interesting. 160 | if abs(velX) <= abs(velY) { 161 | return nil 162 | } 163 | 164 | return velX 165 | } 166 | 167 | private static func touchVelocity(_ touch: NSTouch) -> (Float, Float) { 168 | guard let prevPosition = prevTouchPositions["\(touch.identity)"] else { 169 | return (0, 0) 170 | } 171 | let position = touch.normalizedPosition 172 | return (Float(position.x - prevPosition.x), Float(position.y - prevPosition.y)) 173 | } 174 | 175 | enum EventType { 176 | case startOrContinue(direction: Direction) 177 | case end 178 | 179 | enum Direction { 180 | case left 181 | case right 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Touch-Tab.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 588922D429BB490400F7F843 /* AppSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588922D329BB490400F7F843 /* AppSwitcher.swift */; }; 11 | 58BC5B2C28B3C4C30013EAD3 /* SwipeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BC5B2828B3C4C30013EAD3 /* SwipeManager.swift */; }; 12 | 58BC5B2D28B3C4C30013EAD3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58BC5B2928B3C4C30013EAD3 /* Assets.xcassets */; }; 13 | 58BC5B2E28B3C4C30013EAD3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BC5B2A28B3C4C30013EAD3 /* AppDelegate.swift */; }; 14 | 58BC5B2F28B3C4C30013EAD3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BC5B2B28B3C4C30013EAD3 /* main.swift */; }; 15 | 58D3F9F42A8E14C80021A532 /* PrivacyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D3F9F32A8E14C80021A532 /* PrivacyHelper.swift */; }; 16 | 58DE49CA29D09BB70025BE44 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DE49C929D09BB70025BE44 /* AboutView.swift */; }; 17 | 58FBBBE529D20D3600A15F66 /* BundleInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBBBE429D20D3600A15F66 /* BundleInfo.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 588922D329BB490400F7F843 /* AppSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSwitcher.swift; sourceTree = ""; }; 22 | 58BC5B1528B3C37B0013EAD3 /* Touch-Tab.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Touch-Tab.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 58BC5B2828B3C4C30013EAD3 /* SwipeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeManager.swift; sourceTree = ""; }; 24 | 58BC5B2928B3C4C30013EAD3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 58BC5B2A28B3C4C30013EAD3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 58BC5B2B28B3C4C30013EAD3 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 27 | 58D3F9F32A8E14C80021A532 /* PrivacyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyHelper.swift; sourceTree = ""; }; 28 | 58D3F9F52A8E15E50021A532 /* Touch-Tab.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Touch-Tab.entitlements"; sourceTree = ""; }; 29 | 58DE49C929D09BB70025BE44 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 30 | 58FBBBE429D20D3600A15F66 /* BundleInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleInfo.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXGroup section */ 34 | 58BC5B0C28B3C37B0013EAD3 = { 35 | isa = PBXGroup; 36 | children = ( 37 | 58BC5B1728B3C37B0013EAD3 /* Touch-Tab */, 38 | 58BC5B1628B3C37B0013EAD3 /* Products */, 39 | ); 40 | sourceTree = ""; 41 | }; 42 | 58BC5B1628B3C37B0013EAD3 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 58BC5B1528B3C37B0013EAD3 /* Touch-Tab.app */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58BC5B1728B3C37B0013EAD3 /* Touch-Tab */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 58D3F9F52A8E15E50021A532 /* Touch-Tab.entitlements */, 54 | 58FBBBE429D20D3600A15F66 /* BundleInfo.swift */, 55 | 58BC5B2A28B3C4C30013EAD3 /* AppDelegate.swift */, 56 | 58BC5B2928B3C4C30013EAD3 /* Assets.xcassets */, 57 | 58BC5B2B28B3C4C30013EAD3 /* main.swift */, 58 | 58BC5B2828B3C4C30013EAD3 /* SwipeManager.swift */, 59 | 588922D329BB490400F7F843 /* AppSwitcher.swift */, 60 | 58DE49C929D09BB70025BE44 /* AboutView.swift */, 61 | 58D3F9F32A8E14C80021A532 /* PrivacyHelper.swift */, 62 | ); 63 | path = "Touch-Tab"; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 58BC5B1428B3C37B0013EAD3 /* Touch-Tab */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 58BC5B2428B3C37E0013EAD3 /* Build configuration list for PBXNativeTarget "Touch-Tab" */; 72 | buildPhases = ( 73 | 58BC5B1128B3C37B0013EAD3 /* Sources */, 74 | 58BC5B1328B3C37B0013EAD3 /* Resources */, 75 | ); 76 | buildRules = ( 77 | ); 78 | dependencies = ( 79 | ); 80 | name = "Touch-Tab"; 81 | productName = "Touch-Tab"; 82 | productReference = 58BC5B1528B3C37B0013EAD3 /* Touch-Tab.app */; 83 | productType = "com.apple.product-type.application"; 84 | }; 85 | /* End PBXNativeTarget section */ 86 | 87 | /* Begin PBXProject section */ 88 | 58BC5B0D28B3C37B0013EAD3 /* Project object */ = { 89 | isa = PBXProject; 90 | attributes = { 91 | BuildIndependentTargetsInParallel = 1; 92 | LastSwiftUpdateCheck = 1340; 93 | LastUpgradeCheck = 1340; 94 | TargetAttributes = { 95 | 58BC5B1428B3C37B0013EAD3 = { 96 | CreatedOnToolsVersion = 13.4.1; 97 | LastSwiftMigration = 1340; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = 58BC5B1028B3C37B0013EAD3 /* Build configuration list for PBXProject "Touch-Tab" */; 102 | compatibilityVersion = "Xcode 13.0"; 103 | developmentRegion = en; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | Base, 108 | ); 109 | mainGroup = 58BC5B0C28B3C37B0013EAD3; 110 | productRefGroup = 58BC5B1628B3C37B0013EAD3 /* Products */; 111 | projectDirPath = ""; 112 | projectRoot = ""; 113 | targets = ( 114 | 58BC5B1428B3C37B0013EAD3 /* Touch-Tab */, 115 | ); 116 | }; 117 | /* End PBXProject section */ 118 | 119 | /* Begin PBXResourcesBuildPhase section */ 120 | 58BC5B1328B3C37B0013EAD3 /* Resources */ = { 121 | isa = PBXResourcesBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | 58BC5B2D28B3C4C30013EAD3 /* Assets.xcassets in Resources */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | /* End PBXResourcesBuildPhase section */ 129 | 130 | /* Begin PBXSourcesBuildPhase section */ 131 | 58BC5B1128B3C37B0013EAD3 /* Sources */ = { 132 | isa = PBXSourcesBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 58BC5B2E28B3C4C30013EAD3 /* AppDelegate.swift in Sources */, 136 | 58BC5B2C28B3C4C30013EAD3 /* SwipeManager.swift in Sources */, 137 | 588922D429BB490400F7F843 /* AppSwitcher.swift in Sources */, 138 | 58FBBBE529D20D3600A15F66 /* BundleInfo.swift in Sources */, 139 | 58BC5B2F28B3C4C30013EAD3 /* main.swift in Sources */, 140 | 58DE49CA29D09BB70025BE44 /* AboutView.swift in Sources */, 141 | 58D3F9F42A8E14C80021A532 /* PrivacyHelper.swift in Sources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXSourcesBuildPhase section */ 146 | 147 | /* Begin XCBuildConfiguration section */ 148 | 58BC5B2228B3C37E0013EAD3 /* Debug */ = { 149 | isa = XCBuildConfiguration; 150 | buildSettings = { 151 | ALWAYS_SEARCH_USER_PATHS = NO; 152 | CLANG_ANALYZER_NONNULL = YES; 153 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 154 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 155 | CLANG_ENABLE_MODULES = YES; 156 | CLANG_ENABLE_OBJC_ARC = YES; 157 | CLANG_ENABLE_OBJC_WEAK = YES; 158 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 159 | CLANG_WARN_BOOL_CONVERSION = YES; 160 | CLANG_WARN_COMMA = YES; 161 | CLANG_WARN_CONSTANT_CONVERSION = YES; 162 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 163 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 164 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 165 | CLANG_WARN_EMPTY_BODY = YES; 166 | CLANG_WARN_ENUM_CONVERSION = YES; 167 | CLANG_WARN_INFINITE_RECURSION = YES; 168 | CLANG_WARN_INT_CONVERSION = YES; 169 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 170 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 171 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 172 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 173 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 174 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 175 | CLANG_WARN_STRICT_PROTOTYPES = YES; 176 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 177 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 178 | CLANG_WARN_UNREACHABLE_CODE = YES; 179 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 180 | COPY_PHASE_STRIP = NO; 181 | DEBUG_INFORMATION_FORMAT = dwarf; 182 | ENABLE_STRICT_OBJC_MSGSEND = YES; 183 | ENABLE_TESTABILITY = YES; 184 | GCC_C_LANGUAGE_STANDARD = gnu11; 185 | GCC_DYNAMIC_NO_PIC = NO; 186 | GCC_NO_COMMON_BLOCKS = YES; 187 | GCC_OPTIMIZATION_LEVEL = 0; 188 | GCC_PREPROCESSOR_DEFINITIONS = ( 189 | "DEBUG=1", 190 | "$(inherited)", 191 | ); 192 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 193 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 194 | GCC_WARN_UNDECLARED_SELECTOR = YES; 195 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 196 | GCC_WARN_UNUSED_FUNCTION = YES; 197 | GCC_WARN_UNUSED_VARIABLE = YES; 198 | MACOSX_DEPLOYMENT_TARGET = 10.15; 199 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 200 | MTL_FAST_MATH = YES; 201 | ONLY_ACTIVE_ARCH = YES; 202 | SDKROOT = macosx; 203 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 204 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 205 | }; 206 | name = Debug; 207 | }; 208 | 58BC5B2328B3C37E0013EAD3 /* Release */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_ANALYZER_NONNULL = YES; 213 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 215 | CLANG_ENABLE_MODULES = YES; 216 | CLANG_ENABLE_OBJC_ARC = YES; 217 | CLANG_ENABLE_OBJC_WEAK = YES; 218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_COMMA = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INFINITE_RECURSION = YES; 228 | CLANG_WARN_INT_CONVERSION = YES; 229 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 231 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 233 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 234 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 235 | CLANG_WARN_STRICT_PROTOTYPES = YES; 236 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 237 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 238 | CLANG_WARN_UNREACHABLE_CODE = YES; 239 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 242 | ENABLE_NS_ASSERTIONS = NO; 243 | ENABLE_STRICT_OBJC_MSGSEND = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu11; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | MACOSX_DEPLOYMENT_TARGET = 10.15; 253 | MTL_ENABLE_DEBUG_INFO = NO; 254 | MTL_FAST_MATH = YES; 255 | SDKROOT = macosx; 256 | SWIFT_COMPILATION_MODE = wholemodule; 257 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 258 | }; 259 | name = Release; 260 | }; 261 | 58BC5B2528B3C37E0013EAD3 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 265 | CLANG_ENABLE_MODULES = YES; 266 | CODE_SIGN_ENTITLEMENTS = "Touch-Tab/Touch-Tab.entitlements"; 267 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 268 | CODE_SIGN_STYLE = Automatic; 269 | COMBINE_HIDPI_IMAGES = YES; 270 | CURRENT_PROJECT_VERSION = 10; 271 | DEVELOPMENT_TEAM = ""; 272 | ENABLE_HARDENED_RUNTIME = YES; 273 | ENABLE_PREVIEWS = YES; 274 | GENERATE_INFOPLIST_FILE = YES; 275 | INFOPLIST_KEY_CFBundleDisplayName = "Touch-Tab"; 276 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; 277 | INFOPLIST_KEY_LSUIElement = YES; 278 | INFOPLIST_KEY_NSHumanReadableCopyright = "© Ilya Rodionov"; 279 | LD_RUNPATH_SEARCH_PATHS = ( 280 | "$(inherited)", 281 | "@executable_path/../Frameworks", 282 | ); 283 | MARKETING_VERSION = 1.5.0; 284 | PRODUCT_BUNDLE_IDENTIFIER = "ris58h.Touch-Tab"; 285 | PRODUCT_NAME = "$(TARGET_NAME)"; 286 | SWIFT_EMIT_LOC_STRINGS = YES; 287 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 288 | SWIFT_VERSION = 5.0; 289 | }; 290 | name = Debug; 291 | }; 292 | 58BC5B2628B3C37E0013EAD3 /* Release */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | CLANG_ENABLE_MODULES = YES; 297 | CODE_SIGN_ENTITLEMENTS = "Touch-Tab/Touch-Tab.entitlements"; 298 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 299 | CODE_SIGN_STYLE = Automatic; 300 | COMBINE_HIDPI_IMAGES = YES; 301 | CURRENT_PROJECT_VERSION = 10; 302 | DEVELOPMENT_TEAM = ""; 303 | ENABLE_HARDENED_RUNTIME = YES; 304 | ENABLE_PREVIEWS = YES; 305 | GENERATE_INFOPLIST_FILE = YES; 306 | INFOPLIST_KEY_CFBundleDisplayName = "Touch-Tab"; 307 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; 308 | INFOPLIST_KEY_LSUIElement = YES; 309 | INFOPLIST_KEY_NSHumanReadableCopyright = "© Ilya Rodionov"; 310 | LD_RUNPATH_SEARCH_PATHS = ( 311 | "$(inherited)", 312 | "@executable_path/../Frameworks", 313 | ); 314 | MARKETING_VERSION = 1.5.0; 315 | PRODUCT_BUNDLE_IDENTIFIER = "ris58h.Touch-Tab"; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | SWIFT_EMIT_LOC_STRINGS = YES; 318 | SWIFT_VERSION = 5.0; 319 | }; 320 | name = Release; 321 | }; 322 | /* End XCBuildConfiguration section */ 323 | 324 | /* Begin XCConfigurationList section */ 325 | 58BC5B1028B3C37B0013EAD3 /* Build configuration list for PBXProject "Touch-Tab" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 58BC5B2228B3C37E0013EAD3 /* Debug */, 329 | 58BC5B2328B3C37E0013EAD3 /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | 58BC5B2428B3C37E0013EAD3 /* Build configuration list for PBXNativeTarget "Touch-Tab" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | 58BC5B2528B3C37E0013EAD3 /* Debug */, 338 | 58BC5B2628B3C37E0013EAD3 /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | /* End XCConfigurationList section */ 344 | }; 345 | rootObject = 58BC5B0D28B3C37B0013EAD3 /* Project object */; 346 | } 347 | --------------------------------------------------------------------------------