Compare commits

..
61 Commits
Author SHA1 Message Date
Kaibin Yang 7a1800252c Fix error. (#325)
Colon expected.
2020-08-13 12:32:55 +01:00
Matteo Piccina 588e6ae09b Implement multiple actions (double tap, triple tap) (#349)
* Implement double tap and new actions array in config

* Update native widgets to use new actions parameter

* Refactor new actions parameter moving it to main definition
Renamed old action and longAction to legacy

* Fix tests

* Remove unused code

* Readd test for legacyAction

* Implement triple tap

* Add new actions explanation

* Add support for multiple actions and same trigger
2020-08-03 11:53:39 +01:00
Connor G Meehan 87141e381b Up next calendar widget (#348)
* WIP Implementation of up next widget

* Seperated button view and event source logic

* Adjusted default parameters

* Added the ability to view multiple events

* Added ability to click touchbar item and go to calendar

* renamed nthEvent to maxToShow and changed default

* Updated CFBundleVersion

* Renamed UpNext class and fix ups

* Added "autoResize" property (same functionality as dock)

* Added EKEventStore listener to reduce perfomance impact

* Log cleanup

* Made button blue for current/past events

* Added handling of unauthorised access to calendar
2020-07-27 00:41:05 +01:00
Vladimir Tolstikov 14282b86a9 YandexWeather: fix race condition which randomly leads to displaying wrong data (#345) 2020-07-16 19:11:37 +01:00
Vladimir Tolstikov 810cdeed36 YandexWeather: fix issue with wrong URL which leads to random incorrect location detection and wrong weather display (#344) 2020-07-15 21:04:11 +01:00
Giuseppe Petrosino a65613acaf Allow change of of a group button width (#336) 2020-06-17 22:40:58 +01:00
Anton Palgunov aa69d5f592 Update TECHNICAL_DEBT.md 2020-05-26 17:46:15 +01:00
Anton Palgunov 2f00c9ffb3 Update README.md 2020-05-26 17:45:49 +01:00
Vladimir Tolstikov 1def53878d YandexWeather: update matching array with missing forecast ("Thunderstorm with rain") (#316) 2020-05-21 11:27:50 +01:00
toxblh 3e5fa14494 v0.26.1 2020-05-17 13:42:02 +01:00
Fedor ZaitsevandFedor Zaitsev 2e2f556daf Fixed lost margin (#313)
* Updated README

Added explanation for missing parameters (background, title and image)

* Implemented changable icons for AppleScriptTouchBarItem

AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme

* Fixed #312

Fixed bug that margin is lost on all elements

Co-authored-by: Fedor Zaitsev <lobster@Fedors-MacBook-Pro.local>
2020-05-17 13:39:21 +01:00
toxblh 1e1ae2af61 Release version 0.26 2020-05-14 00:49:53 +01:00
toxblh bc11728c2e Standard english date for build script 2020-05-14 00:44:10 +01:00
toxblh 445584bb1b Added xcpretty for test and build scripts 2020-05-14 00:42:08 +01:00
toxblh b6721f0274 return normal trigggers in workflows 2020-05-14 00:05:17 +01:00
toxblh 75df82a567 ignore sign error 2020-05-13 23:34:45 +01:00
toxblh a1f64028cc Correct file for release 2020-05-13 23:23:03 +01:00
toxblh 3fc75ca8f0 Updated workflows. For delivery unsign app in gihub 2020-05-13 23:22:31 +01:00
toxblh 6d266394a4 Revert "More Productive Gestures (#289)"
This reverts commit f61550e510.
2020-05-13 23:11:45 +01:00
Fedor ZaytsevandFedor Zaitsev 52758f947d Fix shell crash (#297)
* Updated README

Added explanation for missing parameters (background, title and image)

* Implemented changable icons for AppleScriptTouchBarItem

AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme

* Fixed error related to ShellButton

When you execute Process from swift you cannot relay solely on pipe.fileHandleForReading.readDataToEndOfFile()
Sometimes when I close notebook I get exception saying that you cannot access process.terminationStatus variable while process is running.
Apparently it seems that this call can be finished when OS X put disks into sleep mode(?)

What I did:
1. Added Process.waitUntilExit() call
2. Added timeout (equal to the update interval)

Co-authored-by: Fedor Zaitsev <lobster@Fedors-MacBook-Pro.local>
2020-04-21 13:34:03 +01:00
Simon Rogersandmedden 502f989417 Currency bar update (#293)
* Updated CurrenyBarItem to display second currency (TO FROM>RATE) and also correct number of decimal places.
Added INR to list of currencies.

* Update CurrencyBarItem.swift

* Updated else statements

Co-authored-by: medden <si@medden.co.uk>
2020-04-09 18:49:39 +01:00
Jæy f61550e510 More Productive Gestures (#289)
* Scroll Functionality Works

* updated md

* Update README.md

* Update README.md

* attempted to fix readme

* fixed readme

* fixed readme
2020-04-09 00:24:43 +01:00
Fedor ZaytsevandFedor Zaitsev a0fc0b33c5 Custom gestures support (#288)
* Updated README

Added explanation for missing parameters (background, title and image)

* Implemented changable icons for AppleScriptTouchBarItem

AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme

* Implemented custom swipe actions

Co-authored-by: Fedor Zaitsev <lobster@Fedors-MacBook-Pro.local>
2020-04-01 22:11:35 +01:00
Peter Hrvola 42ce95b72e Add Music app (#279) 2020-03-16 12:06:20 +00:00
Fedor Zaytsev 642f0807bb Changeable icons for AppleScriptTouchBarItem & tests (#276)
* Implemented changable icons for AppleScriptTouchBarItem

AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme

* Fixed tests

Some tests started to fail after implementing alternativeIcons field for appleScriptBarItem
2020-02-29 11:23:40 +00:00
Fedor Zaytsev dbb2f16222 Implemented changable icons for AppleScriptTouchBarItem (#275)
AppleScriptTouchBarItem now allow to specify any number of icons which can be changed from the script. You cannot change icon from touch event. To change icon, you need to return array from your script with 2 values - title and icn name. More info in readme
2020-02-28 21:59:14 +00:00
Anton Palgunov 3864591777 Update build-test.yml 2020-02-28 21:27:09 +00:00
Fedor Zaytsev 7e0db70fab Updated README (#274)
Added explanation for missing parameters (background, title and image)
2020-02-28 19:35:09 +00:00
Anton Palgunov 40c684f528 GitHub actions (#271)
* build xcode
* final build test
* delete travisCI
2020-02-11 23:44:19 +00:00
Anton Palgunov 093faa2c02 Create swift.yml 2020-01-14 10:24:24 +00:00
Anton Palgunov aeee9983d1 Merge pull request #260 from samflattery/master
fix pomodoro icon not showing up on catalina
2020-01-08 14:57:23 +00:00
Sam Flattery bdfb7118c9 fix pomodoro icon not showing up on catalina 2020-01-06 19:17:29 +00:00
Anton Palgunov 878cd346c1 Merge pull request #256 from bobrosoft/master
YandexWeather: update matching array with English translations as some users getting English version instead of Russian
2019-12-30 09:18:09 +00:00
bobrosoft 712448f207 YandexWeather: update matching array with English translations as some users getting English version instead of Russian 2019-12-30 11:51:27 +04:00
toxblh e9e5a6f739 fix yandex wheather, for correct lang 2019-11-26 17:33:18 +03:00
toxblh 466c0e5f68 v0.25 2019-11-19 16:19:51 +00:00
Anton Palgunov 85e3deac79 Merge pull request #237 from ReDetection/parser-cleanup
cleanup parser
2019-11-13 15:51:53 +00:00
Anton Palgunov 9e49cf8beb Merge pull request #236 from markrickert/relative-file-paths
Relative file paths
2019-11-12 15:39:37 +00:00
Serg d81998862a cleanup parser: remove other duplicates (they are defined and parsed down below in the ItemType) 2019-11-07 12:09:04 +07:00
Mark Rickert 1ae2041d0d Get rid of all references to /Users/x/ in favor of ~ 2019-11-06 15:03:57 -07:00
Mark Rickert 5eb2d73c94 Add some image filePath documentation. 2019-11-06 14:57:05 -07:00
Mark Rickert 855ddea44e Tests relative file paths and images. 2019-11-06 14:48:26 -07:00
Mark Rickert eaa26645c5 Add ability to use file paths relative to your user directory 2019-11-06 14:48:13 -07:00
Mark Rickert 2cdb705b37 Consolidate the two String extenstions. 2019-11-06 14:43:27 -07:00
Anton Palgunov fa413f2fa2 Merge pull request #234 from ReDetection/dock-improvements
Dock improvements
2019-11-01 15:51:54 +03:00
Serg 6920664fad implement regex filter for dock items 2019-10-27 15:29:52 +07:00
Serg e9a7b6d32a get long tap to kill back into place 2019-10-27 13:01:22 +07:00
Serg dd23a3bda8 don't reload list of apps every time 2019-10-26 22:43:01 +07:00
Serg f82d7694eb rewrite in scrollview; get rid of custom gesture handling 2019-10-26 21:58:23 +07:00
Serg 94717a5ea3 missed a single userdefaults 2019-10-26 17:22:19 +07:00
Anton Palgunov b155cac2b0 Merge pull request #230 from ReDetection/settings-swift-5.1
Multitouch settings & swift 5.1
2019-10-19 15:50:15 +01:00
Serg cd2ec3d032 use property wrappers to have settings in a nice way. now it's the only place for settings key 2019-10-19 00:28:27 +07:00
Serg c4928ee382 enable/disable multitouch gestures 2019-10-19 00:28:27 +07:00
Serg 0eef872f2b Merge pull request #229 from Toxblh/ReDetection-warnings
Redetection warnings
2019-10-18 22:06:29 +07:00
toxblh 3f78bbe42c CI osx_image 11.2 -> 11.1 2019-10-18 12:18:50 +01:00
toxblh f2e6959b71 upd travis-ci 10 to 11.2 2019-10-17 18:27:44 +01:00
Serg 820853d300 little cleanup 2019-10-18 00:05:17 +07:00
Serg 2635e2611f switch to modern build system 2019-10-17 23:40:41 +07:00
Serg e68fa10c42 update swift to 5.0 2019-10-17 23:37:58 +07:00
Serg 92975cb8e4 resolve existing varnings 2019-10-17 22:47:19 +07:00
toxblh 0b39795bd7 v0.24 2019-10-12 13:24:23 +01:00
40 changed files with 1719 additions and 712 deletions
+22
View 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
View 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
-6
View File
@@ -1,6 +0,0 @@
language: swift
xcode_project: MTMR.xcodeproj
xcode_scheme: UnitTests
osx_image: xcode10
install: gem install xcpretty
script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}"
+30 -6
View File
@@ -18,9 +18,12 @@
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; }; 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; }; 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; }; 36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */; };
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 */; };
@@ -66,6 +69,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 */
@@ -94,10 +100,13 @@
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; }; 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; }; 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; }; 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; }; 4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
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>"; };
@@ -153,6 +162,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 */
@@ -225,10 +237,13 @@
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */, 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
B082B252205C7D8000BC04DC /* AppDelegate.swift */, B082B252205C7D8000BC04DC /* AppDelegate.swift */,
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */, B0A7E9A9205D6AA400EEF070 /* KeyPress.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 */,
@@ -255,6 +270,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 */,
@@ -307,6 +324,7 @@
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
); );
path = Widgets; path = Widgets;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -362,7 +380,7 @@
TargetAttributes = { TargetAttributes = {
B082B24E205C7D8000BC04DC = { B082B24E205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
ProvisioningStyle = Manual; ProvisioningStyle = Manual;
SystemCapabilities = { SystemCapabilities = {
com.apple.Sandbox = { com.apple.Sandbox = {
@@ -372,7 +390,7 @@
}; };
B082B260205C7D8000BC04DC = { B082B260205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
}; };
}; };
}; };
@@ -404,6 +422,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 */,
@@ -454,11 +474,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 */,
@@ -469,11 +491,13 @@
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 */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */, B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */,
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */, 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */, 36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */, 60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
@@ -640,7 +664,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -663,7 +687,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -676,7 +700,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -691,7 +715,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict/>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist> </plist>
+26 -28
View File
@@ -42,21 +42,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillTerminate(_: Notification) {} func applicationWillTerminate(_: Notification) {}
func HapticFeedbackUpdate() { func HapticFeedbackUpdate() {
HapticFeedback.shared = TouchBarController.shared.hapticFeedbackState ? HapticFeedback() : nil HapticFeedback.shared = AppSettings.hapticFeedbackState ? HapticFeedback() : nil
} }
@objc func updateIsBlockedApp() { @objc func updateIsBlockedApp() {
var blacklistAppIdentifiers: [String] = [] if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") { isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
blacklistAppIdentifiers = blackListed
}
var frontmostApplicationIdentifier: String? {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
return frontmostId
}
if frontmostApplicationIdentifier != nil {
isBlockedApp = blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil
} else { } else {
isBlockedApp = false isBlockedApp = false
} }
@@ -72,33 +63,36 @@ class AppDelegate: NSObject, NSApplicationDelegate {
task.launch() task.launch()
} }
@objc func toggleControlStrip(_: Any?) { @objc func toggleControlStrip(_ item: NSMenuItem) {
TouchBarController.shared.showControlStripState = !TouchBarController.shared.showControlStripState item.state = item.state == .on ? .off : .on
AppSettings.showControlStripState = item.state == .off
TouchBarController.shared.resetControlStrip() TouchBarController.shared.resetControlStrip()
createMenu()
} }
@objc func toggleBlackListedApp(_: Any?) { @objc func toggleBlackListedApp(_: Any?) {
let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier if let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier {
if appIdentifier != nil { if let index = TouchBarController.shared.blacklistAppIdentifiers.firstIndex(of: appIdentifier) {
if let index = TouchBarController.shared.blacklistAppIdentifiers.index(of: appIdentifier!) {
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index) TouchBarController.shared.blacklistAppIdentifiers.remove(at: index)
} else { } else {
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier!) TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier)
} }
UserDefaults.standard.set(TouchBarController.shared.blacklistAppIdentifiers, forKey: "com.toxblh.mtmr.blackListedApps") AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers
UserDefaults.standard.synchronize()
TouchBarController.shared.updateActiveApp() TouchBarController.shared.updateActiveApp()
updateIsBlockedApp() updateIsBlockedApp()
} }
} }
@objc func toggleHapticFeedback(_: Any?) { @objc func toggleHapticFeedback(_ item: NSMenuItem) {
TouchBarController.shared.hapticFeedbackState = !TouchBarController.shared.hapticFeedbackState item.state = item.state == .on ? .off : .on
AppSettings.hapticFeedbackState = item.state == .on
HapticFeedbackUpdate() HapticFeedbackUpdate()
createMenu() }
@objc func toggleMultitouch(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.multitouchGestures = item.state == .on
TouchBarController.shared.basicView?.legacyGesturesEnabled = item.state == .on
} }
@objc func openPreset(_: Any?) { @objc func openPreset(_: Any?) {
@@ -133,10 +127,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
toggleBlackList.state = isBlockedApp ? .on : .off toggleBlackList.state = isBlockedApp ? .on : .off
let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T") let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
hideControlStrip.state = TouchBarController.shared.showControlStripState ? .off : .on hideControlStrip.state = AppSettings.showControlStripState ? .off : .on
let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H") let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H")
hapticFeedback.state = TouchBarController.shared.hapticFeedbackState ? .on : .off hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off
let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "")
multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off
let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "") let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
settingSeparator.isEnabled = false settingSeparator.isEnabled = false
@@ -151,6 +148,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(hideControlStrip) menu.addItem(hideControlStrip)
menu.addItem(toggleBlackList) menu.addItem(toggleBlackList)
menu.addItem(startAtLogin) menu.addItem(startAtLogin)
menu.addItem(multitouchGestures)
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu statusItem.menu = menu
+33
View File
@@ -0,0 +1,33 @@
import Foundation
struct AppSettings {
@UserDefault(key: "com.toxblh.mtmr.settings.showControlStrip", defaultValue: false)
static var showControlStripState: Bool
@UserDefault(key: "com.toxblh.mtmr.settings.hapticFeedback", defaultValue: true)
static var hapticFeedbackState: Bool
@UserDefault(key: "com.toxblh.mtmr.settings.multitouchGestures", defaultValue: true)
static var multitouchGestures: Bool
@UserDefault(key: "com.toxblh.mtmr.blackListedApps", defaultValue: [])
static var blacklistedAppIds: [String]
@UserDefault(key: "com.toxblh.mtmr.dock.persistent", defaultValue: [])
static var dockPersistentAppIds: [String]
}
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
}
+25 -1
View File
@@ -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
View 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
View 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 ""
+4
View File
@@ -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
View 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 {
GenericKeyPress(keyCode: CGKeyCode(144)).send()
} else if position < prevPos {
GenericKeyPress(keyCode: CGKeyCode(145)).send()
}
legacyPrevPositions[fingers] = position
}
}
}
case .ended:
print("gesture ended \(position - prevPositions[fingers]!) \(fingers)")
for item in swipeItems {
item.processEvent(offset: position - prevPositions[fingers]!, fingers: fingers)
}
default:
break
}
}
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
}
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 3, state: sender!.state)
}
@objc func fourfingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 4, state: sender!.state)
}
}
+119 -23
View File
@@ -8,17 +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: ()->() = {}
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
@@ -30,10 +45,17 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
longClick.isEnabled = false longClick.isEnabled = false
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
@@ -99,32 +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()
} }
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
@@ -174,20 +207,83 @@ 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)
} }
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
} }
} }
class LongPressGestureRecognizer: NSPressGestureRecognizer { class LongPressGestureRecognizer: NSPressGestureRecognizer {
private let recognizeTimeout = 0.4 var recognizeTimeout = 0.4
private var timer: Timer? private var timer: Timer?
override func touchesBegan(with event: NSEvent) { override func touchesBegan(with event: NSEvent) {
+3 -3
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.23.1</string> <string>0.26.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>294</string> <string>425</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>
+244 -214
View File
@@ -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,15 +56,21 @@ 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)]
) }, ) },
"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: [:]
) }, ) },
@@ -67,8 +78,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: .keyPress(keycode: 144))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter] parameters: [.image: imageParameter]
) )
}, },
@@ -77,8 +91,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: .keyPress(keycode: 145))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter] parameters: [.image: imageParameter]
) )
}, },
@@ -87,8 +104,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]
) )
}, },
@@ -97,8 +117,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]
) )
}, },
@@ -107,8 +130,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]
) )
}, },
@@ -117,8 +143,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]
) )
}, },
@@ -127,8 +156,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]
) )
}, },
@@ -137,8 +169,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]
) )
}, },
@@ -147,8 +182,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]
) )
}, },
@@ -157,169 +195,45 @@ 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]
) )
}, },
"weather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key; case icon_type }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let units = try container.decodeIfPresent(String.self, forKey: .units)
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key)
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"),
action,
longAction,
parameters: [:]
)
},
"yandexWeather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .yandexWeather(interval: interval ?? 1800.00),
action,
longAction,
parameters: [:]
)
},
"currency": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to; case full }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let from = try container.decodeIfPresent(String.self, forKey: .from)
let to = try container.decodeIfPresent(String.self, forKey: .to)
let full = try container.decodeIfPresent(Bool.self, forKey: .full)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD", full: full ?? false),
action,
longAction,
parameters: [:]
)
},
"dock": { decoder in
enum CodingKeys: String, CodingKey { case autoResize }
let container = try decoder.container(keyedBy: CodingKeys.self)
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
return (
item: .dock(autoResize: autoResize),
action: .none,
longAction: .none,
parameters: [:]
)
},
"inputsource": { _ in
(
item: .inputsource(),
action: .none,
longAction: .none,
parameters: [:]
)
},
"volume": { decoder in
enum CodingKeys: String, CodingKey { case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (
item: .volume(),
action: .none,
longAction: .none,
parameters: [.image: .image(source: img)]
)
} else {
return (
item: .volume(),
action: .none,
longAction: .none,
parameters: [:]
)
}
},
"brightness": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (
item: .brightness(refreshInterval: interval ?? 0.5),
action: .none,
longAction: .none,
parameters: [.image: .image(source: img)]
)
} else {
return (
item: .brightness(refreshInterval: interval ?? 0.5),
action: .none,
longAction: .none,
parameters: [:]
)
}
},
"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: [:]
) }, ) },
"music": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee)
return (
item: .music(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
action: .none,
longAction: .none,
parameters: [:]
)
},
"group": { decoder in
enum CodingKeys: CodingKey { case items }
let container = try decoder.container(keyedBy: CodingKeys.self)
let items = try container.decode([BarItemDefinition].self, forKey: .items)
return (
item: .groupBar(items: items),
action: .none,
longAction: .none,
parameters: [:]
)
},
] ]
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: [:]
) } ) }
} }
@@ -328,12 +242,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: [:]
) )
} }
@@ -342,24 +257,26 @@ 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
case dock(autoResize: Bool) case dock(autoResize: Bool, filter: String?)
case volume() case volume
case brightness(refreshInterval: Double) case brightness(refreshInterval: Double)
case weather(interval: Double, units: String, api_key: String, icon_type: String) case weather(interval: Double, units: String, api_key: String, icon_type: String)
case yandexWeather(interval: Double) case yandexWeather(interval: Double)
case currency(interval: Double, from: String, to: String, full: Bool) case currency(interval: Double, from: String, to: String, full: Bool)
case inputsource() case inputsource
case music(interval: Double, disableMarquee: Bool) case music(interval: Double, disableMarquee: Bool)
case groupBar(items: [BarItemDefinition]) case group(items: [BarItemDefinition])
case nightShift() case nightShift
case dnd() case dnd
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
@@ -383,7 +300,15 @@ enum ItemType: Decodable {
case restTime case restTime
case flip case flip
case autoResize case autoResize
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 {
@@ -400,12 +325,14 @@ enum ItemType: Decodable {
case currency case currency
case inputsource case inputsource
case music case music
case groupBar case group
case nightShift case nightShift
case dnd case dnd
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 {
@@ -415,7 +342,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)
@@ -433,14 +361,15 @@ enum ItemType: Decodable {
self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale) self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery: case .battery:
self = .battery() self = .battery
case .dock: case .dock:
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
self = .dock(autoResize: autoResize) let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter)
self = .dock(autoResize: autoResize, filter: filterRegexString)
case .volume: case .volume:
self = .volume() self = .volume
case .brightness: case .brightness:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
@@ -465,22 +394,22 @@ enum ItemType: Decodable {
self = .currency(interval: interval, from: from, to: to, full: full) self = .currency(interval: interval, from: from, to: to, full: full)
case .inputsource: case .inputsource:
self = .inputsource() self = .inputsource
case .music: case .music:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false
self = .music(interval: interval, disableMarquee: disableMarquee) self = .music(interval: interval, disableMarquee: disableMarquee)
case .groupBar: case .group:
let items = try container.decode([BarItemDefinition].self, forKey: .items) let items = try container.decode([BarItemDefinition].self, forKey: .items)
self = .groupBar(items: items) self = .group(items: items)
case .nightShift: case .nightShift:
self = .nightShift() self = .nightShift
case .dnd: case .dnd:
self = .dnd() self = .dnd
case .pomodoro: case .pomodoro:
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0 let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0
@@ -492,12 +421,115 @@ enum ItemType: Decodable {
self = .network(flip: flip) self = .network(flip: flip)
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)
@@ -555,7 +587,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)
@@ -734,12 +766,20 @@ extension String {
} }
var fileData: Data? { var fileData: Data? {
return try? Data(contentsOf: URL(fileURLWithPath: self)) return try? Data(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath))
} }
var fileString: String? { var fileString: String? {
var encoding: String.Encoding = .utf8 var encoding: String.Encoding = .utf8
return try? String(contentsOfFile: self, usedEncoding: &encoding) return try? String(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath), usedEncoding: &encoding)
}
var fileURL: URL {
return URL(fileURLWithPath: (self as NSString).expandingTildeInPath)
}
var appleScript: NSAppleScript? {
return NSAppleScript(source: self)
} }
} }
@@ -759,16 +799,6 @@ enum Align: String, Decodable {
case right case right
} }
extension String {
var fileURL: URL {
return URL(fileURLWithPath: self)
}
var appleScript: NSAppleScript? {
return NSAppleScript(source: self)
}
}
extension URL { extension URL {
var appleScript: NSAppleScript? { var appleScript: NSAppleScript? {
guard FileManager.default.fileExists(atPath: path) else { return nil } guard FileManager.default.fileExists(atPath: path) else { return nil }
+1 -56
View File
@@ -1,8 +1,6 @@
import Foundation import Foundation
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
var twofingersPrev: CGFloat = 0.0
var threefingersPrev: CGFloat = 0.0
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) { init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
super.init(identifier: identifier) super.init(identifier: identifier)
@@ -13,63 +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
let twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
twofingers.allowedTouchTypes = .direct
twofingers.numberOfTouchesRequired = 2
view.addGestureRecognizer(twofingers)
let threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
threefingers.allowedTouchTypes = .direct
threefingers.numberOfTouchesRequired = 3
view.addGestureRecognizer(threefingers)
} }
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
}
}
} }
+8
View File
@@ -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
View 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)")
}
}
}
}
}
+145 -73
View File
@@ -27,11 +27,11 @@ extension ItemType {
return "com.toxblh.mtmr.shellScriptButton." return "com.toxblh.mtmr.shellScriptButton."
case .timeButton(formatTemplate: _, timeZone: _, locale: _): case .timeButton(formatTemplate: _, timeZone: _, locale: _):
return "com.toxblh.mtmr.timeButton." return "com.toxblh.mtmr.timeButton."
case .battery(): case .battery:
return "com.toxblh.mtmr.battery." return "com.toxblh.mtmr.battery."
case .dock(autoResize: _): case .dock(autoResize: _, filter: _):
return "com.toxblh.mtmr.dock" return "com.toxblh.mtmr.dock"
case .volume(): case .volume:
return "com.toxblh.mtmr.volume" return "com.toxblh.mtmr.volume"
case .brightness(refreshInterval: _): case .brightness(refreshInterval: _):
return "com.toxblh.mtmr.brightness" return "com.toxblh.mtmr.brightness"
@@ -41,22 +41,26 @@ extension ItemType {
return "com.toxblh.mtmr.yandexWeather" return "com.toxblh.mtmr.yandexWeather"
case .currency(interval: _, from: _, to: _, full: _): case .currency(interval: _, from: _, to: _, full: _):
return "com.toxblh.mtmr.currency" return "com.toxblh.mtmr.currency"
case .inputsource(): case .inputsource:
return "com.toxblh.mtmr.inputsource." return "com.toxblh.mtmr.inputsource."
case .music(interval: _): case .music(interval: _):
return "com.toxblh.mtmr.music." return "com.toxblh.mtmr.music."
case .groupBar(items: _): case .group(items: _):
return "com.toxblh.mtmr.groupBar." return "com.toxblh.mtmr.groupBar."
case .nightShift(items: _): case .nightShift:
return "com.toxblh.mtmr.nightShift." return "com.toxblh.mtmr.nightShift."
case .dnd(items: _): case .dnd:
return "com.toxblh.mtmr.dnd." return "com.toxblh.mtmr.dnd."
case .pomodoro(interval: _): case .pomodoro(interval: _):
return PomodoroBarItem.identifier return PomodoroBarItem.identifier
case .network(flip: _): case .network(flip: _):
return NetworkBarItem.identifier return NetworkBarItem.identifier
case .darkMode(items: _): 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."
} }
} }
} }
@@ -76,49 +80,43 @@ 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: NSCustomTouchBarItem? 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 showControlStripState: Bool {
get {
return UserDefaults.standard.bool(forKey: "com.toxblh.mtmr.settings.showControlStrip")
}
set {
UserDefaults.standard.set(newValue, forKey: "com.toxblh.mtmr.settings.showControlStrip")
}
}
var hapticFeedbackState: Bool {
get {
return UserDefaults.standard.bool(forKey: "com.toxblh.mtmr.settings.hapticFeedback")
}
set {
UserDefaults.standard.set(newValue, forKey: "com.toxblh.mtmr.settings.hapticFeedback")
}
}
var blacklistAppIdentifiers: [String] = [] var blacklistAppIdentifiers: [String] = []
var frontmostApplicationIdentifier: String? { var frontmostApplicationIdentifier: String? {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil } return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
return frontmostId
} }
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 (
guard let `self` = self else { return } item: .staticButton(title: ""),
self.reloadPreset(path: self.lastPresetPath) actions: [
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)]) Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
guard let `self` = self else { return }
self.reloadPreset(path: self.lastPresetPath)
}))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
} }
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") { blacklistAppIdentifiers = AppSettings.blacklistedAppIds
blacklistAppIdentifiers = blackListed
}
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
@@ -135,23 +133,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)
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()
} }
@@ -161,7 +165,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
func updateActiveApp() { func updateActiveApp() {
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil { if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
dismissTouchBar() dismissTouchBar()
} else { } else {
presentTouchBar() presentTouchBar()
@@ -181,7 +185,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)
} }
@@ -207,7 +211,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
}
} }
} }
@@ -224,7 +233,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
@objc private func presentTouchBar() { @objc private func presentTouchBar() {
if showControlStripState { if AppSettings.showControlStripState {
updateControlStripPresence() updateControlStripPresence()
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else { } else {
@@ -243,16 +252,11 @@ 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], return nil
let definition = self.itemDefinitions[identifier],
definition.align != .center else {
return nil
}
return item
} }
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? { func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
@@ -260,16 +264,24 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
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):
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale) barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery(): case .battery:
barItem = BatteryBarItem(identifier: identifier) barItem = BatteryBarItem(identifier: identifier)
case let .dock(autoResize: autoResize): case let .dock(autoResize: autoResize, filter: regexString):
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize) if let regexString = regexString {
guard let regex = try? NSRegularExpression(pattern: regexString, options: []) else {
barItem = CustomButtonTouchBarItem(identifier: identifier, title: "Bad regex")
break
}
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize, filter: regex)
} else {
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize)
}
case .volume: case .volume:
if case let .image(source)? = item.additionalParameters[.image] { if case let .image(source)? = item.additionalParameters[.image] {
barItem = VolumeViewController(identifier: identifier, image: source.image) barItem = VolumeViewController(identifier: identifier, image: source.image)
@@ -288,29 +300,39 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
barItem = YandexWeatherBarItem(identifier: identifier, interval: interval) barItem = YandexWeatherBarItem(identifier: identifier, interval: interval)
case let .currency(interval: interval, from: from, to: to, full: full): case let .currency(interval: interval, from: from, to: to, full: full):
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full) barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full)
case .inputsource(): case .inputsource:
barItem = InputSourceBarItem(identifier: identifier) barItem = InputSourceBarItem(identifier: identifier)
case let .music(interval: interval, disableMarquee: disableMarquee): case let .music(interval: interval, disableMarquee: disableMarquee):
barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee) barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee)
case let .groupBar(items: items): case let .group(items: items):
barItem = GroupBarItem(identifier: identifier, items: items) barItem = GroupBarItem(identifier: identifier, items: items)
case .nightShift(): case .nightShift:
barItem = NightShiftBarItem(identifier: identifier) barItem = NightShiftBarItem(identifier: identifier)
case .dnd(): case .dnd:
barItem = DnDBarItem(identifier: identifier) barItem = DnDBarItem(identifier: identifier)
case let .pomodoro(workTime: workTime, restTime: restTime): case let .pomodoro(workTime: workTime, restTime: restTime):
barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime) barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime)
case let .network(flip: flip): case let .network(flip: flip):
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
@@ -333,9 +355,53 @@ 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):
@@ -379,7 +445,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):
@@ -431,6 +497,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] {
+145 -168
View File
@@ -7,238 +7,158 @@
import Cocoa import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource { class AppScrubberTouchBarItem: NSCustomTouchBarItem {
private var scrubber: NSScrubber! private var scrollView = NSScrollView()
private var timer: Timer!
private var ticks: Int = 0
private let minTicks: Int = 5
private let maxTicks: Int = 20
private var lastSelected: Int = 0
private var autoResize: Bool = false private var autoResize: Bool = false
private var widthConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint?
private let filter: NSRegularExpression?
private var persistentAppIdentifiers: [String] = [] private var persistentAppIdentifiers: [String] = []
private var runningAppsIdentifiers: [String] = [] private var runningAppsIdentifiers: [String] = []
private var frontmostApplicationIdentifier: String? { private var frontmostApplicationIdentifier: String? {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil } return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
return frontmostId
} }
private var applications: [DockItem] = [] private var applications: [DockItem] = []
private var items: [DockBarItem] = []
convenience override init(identifier: NSTouchBarItem.Identifier) {
self.init(identifier: identifier, autoResize: false)
}
static var iconWidth = 36
static var spacingWidth = 2
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) { init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false, filter: NSRegularExpression? = nil) {
self.filter = filter
super.init(identifier: identifier) super.init(identifier: identifier)
self.autoResize = autoResize self.autoResize = autoResize
view = scrollView
scrubber = NSScrubber()
scrubber.delegate = self
scrubber.dataSource = self
scrubber.mode = .free // .fixed
let layout = NSScrubberFlowLayout()
layout.itemSize = NSSize(width: AppScrubberTouchBarItem.iconWidth, height: 32)
layout.itemSpacing = CGFloat(AppScrubberTouchBarItem.spacingWidth)
scrubber.scrubberLayout = layout
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.showsAdditionalContentIndicators = true
view = scrubber NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(softReloadItems), name: NSWorkspace.didActivateApplicationNotification, object: nil)
scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier")) persistentAppIdentifiers = AppSettings.dockPersistentAppIds
hardReloadItems()
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
if let persistent = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.dock.persistent") {
persistentAppIdentifiers = persistent
}
updateRunningApplication()
} }
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func activeApplicationChanged(n _: Notification) { @objc func hardReloadItems() {
updateRunningApplication() applications = launchedApplications()
}
override func observeValue(forKeyPath _: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
updateRunningApplication()
}
func updateRunningApplication() {
let newApplications = launchedApplications()
let index = newApplications.index {
$0.bundleIdentifier == frontmostApplicationIdentifier
}
applications = newApplications
applications += getDockPersistentAppsList() applications += getDockPersistentAppsList()
scrubber.reloadData() reloadData()
softReloadItems()
updateSize() updateSize()
}
scrubber.selectedIndex = index ?? 0
@objc func softReloadItems() {
let frontMostAppId = self.frontmostApplicationIdentifier
let runningAppsIds = NSWorkspace.shared.runningApplications.map { $0.bundleIdentifier }
for barItem in items {
let bundleId = barItem.dockItem.bundleIdentifier
barItem.isRunning = runningAppsIds.contains(bundleId)
barItem.isFrontmost = frontMostAppId == bundleId
}
} }
func updateSize() { func updateSize() {
if self.autoResize { if self.autoResize {
if let constraint: NSLayoutConstraint = self.widthConstraint { self.widthConstraint?.isActive = false
constraint.isActive = false
self.scrubber.removeConstraint(constraint) let width = self.scrollView.documentView?.fittingSize.width ?? 0
} self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth
self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width))
self.widthConstraint!.isActive = true self.widthConstraint!.isActive = true
} }
} }
public func numberOfItems(for _: NSScrubber) -> Int { func reloadData() {
return applications.count items = applications.map { self.createAppButton(for: $0) }
let stackView = NSStackView(views: items.compactMap { $0.view })
stackView.spacing = 1
stackView.orientation = .horizontal
let visibleRect = self.scrollView.documentVisibleRect
scrollView.documentView = stackView
stackView.scroll(visibleRect.origin)
} }
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { public func createAppButton(for app: DockItem) -> DockBarItem {
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() let item = DockBarItem(app)
item.imageView.imageScaling = .scaleProportionallyDown item.isBordered = false
item.actions.append(contentsOf: [
let app = applications[index] ItemAction(trigger: .singleTap) { [weak self] in
self?.switchToApp(app: app)
if let icon = app.icon { },
item.image = icon ItemAction(trigger: .longTap) { [weak self] in
item.image.size = NSSize(width: 26, height: 26) self?.handleHalfLongPress(item: app)
}
])
item.killAppClosure = {[weak self] in
self?.handleLongPress(item: app)
} }
item.removeFromSuperview()
let dotView = NSView(frame: .zero)
dotView.wantsLayer = true
if runningAppsIdentifiers.contains(app.bundleIdentifier!) {
dotView.layer?.backgroundColor = NSColor.white.cgColor
} else {
dotView.layer?.backgroundColor = NSColor.black.cgColor
}
dotView.layer?.cornerRadius = 1.5
dotView.setFrameOrigin(NSPoint(x: 17, y: 1))
dotView.frame.size = NSSize(width: 3, height: 3)
item.addSubview(dotView)
return item return item
} }
public func didBeginInteracting(with _: NSScrubber) { public func switchToApp(app: DockItem) {
stopTimer() let bundleIdentifier = app.bundleIdentifier
ticks = 0
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true)
}
@objc private func checkTimer() {
ticks += 1
if ticks == minTicks {
HapticFeedback.shared?.tap(strong: 2)
}
if ticks > maxTicks {
stopTimer()
HapticFeedback.shared?.tap(strong: 6)
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
lastSelected = 0
}
public func didCancelInteracting(with _: NSScrubber) {
stopTimer()
}
public func didFinishInteracting(with scrubber: NSScrubber) {
stopTimer()
if ticks == 0 {
return
}
if ticks >= minTicks && scrubber.selectedIndex > 0 {
longPress(selected: scrubber.selectedIndex)
return
}
let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier
if bundleIdentifier!.contains("file://") { if bundleIdentifier!.contains("file://") {
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: "")) NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
} else { } else {
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
HapticFeedback.shared?.tap(strong: 6)
} }
updateRunningApplication() softReloadItems()
// NB: if you can't open app which on another space, try to check mark // 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" // "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings // in Mission control settings
} }
private func longPress(selected: Int) { //todo
let bundleIdentifier = applications[selected].bundleIdentifier private func handleLongPress(item: DockItem) {
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
if ticks > maxTicks { if !app.terminate() {
if let processIdentifier = applications[selected].pid { app.forceTerminate()
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
}
} }
} else { hardReloadItems()
HapticFeedback.shared?.tap(strong: 6)
if let index = self.persistentAppIdentifiers.index(of: bundleIdentifier!) {
persistentAppIdentifiers.remove(at: index)
} else {
persistentAppIdentifiers.append(bundleIdentifier!)
}
UserDefaults.standard.set(persistentAppIdentifiers, forKey: "com.toxblh.mtmr.dock.persistent")
UserDefaults.standard.synchronize()
} }
ticks = 0
updateRunningApplication()
} }
private func handleHalfLongPress(item: DockItem) {
if let index = self.persistentAppIdentifiers.firstIndex(of: item.bundleIdentifier) {
persistentAppIdentifiers.remove(at: index)
hardReloadItems()
} else {
persistentAppIdentifiers.append(item.bundleIdentifier)
}
AppSettings.dockPersistentAppIds = persistentAppIdentifiers
}
private func launchedApplications() -> [DockItem] { private func launchedApplications() -> [DockItem] {
runningAppsIdentifiers = [] runningAppsIdentifiers = []
var returnable: [DockItem] = [] var returnable: [DockItem] = []
for app in NSWorkspace.shared.runningApplications { for app in NSWorkspace.shared.runningApplications {
guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue } guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue }
guard let bundleIdentifier = app.bundleIdentifier else { continue } guard let bundleIdentifier = app.bundleIdentifier else { continue }
if let filter = self.filter,
let name = app.localizedName,
filter.numberOfMatches(in: name, options: [], range: NSRange(location: 0, length: name.count)) == 0 {
continue
}
runningAppsIdentifiers.append(bundleIdentifier) runningAppsIdentifiers.append(bundleIdentifier)
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier) let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
returnable.append(dockItem) returnable.append(dockItem)
} }
return returnable return returnable
} }
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType _: String? = nil) -> NSImage { public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage {
if bundleIdentifier != nil { if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) {
if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) { return NSWorkspace.shared.icon(forFile: appPath)
return NSWorkspace.shared.icon(forFile: appPath)
}
} }
if path != nil { if let path = path {
return NSWorkspace.shared.icon(forFile: path!) return NSWorkspace.shared.icon(forFile: path)
} }
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns") let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
@@ -269,3 +189,60 @@ public class DockItem: NSObject {
self.pid = pid self.pid = pid
} }
} }
private let iconWidth = 32.0
class DockBarItem: CustomButtonTouchBarItem {
let dotView = NSView(frame: .zero)
let dockItem: DockItem
fileprivate var killGestureRecognizer: LongPressGestureRecognizer!
var killAppClosure: () -> Void = { }
var isRunning = false {
didSet {
redrawDotView()
}
}
var isFrontmost = false {
didSet {
redrawDotView()
}
}
init(_ app: DockItem) {
self.dockItem = app
super.init(identifier: .init(app.bundleIdentifier), title: "")
dotView.wantsLayer = true
image = app.icon
image?.size = NSSize(width: iconWidth, height: iconWidth)
killGestureRecognizer = LongPressGestureRecognizer(target: self, action: #selector(firePanGestureRecognizer))
killGestureRecognizer.allowedTouchTypes = .direct
killGestureRecognizer.recognizeTimeout = 1.5
killGestureRecognizer.minimumPressDuration = 1.5
killGestureRecognizer.isEnabled = isRunning
self.finishViewConfiguration = { [weak self] in
guard let selfie = self else { return }
selfie.dotView.layer?.cornerRadius = 1.5
selfie.view.addSubview(selfie.dotView)
selfie.redrawDotView()
selfie.view.addGestureRecognizer(selfie.killGestureRecognizer)
}
}
func redrawDotView() {
dotView.layer?.backgroundColor = isRunning ? NSColor.white.cgColor : NSColor.clear.cgColor
dotView.frame.size = NSSize(width: isFrontmost ? iconWidth - 14 : 3, height: 3)
dotView.setFrameOrigin(NSPoint(x: 18.0 - Double(dotView.frame.size.width) / 2.0, y: iconWidth - 5))
}
@objc func firePanGestureRecognizer() {
self.killAppClosure()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+33 -2
View File
@@ -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)
} }
+2 -2
View File
@@ -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)
@@ -47,7 +47,7 @@ struct DarkMode {
static func toggle(force: Bool? = nil) { static func toggle(force: Bool? = nil) {
let value = force.map(String.init) ?? "not dark mode" let value = force.map(String.init) ?? "not dark mode"
runAppleScript("\(prefix) set dark mode to \(value)") _ = runAppleScript("\(prefix) set dark mode to \(value)")
} }
} }
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -52,7 +52,7 @@ class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate {
TouchBarController.shared.touchBar.defaultItemIdentifiers = [] TouchBarController.shared.touchBar.defaultItemIdentifiers = []
TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
if TouchBarController.shared.showControlStripState { if AppSettings.showControlStripState {
presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem)
} else { } else {
presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
+3 -2
View File
@@ -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) {
+68 -4
View File
@@ -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,
@@ -39,9 +41,12 @@ 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!()
@@ -166,6 +180,31 @@ class MusicBarItem: CustomButtonTouchBarItem {
} }
} }
} }
@objc func previousTrack() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .Music {
let mp = (musicPlayer as MusicApplication)
mp.previousTrack!()
updatePlayer()
return
}
}
}
}
}
func refreshAndSchedule() { func refreshAndSchedule() {
DispatchQueue.main.async { DispatchQueue.main.async {
@@ -180,7 +219,7 @@ class MusicBarItem: CustomButtonTouchBarItem {
var iconUpdated = false var iconUpdated = false
var titleUpdated = false var titleUpdated = false
for var ident in playerBundleIdentifiers { for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) { if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning { if musicPlayer.isRunning {
var tempTitle = "" var tempTitle = ""
@@ -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()
+6 -4
View File
@@ -31,7 +31,7 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
var dSpeed: UInt64? var dSpeed: UInt64?
var uSpeed: UInt64? var uSpeed: UInt64?
var curr: Array<Substring>? var curr: Array<Substring>?
var dataAvailable: NSObjectProtocol! var dataAvailable: NSObjectProtocol?
pipe = Pipe() pipe = Pipe()
bandwidthProcess = Process() bandwidthProcess = Process()
@@ -64,19 +64,21 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
} }
} }
outputHandle.waitForDataInBackgroundAndNotify() outputHandle.waitForDataInBackgroundAndNotify()
} else { } else if let dataAvailable = dataAvailable {
NotificationCenter.default.removeObserver(dataAvailable) NotificationCenter.default.removeObserver(dataAvailable)
} }
} }
var dataReady: NSObjectProtocol! var dataReady: NSObjectProtocol?
dataReady = NotificationCenter.default.addObserver( dataReady = NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification, forName: Process.didTerminateNotification,
object: outputHandle, object: outputHandle,
queue: nil queue: nil
) { _ -> Void in ) { _ -> Void in
print("Task terminated!") print("Task terminated!")
NotificationCenter.default.removeObserver(dataReady) if let observer = dataReady {
NotificationCenter.default.removeObserver(observer)
}
} }
bandwidthProcess?.launch() bandwidthProcess?.launch()
+2 -2
View File
@@ -30,8 +30,8 @@ class NightShiftBarItem: CustomButtonTouchBarItem {
super.init(identifier: identifier, title: "") super.init(identifier: identifier, title: "")
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)
+8 -5
View File
@@ -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: [:]
) )
} }
@@ -35,7 +36,7 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
case none case none
} }
private let defaultTitle = "🍅" private let defaultTitle = "🍅 "
private let workTime: TimeInterval private let workTime: TimeInterval
private let restTime: TimeInterval private let restTime: TimeInterval
private var typeTime: TimeTypes = .none private var typeTime: TimeTypes = .none
@@ -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) {
@@ -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
}
}
+38 -28
View File
@@ -12,28 +12,32 @@ 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")
activity.interval = interval activity.interval = interval
super.init(identifier: identifier, title: "⏳") super.init(identifier: identifier, title: "⏳")
let status = CLLocationManager.authorizationStatus() let status = CLLocationManager.authorizationStatus()
if status == .restricted || status == .denied { if status == .restricted || status == .denied {
print("User permission not given") print("User permission not given")
return return
} }
if !CLLocationManager.locationServicesEnabled() { if !CLLocationManager.locationServicesEnabled() {
print("Location services not enabled") print("Location services not enabled")
return return
} }
activity.repeats = true activity.repeats = true
activity.qualityOfService = .utility activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
@@ -41,70 +45,76 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
completion(NSBackgroundActivityScheduler.Result.finished) completion(NSBackgroundActivityScheduler.Result.finished)
} }
updateWeather() updateWeather()
manager = CLLocationManager() manager = CLLocationManager()
manager.delegate = self manager.delegate = self
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) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func updateWeather() { @objc func updateWeather() {
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
} }
// print(response) // print(response)
var matches: [[String]] var matches: [[String]]
var temperature: String? var temperature: String?
matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<") matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<")
temperature = matches.first?.item(at: 1) temperature = matches.first?.item(at: 1)
var icon: String? var icon: String?
matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<") matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<")
icon = matches.first?.item(at: 1) icon = matches.first?.item(at: 1)
if let _ = icon, let test = self.iconsSource[icon!] { if let _ = icon, let test = self.iconsSource[icon!] {
icon = test icon = test
} }
if temperature != nil { if temperature != nil {
DispatchQueue.main.async { DispatchQueue.main.async {
self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)") self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)")
} }
} }
} }
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)" return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&lang=ru"
} else { } else {
return "https://yandex.ru/pogoda/" // 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
} }
} }
func setWeather(text: String) { func setWeather(text: String) {
title = text title = text
} }
func defaultTapAction() { func defaultTapAction() {
print(getWeatherUrl()) print(getWeatherUrl())
if let url = URL(string: getWeatherUrl()) { if let url = URL(string: getWeatherUrl()) {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last! let lastLocation = locations.last!
location = lastLocation location = lastLocation
@@ -113,15 +123,15 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
} }
prevLocation = lastLocation prevLocation = lastLocation
} }
func locationManager(_: CLLocationManager, didFailWithError error: Error) { func locationManager(_: CLLocationManager, didFailWithError error: Error) {
print(error) print(error)
} }
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) { func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) {
updateWeather() updateWeather()
} }
deinit { deinit {
activity.invalidate() activity.invalidate()
} }
@@ -131,9 +141,9 @@ extension String {
func matchingStrings(regex: String) -> [[String]] { func matchingStrings(regex: String) -> [[String]] {
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
let nsString = self as NSString let nsString = self as NSString
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in return results.map { result in
(0..<result.numberOfRanges).map { (0 ..< result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0)) ? nsString.substring(with: result.range(at: $0))
: "" : ""
+20
View File
@@ -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",
+30 -3
View File
@@ -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
} }
@@ -26,12 +26,39 @@ class AppleScriptDefinitionTests: XCTestCase {
XCTAssertEqual(sourceStruct?.filePath, "/ololo/pew") XCTAssertEqual(sourceStruct?.filePath, "/ololo/pew")
} }
// This tests that users can pass paths to files with ~ in them
func testUserPath() {
let buttonNoActionFixture = """
[ { "type": "appleScriptTitledButton", "source": { "filePath": "~/pew" } } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else {
XCTFail()
return
}
let sourceStruct = source as? Source
// gives you a string in the format of file:///Users/your_uer_name/pew
let expandedPath = URL(fileURLWithPath: NSString("~/pew").expandingTildeInPath) as URL
XCTAssertEqual(sourceStruct?.filePath?.fileURL, expandedPath)
}
// This tests that users can pass paths to images with ~ in them
func testUserImagePath() {
let relativeImagePath = """
[ { "filePath": "~/pew/image.png" } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([Source].self, from: relativeImagePath)
// gives you a string in the format of file:///Users/your_uer_name/pew/image.png
let expandedPath = URL(fileURLWithPath: NSString("~/pew/image.png").expandingTildeInPath) as URL
XCTAssertEqual(result?.first?.filePath?.fileURL, expandedPath)
}
func testRefreshInterval() { func testRefreshInterval() {
let buttonNoActionFixture = """ let buttonNoActionFixture = """
[ { "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
} }
+20 -5
View File
@@ -10,7 +10,7 @@ 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
} }
@@ -18,14 +18,29 @@ class ParseConfig: XCTestCase {
func testButtonKeyCodeAction() { func testButtonKeyCodeAction() {
let buttonKeycodeFixture = """ let buttonKeycodeFixture = """
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ] [ { "type": "staticButton", "title": "Pew", "actions": [ { "trigger": "singleTap", "action": "hidKey", "keycode": 123 } ] } ]
""".data(using: .utf8)! """.data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture) let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("Pew")? = result?.first?.type else { guard case .staticButton("Pew")? = result?.first?.type else {
XCTFail() XCTFail()
return return
} }
guard case .hidKey(keycode: 123)? = result?.first?.action else { guard case .hidKey(keycode: 123)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
XCTFail()
return
}
}
func testButtonKeyCodeLegacyAction() {
let buttonKeycodeFixture = """
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("Pew")? = result?.first?.type else {
XCTFail()
return
}
guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else {
XCTFail() 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
} }
+137 -52
View File
@@ -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
@@ -128,15 +149,42 @@ The pre-installed configuration contains less or more than you'll probably want,
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"refreshInterval": 60, //optional "refreshInterval": 60, //optional
"source": { "source": {
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt", "filePath": "~/Library/Application Support/MTMR/iTunes.nowPlaying.scpt",
// or // or
"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`
> Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting) > Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting)
> Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color. > Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color.
@@ -150,12 +198,20 @@ 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}'"
}, },
"action": "appleScript", "actions": [
"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" "trigger": "singleTap",
}, "action": "appleScript",
"actionAppleScript": {
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
}
}
],
"align": "right", "align": "right",
"image": { "image": {
// Or you can specify a filePath here.
// Images will be resized to 24x24.
// "filePath": "~/myproject/myimage.jpg" // or "/fixed/path/to/the.png"
"base64": "base64":
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC" "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
}, },
@@ -173,7 +229,7 @@ Example of "CPU load" button which also changes color based on load value.
"title": "stats", "title": "stats",
"items": [ "items": [
{ "type": "play" }, { "type": "play" },
{ "type": "mute" }, { "type": "mute" },
... ...
] ]
} }
@@ -288,12 +344,51 @@ To close a group, use the button:
```js ```js
{ {
"type": "dock", "type": "dock",
"filter": "(^Xcode$)|(Safari)|(.*player)",
"autoResize": true "autoResize": true
}, },
``` ```
#### `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
@@ -335,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
@@ -371,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
File diff suppressed because one or more lines are too long
+10 -10
View File
@@ -3,7 +3,7 @@
{ {
"type": "exitTouchbar", "type": "exitTouchbar",
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/Resources/logo.png" "filePath": "~/git/selfProjects/MTMR/Resources/logo.png"
}, },
"width": 36, "align": "left" }, "width": 36, "align": "left" },
{ "type": "brightnessDown", "width": 36, "align": "left" }, { "type": "brightnessDown", "width": 36, "align": "left" },
@@ -12,46 +12,46 @@
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.next.scpt" "filePath": "~/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/iTunes.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/iTunes.png"
} }
}, },
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.next.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/Spotify.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/Spotify.png"
} }
}, },
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.next.scpt" "filePath": "~/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/Vox.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/Vox.png"
} }
}, },
{ {
+35
View File
@@ -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?)
+5 -3
View File
@@ -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`
+3 -2
View File
@@ -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