From 7e0db70fabc8715a881540b055eacfa5c7f73f49 Mon Sep 17 00:00:00 2001 From: Fedor Zaytsev Date: Fri, 28 Feb 2020 11:35:09 -0800 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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": "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" + } + }, + // iTunes { "type": "appleScriptTitledButton",