mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-01-10 17:08:39 +00:00
Merge remote-tracking branch 'origin/master' into notification-widget
This commit is contained in:
commit
e5bb90249f
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -3,4 +3,4 @@
|
|||||||
issuehunt: Toxblh
|
issuehunt: Toxblh
|
||||||
patreon: toxblh
|
patreon: toxblh
|
||||||
ko_fi: 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
|
||||||
|
|||||||
22
.github/workflows/build-test.yml
vendored
Normal file
22
.github/workflows/build-test.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Build-and-test
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
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]}
|
||||||
41
.github/workflows/publish.yml
vendored
Normal file
41
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Publish unsign version
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build-and-release:
|
||||||
|
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]}
|
||||||
|
|
||||||
|
- name: Create DMG
|
||||||
|
run: |
|
||||||
|
cd Release
|
||||||
|
create-dmg MTMR.app || true
|
||||||
|
|
||||||
|
- name: GitHub Release
|
||||||
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
prerelease: false
|
||||||
|
files: Release/MTMR*.dmg
|
||||||
@ -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]}"
|
|
||||||
@ -22,6 +22,8 @@
|
|||||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
|
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
|
||||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
|
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
|
||||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.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 */; };
|
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; };
|
||||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
|
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
|
||||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
|
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
|
||||||
@ -71,6 +73,9 @@
|
|||||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; };
|
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; };
|
||||||
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; };
|
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; };
|
||||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
@ -106,6 +111,8 @@
|
|||||||
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
|
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
|
||||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
|
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
|
||||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
|
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
|
||||||
|
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.nowPlaying.scpt; sourceTree = "<group>"; };
|
||||||
|
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.next.scpt; sourceTree = "<group>"; };
|
||||||
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = "<group>"; };
|
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = "<group>"; };
|
||||||
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = "<group>"; };
|
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = "<group>"; };
|
||||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
|
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
|
||||||
@ -165,6 +172,9 @@
|
|||||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeBarItem.swift; sourceTree = "<group>"; };
|
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeBarItem.swift; sourceTree = "<group>"; };
|
||||||
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchBarSupport.m; sourceTree = "<group>"; };
|
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchBarSupport.m; sourceTree = "<group>"; };
|
||||||
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = "<group>"; };
|
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = "<group>"; };
|
||||||
|
BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = "<group>"; };
|
||||||
|
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = "<group>"; };
|
||||||
|
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextScrubberTouchBarItem.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -242,8 +252,10 @@
|
|||||||
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
|
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
|
||||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
||||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
||||||
|
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
|
||||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
||||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||||
|
BAF5AB5624317B4300B04904 /* BasicView.swift */,
|
||||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
||||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
||||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
||||||
@ -270,6 +282,8 @@
|
|||||||
B0B17426207D6AFE0004B740 /* AppleScripts */ = {
|
B0B17426207D6AFE0004B740 /* AppleScripts */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */,
|
||||||
|
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */,
|
||||||
B0B1742A207D6B580004B740 /* Battery.scpt */,
|
B0B1742A207D6B580004B740 /* Battery.scpt */,
|
||||||
B0B17429207D6B580004B740 /* Finder.scpt */,
|
B0B17429207D6B580004B740 /* Finder.scpt */,
|
||||||
B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
|
B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
|
||||||
@ -325,6 +339,7 @@
|
|||||||
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
||||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
||||||
839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */,
|
839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */,
|
||||||
|
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
|
||||||
);
|
);
|
||||||
path = Widgets;
|
path = Widgets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -422,6 +437,8 @@
|
|||||||
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */,
|
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */,
|
||||||
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */,
|
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */,
|
||||||
B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt 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 */,
|
B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */,
|
||||||
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */,
|
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */,
|
||||||
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */,
|
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */,
|
||||||
@ -472,11 +489,13 @@
|
|||||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
||||||
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
|
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
|
||||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
||||||
|
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */,
|
||||||
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
|
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
|
||||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
||||||
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
|
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
|
||||||
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
||||||
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
|
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
|
||||||
|
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */,
|
||||||
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
|
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
|
||||||
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
|
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
|
||||||
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
|
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
|
||||||
@ -487,6 +506,7 @@
|
|||||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
||||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
||||||
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
||||||
|
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */,
|
||||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
||||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
||||||
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
||||||
|
|||||||
@ -92,7 +92,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
@objc func toggleMultitouch(_ item: NSMenuItem) {
|
@objc func toggleMultitouch(_ item: NSMenuItem) {
|
||||||
item.state = item.state == .on ? .off : .on
|
item.state = item.state == .on ? .off : .on
|
||||||
AppSettings.multitouchGestures = item.state == .on
|
AppSettings.multitouchGestures = item.state == .on
|
||||||
TouchBarController.shared.scrollArea?.gesturesEnabled = item.state == .on
|
TouchBarController.shared.basicView?.legacyGesturesEnabled = item.state == .on
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPreset(_: Any?) {
|
@objc func openPreset(_: Any?) {
|
||||||
|
|||||||
@ -4,9 +4,11 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
|||||||
private var script: NSAppleScript!
|
private var script: NSAppleScript!
|
||||||
private let interval: TimeInterval
|
private let interval: TimeInterval
|
||||||
private var forceHideConstraint: NSLayoutConstraint!
|
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.interval = interval
|
||||||
|
self.alternativeImages = alternativeImages
|
||||||
super.init(identifier: identifier, title: "⏳")
|
super.init(identifier: identifier, title: "⏳")
|
||||||
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
||||||
title = "scheduled"
|
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 {
|
func execute() -> String {
|
||||||
var error: NSDictionary?
|
var error: NSDictionary?
|
||||||
let output = script.executeAndReturnError(&error)
|
let output = script.executeAndReturnError(&error)
|
||||||
@ -64,6 +76,18 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
|||||||
print(error)
|
print(error)
|
||||||
return "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 ?? ""
|
return output.stringValue ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
MTMR/AppleScripts/Music.next.scpt
Normal file
7
MTMR/AppleScripts/Music.next.scpt
Normal file
@ -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
|
||||||
10
MTMR/AppleScripts/Music.nowPlaying.scpt
Normal file
10
MTMR/AppleScripts/Music.nowPlaying.scpt
Normal file
@ -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 ""
|
||||||
@ -2,6 +2,10 @@ if application "iTunes" is running then
|
|||||||
tell application "iTunes" to playpause
|
tell application "iTunes" to playpause
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
if application "Music" is running then
|
||||||
|
tell application "Music" to playpause
|
||||||
|
end if
|
||||||
|
|
||||||
if application "Spotify" is running then
|
if application "Spotify" is running then
|
||||||
tell application "Spotify" to playpause
|
tell application "Spotify" to playpause
|
||||||
end if
|
end if
|
||||||
|
|||||||
107
MTMR/BasicView.swift
Normal file
107
MTMR/BasicView.swift
Normal file
@ -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 = 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:
|
||||||
|
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 {
|
||||||
|
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP)
|
||||||
|
} else if position < prevPos {
|
||||||
|
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,18 +8,32 @@
|
|||||||
|
|
||||||
import Cocoa
|
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 {
|
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||||
var tapClosure: (() -> Void)?
|
|
||||||
var longTapClosure: (() -> Void)? {
|
var actions: [ItemAction] = [] {
|
||||||
didSet {
|
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: ()->() = {}
|
var finishViewConfiguration: ()->() = {}
|
||||||
|
|
||||||
private var button: NSButton!
|
private var button: NSButton!
|
||||||
private var singleClick: HapticClickGestureRecognizer!
|
|
||||||
private var longClick: LongPressGestureRecognizer!
|
private var longClick: LongPressGestureRecognizer!
|
||||||
|
private var multiClick: MultiClickGestureRecognizer!
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
||||||
attributedTitle = title.defaultTouchbarAttributedString
|
attributedTitle = title.defaultTouchbarAttributedString
|
||||||
@ -32,9 +46,16 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
longClick.allowedTouchTypes = .direct
|
longClick.allowedTouchTypes = .direct
|
||||||
longClick.delegate = self
|
longClick.delegate = self
|
||||||
|
|
||||||
singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
|
multiClick = MultiClickGestureRecognizer(
|
||||||
singleClick.allowedTouchTypes = .direct
|
target: self,
|
||||||
singleClick.delegate = self
|
action: #selector(handleGestureSingleTap),
|
||||||
|
doubleAction: #selector(handleGestureDoubleTap),
|
||||||
|
tripleAction: #selector(handleGestureTripleTap)
|
||||||
|
)
|
||||||
|
multiClick.allowedTouchTypes = .direct
|
||||||
|
multiClick.delegate = self
|
||||||
|
multiClick.isDoubleClickEnabled = false
|
||||||
|
multiClick.isTripleClickEnabled = false
|
||||||
|
|
||||||
reinstallButton()
|
reinstallButton()
|
||||||
button.attributedTitle = attributedTitle
|
button.attributedTitle = attributedTitle
|
||||||
@ -100,33 +121,43 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
view = button
|
view = button
|
||||||
|
|
||||||
view.addGestureRecognizer(longClick)
|
view.addGestureRecognizer(longClick)
|
||||||
view.addGestureRecognizer(singleClick)
|
// view.addGestureRecognizer(singleClick)
|
||||||
|
view.addGestureRecognizer(multiClick)
|
||||||
finishViewConfiguration()
|
finishViewConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
||||||
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick
|
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
|
||||||
|| gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it
|
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
|
||||||
{
|
{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
|
func callActions(for trigger: Action.Trigger) {
|
||||||
switch gr.state {
|
let itemActions = self.actions.filter { $0.trigger == trigger }
|
||||||
case .ended:
|
for itemAction in itemActions {
|
||||||
tapClosure?()
|
itemAction.closure?()
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureSingleTap() {
|
||||||
|
callActions(for: .singleTap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureDoubleTap() {
|
||||||
|
callActions(for: .doubleTap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureTripleTap() {
|
||||||
|
callActions(for: .tripleTap)
|
||||||
|
}
|
||||||
|
|
||||||
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
||||||
switch gr.state {
|
switch gr.state {
|
||||||
case .possible: // tiny hack because we're calling action manually
|
case .possible: // tiny hack because we're calling action manually
|
||||||
(self.longTapClosure ?? self.tapClosure)?()
|
callActions(for: .longTap)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -176,7 +207,38 @@ 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) {
|
override func touchesBegan(with event: NSEvent) {
|
||||||
HapticFeedback.shared?.tap(strong: 2)
|
HapticFeedback.shared?.tap(strong: 2)
|
||||||
super.touchesBegan(with: event)
|
super.touchesBegan(with: event)
|
||||||
@ -185,6 +247,38 @@ class HapticClickGestureRecognizer: NSClickGestureRecognizer {
|
|||||||
override func touchesEnded(with event: NSEvent) {
|
override func touchesEnded(with event: NSEvent) {
|
||||||
HapticFeedback.shared?.tap(strong: 1)
|
HapticFeedback.shared?.tap(strong: 1)
|
||||||
super.touchesEnded(with: event)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.25</string>
|
<string>0.27</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>297</string>
|
<string>434</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.utilities</string>
|
<string>public.app-category.utilities</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<key>NSHomeKitUsageDescription</key>
|
<key>NSHomeKitUsageDescription</key>
|
||||||
<string>MTMR needs access to HomeKit for work</string>
|
<string>MTMR needs access to HomeKit for work</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2018 Anton Palgunov. All rights reserved.</string>
|
<string>Copyright © 2018 - 2020 Anton Palgunov. All rights reserved.</string>
|
||||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
<string>Weather widget need your location for correct work</string>
|
<string>Weather widget need your location for correct work</string>
|
||||||
<key>NSLocationAlwaysUsageDescription</key>
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
|
|||||||
@ -3,47 +3,52 @@ import Foundation
|
|||||||
|
|
||||||
extension Data {
|
extension Data {
|
||||||
func barItemDefinitions() -> [BarItemDefinition]? {
|
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 {
|
struct BarItemDefinition: Decodable {
|
||||||
let type: ItemType
|
let type: ItemType
|
||||||
let action: ActionType
|
let actions: [Action]
|
||||||
let longAction: LongActionType
|
let legacyAction: LegacyActionType
|
||||||
|
let legacyLongAction: LegacyLongActionType
|
||||||
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type
|
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.type = type
|
||||||
self.action = action
|
self.actions = actions
|
||||||
self.longAction = longAction
|
self.legacyAction = action
|
||||||
|
self.legacyLongAction = legacyLongAction
|
||||||
self.additionalParameters = additionalParameters
|
self.additionalParameters = additionalParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
let type = try container.decode(String.self, forKey: .type)
|
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
|
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||||
|
|
||||||
if let result = try? parametersDecoder(decoder),
|
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 }
|
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 {
|
} 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 -> (
|
typealias ParametersDecoder = (Decoder) throws -> (
|
||||||
item: ItemType,
|
item: ItemType,
|
||||||
action: ActionType,
|
actions: [Action],
|
||||||
longAction: LongActionType,
|
legacyAction: LegacyActionType,
|
||||||
|
legacyLongAction: LegacyLongActionType,
|
||||||
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,8 +56,11 @@ class SupportedTypesHolder {
|
|||||||
private var supportedTypes: [String: ParametersDecoder] = [
|
private var supportedTypes: [String: ParametersDecoder] = [
|
||||||
"escape": { _ in (
|
"escape": { _ in (
|
||||||
item: .staticButton(title: "esc"),
|
item: .staticButton(title: "esc"),
|
||||||
action: .keyPress(keycode: 53),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.align: .align(.left)]
|
parameters: [.align: .align(.left)]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
@ -65,8 +73,11 @@ class SupportedTypesHolder {
|
|||||||
|
|
||||||
"delete": { _ in (
|
"delete": { _ in (
|
||||||
item: .staticButton(title: "del"),
|
item: .staticButton(title: "del"),
|
||||||
action: .keyPress(keycode: 117),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
@ -74,8 +85,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .keyPress(keycode: 144),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -84,8 +98,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .keyPress(keycode: 145),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -94,8 +111,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -104,8 +124,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -114,8 +137,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -124,8 +150,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -134,8 +163,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_MUTE),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -144,8 +176,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -154,8 +189,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_PLAY),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -164,23 +202,32 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_NEXT),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
"sleep": { _ in (
|
"sleep": { _ in (
|
||||||
item: .staticButton(title: "☕️"),
|
item: .staticButton(title: "☕️"),
|
||||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
"displaySleep": { _ in (
|
"displaySleep": { _ in (
|
||||||
item: .staticButton(title: "☕️"),
|
item: .staticButton(title: "☕️"),
|
||||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
@ -209,11 +256,12 @@ class SupportedTypesHolder {
|
|||||||
|
|
||||||
static let sharedInstance = SupportedTypesHolder()
|
static let sharedInstance = SupportedTypesHolder()
|
||||||
|
|
||||||
func lookup(by type: String) -> ParametersDecoder {
|
func lookup(by type: String, actions: [Action]) -> ParametersDecoder {
|
||||||
return supportedTypes[type] ?? { decoder in (
|
return supportedTypes[type] ?? { decoder in (
|
||||||
item: try ItemType(from: decoder),
|
item: try ItemType(from: decoder),
|
||||||
action: try ActionType(from: decoder),
|
actions: actions,
|
||||||
longAction: try LongActionType(from: decoder),
|
legacyAction: try LegacyActionType(from: decoder),
|
||||||
|
legacyLongAction: try LegacyLongActionType(from: decoder),
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) }
|
) }
|
||||||
}
|
}
|
||||||
@ -222,12 +270,13 @@ class SupportedTypesHolder {
|
|||||||
supportedTypes[typename] = decoder
|
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
|
register(typename: typename) { _ in
|
||||||
(
|
(
|
||||||
item: item,
|
item: item,
|
||||||
action,
|
actions,
|
||||||
longAction,
|
legacyAction,
|
||||||
|
legacyLongAction,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -236,7 +285,7 @@ class SupportedTypesHolder {
|
|||||||
|
|
||||||
enum ItemType: Decodable {
|
enum ItemType: Decodable {
|
||||||
case staticButton(title: String)
|
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 shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||||
case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
|
case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
|
||||||
case battery
|
case battery
|
||||||
@ -255,6 +304,8 @@ enum ItemType: Decodable {
|
|||||||
case pomodoro(workTime: Double, restTime: Double)
|
case pomodoro(workTime: Double, restTime: Double)
|
||||||
case network(flip: Bool)
|
case network(flip: Bool)
|
||||||
case darkMode
|
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 {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type
|
case type
|
||||||
@ -280,6 +331,13 @@ enum ItemType: Decodable {
|
|||||||
case autoResize
|
case autoResize
|
||||||
case filter
|
case filter
|
||||||
case disableMarquee
|
case disableMarquee
|
||||||
|
case alternativeImages
|
||||||
|
case sourceApple
|
||||||
|
case sourceBash
|
||||||
|
case direction
|
||||||
|
case fingers
|
||||||
|
case minOffset
|
||||||
|
case maxToShow
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ItemTypeRaw: String, Decodable {
|
enum ItemTypeRaw: String, Decodable {
|
||||||
@ -303,6 +361,8 @@ enum ItemType: Decodable {
|
|||||||
case pomodoro
|
case pomodoro
|
||||||
case network
|
case network
|
||||||
case darkMode
|
case darkMode
|
||||||
|
case swipe
|
||||||
|
case upnext
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
@ -312,7 +372,8 @@ enum ItemType: Decodable {
|
|||||||
case .appleScriptTitledButton:
|
case .appleScriptTitledButton:
|
||||||
let source = try container.decode(Source.self, forKey: .source)
|
let source = try container.decode(Source.self, forKey: .source)
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
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:
|
case .shellScriptTitledButton:
|
||||||
let source = try container.decode(Source.self, forKey: .source)
|
let source = try container.decode(Source.self, forKey: .source)
|
||||||
@ -396,11 +457,114 @@ enum ItemType: Decodable {
|
|||||||
|
|
||||||
case .darkMode:
|
case .darkMode:
|
||||||
self = .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)
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActionType: Decodable {
|
struct FailableDecodable<Base : Decodable> : 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 none
|
||||||
case hidKey(keycode: Int32)
|
case hidKey(keycode: Int32)
|
||||||
case keyPress(keycode: Int)
|
case keyPress(keycode: Int)
|
||||||
@ -458,7 +622,7 @@ enum ActionType: Decodable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LongActionType: Decodable {
|
enum LegacyLongActionType: Decodable {
|
||||||
case none
|
case none
|
||||||
case hidKey(keycode: Int32)
|
case hidKey(keycode: Int32)
|
||||||
case keyPress(keycode: Int)
|
case keyPress(keycode: Int)
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
|
||||||
var twofingersPrev: CGFloat = 0.0
|
|
||||||
var threefingersPrev: CGFloat = 0.0
|
|
||||||
var twofingers: NSPanGestureRecognizer!
|
|
||||||
var threefingers: NSPanGestureRecognizer!
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
|
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
|
||||||
super.init(identifier: identifier)
|
super.init(identifier: identifier)
|
||||||
@ -15,70 +11,10 @@ class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
|||||||
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
|
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
|
||||||
scrollView.documentView = stackView
|
scrollView.documentView = stackView
|
||||||
view = scrollView
|
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) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,11 +62,19 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
|||||||
|
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
task.standardOutput = 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()
|
task.launch()
|
||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
|
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) {
|
if (output == "" && task.terminationStatus != 0) {
|
||||||
output = "error"
|
output = "error"
|
||||||
}
|
}
|
||||||
|
|||||||
66
MTMR/SwipeItem.swift
Normal file
66
MTMR/SwipeItem.swift
Normal file
@ -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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,6 +59,10 @@ extension ItemType {
|
|||||||
return NetworkBarItem.identifier
|
return NetworkBarItem.identifier
|
||||||
case .darkMode:
|
case .darkMode:
|
||||||
return DarkModeBarItem.identifier
|
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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,10 +82,10 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
|
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
|
||||||
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
|
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||||
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
|
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||||
var centerItems: [NSTouchBarItem] = []
|
|
||||||
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
|
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||||
var scrollArea: ScrollViewItem?
|
var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
|
||||||
var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
var basicView: BasicView?
|
||||||
|
var swipeItems: [SwipeItem] = []
|
||||||
|
|
||||||
var blacklistAppIdentifiers: [String] = []
|
var blacklistAppIdentifiers: [String] = []
|
||||||
var frontmostApplicationIdentifier: String? {
|
var frontmostApplicationIdentifier: String? {
|
||||||
@ -90,13 +94,28 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.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
|
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
|
||||||
(item: .staticButton(title: ""), action: .custom(closure: { [weak self] in
|
(
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
actions: [
|
||||||
|
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
|
||||||
guard let `self` = self else { return }
|
guard let `self` = self else { return }
|
||||||
self.reloadPreset(path: self.lastPresetPath)
|
self.reloadPreset(path: self.lastPresetPath)
|
||||||
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
}))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
|
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
|
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
|
||||||
@ -116,24 +135,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
jsonItems = newJsonItems
|
jsonItems = newJsonItems
|
||||||
itemDefinitions = [:]
|
itemDefinitions = [:]
|
||||||
items = [:]
|
items = [:]
|
||||||
leftIdentifiers = []
|
|
||||||
centerItems = []
|
|
||||||
rightIdentifiers = []
|
|
||||||
|
|
||||||
loadItemDefinitions(jsonItems: jsonItems)
|
loadItemDefinitions(jsonItems: jsonItems)
|
||||||
createItems()
|
createItems()
|
||||||
|
|
||||||
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||||
items[identifier]
|
items[identifier]
|
||||||
})
|
})
|
||||||
|
|
||||||
centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||||
scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||||
scrollArea?.gesturesEnabled = AppSettings.multitouchGestures
|
|
||||||
|
|
||||||
touchBar.delegate = self
|
touchBar.delegate = self
|
||||||
touchBar.defaultItemIdentifiers = []
|
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
||||||
touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
|
|
||||||
|
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()
|
updateActiveApp()
|
||||||
}
|
}
|
||||||
@ -163,7 +187,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
|
|
||||||
func reloadPreset(path: String) {
|
func reloadPreset(path: String) {
|
||||||
lastPresetPath = path
|
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)
|
createAndUpdatePreset(newJsonItems: items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +213,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
|
|
||||||
func createItems() {
|
func createItems() {
|
||||||
for (identifier, definition) in itemDefinitions {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,25 +254,20 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||||
if identifier == centerScrollArea {
|
if identifier == basicViewIdentifier {
|
||||||
return scrollArea
|
return basicView
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let item = self.items[identifier],
|
|
||||||
let definition = self.itemDefinitions[identifier],
|
|
||||||
definition.align != .center else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
||||||
var barItem: NSTouchBarItem!
|
var barItem: NSTouchBarItem!
|
||||||
switch item.type {
|
switch item.type {
|
||||||
case let .staticButton(title: title):
|
case let .staticButton(title: title):
|
||||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
||||||
case let .appleScriptTitledButton(source: source, refreshInterval: interval):
|
case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages):
|
||||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages)
|
||||||
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
|
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
|
||||||
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
||||||
case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale):
|
case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale):
|
||||||
@ -300,13 +324,23 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
barItem = NetworkBarItem(identifier: identifier, flip: flip)
|
barItem = NetworkBarItem(identifier: identifier, flip: flip)
|
||||||
case .darkMode:
|
case .darkMode:
|
||||||
barItem = DarkModeBarItem(identifier: identifier)
|
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 {
|
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 {
|
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 {
|
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
|
||||||
item.isBordered = bordered
|
item.isBordered = bordered
|
||||||
@ -330,8 +364,52 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
return barItem
|
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)? {
|
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
switch item.action {
|
switch item.legacyAction {
|
||||||
case let .hidKey(keycode: keycode):
|
case let .hidKey(keycode: keycode):
|
||||||
return { HIDPostAuxKey(keycode) }
|
return { HIDPostAuxKey(keycode) }
|
||||||
case let .keyPress(keycode: keycode):
|
case let .keyPress(keycode: keycode):
|
||||||
@ -375,7 +453,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
switch item.longAction {
|
switch item.legacyLongAction {
|
||||||
case let .hidKey(keycode: keycode):
|
case let .hidKey(keycode: keycode):
|
||||||
return { HIDPostAuxKey(keycode) }
|
return { HIDPostAuxKey(keycode) }
|
||||||
case let .keyPress(keycode: keycode):
|
case let .keyPress(keycode: keycode):
|
||||||
@ -427,6 +505,12 @@ extension NSCustomTouchBarItem: CanSetWidth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NSPopoverTouchBarItem: CanSetWidth {
|
||||||
|
func setWidth(value: CGFloat) {
|
||||||
|
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension BarItemDefinition {
|
extension BarItemDefinition {
|
||||||
var align: Align {
|
var align: Align {
|
||||||
if case let .align(result)? = additionalParameters[.align] {
|
if case let .align(result)? = additionalParameters[.align] {
|
||||||
|
|||||||
@ -82,12 +82,14 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem {
|
|||||||
public func createAppButton(for app: DockItem) -> DockBarItem {
|
public func createAppButton(for app: DockItem) -> DockBarItem {
|
||||||
let item = DockBarItem(app)
|
let item = DockBarItem(app)
|
||||||
item.isBordered = false
|
item.isBordered = false
|
||||||
item.tapClosure = { [weak self] in
|
item.actions.append(contentsOf: [
|
||||||
|
ItemAction(trigger: .singleTap) { [weak self] in
|
||||||
self?.switchToApp(app: app)
|
self?.switchToApp(app: app)
|
||||||
}
|
},
|
||||||
item.longTapClosure = { [weak self] in
|
ItemAction(trigger: .longTap) { [weak self] in
|
||||||
self?.handleHalfLongPress(item: app)
|
self?.handleHalfLongPress(item: app)
|
||||||
}
|
}
|
||||||
|
])
|
||||||
item.killAppClosure = {[weak self] in
|
item.killAppClosure = {[weak self] in
|
||||||
self?.handleLongPress(item: app)
|
self?.handleLongPress(item: app)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
|||||||
private var postfix: String
|
private var postfix: String
|
||||||
private var from: String
|
private var from: String
|
||||||
private var to: String
|
private var to: String
|
||||||
|
private var decimal: Int
|
||||||
|
private var decimalValue: Float32!
|
||||||
|
private var decimalString: String!
|
||||||
private var oldValue: Float32!
|
private var oldValue: Float32!
|
||||||
private var full: Bool = false
|
private var full: Bool = false
|
||||||
|
|
||||||
@ -32,11 +35,29 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
|||||||
"IDR": "Rp",
|
"IDR": "Rp",
|
||||||
"MXN": "$",
|
"MXN": "$",
|
||||||
"SGD": "$",
|
"SGD": "$",
|
||||||
"CHF": "Fr.",
|
|
||||||
"BTC": "฿",
|
"BTC": "฿",
|
||||||
"LTC": "Ł",
|
"LTC": "Ł",
|
||||||
"ETH": "Ξ",
|
"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) {
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) {
|
||||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||||
@ -57,6 +78,14 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
|||||||
postfix = to
|
postfix = to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if let decimal = decimals[to] {
|
||||||
|
self.decimal = decimal
|
||||||
|
} else {
|
||||||
|
decimal = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
super.init(identifier: identifier, title: "⏳")
|
super.init(identifier: identifier, title: "⏳")
|
||||||
|
|
||||||
activity.repeats = true
|
activity.repeats = true
|
||||||
@ -114,10 +143,12 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldValue = value
|
oldValue = value
|
||||||
|
decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal))
|
||||||
|
decimalString = String(decimalValue)
|
||||||
|
|
||||||
var title = ""
|
var title = ""
|
||||||
if full {
|
if full {
|
||||||
title = String(format: "%@‣%.2f%@", prefix, value, postfix)
|
title = String(format: "%@%@‣%@", prefix, postfix, decimalString)
|
||||||
} else {
|
} else {
|
||||||
title = String(format: "%@%.2f", prefix, value)
|
title = String(format: "%@%.2f", prefix, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class DarkModeBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 24)
|
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)
|
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class DnDBarItem: CustomButtonTouchBarItem {
|
|||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 32)
|
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)
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@ -18,9 +18,10 @@ class InputSourceBarItem: CustomButtonTouchBarItem {
|
|||||||
|
|
||||||
observeIputSourceChangedNotification()
|
observeIputSourceChangedNotification()
|
||||||
textInputSourceDidChange()
|
textInputSourceDidChange()
|
||||||
tapClosure = { [weak self] in
|
|
||||||
|
actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
||||||
self?.switchInputSource()
|
self?.switchInputSource()
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import ScriptingBridge
|
|||||||
|
|
||||||
class MusicBarItem: CustomButtonTouchBarItem {
|
class MusicBarItem: CustomButtonTouchBarItem {
|
||||||
private enum Player: String {
|
private enum Player: String {
|
||||||
|
case Music = "com.apple.Music"
|
||||||
case iTunes = "com.apple.iTunes"
|
case iTunes = "com.apple.iTunes"
|
||||||
case Spotify = "com.spotify.client"
|
case Spotify = "com.spotify.client"
|
||||||
case VOX = "com.coppertino.Vox"
|
case VOX = "com.coppertino.Vox"
|
||||||
@ -19,6 +20,7 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let playerBundleIdentifiers = [
|
private let playerBundleIdentifiers = [
|
||||||
|
Player.Music,
|
||||||
Player.iTunes,
|
Player.iTunes,
|
||||||
Player.Spotify,
|
Player.Spotify,
|
||||||
Player.VOX,
|
Player.VOX,
|
||||||
@ -40,8 +42,11 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
super.init(identifier: identifier, title: "⏳")
|
super.init(identifier: identifier, title: "⏳")
|
||||||
isBordered = false
|
isBordered = false
|
||||||
|
|
||||||
tapClosure = { [weak self] in self?.playPause() }
|
actions = [
|
||||||
longTapClosure = { [weak self] in self?.nextTrack() }
|
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()
|
refreshAndSchedule()
|
||||||
}
|
}
|
||||||
@ -71,6 +76,10 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
let mp = (musicPlayer as iTunesApplication)
|
let mp = (musicPlayer as iTunesApplication)
|
||||||
mp.playpause!()
|
mp.playpause!()
|
||||||
return
|
return
|
||||||
|
} else if ident == .Music {
|
||||||
|
let mp = (musicPlayer as MusicApplication)
|
||||||
|
mp.playpause!()
|
||||||
|
return
|
||||||
} else if ident == .VOX {
|
} else if ident == .VOX {
|
||||||
let mp = (musicPlayer as VoxApplication)
|
let mp = (musicPlayer as VoxApplication)
|
||||||
mp.playpause!()
|
mp.playpause!()
|
||||||
@ -134,6 +143,11 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
mp.nextTrack!()
|
mp.nextTrack!()
|
||||||
updatePlayer()
|
updatePlayer()
|
||||||
return
|
return
|
||||||
|
} else if ident == .Music {
|
||||||
|
let mp = (musicPlayer as MusicApplication)
|
||||||
|
mp.nextTrack!()
|
||||||
|
updatePlayer()
|
||||||
|
return
|
||||||
} else if ident == .VOX {
|
} else if ident == .VOX {
|
||||||
let mp = (musicPlayer as VoxApplication)
|
let mp = (musicPlayer as VoxApplication)
|
||||||
mp.next!()
|
mp.next!()
|
||||||
@ -167,6 +181,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() {
|
func refreshAndSchedule() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updatePlayer()
|
self.updatePlayer()
|
||||||
@ -188,6 +227,8 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
tempTitle = (musicPlayer as SpotifyApplication).title
|
tempTitle = (musicPlayer as SpotifyApplication).title
|
||||||
} else if ident == .iTunes {
|
} else if ident == .iTunes {
|
||||||
tempTitle = (musicPlayer as iTunesApplication).title
|
tempTitle = (musicPlayer as iTunesApplication).title
|
||||||
|
} else if ident == .Music {
|
||||||
|
tempTitle = (musicPlayer as MusicApplication).title
|
||||||
} else if ident == .VOX {
|
} else if ident == .VOX {
|
||||||
tempTitle = (musicPlayer as VoxApplication).title
|
tempTitle = (musicPlayer as VoxApplication).title
|
||||||
} else if ident == .Safari {
|
} else if ident == .Safari {
|
||||||
@ -321,6 +362,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 protocol VoxApplication {
|
||||||
@objc optional func playpause()
|
@objc optional func playpause()
|
||||||
@objc optional func next()
|
@objc optional func next()
|
||||||
|
|||||||
@ -31,7 +31,7 @@ class NightShiftBarItem: CustomButtonTouchBarItem {
|
|||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 28)
|
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)
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,9 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
|
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
|
||||||
action: .none,
|
actions: [],
|
||||||
longAction: .none,
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -50,8 +51,10 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
self.workTime = workTime
|
self.workTime = workTime
|
||||||
self.restTime = restTime
|
self.restTime = restTime
|
||||||
super.init(identifier: identifier, title: defaultTitle)
|
super.init(identifier: identifier, title: defaultTitle)
|
||||||
tapClosure = { [weak self] in self?.startStopWork() }
|
actions.append(contentsOf: [
|
||||||
longTapClosure = { [weak self] in self?.startStopRest() }
|
ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() },
|
||||||
|
ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() }
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
|
|||||||
256
MTMR/Widgets/UpNextScrubberTouchBarItem.swift
Normal file
256
MTMR/Widgets/UpNextScrubberTouchBarItem.swift
Normal file
@ -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.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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,10 +12,14 @@ import CoreLocation
|
|||||||
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
||||||
private let activity: NSBackgroundActivityScheduler
|
private let activity: NSBackgroundActivityScheduler
|
||||||
private let unitsStr = "°C"
|
private let unitsStr = "°C"
|
||||||
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
|
private let iconsSource = [
|
||||||
|
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь с грозой": "⛈", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
||||||
|
"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 location: CLLocation!
|
||||||
private var prevLocation: CLLocation!
|
private var prevLocation: CLLocation!
|
||||||
private var manager: CLLocationManager!
|
private var manager: CLLocationManager!
|
||||||
|
private var updateWeatherTask: URLSessionDataTask?
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
||||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||||
@ -47,7 +51,12 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
manager.startUpdatingLocation()
|
manager.startUpdatingLocation()
|
||||||
|
|
||||||
tapClosure = tapClosure ?? defaultTapAction
|
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
|
||||||
|
actions.append(ItemAction(
|
||||||
|
trigger: .singleTap,
|
||||||
|
defaultTapAction
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
@ -58,7 +67,8 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
|
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
|
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 {
|
guard error == nil, let response = data?.utf8string else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -83,12 +93,12 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resume()
|
updateWeatherTask?.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWeatherUrl() -> String {
|
func getWeatherUrl() -> String {
|
||||||
if location != nil {
|
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 {
|
} else {
|
||||||
return "https://yandex.ru/pogoda/?lang=ru" // Yandex will try to determine your location by default
|
return "https://yandex.ru/pogoda/?lang=ru" // Yandex will try to determine your location by default
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
// iTunes
|
||||||
{
|
{
|
||||||
"type": "appleScriptTitledButton",
|
"type": "appleScriptTitledButton",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
|||||||
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" } } ]
|
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" } } ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
|||||||
[ { "type": "appleScriptTitledButton", "source": { "filePath": "/ololo/pew" } } ]
|
[ { "type": "appleScriptTitledButton", "source": { "filePath": "/ololo/pew" } } ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
|||||||
[ { "type": "appleScriptTitledButton", "source": { "filePath": "~/pew" } } ]
|
[ { "type": "appleScriptTitledButton", "source": { "filePath": "~/pew" } } ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
|||||||
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ]
|
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,13 +10,28 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .none? = result?.first?.action else {
|
guard result?.first?.actions.count == 0 else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testButtonKeyCodeAction() {
|
func testButtonKeyCodeAction() {
|
||||||
|
let buttonKeycodeFixture = """
|
||||||
|
[ { "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?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testButtonKeyCodeLegacyAction() {
|
||||||
let buttonKeycodeFixture = """
|
let buttonKeycodeFixture = """
|
||||||
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
|
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
@ -25,7 +40,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .hidKey(keycode: 123)? = result?.first?.action else {
|
guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,7 +55,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -55,7 +70,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
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()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
175
README.md
175
README.md
@ -18,7 +18,7 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
|
|||||||
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><a href="https://www.paypal.me/toxblh/10" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
<p align="center"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
||||||
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
||||||
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
||||||
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
||||||
@ -84,6 +84,7 @@ The pre-installed configuration contains less or more than you'll probably want,
|
|||||||
- darkMode
|
- darkMode
|
||||||
- pomodoro
|
- pomodoro
|
||||||
- network
|
- network
|
||||||
|
- upnext (Calendar events)
|
||||||
|
|
||||||
> Media Keys
|
> Media Keys
|
||||||
|
|
||||||
@ -102,11 +103,31 @@ The pre-installed configuration contains less or more than you'll probably want,
|
|||||||
- appleScriptTitledButton
|
- appleScriptTitledButton
|
||||||
- shellScriptTitledButton
|
- 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
|
- two finger slide: change you Volume
|
||||||
- three finger slide: change you Brightness
|
- 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:
|
## Built-in slider types:
|
||||||
|
|
||||||
- brightness
|
- brightness
|
||||||
@ -133,8 +154,35 @@ 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",
|
"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
|
// or
|
||||||
"base64": "StringInbase64"
|
"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`
|
#### `shellScriptTitledButton`
|
||||||
@ -150,10 +198,15 @@ Example of "CPU load" button which also changes color based on load value.
|
|||||||
"source": {
|
"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}'"
|
"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}'"
|
||||||
},
|
},
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"trigger": "singleTap",
|
||||||
"action": "appleScript",
|
"action": "appleScript",
|
||||||
"actionAppleScript": {
|
"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"
|
"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",
|
"align": "right",
|
||||||
"image": {
|
"image": {
|
||||||
// Or you can specify a filePath here.
|
// Or you can specify a filePath here.
|
||||||
@ -296,8 +349,46 @@ 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:
|
## Actions:
|
||||||
|
|
||||||
|
### Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"trigger": "singleTap",
|
||||||
|
"action": "hidKey",
|
||||||
|
"keycode": 53
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Triggers:
|
||||||
|
|
||||||
|
- `singleTap`
|
||||||
|
- `doubleTap`
|
||||||
|
- `tripleTap`
|
||||||
|
- `longTap`
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
- `hidKey`
|
- `hidKey`
|
||||||
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
||||||
|
|
||||||
@ -339,22 +430,6 @@ To close a group, use the button:
|
|||||||
"url": "https://google.com",
|
"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:
|
## Additional parameters:
|
||||||
|
|
||||||
- `width` restrict how much room a particular button will take
|
- `width` restrict how much room a particular button will take
|
||||||
@ -375,39 +450,45 @@ If you want to longPress for some operations, it is similar to the configuration
|
|||||||
"bordered": "false" // "true" or "false"
|
"bordered": "false" // "true" or "false"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Roadmap
|
- `background` allow to specify you button background color
|
||||||
|
|
||||||
- [x] Create the first prototype with TouchBar in Storyboard
|
```js
|
||||||
- [x] Put in stripe menu on startup the application
|
"background": "#FF0000",
|
||||||
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
```
|
||||||
- [x] Time in touchbar!
|
by using background with color "#000000" and bordered == false you can create button without gray background but with background when the button is pressed
|
||||||
- [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:
|
- `title` specify button title
|
||||||
|
|
||||||
- [ ] Interface for plugins and export like presets
|
```js
|
||||||
- [x] Startup at login
|
"title": "hello"
|
||||||
- [ ] Show on/off in Dock
|
```
|
||||||
- [ ] Show on/off in StatusBar
|
|
||||||
- [ ] On/off Haptic Feedback
|
|
||||||
|
|
||||||
Maybe:
|
- `image` specify button icon
|
||||||
|
|
||||||
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
```js
|
||||||
|
"image": {
|
||||||
|
//Can be either of those
|
||||||
|
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdB...."
|
||||||
|
//or
|
||||||
|
"filePath": "~/img.png"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
#### 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 <kbd>Enter</kbd>
|
||||||
|
|
||||||
|
|
||||||
|
#### 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"
|
||||||
|
|
||||||
|
Re-tick or check a tick for access 🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|||||||
@ -4,3 +4,38 @@
|
|||||||
* try move away from enums when parse preset – enums are hard to extend
|
* try move away from enums when parse preset – enums are hard to extend
|
||||||
* find better way to hide bar items
|
* find better way to hide bar items
|
||||||
* extract bar items creating from TouchBarController to separate class, cover with tests
|
* 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?)
|
||||||
|
|||||||
12
build.sh
12
build.sh
@ -1,16 +1,18 @@
|
|||||||
|
# INSTALL xcpretty: sudo gem install xcpretty
|
||||||
|
|
||||||
NAME='MTMR'
|
NAME='MTMR'
|
||||||
|
|
||||||
rm -r Release 2>/dev/null
|
rm -r Release 2>/dev/null
|
||||||
|
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "$NAME" \
|
-scheme "$NAME" \
|
||||||
-archivePath Release/App.xcarchive
|
-archivePath Release/App.xcarchive | xcpretty -c
|
||||||
|
|
||||||
xcodebuild \
|
xcodebuild \
|
||||||
-exportArchive \
|
-exportArchive \
|
||||||
-archivePath Release/App.xcarchive \
|
-archivePath Release/App.xcarchive \
|
||||||
-exportOptionsPlist export-options.plist \
|
-exportOptionsPlist export-options.plist \
|
||||||
-exportPath Release
|
-exportPath Release | xcpretty -c
|
||||||
|
|
||||||
cd Release
|
cd Release
|
||||||
rm -r App.xcarchive
|
rm -r App.xcarchive
|
||||||
@ -20,7 +22,7 @@ NAME_DMG="${NAME}.app"
|
|||||||
echo $NAME_DMG
|
echo $NAME_DMG
|
||||||
create-dmg $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`
|
BUILD=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${NAME}.app/Contents/Info.plist`
|
||||||
VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${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`
|
MINIMUM=`/usr/libexec/PlistBuddy -c "Print LSMinimumSystemVersion" ${NAME}.app/Contents/Info.plist`
|
||||||
@ -61,8 +63,8 @@ echo "<?xml version=\"1.0\" standalone=\"yes\"?>
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Homebrew https://github.com/Homebrew/homebrew-cask/edit/master/Casks/mtmr.rb"
|
echo "Homebrew https://github.com/Homebrew/homebrew-cask/edit/master/Casks/mtmr.rb"
|
||||||
echo ""
|
echo ""
|
||||||
echo " version '${VERSION}'"
|
echo " version \"${VERSION}\""
|
||||||
echo " sha256 '${SHA256}'"
|
echo " sha256 \"${SHA256}\""
|
||||||
echo ""
|
echo ""
|
||||||
echo "Update MTMR v${VERSION}"
|
echo "Update MTMR v${VERSION}"
|
||||||
|
|
||||||
|
|||||||
5
test.sh
5
test.sh
@ -1,3 +1,4 @@
|
|||||||
|
# INSTALL xcpretty: sudo gem install xcpretty
|
||||||
|
|
||||||
NAME='MTMR'
|
NAME='MTMR'
|
||||||
killall $NAME
|
killall $NAME
|
||||||
@ -11,13 +12,13 @@ rm -r Release 2>/dev/null
|
|||||||
|
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "$NAME" \
|
-scheme "$NAME" \
|
||||||
-archivePath Release/App.xcarchive
|
-archivePath Release/App.xcarchive | xcpretty
|
||||||
|
|
||||||
xcodebuild \
|
xcodebuild \
|
||||||
-exportArchive \
|
-exportArchive \
|
||||||
-archivePath Release/App.xcarchive \
|
-archivePath Release/App.xcarchive \
|
||||||
-exportOptionsPlist export-options.plist \
|
-exportOptionsPlist export-options.plist \
|
||||||
-exportPath Release
|
-exportPath Release | xcpretty
|
||||||
|
|
||||||
cd Release
|
cd Release
|
||||||
rm -r App.xcarchive
|
rm -r App.xcarchive
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user