diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 55233c8..855eff5 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -69,6 +69,8 @@ B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; }; B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; }; B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; }; + BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; }; + BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -159,6 +161,8 @@ B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeBarItem.swift; sourceTree = ""; }; B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchBarSupport.m; sourceTree = ""; }; B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = ""; }; + BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = ""; }; + BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -234,8 +238,10 @@ 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */, B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */, 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */, + BAF5AB5824317CAF00B04904 /* SwipeItem.swift */, 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */, B059D621205E03F5006E6B86 /* TouchBarController.swift */, + BAF5AB5624317B4300B04904 /* BasicView.swift */, 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */, B0008E542080286C003AD4DD /* SupportHelpers.swift */, B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */, @@ -465,11 +471,13 @@ B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */, B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */, 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */, + BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */, B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */, B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */, B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */, B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */, 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */, + BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */, B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */, diff --git a/MTMR/AppDelegate.swift b/MTMR/AppDelegate.swift index ab27818..f9c5084 100644 --- a/MTMR/AppDelegate.swift +++ b/MTMR/AppDelegate.swift @@ -92,7 +92,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { @objc func toggleMultitouch(_ item: NSMenuItem) { item.state = item.state == .on ? .off : .on AppSettings.multitouchGestures = item.state == .on - TouchBarController.shared.scrollArea?.gesturesEnabled = item.state == .on + TouchBarController.shared.basicView?.legacyGesturesEnabled = item.state == .on } @objc func openPreset(_: Any?) { diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift new file mode 100644 index 0000000..ce30af8 --- /dev/null +++ b/MTMR/BasicView.swift @@ -0,0 +1,107 @@ +// +// BasicView.swift +// MTMR +// +// Created by Fedor Zaitsev on 3/29/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + + +class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { + var twofingers: NSPanGestureRecognizer! + var threefingers: NSPanGestureRecognizer! + var fourfingers: NSPanGestureRecognizer! + var swipeItems: [SwipeItem] = [] + var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0] + + // legacy gesture positions + // by legacy I mean gestures to increse/decrease volume/brigtness which can be checked from app menu + var legacyPrevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0] + var legacyGesturesEnabled = false + + init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) { + super.init(identifier: identifier) + self.swipeItems = swipeItems + let views = items.compactMap { $0.view } + let stackView = NSStackView(views: views) + stackView.spacing = 1 + stackView.orientation = .horizontal + view = stackView + + twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:))) + twofingers.numberOfTouchesRequired = 2 + twofingers.allowedTouchTypes = .direct + view.addGestureRecognizer(twofingers) + + threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:))) + threefingers.numberOfTouchesRequired = 3 + threefingers.allowedTouchTypes = .direct + view.addGestureRecognizer(threefingers) + + fourfingers = NSPanGestureRecognizer(target: self, action: #selector(fourfingersHandler(_:))) + fourfingers.numberOfTouchesRequired = 4 + fourfingers.allowedTouchTypes = .direct + view.addGestureRecognizer(fourfingers) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func gestureHandler(position: CGFloat, fingers: Int, state: NSGestureRecognizer.State) { + switch state { + case .began: + prevPositions[fingers] = position + legacyPrevPositions[fingers] = position + case .changed: + if self.legacyGesturesEnabled { + if fingers == 2 { + let prevPos = legacyPrevPositions[fingers]! + if ((position - prevPos) > 10) || ((prevPos - position) > 10) { + if position > prevPos { + HIDPostAuxKey(NX_KEYTYPE_SOUND_UP) + } else if position < prevPos { + HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN) + } + legacyPrevPositions[fingers] = position + } + } + if fingers == 3 { + let prevPos = legacyPrevPositions[fingers]! + if ((position - prevPos) > 15) || ((prevPos - position) > 15) { + if position > prevPos { + GenericKeyPress(keyCode: CGKeyCode(144)).send() + } else if position < prevPos { + GenericKeyPress(keyCode: CGKeyCode(145)).send() + } + legacyPrevPositions[fingers] = position + } + } + } + case .ended: + print("gesture ended \(position - prevPositions[fingers]!) \(fingers)") + for item in swipeItems { + item.processEvent(offset: position - prevPositions[fingers]!, fingers: fingers) + } + default: + break + } + } + + @objc func twofingersHandler(_ sender: NSGestureRecognizer?) { + let position = (sender?.location(in: sender?.view).x)! + self.gestureHandler(position: position, fingers: 2, state: sender!.state) + } + + @objc func threefingersHandler(_ sender: NSGestureRecognizer?) { + let position = (sender?.location(in: sender?.view).x)! + self.gestureHandler(position: position, fingers: 3, state: sender!.state) + } + + @objc func fourfingersHandler(_ sender: NSGestureRecognizer?) { + let position = (sender?.location(in: sender?.view).x)! + self.gestureHandler(position: position, fingers: 4, state: sender!.state) + } +} diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 8c7e7e0..8ee6ef2 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.25 CFBundleVersion - 297 + 385 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 5248a2c..851cf15 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -226,6 +226,7 @@ enum ItemType: Decodable { case pomodoro(workTime: Double, restTime: Double) case network(flip: Bool) case darkMode + case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) private enum CodingKeys: String, CodingKey { case type @@ -252,6 +253,11 @@ enum ItemType: Decodable { case filter case disableMarquee case alternativeImages + case sourceApple + case sourceBash + case direction + case fingers + case minOffset } enum ItemTypeRaw: String, Decodable { @@ -274,6 +280,7 @@ enum ItemType: Decodable { case pomodoro case network case darkMode + case swipe } init(from decoder: Decoder) throws { @@ -363,6 +370,14 @@ enum ItemType: Decodable { case .darkMode: self = .darkMode + + case .swipe: + let sourceApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple) + let sourceBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash) + let direction = try container.decode(String.self, forKey: .direction) + let fingers = try container.decode(Int.self, forKey: .fingers) + let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0 + self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash) } } } diff --git a/MTMR/ScrollViewItem.swift b/MTMR/ScrollViewItem.swift index 96b0691..8a39af1 100644 --- a/MTMR/ScrollViewItem.swift +++ b/MTMR/ScrollViewItem.swift @@ -1,10 +1,6 @@ import Foundation -class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { - var twofingersPrev: CGFloat = 0.0 - var threefingersPrev: CGFloat = 0.0 - var twofingers: NSPanGestureRecognizer! - var threefingers: NSPanGestureRecognizer! +class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ { init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) { super.init(identifier: identifier) @@ -15,70 +11,10 @@ class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize)) scrollView.documentView = stackView view = scrollView - - twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:))) - twofingers.allowedTouchTypes = .direct - twofingers.numberOfTouchesRequired = 2 - view.addGestureRecognizer(twofingers) - - threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:))) - threefingers.allowedTouchTypes = .direct - threefingers.numberOfTouchesRequired = 3 - view.addGestureRecognizer(threefingers) - } - - var gesturesEnabled = true { - didSet { - twofingers.isEnabled = gesturesEnabled - threefingers.isEnabled = gesturesEnabled - } } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - @objc func twofingersHandler(_ sender: NSGestureRecognizer?) { // Volume - let position = (sender?.location(in: sender?.view).x)! - - switch sender!.state { - case .began: - twofingersPrev = position - case .changed: - if ((position - twofingersPrev) > 10) || ((twofingersPrev - position) > 10) { - if position > twofingersPrev { - HIDPostAuxKey(NX_KEYTYPE_SOUND_UP) - } else if position < twofingersPrev { - HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN) - } - twofingersPrev = position - } - case .ended: - twofingersPrev = 0.0 - default: - break - } - } - - @objc func threefingersHandler(_ sender: NSGestureRecognizer?) { // Brightness - let position = (sender?.location(in: sender?.view).x)! - - switch sender!.state { - case .began: - threefingersPrev = position - case .changed: - if ((position - threefingersPrev) > 15) || ((threefingersPrev - position) > 15) { - if position > threefingersPrev { - GenericKeyPress(keyCode: CGKeyCode(144)).send() - } else if position < threefingersPrev { - GenericKeyPress(keyCode: CGKeyCode(145)).send() - } - threefingersPrev = position - } - case .ended: - threefingersPrev = 0.0 - default: - break - } - } } diff --git a/MTMR/SwipeItem.swift b/MTMR/SwipeItem.swift new file mode 100644 index 0000000..baf7949 --- /dev/null +++ b/MTMR/SwipeItem.swift @@ -0,0 +1,66 @@ +// +// SwipeItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 3/29/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation +import Foundation + +class SwipeItem: NSCustomTouchBarItem { + private var scriptApple: NSAppleScript? + private var scriptBash: String? + private var direction: String + private var fingers: Int + private var minOffset: Float + init?(identifier: NSTouchBarItem.Identifier, direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) { + self.direction = direction + self.fingers = fingers + self.scriptBash = sourceBash?.string + self.scriptApple = sourceApple?.appleScript + self.minOffset = minOffset + super.init(identifier: identifier) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func processEvent(offset: CGFloat, fingers: Int) { + if direction == "right" && Float(offset) > self.minOffset && self.fingers == fingers { + self.execute() + } + if direction == "left" && Float(offset) < -self.minOffset && self.fingers == fingers { + self.execute() + } + } + + func execute() { + if scriptApple != nil { + DispatchQueue.appleScriptQueue.async { + var error: NSDictionary? + self.scriptApple?.executeAndReturnError(&error) + if let error = error { + print("SwipeItem apple script error: \(error)") + return + } + } + } + if scriptBash != nil { + DispatchQueue.shellScriptQueue.async { + let task = Process() + task.launchPath = "/bin/bash" + task.arguments = ["-c", self.scriptBash!] + task.launch() + task.waitUntilExit() + + + if (task.terminationStatus != 0) { + print("SwipeItem bash script error. Status: \(task.terminationStatus)") + } + } + } + } +} diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index e28598c..9bf1a78 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -57,6 +57,8 @@ extension ItemType { return NetworkBarItem.identifier case .darkMode: return DarkModeBarItem.identifier + case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _): + return "com.toxblh.mtmr.swipe." } } } @@ -76,10 +78,10 @@ class TouchBarController: NSObject, NSTouchBarDelegate { var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:] var leftIdentifiers: [NSTouchBarItem.Identifier] = [] var centerIdentifiers: [NSTouchBarItem.Identifier] = [] - var centerItems: [NSTouchBarItem] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = [] - var scrollArea: ScrollViewItem? - var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) + var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString)) + var basicView: BasicView? + var swipeItems: [SwipeItem] = [] var blacklistAppIdentifiers: [String] = [] var frontmostApplicationIdentifier: String? { @@ -114,24 +116,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate { jsonItems = newJsonItems itemDefinitions = [:] items = [:] - leftIdentifiers = [] - centerItems = [] - rightIdentifiers = [] loadItemDefinitions(jsonItems: jsonItems) createItems() - centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in + let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in items[identifier] }) - - centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) - scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) - scrollArea?.gesturesEnabled = AppSettings.multitouchGestures + + let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) + let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) touchBar.delegate = self - touchBar.defaultItemIdentifiers = [] - touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers + touchBar.defaultItemIdentifiers = [basicViewIdentifier] + + let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in + items[identifier] + }) + let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in + items[identifier] + }) + + basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems) + basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures updateActiveApp() } @@ -187,7 +194,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate { func createItems() { for (identifier, definition) in itemDefinitions { - items[identifier] = createItem(forIdentifier: identifier, definition: definition) + let item = createItem(forIdentifier: identifier, definition: definition) + if item is SwipeItem { + swipeItems.append(item as! SwipeItem) + } else { + items[identifier] = item + } } } @@ -223,16 +235,11 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { - if identifier == centerScrollArea { - return scrollArea + if identifier == basicViewIdentifier { + return basicView } - guard let item = self.items[identifier], - let definition = self.itemDefinitions[identifier], - definition.align != .center else { - return nil - } - return item + return nil } func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? { @@ -292,6 +299,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = NetworkBarItem(identifier: identifier, flip: flip) case .darkMode: barItem = DarkModeBarItem(identifier: identifier) + case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash): + barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash) } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { diff --git a/README.md b/README.md index 93d21e8..1e51a50 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,31 @@ The pre-installed configuration contains less or more than you'll probably want, - appleScriptTitledButton - shellScriptTitledButton -## Gestures on central part: +## Gestures +By default you can enable basic gestures from application menu (status bar -> MTMR icon -> Volume/Brightness gestures): - two finger slide: change you Volume - three finger slide: change you Brightness +### Custom gestures + +You can add custom actions for two/three/four finger swipes. To do it, you need to use `swipe` type: + +```json + "type": "swipe", + "fingers": 2, // number of fingers required (2,3 or 4) + "direction": "right", // direction of swipe (right/left) + "minOffset" 10, // optional: minimal required offset for gesture to emit event + "sourceApple": { // optional: apple script to run + "inline": "beep" + }, + "sourceBash": { // optional: bash script to run + "inline": "touch /Users/lobster/test" + } +``` + +You may create as many `swipe` objects in the preset as you want. + ## Built-in slider types: - brightness @@ -455,7 +475,7 @@ Settings: - [x] Startup at login - [ ] Show on/off in Dock - [ ] Show on/off in StatusBar -- [ ] On/off Haptic Feedback +- [ x] On/off Haptic Feedback Maybe: