From 712448f207f568d88402f0e9df575fcd7e23a997 Mon Sep 17 00:00:00 2001 From: bobrosoft Date: Mon, 30 Dec 2019 11:51:27 +0400 Subject: [PATCH 01/37] YandexWeather: update matching array with English translations as some users getting English version instead of Russian --- MTMR/Widgets/YandexWeatherBarItem.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index c6a050b..740ea2c 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -12,7 +12,10 @@ import CoreLocation class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { private let activity: NSBackgroundActivityScheduler private let unitsStr = "°C" - private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"] + private let iconsSource = [ + "Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫", + "Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫" + ] private var location: CLLocation! private var prevLocation: CLLocation! private var manager: CLLocationManager! From bdfb7118c951ce1fc412197e051e103ee48aad02 Mon Sep 17 00:00:00 2001 From: Sam Flattery Date: Mon, 6 Jan 2020 19:17:29 +0000 Subject: [PATCH 02/37] fix pomodoro icon not showing up on catalina --- MTMR/Widgets/PomodoroBarItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MTMR/Widgets/PomodoroBarItem.swift b/MTMR/Widgets/PomodoroBarItem.swift index 7b824f7..7e081f7 100644 --- a/MTMR/Widgets/PomodoroBarItem.swift +++ b/MTMR/Widgets/PomodoroBarItem.swift @@ -35,7 +35,7 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget { case none } - private let defaultTitle = "🍅" + private let defaultTitle = "🍅 " private let workTime: TimeInterval private let restTime: TimeInterval private var typeTime: TimeTypes = .none From 093faa2c021a12e334dc28693b4b069412e47319 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Tue, 14 Jan 2020 10:24:24 +0000 Subject: [PATCH 03/37] Create swift.yml --- .github/workflows/swift.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/swift.yml diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..2e76632 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,15 @@ +name: Swift + +on: [push] + +jobs: + build: + + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + - name: Build + run: swift build -v + - name: Run tests + run: swift test -v From 40c684f52805a8143eb4ad88b41349cc8912e53c Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Tue, 11 Feb 2020 23:44:19 +0000 Subject: [PATCH 04/37] GitHub actions (#271) * build xcode * final build test * delete travisCI --- .github/workflows/build-test.yml | 22 ++++++++++++++++++++++ .github/workflows/publish.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/swift.yml | 15 --------------- .travis.yml | 6 ------ MTMR/Info.plist | 2 +- 5 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/build-test.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/swift.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..30a9557 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,22 @@ +name: Swift + +on: [push] + +jobs: + test: + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + + - name: Run tests + run: xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]} + + build: + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + + - name: Build + run: xcodebuild archive -project "MTMR.xcodeproj" -scheme "MTMR" -archivePath Release/App.xcarchive DEVELOPMENT_TEAM="" CODE_SIGN_IDENTITY="" | xcpretty -c && exit ${PIPESTATUS[0]} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..31cb0e7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,31 @@ +name: Swift + +on: + push: + branches: + - master + tags: + - "v*" + +jobs: + test: + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install create-dmg + run: npm i -g create-dmg + + - name: Build Archive + run: xcodebuild archive -project "MTMR.xcodeproj" -scheme "MTMR" -archivePath Release/App.xcarchive DEVELOPMENT_TEAM="" CODE_SIGN_IDENTITY="" | xcpretty -c && exit ${PIPESTATUS[0]} + + - name: Build App + run: xcodebuild -project "MTMR.xcodeproj" -exportArchive -archivePath Release/App.xcarchive -exportOptionsPlist export-options.plist -exportPath Release | xcpretty -c && exit ${PIPESTATUS[0]} + + diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml deleted file mode 100644 index 2e76632..0000000 --- a/.github/workflows/swift.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Swift - -on: [push] - -jobs: - build: - - runs-on: macOS-latest - - steps: - - uses: actions/checkout@v1 - - name: Build - run: swift build -v - - name: Run tests - run: swift test -v diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8fd2c35..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: swift -xcode_project: MTMR.xcodeproj -xcode_scheme: UnitTests -osx_image: xcode11.1 -install: gem install xcpretty -script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}" diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 99603a5..8c7e7e0 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -39,7 +39,7 @@ NSHomeKitUsageDescription MTMR needs access to HomeKit for work NSHumanReadableCopyright - Copyright © 2018 Anton Palgunov. All rights reserved. + Copyright © 2018 - 2020 Anton Palgunov. All rights reserved. NSLocationAlwaysAndWhenInUseUsageDescription Weather widget need your location for correct work NSLocationAlwaysUsageDescription From 7e0db70fabc8715a881540b055eacfa5c7f73f49 Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Fri, 28 Feb 2020 11:35:09 -0800 Subject: [PATCH 05/37] Updated README (#274) Added explanation for missing parameters (background, title and image) --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index e985d68..7cff707 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,31 @@ If you want to longPress for some operations, it is similar to the configuration "bordered": "false" // "true" or "false" ``` +- `background` allow to specify you button background color + +```js + "background": "#FF0000", +``` +by using background with color "#000000" and bordered == false you can create button without gray background but with background when the button is pressed + +- `title` specify button title + +```js + "title": "hello" +``` + +- `image` specify button icon + +```js + "image": { + //Can be either of those + "base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdB...." + //or + "filePath": "~/img.png" + } +``` + + ### Roadmap - [x] Create the first prototype with TouchBar in Storyboard From 38645917777fe453a80077a0439fb0cbf9bb8cdc Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Fri, 28 Feb 2020 21:27:09 +0000 Subject: [PATCH 06/37] Update build-test.yml --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 30a9557..fa2af3c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,6 +1,6 @@ name: Swift -on: [push] +on: [push, pull_request] jobs: test: From dbb2f162222c4302c03c58f3231509d8630dd9a7 Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Fri, 28 Feb 2020 13:59:14 -0800 Subject: [PATCH 07/37] Implemented changable icons for AppleScriptTouchBarItem (#275) AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme --- MTMR/AppleScriptTouchBarItem.swift | 26 +++++++++++++++++++++++++- MTMR/ItemsParsing.swift | 6 ++++-- MTMR/TouchBarController.swift | 4 ++-- README.md | 29 ++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/MTMR/AppleScriptTouchBarItem.swift b/MTMR/AppleScriptTouchBarItem.swift index 33d89cf..1ee67a7 100644 --- a/MTMR/AppleScriptTouchBarItem.swift +++ b/MTMR/AppleScriptTouchBarItem.swift @@ -4,9 +4,11 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { private var script: NSAppleScript! private let interval: TimeInterval private var forceHideConstraint: NSLayoutConstraint! + private let alternativeImages: [String: SourceProtocol] - init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) { + init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) { self.interval = interval + self.alternativeImages = alternativeImages super.init(identifier: identifier, title: "⏳") forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0) title = "scheduled" @@ -57,6 +59,16 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { } } + func updateIcon(iconLabel: String) { + if alternativeImages[iconLabel] != nil { + DispatchQueue.main.async { + self.image = self.alternativeImages[iconLabel]!.image + } + } else { + print("Cannot find icon with label \"\(iconLabel)\"") + } + } + func execute() -> String { var error: NSDictionary? let output = script.executeAndReturnError(&error) @@ -64,6 +76,18 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { print(error) return "error" } + if output.descriptorType == typeAEList { + let arr = Array(1...output.numberOfItems).compactMap({ output.atIndex($0)!.stringValue ?? "" }) + + if arr.count <= 0 { + return "" + } else if arr.count == 1 { + return arr[0] + } else { + updateIcon(iconLabel: arr[1]) + return arr[0] + } + } return output.stringValue ?? "" } } diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 062409b..5248a2c 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -208,7 +208,7 @@ class SupportedTypesHolder { enum ItemType: Decodable { case staticButton(title: String) - case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) + case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double, alternativeImages: [String: SourceProtocol]) case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case timeButton(formatTemplate: String, timeZone: String?, locale: String?) case battery @@ -251,6 +251,7 @@ enum ItemType: Decodable { case autoResize case filter case disableMarquee + case alternativeImages } enum ItemTypeRaw: String, Decodable { @@ -282,7 +283,8 @@ enum ItemType: Decodable { case .appleScriptTitledButton: let source = try container.decode(Source.self, forKey: .source) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 - self = .appleScriptTitledButton(source: source, refreshInterval: interval) + let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:] + self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages) case .shellScriptTitledButton: let source = try container.decode(Source.self, forKey: .source) diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 7c7b559..e28598c 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -240,8 +240,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { switch item.type { case let .staticButton(title: title): barItem = CustomButtonTouchBarItem(identifier: identifier, title: title) - case let .appleScriptTitledButton(source: source, refreshInterval: interval): - barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval) + case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages): + barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages) case let .shellScriptTitledButton(source: source, refreshInterval: interval): barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval) case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale): diff --git a/README.md b/README.md index 7cff707..93d21e8 100644 --- a/README.md +++ b/README.md @@ -133,10 +133,37 @@ The pre-installed configuration contains less or more than you'll probably want, "inline": "tell application \"Finder\"\rif not (exists window 1) then\rmake new Finder window\rset target of front window to path to home folder as string\rend if\ractivate\rend tell", // or "base64": "StringInbase64" - } + }, } ``` +> Note: appleScriptTitledButton can change its icon. To do it, you need to do the following things: +1. Declarate dictionary of icons in `alternativeImages` field +2. Make you script return array of two values - `{"TITLE", "IMAGE_LABEL"}` +3. Make sure that your `IMAGE_LABEL` is declared in `alternativeImages` field + +Example: +```js + { + "type": "appleScriptTitledButton", + "source": { + "inline": "if (random number from 1 to 2) = 1 then\n\tset val to {\"title\", \"play\"}\nelse\n\tset val to {\"title\", \"pause\"}\nend if\nreturn val" + }, + "refreshInterval": 1, + "image": { + "base64": "iVBORw0KGgoAAAANSUhEUgA..." + }, + "alternativeImages": { + "play": { + "base64": "iVBORw0KGgoAAAANSUhEUgAAAAAA..." + }, + "pause": { + "base64": "iVBORw0KGgoAAAANSUhEUgAAAIAA..." + } + } + }, +``` + #### `shellScriptTitledButton` > Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting) > Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color. From 642f0807bb2af9cb426e5c4f2ef1a61a39c46926 Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Sat, 29 Feb 2020 03:23:41 -0800 Subject: [PATCH 08/37] Changeable icons for AppleScriptTouchBarItem & tests (#276) * Implemented changable icons for AppleScriptTouchBarItem AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme * Fixed tests Some tests started to fail after implementing alternativeIcons field for appleScriptBarItem --- MTMRTests/AppleScriptDefinitionTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MTMRTests/AppleScriptDefinitionTests.swift b/MTMRTests/AppleScriptDefinitionTests.swift index 98101b6..6e0d425 100644 --- a/MTMRTests/AppleScriptDefinitionTests.swift +++ b/MTMRTests/AppleScriptDefinitionTests.swift @@ -6,7 +6,7 @@ class AppleScriptDefinitionTests: XCTestCase { [ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" } } ] """.data(using: .utf8)! let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture) - guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else { + guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else { XCTFail() return } @@ -18,7 +18,7 @@ class AppleScriptDefinitionTests: XCTestCase { [ { "type": "appleScriptTitledButton", "source": { "filePath": "/ololo/pew" } } ] """.data(using: .utf8)! let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture) - guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else { + guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else { XCTFail() return } @@ -32,7 +32,7 @@ class AppleScriptDefinitionTests: XCTestCase { [ { "type": "appleScriptTitledButton", "source": { "filePath": "~/pew" } } ] """.data(using: .utf8)! let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture) - guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else { + guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else { XCTFail() return } @@ -58,7 +58,7 @@ class AppleScriptDefinitionTests: XCTestCase { [ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ] """.data(using: .utf8)! let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture) - guard case .appleScriptTitledButton(_, 305)? = result?.first?.type else { + guard case .appleScriptTitledButton(_, 305, _)? = result?.first?.type else { XCTFail() return } From 42ce95b72e9c377a3a856daf6da32ba30adb9175 Mon Sep 17 00:00:00 2001 From: Peter Hrvola Date: Mon, 16 Mar 2020 13:06:20 +0100 Subject: [PATCH 09/37] Add Music app (#279) --- MTMR.xcodeproj/project.pbxproj | 8 ++++++ MTMR/AppleScripts/Music.next.scpt | 7 +++++ MTMR/AppleScripts/Music.nowPlaying.scpt | 10 +++++++ MTMR/AppleScripts/PlaySmart.scpt | 4 +++ MTMR/Widgets/MusicBarItem.swift | 36 +++++++++++++++++++++++++ MTMR/defaultPreset.json | 20 ++++++++++++++ 6 files changed, 85 insertions(+) create mode 100644 MTMR/AppleScripts/Music.next.scpt create mode 100644 MTMR/AppleScripts/Music.nowPlaying.scpt diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index a720a6d..55233c8 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; }; 4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; }; 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; }; + 5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */; }; + 5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */; }; 60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; }; 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; }; 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; @@ -100,6 +102,8 @@ 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = ""; }; 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = ""; }; 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = ""; }; + 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.nowPlaying.scpt; sourceTree = ""; }; + 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.next.scpt; sourceTree = ""; }; 60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = ""; }; 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = ""; }; 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = ""; }; @@ -258,6 +262,8 @@ B0B17426207D6AFE0004B740 /* AppleScripts */ = { isa = PBXGroup; children = ( + 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */, + 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */, B0B1742A207D6B580004B740 /* Battery.scpt */, B0B17429207D6B580004B740 /* Finder.scpt */, B0B1742C207D6B590004B740 /* iTunes.next.scpt */, @@ -407,6 +413,8 @@ B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */, B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */, B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt in Resources */, + 5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */, + 5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */, B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */, B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */, B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */, diff --git a/MTMR/AppleScripts/Music.next.scpt b/MTMR/AppleScripts/Music.next.scpt new file mode 100644 index 0000000..9101064 --- /dev/null +++ b/MTMR/AppleScripts/Music.next.scpt @@ -0,0 +1,7 @@ +if application "Music" is running then + tell application "Music" + if player state is playing then + next track + end if + end tell +end if diff --git a/MTMR/AppleScripts/Music.nowPlaying.scpt b/MTMR/AppleScripts/Music.nowPlaying.scpt new file mode 100644 index 0000000..81644b4 --- /dev/null +++ b/MTMR/AppleScripts/Music.nowPlaying.scpt @@ -0,0 +1,10 @@ +if application "Music" is running then + tell application "Music" + if player state is playing then + return (get artist of current track) & " – " & (get name of current track) + else + return "" + end if + end tell +end if +return "" diff --git a/MTMR/AppleScripts/PlaySmart.scpt b/MTMR/AppleScripts/PlaySmart.scpt index eed3e02..2807287 100644 --- a/MTMR/AppleScripts/PlaySmart.scpt +++ b/MTMR/AppleScripts/PlaySmart.scpt @@ -2,6 +2,10 @@ if application "iTunes" is running then tell application "iTunes" to playpause end if +if application "Music" is running then + tell application "Music" to playpause +end if + if application "Spotify" is running then tell application "Spotify" to playpause end if diff --git a/MTMR/Widgets/MusicBarItem.swift b/MTMR/Widgets/MusicBarItem.swift index 5bc8e7b..23b2029 100644 --- a/MTMR/Widgets/MusicBarItem.swift +++ b/MTMR/Widgets/MusicBarItem.swift @@ -11,6 +11,7 @@ import ScriptingBridge class MusicBarItem: CustomButtonTouchBarItem { private enum Player: String { + case Music = "com.apple.Music" case iTunes = "com.apple.iTunes" case Spotify = "com.spotify.client" case VOX = "com.coppertino.Vox" @@ -19,6 +20,7 @@ class MusicBarItem: CustomButtonTouchBarItem { } private let playerBundleIdentifiers = [ + Player.Music, Player.iTunes, Player.Spotify, Player.VOX, @@ -71,6 +73,10 @@ class MusicBarItem: CustomButtonTouchBarItem { let mp = (musicPlayer as iTunesApplication) mp.playpause!() return + } else if ident == .Music { + let mp = (musicPlayer as MusicApplication) + mp.playpause!() + return } else if ident == .VOX { let mp = (musicPlayer as VoxApplication) mp.playpause!() @@ -134,6 +140,11 @@ class MusicBarItem: CustomButtonTouchBarItem { mp.nextTrack!() updatePlayer() return + } else if ident == .Music { + let mp = (musicPlayer as MusicApplication) + mp.nextTrack!() + updatePlayer() + return } else if ident == .VOX { let mp = (musicPlayer as VoxApplication) mp.next!() @@ -188,6 +199,8 @@ class MusicBarItem: CustomButtonTouchBarItem { tempTitle = (musicPlayer as SpotifyApplication).title } else if ident == .iTunes { tempTitle = (musicPlayer as iTunesApplication).title + } else if ident == .Music { + tempTitle = (musicPlayer as MusicApplication).title } else if ident == .VOX { tempTitle = (musicPlayer as VoxApplication).title } else if ident == .Safari { @@ -321,6 +334,29 @@ extension iTunesApplication { } } +@objc protocol MusicApplication { + @objc optional var currentTrack: MusicTrack { get } + @objc optional func playpause() + @objc optional func nextTrack() + @objc optional func previousTrack() +} + +extension SBApplication: MusicApplication {} + +@objc protocol MusicTrack { + @objc optional var artist: String { get } + @objc optional var name: String { get } +} + +extension SBObject: MusicTrack {} + +extension MusicApplication { + var title: String { + guard let t = currentTrack else { return "" } + return (t.artist ?? "") + " — " + (t.name ?? "") + } +} + @objc protocol VoxApplication { @objc optional func playpause() @objc optional func next() diff --git a/MTMR/defaultPreset.json b/MTMR/defaultPreset.json index 62b43d8..dfa019e 100644 --- a/MTMR/defaultPreset.json +++ b/MTMR/defaultPreset.json @@ -28,6 +28,26 @@ } }, + // Music + { + "type": "appleScriptTitledButton", + "source": { + "inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r" + }, + "action": "appleScript", + "actionAppleScript": { + "inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r" + }, + "longAction": "appleScript", + "longActionAppleScript": { + "inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rprev track\rend if\rend tell\rend if\r" + }, + "refreshInterval": 2, + "image": { + "base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADAUExURUdwTOVVZCzB+3qc0gkDBgEBAgcKEwAAAA4EBP5aVU2V95iJv7V3rtBOvH5W6jaOyclScKZGX3wuQCMuUqZN7+NQYXtDvFd9/sxYni2z6UhBhyhvnIp7sf38/PXz9ePm69/k6fHv8/j29+3q7/v6+ufq7uvu8fTw8+1egOFki/dbboVj/HNy/T+j/dNtnEul81vC8Vmf3OeRqOBVofK4xZfF7sDb7PLe6LxU1KKK79PL6vrW3fh4g5Zi4bi16daa0A3Qc90AAAAddFJOUwD3/v0uOlYNG/z+/v7998OYYztt/Le2/eDqi5jEo2rNTwAABMtJREFUWMPdl1tjqjoQhauC0Hqvta26JVxUQBQFRPBS/f//as8kISBqd/twXs5KjBDyrZkEFfP09D+WoigqFxz+mlbVXncweEZVBoNuD1x+E1vtMVZoURn01J/mob52KRNuj4k5mZjJcRueFotFpfv6EwdVRfy0tSdXSranheN0/zkRSB7x42Q6udExdBy3972Fog4o/kBg4X6bhPJaeX7eTqeT6b0MJtPpdOu6n49XQgX+dOQ8vpWEvYn/2AHiL052PpoROcsOtND17ztQ3rwTuCQz9O/moKiMf6BkG/puKBzurKQ64PmbU2bDzUxk3Uql4lZcl3Vpvt9VbxLoLZwjY7E1WcNZoB0XpbELie/3Sg6KVHG2jGPs1LTCE2UXFfgIgtBgyq8d/E/pehJq1zmZGc7kAPsMX4Ec932T25uX5vUklFcHJlDU1OT4wllkvOtn9lrSbF7dCUggNEtaMOXhQZq4WkpBcksJQBCOnyjvM4P8KqQgFW9BFJka2NMKB1gw+VMxvN9smnwI1EuzpxS+g9FWYySjsTpOtnq+H162iW01m/wyXLUPzT/5HKQoSjQmU8vE6TAElvWggbhuNRpScQa002bVtJmBCz9qNusWBkJmoyHmoHajC4yybVujhR26mJVha7lDo2FrhnA4N0aq+BpE24zjgsMoEsfU0AADaKCwemiIRZA+o6N9oygyMi/EAWk0DMNgFvCmN/5IwqCV3PCGzzIwbINrzgwykVq2iorUalm2UTZotXKWqVYz5uBjzDUoxrxWyzKQWy061LZsNIJ3PAMDIcbVauwdGmxrNblgYNnCgStN54ylBSsYoAxerwwgJsCWTS0sepym0Mdp1gYBw5lmwgDXIDEoaeHLYE36BafzuQWFQ9RASM/XQPpMD5YQ2gA/AwPArQJyZWDsgo64C+/pBRkDG4s31hdmwFNAGz1mBjPukBs8qdSgLDBA1LJm1lw/14IgWAdAQ5nhax4HY/FR7qfpHQMPUChzS0c6eFmv17MZo7HZBP3MQJHTNCnzxPMgOFUteAGtMwMmfRPIijBgc+AmNK6+9zw+9Iw05YsG8aaT/7Kro7eUcoSWw3n/1W57SxgGOZCYxl+VDDabcf6LpNTf3g6IQ4XY7TbiyyUBmhqsViswWK02cE7ITIdmtxnWlcJz6f1tPyMsha+2R4UGyJPdywotwGSHOKHdm+IMYA40BRhtkXOb42DAh8crppjw8CyB4nMBlvFtD/0WSfZebkDHguLNar2JdyTXptqRrx6OmMKZkISQc4Yv9yyDXHiiEx1qXL1OAFdhBJPAAQeRQDEiRZEm+kwnu2p1XHo64yQ8j47bL1kCZ87pDKWxuW4mQJ9O9ba31xE5Y/rnA4VoTCJQwvnyBNgk+pkDi8sSJjlKRPxhX7r3Lytz0LPMi1H1Qv7VuzwuAzh4h1ukKFi/YV9+9E8THZbne2Ezxd/xsNGQ6u+wgoeH4SH9Tl367t+2Ko/acA8Oj/DhWP7X/30Zkvj4WMYlj10MOISXf7DlkPvvH6g43u0oCzDS1U5f/sHWC3d7cn3UAQf4HeHfwxXQY4yu/HTDKNXro3Gngw4vw2FnPKrXJfUXu0fqIdeFZOnXm08FTRSxcf391pW7oNGT8vRf6i9jqljwYzAm6AAAAABJRU5ErkJggg==" + } + }, + // iTunes { "type": "appleScriptTitledButton", From a0fc0b33c5759a24778627d3b8db6745d7b16d8b Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Wed, 1 Apr 2020 14:11:35 -0700 Subject: [PATCH 10/37] Custom gestures support (#288) * Updated README Added explanation for missing parameters (background, title and image) * Implemented changable icons for AppleScriptTouchBarItem AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme * Implemented custom swipe actions Co-authored-by: Fedor Zaitsev --- MTMR.xcodeproj/project.pbxproj | 8 +++ MTMR/AppDelegate.swift | 2 +- MTMR/BasicView.swift | 107 +++++++++++++++++++++++++++++++++ MTMR/Info.plist | 2 +- MTMR/ItemsParsing.swift | 15 +++++ MTMR/ScrollViewItem.swift | 66 +------------------- MTMR/SwipeItem.swift | 66 ++++++++++++++++++++ MTMR/TouchBarController.swift | 53 +++++++++------- README.md | 24 +++++++- 9 files changed, 252 insertions(+), 91 deletions(-) create mode 100644 MTMR/BasicView.swift create mode 100644 MTMR/SwipeItem.swift 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: From f61550e5107b85eb3d59dfe825d7340b235279aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A6y?= Date: Thu, 9 Apr 2020 04:54:43 +0530 Subject: [PATCH 11/37] More Productive Gestures (#289) * Scroll Functionality Works * updated md * Update README.md * Update README.md * attempted to fix readme * fixed readme * fixed readme --- MTMR/AppDelegate.swift | 4 ++-- MTMR/BasicView.swift | 37 +++++++++++++++++++++++++++++++++++-- MTMR/Info.plist | 2 +- README.md | 13 +++++++++---- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/MTMR/AppDelegate.swift b/MTMR/AppDelegate.swift index f9c5084..f62ed9f 100644 --- a/MTMR/AppDelegate.swift +++ b/MTMR/AppDelegate.swift @@ -76,7 +76,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } else { TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier) } - + AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers TouchBarController.shared.updateActiveApp() updateIsBlockedApp() @@ -132,7 +132,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H") hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off - let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "") + let multitouchGestures = NSMenuItem(title: "Default Swipe Gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "") multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "") diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift index ce30af8..5d388cb 100644 --- a/MTMR/BasicView.swift +++ b/MTMR/BasicView.swift @@ -10,6 +10,7 @@ import Foundation class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { + var onefinger: NSPanGestureRecognizer! var twofingers: NSPanGestureRecognizer! var threefingers: NSPanGestureRecognizer! var fourfingers: NSPanGestureRecognizer! @@ -30,6 +31,11 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { stackView.orientation = .horizontal view = stackView + onefinger = NSPanGestureRecognizer(target: self, action: #selector(onefingerHandler(_:))) + onefinger.numberOfTouchesRequired = 1 + onefinger.allowedTouchTypes = .direct + view.addGestureRecognizer(onefinger) + twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:))) twofingers.numberOfTouchesRequired = 2 twofingers.allowedTouchTypes = .direct @@ -57,9 +63,31 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { legacyPrevPositions[fingers] = position case .changed: if self.legacyGesturesEnabled { + if fingers == 1 { + let prevPos = legacyPrevPositions[fingers]! + if ((position - prevPos) > 3) || ((prevPos - position) > 3) { + if position > prevPos { + GenericKeyPress(keyCode: CGKeyCode(124)).send() + } else if position < prevPos { + GenericKeyPress(keyCode: CGKeyCode(123)).send() + } + legacyPrevPositions[fingers] = position + } + } if fingers == 2 { let prevPos = legacyPrevPositions[fingers]! - if ((position - prevPos) > 10) || ((prevPos - position) > 10) { + if ((position - prevPos) > 50) || ((prevPos - position) > 50) { + if position > prevPos { + GenericKeyPress(keyCode: CGKeyCode(124)).send() + } else if position < prevPos { + GenericKeyPress(keyCode: CGKeyCode(123)).send() + } + legacyPrevPositions[fingers] = position + } + } + if fingers == 3 { + let prevPos = legacyPrevPositions[fingers]! + if ((position - prevPos) > 15) || ((prevPos - position) > 15) { if position > prevPos { HIDPostAuxKey(NX_KEYTYPE_SOUND_UP) } else if position < prevPos { @@ -68,7 +96,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { legacyPrevPositions[fingers] = position } } - if fingers == 3 { + if fingers == 4 { let prevPos = legacyPrevPositions[fingers]! if ((position - prevPos) > 15) || ((prevPos - position) > 15) { if position > prevPos { @@ -90,6 +118,11 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { } } + @objc func onefingerHandler(_ sender: NSGestureRecognizer?) { + let position = (sender?.location(in: sender?.view).x)! + self.gestureHandler(position: position, fingers: 1, state: sender!.state) + } + @objc func twofingersHandler(_ sender: NSGestureRecognizer?) { let position = (sender?.location(in: sender?.view).x)! self.gestureHandler(position: position, fingers: 2, state: sender!.state) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 8ee6ef2..ae0a326 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.25 CFBundleVersion - 385 + 402 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/README.md b/README.md index 1e51a50..f69f6f7 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,16 @@ The pre-installed configuration contains less or more than you'll probably want, ## 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 +### Default Gestures -### Custom gestures +By default you can enable basic gestures from application menu (status bar -> MTMR icon -> Default Swipe Gestures): + +- ```one finger slide```: Move Caret +- ```two finger slide```: Move Caret with precision +- ```three finger slide```: Increase/Decrease Volume +- ```four finger slide```: Increase/Decrease Brightness + +### Custom Gestures You can add custom actions for two/three/four finger swipes. To do it, you need to use `swipe` type: From 502f9894171d992c62851ebf3f1db754556f0b64 Mon Sep 17 00:00:00 2001 From: Simon Rogers <63044346+siroger@users.noreply.github.com> Date: Thu, 9 Apr 2020 18:49:39 +0100 Subject: [PATCH 12/37] Currency bar update (#293) * Updated CurrenyBarItem to display second currency (TO FROM>RATE) and also correct number of decimal places. Added INR to list of currencies. * Update CurrencyBarItem.swift * Updated else statements Co-authored-by: medden --- MTMR/Widgets/CurrencyBarItem.swift | 35 ++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/MTMR/Widgets/CurrencyBarItem.swift b/MTMR/Widgets/CurrencyBarItem.swift index dfe3479..71e5599 100644 --- a/MTMR/Widgets/CurrencyBarItem.swift +++ b/MTMR/Widgets/CurrencyBarItem.swift @@ -15,6 +15,9 @@ class CurrencyBarItem: CustomButtonTouchBarItem { private var postfix: String private var from: String private var to: String + private var decimal: Int + private var decimalValue: Float32! + private var decimalString: String! private var oldValue: Float32! private var full: Bool = false @@ -32,11 +35,29 @@ class CurrencyBarItem: CustomButtonTouchBarItem { "IDR": "Rp", "MXN": "$", "SGD": "$", - "CHF": "Fr.", "BTC": "฿", "LTC": "Ł", "ETH": "Ξ", ] + private let decimals = [ + "USD": 4, + "EUR": 4, + "RUB": 2, + "JPY": 2, + "GBP": 4, + "CAD": 4, + "KRW": 4, + "CNY": 4, + "AUD": 4, + "BRL": 4, + "IDR": 1, + "MXN": 2, + "SGD": 4, + "CHF": 4, + "BTC": 2, + "LTC": 2, + "ETH": 2, + ] init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) { activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") @@ -57,6 +78,14 @@ class CurrencyBarItem: CustomButtonTouchBarItem { postfix = to } + + if let decimal = decimals[to] { + self.decimal = decimal + } else { + decimal = 2 + } + + super.init(identifier: identifier, title: "⏳") activity.repeats = true @@ -114,10 +143,12 @@ class CurrencyBarItem: CustomButtonTouchBarItem { } oldValue = value + decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal)) + decimalString = String(decimalValue) var title = "" if full { - title = String(format: "%@‣%.2f%@", prefix, value, postfix) + title = String(format: "%@%@‣%@", prefix, postfix, decimalString) } else { title = String(format: "%@%.2f", prefix, value) } From 52758f947d4594f2ff6971a7f2bf8c8496dc1cbb Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Tue, 21 Apr 2020 05:34:03 -0700 Subject: [PATCH 13/37] Fix shell crash (#297) * Updated README Added explanation for missing parameters (background, title and image) * Implemented changable icons for AppleScriptTouchBarItem AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme * Fixed error related to ShellButton When you execute Process from swift you cannot relay solely on pipe.fileHandleForReading.readDataToEndOfFile() Sometimes when I close notebook I get exception saying that you cannot access process.terminationStatus variable while process is running. Apparently it seems that this call can be finished when OS X put disks into sleep mode(?) What I did: 1. Added Process.waitUntilExit() call 2. Added timeout (equal to the update interval) Co-authored-by: Fedor Zaitsev --- MTMR/ShellScriptTouchBarItem.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MTMR/ShellScriptTouchBarItem.swift b/MTMR/ShellScriptTouchBarItem.swift index 94d51ec..4fd51f3 100644 --- a/MTMR/ShellScriptTouchBarItem.swift +++ b/MTMR/ShellScriptTouchBarItem.swift @@ -62,11 +62,19 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem { let pipe = Pipe() task.standardOutput = pipe + + // kill process if it is over update interval + DispatchQueue.main.asyncAfter(deadline: .now() + interval) { [weak task] in + task?.terminate() + } + task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? "" + //always wait until task end or you can catch "task still running" error while accessing task.terminationStatus variable + task.waitUntilExit() if (output == "" && task.terminationStatus != 0) { output = "error" } From 6d266394a494c4b0dc4f23e91356c5fa713b1ce8 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Wed, 13 May 2020 23:11:45 +0100 Subject: [PATCH 14/37] Revert "More Productive Gestures (#289)" This reverts commit f61550e5107b85eb3d59dfe825d7340b235279aa. --- MTMR/AppDelegate.swift | 4 ++-- MTMR/BasicView.swift | 37 ++----------------------------------- MTMR/Info.plist | 2 +- README.md | 13 ++++--------- 4 files changed, 9 insertions(+), 47 deletions(-) diff --git a/MTMR/AppDelegate.swift b/MTMR/AppDelegate.swift index f62ed9f..f9c5084 100644 --- a/MTMR/AppDelegate.swift +++ b/MTMR/AppDelegate.swift @@ -76,7 +76,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } else { TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier) } - + AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers TouchBarController.shared.updateActiveApp() updateIsBlockedApp() @@ -132,7 +132,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H") hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off - let multitouchGestures = NSMenuItem(title: "Default Swipe Gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "") + let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "") multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "") diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift index 5d388cb..ce30af8 100644 --- a/MTMR/BasicView.swift +++ b/MTMR/BasicView.swift @@ -10,7 +10,6 @@ import Foundation class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { - var onefinger: NSPanGestureRecognizer! var twofingers: NSPanGestureRecognizer! var threefingers: NSPanGestureRecognizer! var fourfingers: NSPanGestureRecognizer! @@ -31,11 +30,6 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { stackView.orientation = .horizontal view = stackView - onefinger = NSPanGestureRecognizer(target: self, action: #selector(onefingerHandler(_:))) - onefinger.numberOfTouchesRequired = 1 - onefinger.allowedTouchTypes = .direct - view.addGestureRecognizer(onefinger) - twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:))) twofingers.numberOfTouchesRequired = 2 twofingers.allowedTouchTypes = .direct @@ -63,31 +57,9 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { legacyPrevPositions[fingers] = position case .changed: if self.legacyGesturesEnabled { - if fingers == 1 { - let prevPos = legacyPrevPositions[fingers]! - if ((position - prevPos) > 3) || ((prevPos - position) > 3) { - if position > prevPos { - GenericKeyPress(keyCode: CGKeyCode(124)).send() - } else if position < prevPos { - GenericKeyPress(keyCode: CGKeyCode(123)).send() - } - legacyPrevPositions[fingers] = position - } - } if fingers == 2 { let prevPos = legacyPrevPositions[fingers]! - if ((position - prevPos) > 50) || ((prevPos - position) > 50) { - if position > prevPos { - GenericKeyPress(keyCode: CGKeyCode(124)).send() - } else if position < prevPos { - GenericKeyPress(keyCode: CGKeyCode(123)).send() - } - legacyPrevPositions[fingers] = position - } - } - if fingers == 3 { - let prevPos = legacyPrevPositions[fingers]! - if ((position - prevPos) > 15) || ((prevPos - position) > 15) { + if ((position - prevPos) > 10) || ((prevPos - position) > 10) { if position > prevPos { HIDPostAuxKey(NX_KEYTYPE_SOUND_UP) } else if position < prevPos { @@ -96,7 +68,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { legacyPrevPositions[fingers] = position } } - if fingers == 4 { + if fingers == 3 { let prevPos = legacyPrevPositions[fingers]! if ((position - prevPos) > 15) || ((prevPos - position) > 15) { if position > prevPos { @@ -118,11 +90,6 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { } } - @objc func onefingerHandler(_ sender: NSGestureRecognizer?) { - let position = (sender?.location(in: sender?.view).x)! - self.gestureHandler(position: position, fingers: 1, state: sender!.state) - } - @objc func twofingersHandler(_ sender: NSGestureRecognizer?) { let position = (sender?.location(in: sender?.view).x)! self.gestureHandler(position: position, fingers: 2, state: sender!.state) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index ae0a326..8ee6ef2 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.25 CFBundleVersion - 402 + 385 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/README.md b/README.md index f69f6f7..1e51a50 100644 --- a/README.md +++ b/README.md @@ -104,16 +104,11 @@ The pre-installed configuration contains less or more than you'll probably want, ## Gestures -### Default 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 -By default you can enable basic gestures from application menu (status bar -> MTMR icon -> Default Swipe Gestures): - -- ```one finger slide```: Move Caret -- ```two finger slide```: Move Caret with precision -- ```three finger slide```: Increase/Decrease Volume -- ```four finger slide```: Increase/Decrease Brightness - -### Custom Gestures +### Custom gestures You can add custom actions for two/three/four finger swipes. To do it, you need to use `swipe` type: From 3fc75ca8f01cc89fbb4b665265c5fadc21c67a1c Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Wed, 13 May 2020 23:22:31 +0100 Subject: [PATCH 15/37] Updated workflows. For delivery unsign app in gihub --- .github/workflows/build-test.yml | 2 +- .github/workflows/publish.yml | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index fa2af3c..d341f6c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,4 +1,4 @@ -name: Swift +name: Build-and-test on: [push, pull_request] diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 31cb0e7..347ff4a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,11 +1,12 @@ -name: Swift +name: Publish unsign version -on: - push: - branches: - - master - tags: - - "v*" +# on: +# push: +# branches: +# - master +# tags: +# - "v*" +on: [push, pull_request] jobs: test: @@ -28,4 +29,14 @@ jobs: - name: Build App run: xcodebuild -project "MTMR.xcodeproj" -exportArchive -archivePath Release/App.xcarchive -exportOptionsPlist export-options.plist -exportPath Release | xcpretty -c && exit ${PIPESTATUS[0]} + - name: Create DMG + run: | + cd Release + create-dmg MTMR.app + - name: GitHub Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + files: Release/MTMR*.dmg.tgz From a1f64028cced747f63c7b638307c3a7737daaceb Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Wed, 13 May 2020 23:23:03 +0100 Subject: [PATCH 16/37] Correct file for release --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 347ff4a..669fc63 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,4 +39,4 @@ jobs: with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false - files: Release/MTMR*.dmg.tgz + files: Release/MTMR*.dmg From 75df82a5675c071e5b8a10b9b7b1237ee91c5477 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Wed, 13 May 2020 23:34:45 +0100 Subject: [PATCH 17/37] ignore sign error --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 669fc63..1ede210 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: - name: Create DMG run: | cd Release - create-dmg MTMR.app + create-dmg MTMR.app || true - name: GitHub Release uses: "marvinpinto/action-automatic-releases@latest" From b6721f0274a04751a71135ad8ddbd42c509e03a0 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Thu, 14 May 2020 00:05:17 +0100 Subject: [PATCH 18/37] return normal trigggers in workflows --- .github/workflows/publish.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1ede210..d1b03be 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,15 +1,14 @@ name: Publish unsign version -# on: -# push: -# branches: -# - master -# tags: -# - "v*" -on: [push, pull_request] +on: + push: + branches: + - master + tags: + - "v*" jobs: - test: + Build-and-release: runs-on: macOS-latest steps: From 445584bb1be3ec452e1a198a6220108516d661d1 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Thu, 14 May 2020 00:42:08 +0100 Subject: [PATCH 19/37] Added xcpretty for test and build scripts --- build.sh | 6 ++++-- test.sh | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index cc5f8ee..a28d539 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,18 @@ +# INSTALL xcpretty: sudo gem install xcpretty + NAME='MTMR' rm -r Release 2>/dev/null xcodebuild archive \ -scheme "$NAME" \ - -archivePath Release/App.xcarchive + -archivePath Release/App.xcarchive | xcpretty -c xcodebuild \ -exportArchive \ -archivePath Release/App.xcarchive \ -exportOptionsPlist export-options.plist \ - -exportPath Release + -exportPath Release | xcpretty -c cd Release rm -r App.xcarchive diff --git a/test.sh b/test.sh index ade0dd7..c140089 100755 --- a/test.sh +++ b/test.sh @@ -1,3 +1,4 @@ +# INSTALL xcpretty: sudo gem install xcpretty NAME='MTMR' killall $NAME @@ -11,13 +12,13 @@ rm -r Release 2>/dev/null xcodebuild archive \ -scheme "$NAME" \ - -archivePath Release/App.xcarchive + -archivePath Release/App.xcarchive | xcpretty xcodebuild \ -exportArchive \ -archivePath Release/App.xcarchive \ -exportOptionsPlist export-options.plist \ - -exportPath Release + -exportPath Release | xcpretty cd Release rm -r App.xcarchive From bc11728c2e77fda561bd3e76ee89d5de59c37c8b Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Thu, 14 May 2020 00:44:10 +0100 Subject: [PATCH 20/37] Standard english date for build script --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index a28d539..70993f8 100755 --- a/build.sh +++ b/build.sh @@ -22,7 +22,7 @@ NAME_DMG="${NAME}.app" echo $NAME_DMG create-dmg $NAME_DMG -DATE=`date +"%a, %d %b %Y %H:%M:%S %z"` +DATE=`LC_ALL=en_US.utf8 date +"%a, %d %b %Y %H:%M:%S %z"` BUILD=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${NAME}.app/Contents/Info.plist` VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${NAME}.app/Contents/Info.plist` MINIMUM=`/usr/libexec/PlistBuddy -c "Print LSMinimumSystemVersion" ${NAME}.app/Contents/Info.plist` From 1e1ae2af61ba11c969786a8ee49cb4e97f98f17b Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Thu, 14 May 2020 00:49:53 +0100 Subject: [PATCH 21/37] Release version 0.26 --- MTMR/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 8ee6ef2..a505c36 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.25 + 0.26 CFBundleVersion - 385 + 401 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion From 2e2f556daf45f3ca8187930c94b04c1941210c94 Mon Sep 17 00:00:00 2001 From: Fedor Zaitsev Date: Sun, 17 May 2020 05:39:21 -0700 Subject: [PATCH 22/37] Fixed lost margin (#313) * Updated README Added explanation for missing parameters (background, title and image) * Implemented changable icons for AppleScriptTouchBarItem AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme * Fixed #312 Fixed bug that margin is lost on all elements Co-authored-by: Fedor Zaitsev --- MTMR/BasicView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift index ce30af8..c0d3f70 100644 --- a/MTMR/BasicView.swift +++ b/MTMR/BasicView.swift @@ -26,7 +26,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { self.swipeItems = swipeItems let views = items.compactMap { $0.view } let stackView = NSStackView(views: views) - stackView.spacing = 1 + stackView.spacing = 8 stackView.orientation = .horizontal view = stackView From 3e5fa14494c1eaf601305b5493b583061a7e6caf Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Sun, 17 May 2020 13:42:02 +0100 Subject: [PATCH 23/37] v0.26.1 --- MTMR/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index a505c36..e1bbf12 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.26 + 0.26.1 CFBundleVersion - 401 + 405 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion From 1def53878d94971c1a55bd5a8f72beded215a3bb Mon Sep 17 00:00:00 2001 From: Vladimir Tolstikov Date: Thu, 21 May 2020 14:27:50 +0400 Subject: [PATCH 24/37] YandexWeather: update matching array with missing forecast ("Thunderstorm with rain") (#316) --- MTMR/Widgets/YandexWeatherBarItem.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index 740ea2c..ebdc8aa 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -13,8 +13,8 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate private let activity: NSBackgroundActivityScheduler private let unitsStr = "°C" private let iconsSource = [ - "Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫", - "Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫" + "Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь с грозой": "⛈", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫", + "Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Thunderstorm with rain": "⛈", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫" ] private var location: CLLocation! private var prevLocation: CLLocation! From 2f00c9ffb3ca8a1ce202e6d605406eac084789e5 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Tue, 26 May 2020 17:45:49 +0100 Subject: [PATCH 25/37] Update README.md --- README.md | 45 +++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 1e51a50..e2b2332 100644 --- a/README.md +++ b/README.md @@ -446,40 +446,21 @@ by using background with color "#000000" and bordered == false you can create bu } ``` +## Troubleshooting -### Roadmap +#### If you can't open preferences: +- Opening another program which can't edit text + 1. Open Terminal.app + 2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press Enter + + +#### Buttons or gestures doesn't work: +- "After the last update my mtmr is not working anymore!" +- "Buttons sometimes do not trigger action" +- "ESC don't work" +- "Gestures don't work" -- [x] Create the first prototype with TouchBar in Storyboard -- [x] Put in stripe menu on startup the application -- [x] Find how to simulate real buttons like brightness, volume, night shift and etc. -- [x] Time in touchbar! -- [x] First the weather plugin -- [x] Find how to open full-screen TouchBar without the cross and stripe menu -- [x] Find how to add haptic feedback -- [x] Add icon and menu in StatusBar -- [x] Hide from Dock -- [x] Status menu: "preferences", "quit" -- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/` -- [x] Custom buttons size, actions by click -- [x] Layout: [always left, NSSliderView for center, always right] -- [x] System for autoupdate (https://sparkle-project.org/) -- [ ] Overwrite default values from item types (e.g. title for brightness) -- [ ] Custom settings for paddings and margins for buttons -- [ ] XPC Service for scripts -- [ ] UI for settings -- [ ] Import config from BTT - -Settings: - -- [ ] Interface for plugins and export like presets -- [x] Startup at login -- [ ] Show on/off in Dock -- [ ] Show on/off in StatusBar -- [ x] On/off Haptic Feedback - -Maybe: - -- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?) +Re-tick or check a tick for access 🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR ## Credits From aa69d5f592044862c79b38d7228ef6dae457a927 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Tue, 26 May 2020 17:46:15 +0100 Subject: [PATCH 26/37] Update TECHNICAL_DEBT.md --- TECHNICAL_DEBT.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/TECHNICAL_DEBT.md b/TECHNICAL_DEBT.md index 94f77b3..2b6df1d 100644 --- a/TECHNICAL_DEBT.md +++ b/TECHNICAL_DEBT.md @@ -4,3 +4,38 @@ * try move away from enums when parse preset – enums are hard to extend * find better way to hide bar items * extract bar items creating from TouchBarController to separate class, cover with tests + + +### Roadmap + +- [x] Create the first prototype with TouchBar in Storyboard +- [x] Put in stripe menu on startup the application +- [x] Find how to simulate real buttons like brightness, volume, night shift and etc. +- [x] Time in touchbar! +- [x] First the weather plugin +- [x] Find how to open full-screen TouchBar without the cross and stripe menu +- [x] Find how to add haptic feedback +- [x] Add icon and menu in StatusBar +- [x] Hide from Dock +- [x] Status menu: "preferences", "quit" +- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/` +- [x] Custom buttons size, actions by click +- [x] Layout: [always left, NSSliderView for center, always right] +- [x] System for autoupdate (https://sparkle-project.org/) +- [ ] Overwrite default values from item types (e.g. title for brightness) +- [ ] Custom settings for paddings and margins for buttons +- [ ] XPC Service for scripts +- [ ] UI for settings +- [ ] Import config from BTT + +Settings: + +- [ ] Interface for plugins and export like presets +- [x] Startup at login +- [ ] Show on/off in Dock +- [ ] Show on/off in StatusBar +- [x] On/off Haptic Feedback + +Maybe: + +- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?) From a65613acafe3c7f1917fb00577f80609b61332aa Mon Sep 17 00:00:00 2001 From: Giuseppe Petrosino Date: Wed, 17 Jun 2020 23:40:58 +0200 Subject: [PATCH 27/37] Allow change of of a group button width (#336) --- MTMR/TouchBarController.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 9bf1a78..e0af17e 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -100,7 +100,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } blacklistAppIdentifiers = AppSettings.blacklistedAppIds - + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil) @@ -123,20 +123,20 @@ class TouchBarController: NSObject, NSTouchBarDelegate { let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in items[identifier] }) - + let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) touchBar.delegate = self 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 @@ -428,6 +428,12 @@ extension NSCustomTouchBarItem: CanSetWidth { } } +extension NSPopoverTouchBarItem: CanSetWidth { + func setWidth(value: CGFloat) { + view?.widthAnchor.constraint(equalToConstant: value).isActive = true + } +} + extension BarItemDefinition { var align: Align { if case let .align(result)? = additionalParameters[.align] { From 810cdeed3610a2a4774de6332835b23fb9c8637e Mon Sep 17 00:00:00 2001 From: Vladimir Tolstikov Date: Thu, 16 Jul 2020 00:04:11 +0400 Subject: [PATCH 28/37] YandexWeather: fix issue with wrong URL which leads to random incorrect location detection and wrong weather display (#344) --- MTMR/Widgets/YandexWeatherBarItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index ebdc8aa..58abef1 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -91,7 +91,7 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate func getWeatherUrl() -> String { if location != nil { - return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)?lang=ru" + return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&lang=ru" } else { return "https://yandex.ru/pogoda/?lang=ru" // Yandex will try to determine your location by default } From 14282b86a9181747b3166a2a5eb84a4159977753 Mon Sep 17 00:00:00 2001 From: Vladimir Tolstikov Date: Thu, 16 Jul 2020 22:11:37 +0400 Subject: [PATCH 29/37] YandexWeather: fix race condition which randomly leads to displaying wrong data (#345) --- MTMR/Widgets/YandexWeatherBarItem.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index 58abef1..ed47009 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -19,6 +19,7 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate private var location: CLLocation! private var prevLocation: CLLocation! private var manager: CLLocationManager! + private var updateWeatherTask: URLSessionDataTask? init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) { activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") @@ -61,7 +62,8 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!) urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format - let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in + updateWeatherTask?.cancel() + updateWeatherTask = URLSession.shared.dataTask(with: urlRequest) { data, _, error in guard error == nil, let response = data?.utf8string else { return } @@ -86,7 +88,7 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate } } - task.resume() + updateWeatherTask?.resume() } func getWeatherUrl() -> String { From 87141e381bdc8cb7bd6c930c2e55095ca58c0c89 Mon Sep 17 00:00:00 2001 From: Connor G Meehan Date: Mon, 27 Jul 2020 09:41:05 +1000 Subject: [PATCH 30/37] Up next calendar widget (#348) * WIP Implementation of up next widget * Seperated button view and event source logic * Adjusted default parameters * Added the ability to view multiple events * Added ability to click touchbar item and go to calendar * renamed nthEvent to maxToShow and changed default * Updated CFBundleVersion * Renamed UpNext class and fix ups * Added "autoResize" property (same functionality as dock) * Added EKEventStore listener to reduce perfomance impact * Log cleanup * Made button blue for current/past events * Added handling of unauthorised access to calendar --- MTMR.xcodeproj/project.pbxproj | 4 + MTMR/Info.plist | 2 +- MTMR/ItemsParsing.swift | 11 + MTMR/TouchBarController.swift | 4 + MTMR/Widgets/UpNextScrubberTouchBarItem.swift | 256 ++++++++++++++++++ README.md | 18 ++ 6 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 MTMR/Widgets/UpNextScrubberTouchBarItem.swift diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 855eff5..e14e8aa 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ 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 */; }; + F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -163,6 +164,7 @@ 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 = ""; }; + F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextScrubberTouchBarItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -322,6 +324,7 @@ 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, + F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */, ); path = Widgets; sourceTree = ""; @@ -488,6 +491,7 @@ 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, 4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */, 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */, + F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */, B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */, 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, diff --git a/MTMR/Info.plist b/MTMR/Info.plist index e1bbf12..ead0085 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.26.1 CFBundleVersion - 405 + 425 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 851cf15..a16beef 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -227,6 +227,7 @@ enum ItemType: Decodable { case network(flip: Bool) case darkMode case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) + case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool) private enum CodingKeys: String, CodingKey { case type @@ -258,6 +259,7 @@ enum ItemType: Decodable { case direction case fingers case minOffset + case maxToShow } enum ItemTypeRaw: String, Decodable { @@ -281,6 +283,7 @@ enum ItemType: Decodable { case network case darkMode case swipe + case upnext } init(from decoder: Decoder) throws { @@ -378,6 +381,14 @@ enum ItemType: Decodable { 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) + + case .upnext: + let from = try container.decodeIfPresent(Double.self, forKey: .from) ?? 0 // Lower bounds of period of time in hours to search for events + let to = try container.decodeIfPresent(Double.self, forKey: .to) ?? 12 // Upper bounds of period of time in hours to search for events + let maxToShow = try container.decodeIfPresent(Int.self, forKey: .maxToShow) ?? 3 // 1 indexed array. Get the 1st, 2nd, 3rd event to display multiple notifications + let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 60.0 + self = .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize) } } } diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index e0af17e..e3a5898 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -59,6 +59,8 @@ extension ItemType { return DarkModeBarItem.identifier case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _): return "com.toxblh.mtmr.swipe." + case .upnext(from: _, to: _, maxToShow: _, autoResize: _): + return "com.connorgmeehan.mtmrup.next." } } } @@ -301,6 +303,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { 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) + case let .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize): + barItem = UpNextScrubberTouchBarItem(identifier: identifier, interval: 60, from: from, to: to, maxToShow: maxToShow, autoResize: autoResize) } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { diff --git a/MTMR/Widgets/UpNextScrubberTouchBarItem.swift b/MTMR/Widgets/UpNextScrubberTouchBarItem.swift new file mode 100644 index 0000000..fb0f1ea --- /dev/null +++ b/MTMR/Widgets/UpNextScrubberTouchBarItem.swift @@ -0,0 +1,256 @@ +// +// UpNextScrubberTouchBarItems.swift +// MTMR +// +// Created by Connor Meehan on 13/7/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// +// + +import Foundation +import EventKit + +class UpNextScrubberTouchBarItem: NSCustomTouchBarItem { + // Dependencies + private let scrollView = NSScrollView() + private let activity: NSBackgroundActivityScheduler // Update scheduler + private var eventSources : [IUpNextSource] = [] + private var items: [UpNextItem] = [] + + // Settings + private var futureSearchCutoff: Double + private var pastSearchCutoff: Double + private var maxToShow: Int + private var widthConstraint: NSLayoutConstraint? + private var autoResize: Bool = false + + /// <#Description#> + /// - Parameters: + /// - identifier: Unique identifier of widget + /// - interval: Update view interval in seconds + /// - from: Relative to current time, how far back we search for events in hours + /// - to: Relative to current time, how far forward we search for events in hours + /// - maxToShow: Which event to show (1 is first, 2 is second, and so on) + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, maxToShow: Int, autoResize: Bool) { + // Initialise member properties + activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck") + pastSearchCutoff = from * 3600 + futureSearchCutoff = to * 3600 + self.maxToShow = maxToShow + self.autoResize = autoResize + UpNextItem.df.dateFormat = "HH:mm" + // Error handling + if (maxToShow <= 0) { + fatalError("Error on UpNext bar item. maxToShow property must be greater than 0.") + } + // Init super + super.init(identifier: identifier) + view = scrollView + // Add event sources + // Can optionally pass an update view callback to an event source to redraw element + self.eventSources.append(UpNextCalenderSource(updateCallback: self.updateView)) + // Fallback interactivity via interval + activity.interval = interval + activity.repeats = true + activity.qualityOfService = .utility + activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in + self.updateView() + completion(NSBackgroundActivityScheduler.Result.finished) + } + updateView() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateView() -> Void { + items = [] + var upcomingEvents = self.getUpcomingEvents() + upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending}) + var index = 1 + DispatchQueue.main.async { + for event in upcomingEvents { + // Create UpNextItem + let item = UpNextItem(event: event) + item.backgroundColor = self.getBackgroundColor(startDate: event.startDate) + // Bind tap event + item.tapClosure = { [weak self] in + self?.switchToApp(event: event) + } + // Add to view + self.items.append(item) + // Check if should display any more + if (index == self.maxToShow) { + break; + } + index += 1 + } + self.reloadData() + self.updateSize() + } + } + + private func reloadData() { + let stackView = NSStackView(views: items.compactMap { $0.view }) + stackView.spacing = 5 + stackView.orientation = .horizontal + let visibleRect = self.scrollView.documentVisibleRect + self.scrollView.documentView = stackView + stackView.scroll(visibleRect.origin) + } + + func updateSize() { + if self.autoResize { + self.widthConstraint?.isActive = false + + let width = self.scrollView.documentView?.fittingSize.width ?? 0 + self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width) + self.widthConstraint!.isActive = true + } + } + + + private func getUpcomingEvents() -> [UpNextEventModel] { + var upcomingEvents: [UpNextEventModel] = [] + + // Calculate the range we're going to search for events in + let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff) + let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff) + + // Get all events from all sources + for eventSource in self.eventSources { + if (eventSource.hasPermission) { + let events = eventSource.getUpcomingEvents(dateLowerBounds: dateLowerBounds, dateUpperBounds: dateUpperBounds) + upcomingEvents.append(contentsOf: events) + } + } + + return upcomingEvents + } + + public func switchToApp(event: UpNextEventModel) { + var bundleIdentifier: String + switch(event.sourceType) { + case .iCalendar: + bundleIdentifier = UpNextCalenderSource.bundleIdentifier + } + + NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) + + // NB: if you can't open app which on another space, try to check mark + // "When switching to an application, switch to a Space with open windows for the application" + // in Mission control settings + } + + + func getBackgroundColor(startDate: Date) -> NSColor { + let distance = startDate.timeIntervalSinceReferenceDate/60 - Date().timeIntervalSinceReferenceDate/60 // Get time difference in minutes + if (distance < 0 as TimeInterval) { // If it's in the past, draw as blue + return NSColor.systemBlue + } else if (distance < 30 as TimeInterval) { // Less than 30 minutes, backround is red + return NSColor.systemRed + } + return NSColor.clear + } +} + +private class UpNextItem : CustomButtonTouchBarItem { + static public let df = DateFormatter() + + init(event: UpNextEventModel) { + let identifier = UpNextItem.getIdentifier(event: event) + let title = UpNextItem.getTitle(event: event) + super.init(identifier: NSTouchBarItem.Identifier(rawValue: identifier), title: title) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private static func getTitle(event: UpNextEventModel) -> String { + var title = "" + let startDateString = UpNextItem.df.string(for: event.startDate) + switch event.sourceType { + case .iCalendar: + title = String.init(format: "🗓 %@ - %@ ", event.title, startDateString!) + } + return title + } + + private static func getIdentifier(event: UpNextEventModel) -> String { + var identifier : String + switch event.sourceType { + case .iCalendar: + identifier = "com.mtmr.iCalendarEvent" + } + return identifier + "." + event.title + } +} + +enum UpNextSourceType { + case iCalendar +} + +// Model for events to be displayed in dock +struct UpNextEventModel { + let title: String + let startDate: Date + let sourceType: UpNextSourceType +} + + +// Interface for any event source +protocol IUpNextSource { + static var bundleIdentifier: String { get } + var hasPermission : Bool { get } + var updateCallback : () -> Void { get set } + + init(updateCallback: @escaping () -> Void) + func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] +} + +class UpNextCalenderSource : IUpNextSource { + static public let bundleIdentifier: String = "com.apple.iCal" + + public var hasPermission: Bool = false + private var eventStore : EKEventStore + internal var updateCallback: () -> Void + + required init(updateCallback: @escaping () -> Void = {}) { + self.updateCallback = updateCallback + eventStore = EKEventStore() + NotificationCenter.default.addObserver(forName: .EKEventStoreChanged, object: eventStore, queue: nil, using: handleUpdate) + let authStatus = EKEventStore.authorizationStatus(for: .event) + if (authStatus != .authorized) { + eventStore.requestAccess(to: .event){ granted, error in + self.hasPermission = granted; + self.handleUpdate() + if(!granted) { + NSLog("Error: MTMR UpNextBarWidget not given calendar access.") + return + } + } + } else { + self.handleUpdate() + } + + } + public func handleUpdate() { + self.handleUpdate(note: Notification(name: Notification.Name("refresh view"))) + } + public func handleUpdate(note: Notification) { + self.updateCallback() + } + + public func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] { + var upcomingEvents: [UpNextEventModel] = [] + let calendars = self.eventStore.calendars(for: .event) + let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: calendars) + let events = self.eventStore.events(matching: predicate) + for event in events { + upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate, sourceType: UpNextSourceType.iCalendar)) + } + return upcomingEvents + } +} diff --git a/README.md b/README.md index e2b2332..e9babc9 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ The pre-installed configuration contains less or more than you'll probably want, - darkMode - pomodoro - network +- upnext (Calendar events) > Media Keys @@ -343,6 +344,23 @@ To close a group, use the button: }, ``` +#### `upnext` + +> Calender next event plugin +Displays upcoming events from MacOS Calendar. Does not display current event. + +```js +{ + "type": "upnext", + "from": 0, // Lower bound of search range for next event in hours. Default 0 (current time)(can be negative to view events in the past) + "to": 12, // Upper bounds of search range for next event in hours. Default 12 (12 hours in the future) + "maxToShow": 3 // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events) + "autoResize": false // If true, widget will expand to display all events. Default false (scrollable view within "width") +}, +``` + + + ## Actions: - `hidKey` From 588e6ae09b5bc4af07cb8c2e57f955d6dd3eaad1 Mon Sep 17 00:00:00 2001 From: Matteo Piccina Date: Mon, 3 Aug 2020 12:53:39 +0200 Subject: [PATCH 31/37] Implement multiple actions (double tap, triple tap) (#349) * Implement double tap and new actions array in config * Update native widgets to use new actions parameter * Refactor new actions parameter moving it to main definition Renamed old action and longAction to legacy * Fix tests * Remove unused code * Readd test for legacyAction * Implement triple tap * Add new actions explanation * Add support for multiple actions and same trigger --- MTMR/CustomButtonTouchBarItem.swift | 138 +++++++++-- MTMR/ItemsParsing.swift | 232 ++++++++++++++---- MTMR/TouchBarController.swift | 85 ++++++- MTMR/Widgets/AppScrubberTouchBarItem.swift | 14 +- MTMR/Widgets/DarkModeBarItem.swift | 2 +- MTMR/Widgets/DnDBarItem.swift | 2 +- MTMR/Widgets/InputSourceBarItem.swift | 5 +- MTMR/Widgets/MusicBarItem.swift | 34 ++- MTMR/Widgets/NightShiftBarItem.swift | 4 +- MTMR/Widgets/PomodoroBarItem.swift | 11 +- MTMR/Widgets/UpNextScrubberTouchBarItem.swift | 4 +- MTMR/Widgets/YandexWeatherBarItem.swift | 9 +- MTMRTests/ParseConfigTests.swift | 25 +- README.md | 50 ++-- 14 files changed, 487 insertions(+), 128 deletions(-) diff --git a/MTMR/CustomButtonTouchBarItem.swift b/MTMR/CustomButtonTouchBarItem.swift index 6b734d2..a10976d 100644 --- a/MTMR/CustomButtonTouchBarItem.swift +++ b/MTMR/CustomButtonTouchBarItem.swift @@ -8,18 +8,32 @@ import Cocoa +struct ItemAction { + typealias TriggerClosure = (() -> Void)? + + let trigger: Action.Trigger + let closure: TriggerClosure + + init(trigger: Action.Trigger, _ closure: TriggerClosure) { + self.trigger = trigger + self.closure = closure + } +} + class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { - var tapClosure: (() -> Void)? - var longTapClosure: (() -> Void)? { + + var actions: [ItemAction] = [] { didSet { - longClick.isEnabled = longTapClosure != nil + multiClick.isDoubleClickEnabled = actions.filter({ $0.trigger == .doubleTap }).count > 0 + multiClick.isTripleClickEnabled = actions.filter({ $0.trigger == .tripleTap }).count > 0 + longClick.isEnabled = actions.filter({ $0.trigger == .longTap }).count > 0 } } var finishViewConfiguration: ()->() = {} private var button: NSButton! - private var singleClick: HapticClickGestureRecognizer! private var longClick: LongPressGestureRecognizer! + private var multiClick: MultiClickGestureRecognizer! init(identifier: NSTouchBarItem.Identifier, title: String) { attributedTitle = title.defaultTouchbarAttributedString @@ -31,10 +45,17 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat longClick.isEnabled = false longClick.allowedTouchTypes = .direct longClick.delegate = self - - singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle)) - singleClick.allowedTouchTypes = .direct - singleClick.delegate = self + + multiClick = MultiClickGestureRecognizer( + target: self, + action: #selector(handleGestureSingleTap), + doubleAction: #selector(handleGestureDoubleTap), + tripleAction: #selector(handleGestureTripleTap) + ) + multiClick.allowedTouchTypes = .direct + multiClick.delegate = self + multiClick.isDoubleClickEnabled = false + multiClick.isTripleClickEnabled = false reinstallButton() button.attributedTitle = attributedTitle @@ -100,33 +121,43 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat view = button view.addGestureRecognizer(longClick) - view.addGestureRecognizer(singleClick) + // view.addGestureRecognizer(singleClick) + view.addGestureRecognizer(multiClick) finishViewConfiguration() } func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool { - if gestureRecognizer == singleClick && otherGestureRecognizer == longClick - || gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it + if gestureRecognizer == multiClick && otherGestureRecognizer == longClick + || gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it { return false } return true } - - @objc func handleGestureSingle(gr: NSClickGestureRecognizer) { - switch gr.state { - case .ended: - tapClosure?() - break - default: - break + + func callActions(for trigger: Action.Trigger) { + let itemActions = self.actions.filter { $0.trigger == trigger } + for itemAction in itemActions { + itemAction.closure?() } } + + @objc func handleGestureSingleTap() { + callActions(for: .singleTap) + } + + @objc func handleGestureDoubleTap() { + callActions(for: .doubleTap) + } + + @objc func handleGestureTripleTap() { + callActions(for: .tripleTap) + } @objc func handleGestureLong(gr: NSPressGestureRecognizer) { switch gr.state { case .possible: // tiny hack because we're calling action manually - (self.longTapClosure ?? self.tapClosure)?() + callActions(for: .longTap) break default: break @@ -176,15 +207,78 @@ class CustomButtonCell: NSButtonCell { } } -class HapticClickGestureRecognizer: NSClickGestureRecognizer { +// Thanks to https://stackoverflow.com/a/49843893 +final class MultiClickGestureRecognizer: NSClickGestureRecognizer { + + private let _action: Selector + private let _doubleAction: Selector + private let _tripleAction: Selector + private var _clickCount: Int = 0 + + public var isDoubleClickEnabled = true + public var isTripleClickEnabled = true + + override var action: Selector? { + get { + return nil /// prevent base class from performing any actions + } set { + if newValue != nil { // if they are trying to assign an actual action + fatalError("Only use init(target:action:doubleAction) for assigning actions") + } + } + } + + required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) { + _action = action + _doubleAction = doubleAction + _tripleAction = tripleAction + super.init(target: target, action: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(target:action:doubleAction:tripleAction) is only support atm") + } + override func touchesBegan(with event: NSEvent) { HapticFeedback.shared?.tap(strong: 2) super.touchesBegan(with: event) } - + override func touchesEnded(with event: NSEvent) { HapticFeedback.shared?.tap(strong: 1) super.touchesEnded(with: event) + _clickCount += 1 + + var delayThreshold: TimeInterval // fine tune this as needed + + guard isDoubleClickEnabled || isTripleClickEnabled else { + _ = target?.perform(_action) + return + } + + if (isTripleClickEnabled) { + delayThreshold = 0.4 + perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold) + if _clickCount == 3 { + _ = target?.perform(_tripleAction) + } + } else { + delayThreshold = 0.3 + perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold) + if _clickCount == 2 { + _ = target?.perform(_doubleAction) + } + } + } + + @objc private func _resetAndPerformActionIfNecessary() { + if _clickCount == 1 { + _ = target?.perform(_action) + } + if isTripleClickEnabled && _clickCount == 2 { + _ = target?.perform(_doubleAction) + } + _clickCount = 0 } } diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index a16beef..7aa3c6e 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -3,47 +3,52 @@ import Foundation extension Data { func barItemDefinitions() -> [BarItemDefinition]? { - return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) + return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) } } struct BarItemDefinition: Decodable { let type: ItemType - let action: ActionType - let longAction: LongActionType + let actions: [Action] + let legacyAction: LegacyActionType + let legacyLongAction: LegacyLongActionType let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter] private enum CodingKeys: String, CodingKey { case type + case actions } - init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) { + init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) { self.type = type - self.action = action - self.longAction = longAction + self.actions = actions + self.legacyAction = action + self.legacyLongAction = legacyLongAction self.additionalParameters = additionalParameters } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: .type) - let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type) + let actions = try container.decodeIfPresent([Action].self, forKey: .actions) + let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type, actions: actions ?? []) var additionalParameters = try GeneralParameters(from: decoder).parameters if let result = try? parametersDecoder(decoder), - case let (itemType, action, longAction, parameters) = result { + case let (itemType, actions, action, longAction, parameters) = result { parameters.forEach { additionalParameters[$0] = $1 } - self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters) + self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters) } else { - self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters) + self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters) } } } typealias ParametersDecoder = (Decoder) throws -> ( item: ItemType, - action: ActionType, - longAction: LongActionType, + actions: [Action], + legacyAction: LegacyActionType, + legacyLongAction: LegacyLongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter] ) @@ -51,15 +56,21 @@ class SupportedTypesHolder { private var supportedTypes: [String: ParametersDecoder] = [ "escape": { _ in ( item: .staticButton(title: "esc"), - action: .keyPress(keycode: 53), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .keyPress(keycode: 53)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.align: .align(.left)] ) }, "delete": { _ in ( item: .staticButton(title: "del"), - action: .keyPress(keycode: 117), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .keyPress(keycode: 117)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [:] ) }, @@ -67,8 +78,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp")) return ( item: .staticButton(title: ""), - action: .keyPress(keycode: 144), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .keyPress(keycode: 144)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -77,8 +91,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown")) return ( item: .staticButton(title: ""), - action: .keyPress(keycode: 145), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .keyPress(keycode: 145)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -87,8 +104,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up")) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -97,8 +117,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down")) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -107,8 +130,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -117,8 +143,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -127,8 +156,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_MUTE), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -137,8 +169,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -147,8 +182,11 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_PLAY), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, @@ -157,23 +195,32 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!) return ( item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_NEXT), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT)) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [.image: imageParameter] ) }, "sleep": { _ in ( item: .staticButton(title: "☕️"), - action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"])) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [:] ) }, "displaySleep": { _ in ( item: .staticButton(title: "☕️"), - action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), - longAction: .none, + actions: [ + Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"])) + ], + legacyAction: .none, + legacyLongAction: .none, parameters: [:] ) }, @@ -181,11 +228,12 @@ class SupportedTypesHolder { static let sharedInstance = SupportedTypesHolder() - func lookup(by type: String) -> ParametersDecoder { + func lookup(by type: String, actions: [Action]) -> ParametersDecoder { return supportedTypes[type] ?? { decoder in ( item: try ItemType(from: decoder), - action: try ActionType(from: decoder), - longAction: try LongActionType(from: decoder), + actions: actions, + legacyAction: try LegacyActionType(from: decoder), + legacyLongAction: try LegacyLongActionType(from: decoder), parameters: [:] ) } } @@ -194,12 +242,13 @@ class SupportedTypesHolder { supportedTypes[typename] = decoder } - func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) { + func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) { register(typename: typename) { _ in ( item: item, - action, - longAction, + actions, + legacyAction, + legacyLongAction, parameters: [:] ) } @@ -393,7 +442,94 @@ enum ItemType: Decodable { } } -enum ActionType: Decodable { +struct FailableDecodable : Decodable { + + let base: Base? + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.base = try? container.decode(Base.self) + } +} + +struct Action: Decodable { + enum Trigger: String, Decodable { + case singleTap + case doubleTap + case tripleTap + case longTap + } + + enum Value { + case none + case hidKey(keycode: Int32) + case keyPress(keycode: Int) + case appleScript(source: SourceProtocol) + case shellScript(executable: String, parameters: [String]) + case custom(closure: () -> Void) + case openUrl(url: String) + } + + private enum ActionTypeRaw: String, Decodable { + case hidKey + case keyPress + case appleScript + case shellScript + case openUrl + } + + enum CodingKeys: String, CodingKey { + case trigger + case action + case keycode + case actionAppleScript + case executablePath + case shellArguments + case url + } + + let trigger: Trigger + let value: Value + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + trigger = try container.decode(Trigger.self, forKey: .trigger) + let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action) + + switch type { + case .some(.hidKey): + let keycode = try container.decode(Int32.self, forKey: .keycode) + value = .hidKey(keycode: keycode) + + case .some(.keyPress): + let keycode = try container.decode(Int.self, forKey: .keycode) + value = .keyPress(keycode: keycode) + + case .some(.appleScript): + let source = try container.decode(Source.self, forKey: .actionAppleScript) + value = .appleScript(source: source) + + case .some(.shellScript): + let executable = try container.decode(String.self, forKey: .executablePath) + let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] + value = .shellScript(executable: executable, parameters: parameters) + + case .some(.openUrl): + let url = try container.decode(String.self, forKey: .url) + value = .openUrl(url: url) + case .none: + value = .none + } + } + + init(trigger: Trigger, value: Value) { + self.trigger = trigger + self.value = value + } +} + +enum LegacyActionType: Decodable { case none case hidKey(keycode: Int32) case keyPress(keycode: Int) @@ -451,7 +587,7 @@ enum ActionType: Decodable { } } -enum LongActionType: Decodable { +enum LegacyLongActionType: Decodable { case none case hidKey(keycode: Int32) case keyPress(keycode: Int) diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index e3a5898..9860ee6 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -92,13 +92,28 @@ class TouchBarController: NSObject, NSTouchBarDelegate { private override init() { super.init() - SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar() }), longAction: .none) + SupportedTypesHolder.sharedInstance.register( + typename: "exitTouchbar", + item: .staticButton(title: "exit"), + actions: [ + Action(trigger: .singleTap, value: .custom(closure: { [weak self] in self?.dismissTouchBar() })) + ], + legacyAction: .none, + legacyLongAction: .none + ) SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in - (item: .staticButton(title: ""), action: .custom(closure: { [weak self] in - guard let `self` = self else { return } - self.reloadPreset(path: self.lastPresetPath) - }), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)]) + ( + item: .staticButton(title: ""), + actions: [ + Action(trigger: .singleTap, value: .custom(closure: { [weak self] in + guard let `self` = self else { return } + self.reloadPreset(path: self.lastPresetPath) + })) + ], + legacyAction: .none, + legacyLongAction: .none, + parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)]) } blacklistAppIdentifiers = AppSettings.blacklistedAppIds @@ -170,7 +185,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { func reloadPreset(path: String) { lastPresetPath = path - let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])] + let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: [:])] createAndUpdatePreset(newJsonItems: items) } @@ -308,10 +323,16 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { - item.tapClosure = action + item.actions.append(ItemAction(trigger: .singleTap, action)) } if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem { - item.longTapClosure = longAction + item.actions.append(ItemAction(trigger: .longTap, longAction)) + } + + if let touchBarItem = barItem as? CustomButtonTouchBarItem { + for action in item.actions { + touchBarItem.actions.append(ItemAction(trigger: action.trigger, self.closure(for: action))) + } } if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem { item.isBordered = bordered @@ -334,9 +355,53 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } return barItem } + + func closure(for action: Action) -> (() -> Void)? { + switch action.value { + case let .hidKey(keycode: keycode): + return { HIDPostAuxKey(keycode) } + case let .keyPress(keycode: keycode): + return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() } + case let .appleScript(source: source): + guard let appleScript = source.appleScript else { + print("cannot create apple script for item \(action)") + return {} + } + return { + DispatchQueue.appleScriptQueue.async { + var error: NSDictionary? + appleScript.executeAndReturnError(&error) + if let error = error { + print("error \(error) when handling \(action) ") + } + } + } + case let .shellScript(executable: executable, parameters: parameters): + return { + let task = Process() + task.launchPath = executable + task.arguments = parameters + task.launch() + } + case let .openUrl(url: url): + return { + if let url = URL(string: url), NSWorkspace.shared.open(url) { + #if DEBUG + print("URL was successfully opened") + #endif + } else { + print("error", url) + } + } + case let .custom(closure: closure): + return closure + case .none: + return nil + } + } func action(forItem item: BarItemDefinition) -> (() -> Void)? { - switch item.action { + switch item.legacyAction { case let .hidKey(keycode: keycode): return { HIDPostAuxKey(keycode) } case let .keyPress(keycode: keycode): @@ -380,7 +445,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } func longAction(forItem item: BarItemDefinition) -> (() -> Void)? { - switch item.longAction { + switch item.legacyLongAction { case let .hidKey(keycode: keycode): return { HIDPostAuxKey(keycode) } case let .keyPress(keycode: keycode): diff --git a/MTMR/Widgets/AppScrubberTouchBarItem.swift b/MTMR/Widgets/AppScrubberTouchBarItem.swift index adc5541..b8a0300 100644 --- a/MTMR/Widgets/AppScrubberTouchBarItem.swift +++ b/MTMR/Widgets/AppScrubberTouchBarItem.swift @@ -82,12 +82,14 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem { public func createAppButton(for app: DockItem) -> DockBarItem { let item = DockBarItem(app) item.isBordered = false - item.tapClosure = { [weak self] in - self?.switchToApp(app: app) - } - item.longTapClosure = { [weak self] in - self?.handleHalfLongPress(item: app) - } + item.actions.append(contentsOf: [ + ItemAction(trigger: .singleTap) { [weak self] in + self?.switchToApp(app: app) + }, + ItemAction(trigger: .longTap) { [weak self] in + self?.handleHalfLongPress(item: app) + } + ]) item.killAppClosure = {[weak self] in self?.handleLongPress(item: app) } diff --git a/MTMR/Widgets/DarkModeBarItem.swift b/MTMR/Widgets/DarkModeBarItem.swift index ceaba78..ecad30b 100644 --- a/MTMR/Widgets/DarkModeBarItem.swift +++ b/MTMR/Widgets/DarkModeBarItem.swift @@ -11,7 +11,7 @@ class DarkModeBarItem: CustomButtonTouchBarItem, Widget { isBordered = false setWidth(value: 24) - tapClosure = { [weak self] in self?.DarkModeToggle() } + actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DarkModeToggle() }) timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) diff --git a/MTMR/Widgets/DnDBarItem.swift b/MTMR/Widgets/DnDBarItem.swift index 18344ca..eb15f7a 100644 --- a/MTMR/Widgets/DnDBarItem.swift +++ b/MTMR/Widgets/DnDBarItem.swift @@ -16,7 +16,7 @@ class DnDBarItem: CustomButtonTouchBarItem { isBordered = false setWidth(value: 32) - tapClosure = { [weak self] in self?.DnDToggle() } + actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DnDToggle() }) timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) diff --git a/MTMR/Widgets/InputSourceBarItem.swift b/MTMR/Widgets/InputSourceBarItem.swift index 6e8b15b..1d3e367 100644 --- a/MTMR/Widgets/InputSourceBarItem.swift +++ b/MTMR/Widgets/InputSourceBarItem.swift @@ -18,9 +18,10 @@ class InputSourceBarItem: CustomButtonTouchBarItem { observeIputSourceChangedNotification() textInputSourceDidChange() - tapClosure = { [weak self] in + + actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.switchInputSource() - } + }) } required init?(coder _: NSCoder) { diff --git a/MTMR/Widgets/MusicBarItem.swift b/MTMR/Widgets/MusicBarItem.swift index 23b2029..a1d1e11 100644 --- a/MTMR/Widgets/MusicBarItem.swift +++ b/MTMR/Widgets/MusicBarItem.swift @@ -41,9 +41,12 @@ class MusicBarItem: CustomButtonTouchBarItem { super.init(identifier: identifier, title: "⏳") isBordered = false - - tapClosure = { [weak self] in self?.playPause() } - longTapClosure = { [weak self] in self?.nextTrack() } + + actions = [ + ItemAction(trigger: .singleTap) { [weak self] in self?.playPause() }, + ItemAction(trigger: .doubleTap) { [weak self] in self?.previousTrack() }, + ItemAction(trigger: .longTap) { [weak self] in self?.nextTrack() } + ] refreshAndSchedule() } @@ -177,6 +180,31 @@ class MusicBarItem: CustomButtonTouchBarItem { } } } + + @objc func previousTrack() { + for ident in playerBundleIdentifiers { + if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) { + if musicPlayer.isRunning { + if ident == .Spotify { + let mp = (musicPlayer as SpotifyApplication) + mp.previousTrack!() + updatePlayer() + return + } else if ident == .iTunes { + let mp = (musicPlayer as iTunesApplication) + mp.previousTrack!() + updatePlayer() + return + } else if ident == .Music { + let mp = (musicPlayer as MusicApplication) + mp.previousTrack!() + updatePlayer() + return + } + } + } + } + } func refreshAndSchedule() { DispatchQueue.main.async { diff --git a/MTMR/Widgets/NightShiftBarItem.swift b/MTMR/Widgets/NightShiftBarItem.swift index 517a8e7..04715ed 100644 --- a/MTMR/Widgets/NightShiftBarItem.swift +++ b/MTMR/Widgets/NightShiftBarItem.swift @@ -30,8 +30,8 @@ class NightShiftBarItem: CustomButtonTouchBarItem { super.init(identifier: identifier, title: "") isBordered = false setWidth(value: 28) - - tapClosure = { [weak self] in self?.nightShiftAction() } + + actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.nightShiftAction() }) timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) diff --git a/MTMR/Widgets/PomodoroBarItem.swift b/MTMR/Widgets/PomodoroBarItem.swift index 7e081f7..9f7974f 100644 --- a/MTMR/Widgets/PomodoroBarItem.swift +++ b/MTMR/Widgets/PomodoroBarItem.swift @@ -23,8 +23,9 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget { return ( item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300), - action: .none, - longAction: .none, + actions: [], + legacyAction: .none, + legacyLongAction: .none, parameters: [:] ) } @@ -50,8 +51,10 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget { self.workTime = workTime self.restTime = restTime super.init(identifier: identifier, title: defaultTitle) - tapClosure = { [weak self] in self?.startStopWork() } - longTapClosure = { [weak self] in self?.startStopRest() } + actions.append(contentsOf: [ + ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() }, + ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() } + ]) } required init?(coder _: NSCoder) { diff --git a/MTMR/Widgets/UpNextScrubberTouchBarItem.swift b/MTMR/Widgets/UpNextScrubberTouchBarItem.swift index fb0f1ea..6ea7e78 100644 --- a/MTMR/Widgets/UpNextScrubberTouchBarItem.swift +++ b/MTMR/Widgets/UpNextScrubberTouchBarItem.swift @@ -75,9 +75,9 @@ class UpNextScrubberTouchBarItem: NSCustomTouchBarItem { let item = UpNextItem(event: event) item.backgroundColor = self.getBackgroundColor(startDate: event.startDate) // Bind tap event - item.tapClosure = { [weak self] in + item.actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.switchToApp(event: event) - } + }) // Add to view self.items.append(item) // Check if should display any more diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index ed47009..c59309d 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -50,8 +50,13 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyHundredMeters manager.startUpdatingLocation() - - tapClosure = tapClosure ?? defaultTapAction + + if actions.filter({ $0.trigger == .singleTap }).isEmpty { + actions.append(ItemAction( + trigger: .singleTap, + defaultTapAction + )) + } } required init?(coder _: NSCoder) { diff --git a/MTMRTests/ParseConfigTests.swift b/MTMRTests/ParseConfigTests.swift index a3321cc..6cec324 100644 --- a/MTMRTests/ParseConfigTests.swift +++ b/MTMRTests/ParseConfigTests.swift @@ -10,7 +10,7 @@ class ParseConfig: XCTestCase { XCTFail() return } - guard case .none? = result?.first?.action else { + guard result?.first?.actions.count == 0 else { XCTFail() return } @@ -18,14 +18,29 @@ class ParseConfig: XCTestCase { func testButtonKeyCodeAction() { let buttonKeycodeFixture = """ - [ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ] + [ { "type": "staticButton", "title": "Pew", "actions": [ { "trigger": "singleTap", "action": "hidKey", "keycode": 123 } ] } ] """.data(using: .utf8)! let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture) guard case .staticButton("Pew")? = result?.first?.type else { XCTFail() return } - guard case .hidKey(keycode: 123)? = result?.first?.action else { + guard case .hidKey(keycode: 123)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else { + XCTFail() + return + } + } + + func testButtonKeyCodeLegacyAction() { + let buttonKeycodeFixture = """ + [ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ] + """.data(using: .utf8)! + let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture) + guard case .staticButton("Pew")? = result?.first?.type else { + XCTFail() + return + } + guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else { XCTFail() return } @@ -40,7 +55,7 @@ class ParseConfig: XCTestCase { XCTFail() return } - guard case .keyPress(keycode: 53)? = result?.first?.action else { + guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else { XCTFail() return } @@ -55,7 +70,7 @@ class ParseConfig: XCTestCase { XCTFail() return } - guard case .keyPress(keycode: 53)? = result?.first?.action else { + guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else { XCTFail() return } diff --git a/README.md b/README.md index e9babc9..5daea2a 100644 --- a/README.md +++ b/README.md @@ -198,10 +198,15 @@ Example of "CPU load" button which also changes color based on load value. "source": { "inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'" }, - "action": "appleScript", - "actionAppleScript": { - "inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell" - }, + "actions": [ + { + "trigger": "singleTap", + "action": "appleScript", + "actionAppleScript": { + "inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell" + } + } + ], "align": "right", "image": { // Or you can specify a filePath here. @@ -363,6 +368,27 @@ Displays upcoming events from MacOS Calendar. Does not display current event. ## Actions: +### Example: + +```js +"actions": [ + { + "trigger": "singleTap", + "action": "hidKey", + "keycode": 53 + } +] +``` + +### Triggers: + +- `singleTap` +- `doubleTap` +- `tripleTap` +- `longTap` + +### Types + - `hidKey` > https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers @@ -404,22 +430,6 @@ Displays upcoming events from MacOS Calendar. Does not display current event. "url": "https://google.com", ``` -## LongActions - -If you want to longPress for some operations, it is similar to the configuration for Actions but with additional parameters, for example: - -```js - "longAction": "hidKey", - "longKeycode": 53, -``` - -- longAction -- longKeycode -- longActionAppleScript -- longExecutablePath -- longShellArguments -- longUrl - ## Additional parameters: - `width` restrict how much room a particular button will take From 7a1800252cbfe9b9385fd2bcf2eb320a31058a7e Mon Sep 17 00:00:00 2001 From: Kaibin Yang <44992049+SkyYkb@users.noreply.github.com> Date: Thu, 13 Aug 2020 19:32:55 +0800 Subject: [PATCH 32/37] Fix error. (#325) Colon expected. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5daea2a..ed2cc03 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ You can add custom actions for two/three/four finger swipes. To do it, you need "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 + "minOffset": 10, // optional: minimal required offset for gesture to emit event "sourceApple": { // optional: apple script to run "inline": "beep" }, From 6660bb2d8fd3bc5a32f5ec14593e1e4f062ff85e Mon Sep 17 00:00:00 2001 From: Matteo Piccina Date: Thu, 19 Nov 2020 22:27:30 +0100 Subject: [PATCH 33/37] Fix brightness keys (#367) --- MTMR/ItemsParsing.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 7aa3c6e..2b590ac 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -79,7 +79,7 @@ class SupportedTypesHolder { return ( item: .staticButton(title: ""), actions: [ - Action(trigger: .singleTap, value: .keyPress(keycode: 144)) + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP)) ], legacyAction: .none, legacyLongAction: .none, @@ -92,7 +92,7 @@ class SupportedTypesHolder { return ( item: .staticButton(title: ""), actions: [ - Action(trigger: .singleTap, value: .keyPress(keycode: 145)) + Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN)) ], legacyAction: .none, legacyLongAction: .none, From 54eaa3fd9f102b38129ed35123e5d09c18948ba7 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Thu, 19 Nov 2020 21:32:35 +0000 Subject: [PATCH 34/37] Change brightness for 3 finger gesture. Close #370, #372 --- MTMR/BasicView.swift | 20 ++++++++++---------- MTMR/Info.plist | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift index c0d3f70..5845380 100644 --- a/MTMR/BasicView.swift +++ b/MTMR/BasicView.swift @@ -15,12 +15,12 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { 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 @@ -29,27 +29,27 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { stackView.spacing = 8 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: @@ -72,9 +72,9 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { let prevPos = legacyPrevPositions[fingers]! if ((position - prevPos) > 15) || ((prevPos - position) > 15) { if position > prevPos { - GenericKeyPress(keyCode: CGKeyCode(144)).send() + HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP) } else if position < prevPos { - GenericKeyPress(keyCode: CGKeyCode(145)).send() + HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN) } legacyPrevPositions[fingers] = position } @@ -89,7 +89,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { break } } - + @objc func twofingersHandler(_ sender: NSGestureRecognizer?) { let position = (sender?.location(in: sender?.view).x)! self.gestureHandler(position: position, fingers: 2, state: sender!.state) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index ead0085..ca391aa 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.26.1 CFBundleVersion - 425 + 428 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion From bbe901a5723194c1da4313e0ddb27f868756c10d Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Fri, 20 Nov 2020 01:42:50 +0000 Subject: [PATCH 35/37] version 0.27 --- MTMR/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MTMR/Info.plist b/MTMR/Info.plist index ca391aa..ae32a4b 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.26.1 + 0.27 CFBundleVersion - 428 + 429 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion From a2ad47c7ba2864b9e18ad0c6a3a76921970d9f85 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Fri, 20 Nov 2020 02:21:05 +0000 Subject: [PATCH 36/37] build counter --- .github/FUNDING.yml | 2 +- MTMR/Info.plist | 2 +- README.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 4f6cea0..6d99015 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -3,4 +3,4 @@ issuehunt: Toxblh patreon: toxblh ko_fi: toxblh -custom: https://www.paypal.me/toxblh +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4 diff --git a/MTMR/Info.plist b/MTMR/Info.plist index ae32a4b..90a0507 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.27 CFBundleVersion - 429 + 434 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/README.md b/README.md index ed2cc03..354d442 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ My idea is to create a platform for creating plugins to customize the TouchBar. Telegram

-

PayPal donate button +

PayPal donate button Buy Me A Coffee @@ -480,8 +480,8 @@ by using background with color "#000000" and bordered == false you can create bu - Opening another program which can't edit text 1. Open Terminal.app 2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press Enter - - + + #### Buttons or gestures doesn't work: - "After the last update my mtmr is not working anymore!" - "Buttons sometimes do not trigger action" From 3e82676008925314beb87d9aac69cd61547ce5c5 Mon Sep 17 00:00:00 2001 From: Anton Palgunov Date: Fri, 20 Nov 2020 02:24:31 +0000 Subject: [PATCH 37/37] upd build.sh --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 70993f8..28283cd 100755 --- a/build.sh +++ b/build.sh @@ -63,8 +63,8 @@ echo " echo "" echo "Homebrew https://github.com/Homebrew/homebrew-cask/edit/master/Casks/mtmr.rb" echo "" -echo " version '${VERSION}'" -echo " sha256 '${SHA256}'" +echo " version \"${VERSION}\"" +echo " sha256 \"${SHA256}\"" echo "" echo "Update MTMR v${VERSION}"