1
0
mirror of https://github.com/Toxblh/MTMR.git synced 2026-01-12 09:58:38 +00:00

Compare commits

...

621 Commits

Author SHA1 Message Date
AoMe · 青目
dd99e9d73d
fix: discord broken logo url (#466) 2024-03-13 19:07:03 +00:00
_lordBucket (Antoniuk Orest)
58beb5a213
Add PLN and UAH signs to CurrencyBarItem.swift (#463)
I just added them, to give more flexibility for some people from eastern Europe. Currently it looks like: "PLN9.53"
2023-12-24 17:23:38 +00:00
Marcus Förster
7da9ca2c68
add visual link for keycode (#462) 2023-12-21 18:27:38 +00:00
Zaheer
88a4ce82db
Currency update (#446)
* Added new crypto icons

* Added new crypto icons

* Create swift.yml

* Added new crypto icons

* Delete swift.yml

* Delete publish.yml

* Revert publish.yml

---------

Co-authored-by: Anton Palgunov <Toxblh@users.noreply.github.com>
2023-07-28 20:02:18 +01:00
Vladimir Tolstikov
d39b4c0c31
YandexWeatherBarItem: fixes in parsing (#454) 2023-02-28 17:27:06 +00:00
ckfear
5e609c2446
fix incorrect value of audio slider when audio output is switched (#444) 2022-08-15 22:47:24 +01:00
ak0nst
14301c4dbd
update of Sparkle framework for fix apple m1 local builds (#441)
* added speed units for network

* update of Sparkle framework for fix apple m1 local builds

Co-authored-by: akonst <akonst@cqg.com>
2022-06-22 17:37:36 +01:00
ak0nst
a879498e4c
added speed units for network (#440)
Co-authored-by: akonst <akonst@cqg.com>
2022-05-11 10:57:17 +01:00
Wiktor Latanowicz
36bf749a46
App id matching (#432)
* Add app id matching for buttons

* Hide MT button from  control strip when touch bar is empty

Co-authored-by: Wiktor Latanowicz <wiktor@latanowicz.com>
Co-authored-by: Anton Palgunov <Toxblh@users.noreply.github.com>
2022-02-16 19:01:09 +00:00
Piss Man
ac0e44db4d
Modified the way shell script are called (#430)
* Added shell detection

* Replaced deprecated object to supported version

* Updated haptic feedback.swift to support for newer mac

Added support for m1 mac
2022-02-16 19:00:28 +00:00
Dennis Wurster
26ad83be70
fixed grammar and clarity of README (#427)
* fixed grammar and clarity of README

* Update README.md

Co-authored-by: Anton Palgunov <Toxblh@users.noreply.github.com>
2022-02-16 18:59:41 +00:00
Vladimir Tolstikov
d199bbd852
Add new "cpu" widget (#415) 2021-09-16 16:49:39 +01:00
ash14
352bf4887c
Parse title/image from a script's output (#418) 2021-09-16 16:49:09 +01:00
Vladimir Tolstikov
211ca4be32
YandexWeather: update matching array with missing forecast ("Drizzle") (#414) 2021-08-17 15:01:07 +01:00
Sergey Ryazanov
d270a7bbcd
Fix HapticFeedback; Add MBP 13, M1 2020 (#408)
* Fix HapticFeedback; Add MBP 13, M1 2020

* Fix warnings

Co-authored-by: Sergey Ryazanov <sergey.ryazanov@rightperception.company>
2021-06-23 10:58:18 +01:00
Taymuraz Kaytmazov
44732e8ad6
chore: better grammar (#409) 2021-06-21 12:23:45 +01:00
侑夕
8c57342070
add missing punctuation (#400) 2021-06-21 12:23:36 +01:00
Harsh Anand
3add660d72
fix brew install cmd (#385) 2021-01-26 10:32:41 +00:00
ShanHui
eb617ff31b
update logo (#375)
* update logo

* fix the unauthorized use of the Apple Logo problem
2020-11-24 11:01:20 +00:00
3e82676008 upd build.sh 2020-11-20 02:24:31 +00:00
a2ad47c7ba build counter 2020-11-20 02:21:05 +00:00
bbe901a572 version 0.27 2020-11-20 01:42:50 +00:00
54eaa3fd9f Change brightness for 3 finger gesture. Close #370, #372 2020-11-19 21:32:35 +00:00
Matteo Piccina
6660bb2d8f
Fix brightness keys (#367) 2020-11-19 21:27:30 +00:00
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
3e5fa14494 v0.26.1 2020-05-17 13:42:02 +01:00
Fedor 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
1e1ae2af61 Release version 0.26 2020-05-14 00:49:53 +01:00
bc11728c2e Standard english date for build script 2020-05-14 00:44:10 +01:00
445584bb1b Added xcpretty for test and build scripts 2020-05-14 00:42:08 +01:00
b6721f0274 return normal trigggers in workflows 2020-05-14 00:05:17 +01:00
75df82a567 ignore sign error 2020-05-13 23:34:45 +01:00
a1f64028cc Correct file for release 2020-05-13 23:23:03 +01:00
3fc75ca8f0 Updated workflows. For delivery unsign app in gihub 2020-05-13 23:22:31 +01:00
6d266394a4 Revert "More Productive Gestures (#289)"
This reverts commit f61550e510.
2020-05-13 23:11:45 +01:00
Fedor Zaytsev
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 Rogers
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 Zaytsev
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
e9e5a6f739 fix yandex wheather, for correct lang 2019-11-26 17:33:18 +03:00
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
3f78bbe42c CI osx_image 11.2 -> 11.1 2019-10-18 12:18:50 +01:00
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
0b39795bd7 v0.24 2019-10-12 13:24:23 +01:00
d4b950ab64 Merge branch 'ReDetection-disable-haptic' 2019-10-12 13:06:27 +01:00
41a9544c3a More consistent settings 2019-10-12 13:04:28 +01:00
Serg
e44ff00f3b support for global settings. haptic feedback as the first one
resolves #185
2019-10-12 12:54:48 +07:00
Anton Palgunov
f378de675e
Merge pull request #219 from gtataranni/master
Activate Finder
2019-10-08 21:41:03 +01:00
Giovanni Tataranni
66b175d5ba
Update README.md 2019-10-06 10:54:17 +02:00
Giovanni Tataranni
59cde098f2
Update Finder.scpt
Bring to front existing window, or create new one
2019-10-06 10:52:21 +02:00
Giovanni Tataranni
f3015df82a
Update finder example script 2019-10-06 10:48:41 +02:00
Anton Palgunov
6629cfd11c
Update FUNDING.yml 2019-09-11 12:26:00 +01:00
Anton Palgunov
d6e48c8197
Update README.md 2019-09-10 23:41:11 +01:00
Anton Palgunov
0e8dab4677
Update README.md 2019-09-04 12:11:50 +01:00
aa73fd1dc3 add locale to timeButton 2019-09-04 12:06:53 +01:00
Anton Palgunov
29da70c477
ShellScriptTouchBarItem: fix crash when script returns with error (#209)
ShellScriptTouchBarItem: fix crash when script returns with error
2019-08-29 01:26:52 +01:00
bobrosoft
aa67dc10b7 ShellScriptTouchBarItem: fix crash when script returns with error 2019-08-27 14:18:24 +04:00
Anton Palgunov
aaa54e2709
Update README.md 2019-08-21 21:46:16 +01:00
a110e00b9c v0.23 2019-08-19 12:34:52 +01:00
Anton Palgunov
12137c6732
Add new "shellScriptTitledButton" button type (#203)
Add new "shellScriptTitledButton" button type
2019-08-14 10:59:27 +01:00
bobrosoft
36d1028af1 ShellScriptTouchBarItem: performance optimization 2019-08-11 15:46:08 +02:00
bobrosoft
77f56df144 README: remove duplicate "timeButton" description 2019-08-11 15:29:37 +02:00
bobrosoft
80b56779fb Add new "shellScriptTitledButton" button type 2019-08-11 15:25:44 +02:00
Anton Palgunov
580f0275fc
CustomButtonTouchBarItem: recognise longPress in more expected m… (#202)
CustomButtonTouchBarItem: recognise longPress in more expected manner without long wait and requirement to release the button
2019-08-09 16:03:38 +01:00
bobrosoft
168b629810 CustomButtonTouchBarItem: fix time interval constant 2019-08-06 16:51:40 +02:00
bobrosoft
2d64c091e3 fix: need to properly stop periodic timer/activity on destroy to eliminate possible memory leaks 2019-08-06 16:37:57 +02:00
bobrosoft
63e3de7313 CustomButtonTouchBarItem: try to fix strange app hanging (attempt 2) 2019-08-06 15:03:01 +02:00
bobrosoft
229c55c367 CustomButtonTouchBarItem: try to fix strange app hanging 2019-08-02 14:50:55 +02:00
bobrosoft
11530ef180 CustomButtonTouchBarItem: recognise longPress in more expected manner without long wait and requirement to release the button 2019-08-01 12:25:59 +02:00
96f26ab7f7 upd info 2019-07-29 12:16:07 +01:00
Anton Palgunov
95271dba0e
HapticFeedback: fix broken haptic for Release builds + refactori… (#198)
HapticFeedback: fix broken haptic for Release builds + refactoring
2019-07-28 00:17:18 +01:00
bobrosoft
67aaa2abf4 CurrencyBarItem: fix text vertical alignment 2019-07-27 18:10:48 +02:00
bobrosoft
58e4160649 HapticFeedback: fix broken haptic for Release builds + refactoring 2019-07-27 13:05:02 +02:00
77846d0436 v0.22 2019-07-26 13:37:49 +01:00
Anton Palgunov
bc66b375d3
Merge pull request #195 from bobrosoft/master
README: add "yandexWeather" to the list of "Native Plugins" on top
2019-07-23 16:31:06 +01:00
bobrosoft
ce8004641d README: add "yandexWeather" to the list of "Native Plugins" on top 2019-07-23 18:54:07 +04:00
Anton Palgunov
155e48a693
Add new "yandexWeather" widget (#194)
Add new "yandexWeather" widget
2019-07-23 14:41:17 +01:00
bobrosoft
76a7d59fe5 README: add "experimental" flag to yandexWeather widget 2019-07-23 17:11:13 +04:00
bobrosoft
6a85bea5b5 Add new "yandexWeather" widget 2019-07-23 17:07:47 +04:00
Anton Palgunov
000b825ec9
Merge pull request #193 from bobrosoft:master
MusicBarItem: fix for latest macOS + add "disableMarquee" param
2019-07-23 13:36:28 +01:00
bobrosoft
3432e24a55 MusicBarItem: add support for "disableMarquee" param 2019-07-18 18:29:08 +04:00
bobrosoft
705d0a64b5 MusicBarItem: fix for latest macOS 2019-07-17 17:42:03 +04:00
Anton Palgunov
9f0944b06b
HapticFeedback: fix recovery from the long sleep (#192)
HapticFeedback: fix recovery from the long sleep
2019-07-17 09:27:26 +01:00
bobrosoft
efc52293a8 HapticFeedback: fix recovery from the long sleep (minor comment changes) 2019-07-17 12:21:51 +04:00
bobrosoft
7b853b5d47 HapticFeedback: fix recovery from the long sleep 2019-07-17 12:17:35 +04:00
Anton Palgunov
439246e85b
CustomButtonTouchBarItem: add better haptic feedback right when… (#188)
CustomButtonTouchBarItem: add better haptic feedback right when button been touched
2019-07-15 12:57:50 +01:00
bobrosoft
c254ee430d CustomButtonTouchBarItem: add better haptic feedback right when button been touched 2019-07-12 17:39:10 +04:00
cfcda6e46f upd CFBundleVersion 2019-07-11 17:51:13 +01:00
144ff9cf79 v0.21.1 2019-07-11 17:49:15 +01:00
Anton Palgunov
91a4e5bded
Fix HapticFeedback for new MacBook models (#187)
Fix HapticFeedback for new MacBook models
2019-07-11 17:40:37 +01:00
bobrosoft
68dfcddb29 Fix HapticFeedback for new MacBook models 2019-07-11 20:16:34 +04:00
Anton Palgunov
82ec231700
README: add more details on how to setup "weather" plugin (#186)
README: add more details on how to setup "weather" plugin
2019-07-09 19:27:42 +01:00
Vladimir Tolstikov
ed261c2349
README: add more details on how to setup "weather" plugin 2019-07-09 22:25:00 +04:00
Anton Palgunov
f06d1f4329
Create FUNDING.yml 2019-05-23 14:16:25 +01:00
3dabd598f4 upd Readme 2019-05-22 22:52:49 +01:00
Anton Palgunov
0a09ea7117
Merge pull request #169 from FinHorsley:patch-1
improved readme
2019-05-22 22:48:51 +01:00
02cf911336 move ss.png to Resources 2019-05-22 22:47:58 +01:00
d831069025 Merge remote-tracking branch 'origin/master' into pr/FinHorsley/169-2 2019-05-22 22:44:57 +01:00
33206ab457 update standart preset for first install 2019-05-22 22:44:21 +01:00
c6c808369f Edited Readme. Also updated paths 2019-05-22 22:44:01 +01:00
cbad06ac07 Move files to correct folder. 2019-05-22 22:43:44 +01:00
64171d5c51 merge 2019-05-22 18:26:24 +01:00
5504e5d640 #171 Added DarkMode button v0.21 2019-05-22 18:25:18 +01:00
Anton Palgunov
164295820b
Merge pull request #167 from willsunnn:master
Added an additional option for the dock to automatically resize
2019-05-15 17:04:52 +01:00
ae469bb92a add Readme info 2019-05-15 17:01:06 +01:00
57b5129135 info.plist 2019-05-15 16:57:40 +01:00
7cc72de66f merge master 2019-05-15 16:56:53 +01:00
2e5db4ffa0 revert project settings 2019-05-15 16:55:44 +01:00
8a73fe01e4 Revert "#154 .cghidEventTap -> .cgAnnotatedSessionEventTap"
This reverts commit 5a8117ac7f.
2019-05-15 16:40:09 +01:00
5fbb2bafc6 Revert "Fix #164"
This reverts commit 40fa61fcb5.
2019-05-15 16:39:52 +01:00
5b6b6dfa56 Revert "fixed tests"
This reverts commit 7c9dd26eb0.
2019-05-15 16:39:32 +01:00
FinHorsley
65e5a52383
shadow to images 2019-05-15 14:44:15 +01:00
FinHorsley
03304bd030
Add files via upload 2019-05-15 14:43:17 +01:00
FinHorsley
f264425ba7
Update README.md 2019-05-13 20:56:21 +01:00
FinHorsley
c377b9494c
Update README.md 2019-05-13 20:54:43 +01:00
FinHorsley
f5b36ec012
Update README.md 2019-05-13 20:48:01 +01:00
FinHorsley
999c4cf6e2
Update README.md 2019-05-13 20:38:30 +01:00
FinHorsley
b8d5d0a8a5
Update README.md 2019-05-13 20:35:36 +01:00
FinHorsley
6bb5aade07
Update README.md 2019-05-13 20:35:17 +01:00
FinHorsley
b6bcc31456
Update README.md 2019-05-13 20:34:01 +01:00
FinHorsley
9d09646742
Update README.md 2019-05-13 20:32:53 +01:00
FinHorsley
55f988e393
Update README.md 2019-05-13 20:32:26 +01:00
FinHorsley
2f4cf9e5f6
Update README.md 2019-05-13 20:31:46 +01:00
FinHorsley
327d38e47d
Update README.md 2019-05-13 20:30:35 +01:00
FinHorsley
cd380b289d
Update README.md 2019-05-13 20:26:41 +01:00
FinHorsley
6a26b2f5ca
Update README.md 2019-05-13 20:24:31 +01:00
FinHorsley
d7186aac18
Update README.md 2019-05-13 20:23:43 +01:00
FinHorsley
cef07dc965
Update README.md 2019-05-13 20:22:45 +01:00
FinHorsley
471c0c5d57
Update README.md 2019-05-13 20:16:56 +01:00
FinHorsley
5b93525806
Add files via upload 2019-05-13 20:00:31 +01:00
FinHorsley
b87bc632ab
Delete some.png 2019-05-13 20:00:10 +01:00
FinHorsley
1763a24942
Add files via upload 2019-05-13 19:57:41 +01:00
FinHorsley
627e6951c1
Update README.md 2019-05-13 18:20:28 +01:00
FinHorsley
918054df78
Update README.md 2019-05-13 18:17:17 +01:00
FinHorsley
25fb0d4f04
center things 2019-05-13 18:06:21 +01:00
FinHorsley
61a357ffc3
Update README.md 2019-05-13 18:01:41 +01:00
FinHorsley
f4116a95df
Update README.md 2019-05-13 18:00:03 +01:00
FinHorsley
c952505c1c
Update README.md 2019-05-13 17:57:50 +01:00
FinHorsley
f10b58b996
Update README.md 2019-05-13 17:56:34 +01:00
FinHorsley
88a8ae3ae8
Add files via upload 2019-05-13 17:55:43 +01:00
FinHorsley
d783448567
Update README.md 2019-05-13 17:36:55 +01:00
FinHorsley
595ae165d3
Update README.md 2019-05-13 17:32:55 +01:00
FinHorsley
da406713dd
Update README.md 2019-05-13 17:32:01 +01:00
FinHorsley
938b83f37e
Update README.md 2019-05-13 17:31:40 +01:00
FinHorsley
a8a085df1e
Update README.md 2019-05-13 17:30:43 +01:00
FinHorsley
b188f45c8e
Update README.md 2019-05-13 17:30:14 +01:00
FinHorsley
3319c5d45a
Update README.md 2019-05-13 17:27:51 +01:00
FinHorsley
6174d14925
Update README.md 2019-05-13 17:27:06 +01:00
FinHorsley
c8197239d7
Update README.md 2019-05-13 17:25:51 +01:00
FinHorsley
d2854e3dae
Update README.md 2019-05-13 17:24:28 +01:00
FinHorsley
4f9c3258ae
Add files via upload 2019-05-13 17:22:26 +01:00
FinHorsley
dfeda1e874
Update README.md 2019-05-13 17:20:40 +01:00
FinHorsley
82f82039ae
Update readme
Bit of a refresh
2019-05-13 17:19:39 +01:00
willsunnn
324c3f711e
Fixed a bug where the dock would not change size after the first time due to conflicting constraints 2019-05-09 22:39:47 -07:00
willsunnn
a81d6fc595
Made the dock resize when Apps are removed 2019-05-09 22:20:45 -07:00
willsunnn
3bde1fe4b1
Created and passed the autoResize parameter to the dock widget 2019-05-09 22:04:10 -07:00
7c9dd26eb0 fixed tests 2019-05-09 22:19:21 +01:00
40fa61fcb5 Fix #164 2019-05-09 22:14:48 +01:00
5a8117ac7f #154 .cghidEventTap -> .cgAnnotatedSessionEventTap 2019-05-07 10:37:29 +01:00
a606767ddc Revert "migration to swift 5"
This reverts commit 4497c7102c.
2019-04-02 17:10:18 +01:00
6badc37dc3 Merge branch 'master' of https://github.com/Toxblh/MTMR 2019-04-02 17:07:27 +01:00
4497c7102c migration to swift 5 2019-04-02 17:07:23 +01:00
Anton Palgunov
b6c6bb945b
Update README.md 2019-03-28 13:30:16 +00:00
Anton Palgunov
3e399d14c2
Update README.md 2019-03-27 10:58:41 +00:00
624c8ac6d8 upd readme 2019-03-21 21:52:55 +00:00
ea68ce33c1 Merge branch 'master' of https://github.com/Toxblh/MTMR 2019-03-21 21:52:01 +00:00
744ea067db v0.20.1 2019-03-21 21:51:05 +00:00
8064ab759c test old build type. try resolve #155 2019-03-21 21:50:38 +00:00
556c28df85 flip param for network 2019-03-21 21:49:34 +00:00
Anton Palgunov
e68f930efa
Update README.md 2019-03-21 12:26:29 +00:00
Anton Palgunov
647ac0ba20
Update README.md 2019-03-03 14:37:56 +00:00
Anton Palgunov
780a8ba81e
Update README.md 2019-02-25 09:20:08 +00:00
Anton Palgunov
71f5030fa6
spellchecking 2019-02-24 23:37:58 +00:00
Anton Palgunov
f435c0b530
Troubleshooting in Readme 2019-02-24 23:34:56 +00:00
e278482807 Network plugin 2019-02-24 23:10:53 +00:00
51d246bf7f fix tesh script 2019-02-24 23:08:22 +00:00
c1cd402241 fixed codesign. 2019-02-22 18:12:07 +00:00
c496156cf2 0.19.5 2019-02-12 21:35:47 +00:00
Anton Palgunov
154ea09e98
Merge pull request #139 from ReDetection/apple-script-robustness
Stable AppleScript
2019-02-04 05:55:38 +00:00
Serg
3b493fd5be create applescript on the queue it will run on 2019-01-28 10:36:04 +07:00
b718b1c9dd Style format 2019-01-24 00:04:30 +03:00
acc248a579 Close #135 2019-01-24 00:04:14 +03:00
59581ffdc8 Merge branch 'master' of https://github.com/Toxblh/MTMR 2019-01-23 12:43:54 +03:00
238b15b6a4 fix drop MTMR without timeZone 2019-01-23 12:43:25 +03:00
732fd5c5b5 add helpers for fast update homebrew 2019-01-22 19:20:40 +03:00
f4b8e1f39a switch from localized to custom templates 2019-01-22 12:46:17 +03:00
Anton Palgunov
e988951ff9
Update README.md 2019-01-22 12:26:42 +03:00
807290ff0c improve build.sh 2019-01-22 12:21:38 +03:00
0775ea0412 added timeZone settings for timeButton. #136 2019-01-22 12:11:31 +03:00
1d1d666986 added key full for currency widget. 2019-01-21 19:59:10 +03:00
560d8ed508 test.sh for build without xcode 2019-01-17 12:51:39 +00:00
Anton Palgunov
06d6f06adf
Merge pull request #134 from koenpunt/patch-1
remove force cast
2019-01-17 11:57:37 +00:00
0fafe3912e upd gitignore 2019-01-17 01:51:42 +00:00
Anton Palgunov
319241d4d4
Update README.md 2019-01-17 00:11:40 +00:00
Koen Punt
61d5e8f77e
remove force cast 2019-01-16 11:00:31 +01:00
Anton Palgunov
e62530d7ab
add user preset aadi_vs_anand 2019-01-15 11:39:22 +00:00
Anton Palgunov
43675027d5
aadi_vs_anand files 2019-01-15 11:37:14 +00:00
Anton Palgunov
b70c040111
payPal link 2019-01-02 15:58:36 +00:00
e795f77ba1 Merge branch 'master' of https://github.com/Toxblh/MTMR 2019-01-02 15:56:02 +00:00
37b0462bc1 add image 2019-01-02 15:55:56 +00:00
Anton Palgunov
d2d33dee3b
Update README.md 2018-12-28 23:07:31 +00:00
Anton Palgunov
c0fc40b1c9
add Discord link 2018-12-28 23:06:53 +00:00
Anton Palgunov
47a2027443
Merge pull request #122 from Bassoon08/update-readme
Update README
2018-10-30 07:36:51 +00:00
Mel Shafer
b3c1d6b323 update readME 2018-10-29 22:06:09 -04:00
Anton Palgunov
a15cc6506f
Update README.md 2018-10-25 10:33:37 +01:00
35a6ceae07 fix tests. Deleted not used code 2018-10-21 00:01:19 +01:00
be1c439867 upd version 2018-10-20 23:22:42 +01:00
9f84b38084 Added pomodoro timer 2018-10-20 23:21:30 +01:00
86954a7981 code format 2018-10-20 17:53:49 +01:00
8554dfeb5e Fix problem with blocked apps if turn off Hide control Strip.
Also for blocked apps available state in Menu
2018-10-20 12:09:33 +01:00
bd2cd6d0b7 fix issue "cant re-open mtmr after closing" close #117 2018-10-14 18:10:38 +01:00
Anton Palgunov
06ecdfe016
Merge pull request #118 from ReDetection/fix-menu-checkmark
Fix reversed "hide control strip" menu checkmark behaviour
2018-10-14 13:23:57 +01:00
Serg
85d54e4f53 fix reversed "hide control strip" menu checkmark beaviour 2018-10-13 13:22:11 +07:00
b368a21f62 build upd. Added autodeploy for autoupdate 2018-10-10 18:25:41 +01:00
644b15ffcd Fix for brightness slider. Correct API for set new level. Used CoreDisplay.framework 2018-10-09 12:28:48 +01:00
b0b10074f8 fix all privacy request in info.plist for fututre 2018-09-26 14:53:08 +01:00
2023ab29f3 fix AppleEvents for Majave 2018-09-26 12:40:48 +01:00
9a265887cf done updater 2018-09-26 00:19:27 +01:00
62e87c53f5 added updater 2018-09-26 00:18:27 +01:00
dbab502d14 change preset to without errors on start 2018-09-24 09:01:52 +01:00
ae79e8b90e travis update 2018-09-20 12:20:58 +01:00
d7eddb8484 Migrate to swift 4.2 2018-09-20 12:13:29 +01:00
457fa9481e Ready to autoupdates 2018-09-19 23:53:06 +01:00
69e8e6eefd Done 2018-09-18 14:01:05 +01:00
ffc101b455 v0.18 2018-08-30 16:40:58 +01:00
bbe9a1f10e Merge remote-tracking branch 'origin/master' 2018-08-30 16:35:52 +01:00
71051d7ad0 + DnD Widget 2018-08-30 16:35:41 +01:00
Anton Palgunov
e5136d5991
Update README.md 2018-08-29 11:53:26 +01:00
09d9f9618f help with license in readme 2018-08-29 11:14:51 +01:00
534dbc2284 change upd time for nightshift 2018-08-29 11:14:32 +01:00
9e0f103ba6 + DND icons 2018-08-29 00:57:39 +01:00
c6b6f7bbd5 + Dirty hack for show actual status for NightShift 2018-08-29 00:11:30 +01:00
eee4082a1a + Added NightShift Widget
+ Updated icons for illumination and added icons for NightShift
2018-08-29 00:08:46 +01:00
10d94d65e2 version 0.17 2018-08-26 21:12:18 +01:00
7de87ad029 Merge remote-tracking branch 'origin/master' 2018-08-26 21:10:27 +01:00
ad27d26c86 Added keyboard illumination buttons #106 2018-08-26 21:10:16 +01:00
Anton Palgunov
ecbd2beb48
Merge pull request #108 from luongvo209/master
Add Luong Vo's customisation
2018-08-22 21:14:52 +01:00
Luong Vo
0aed9a8680
Add Luong Vo's customisation 2018-08-23 00:49:31 +07:00
1f764d539a longActions 2018-06-29 15:08:09 +01:00
e95c707009 fix for 10.14 2018-06-29 15:07:59 +01:00
Anton Palgunov
1914dbb08f
Update README.md 2018-06-26 17:17:26 +01:00
Anton Palgunov
34074a1b9b
Update README.md 2018-06-20 15:27:13 +01:00
Anton Palgunov
e07ee1e263
Update README.md 2018-06-13 09:06:29 +01:00
9228eaffc8 upd 0.16.2 2018-06-10 11:13:10 +01:00
Anton Palgunov
f89871d294
Merge pull request #96 from Toxblh/prepare10.14
Prepare10.14
2018-06-08 09:32:25 +01:00
8e35d38b59 prepare for 10.14 2018-06-08 00:08:15 +01:00
17e023d2d2 correct state in menu 2018-06-08 00:07:25 +01:00
Anton Palgunov
dc94857ac4
Merge pull request #93 from ReDetection/cleanup-and-fix-close-group
Fix close group and cleanup
2018-06-04 11:19:51 +01:00
Serg
ec228fddde fix wrong preset on close group 2018-06-03 10:30:03 +07:00
Serg
958f93f440 handy method to open particular preset file, expose bug with wrong preset reload on group close 2018-06-03 00:49:48 +07:00
Serg
d03b16eb89 simplify logic 2018-06-03 00:28:48 +07:00
Anton Palgunov
ea573882a0
Merge pull request #92 from ReDetection/compiled-applescript
Support compiled applescript files
2018-06-01 15:52:41 +01:00
Serg
7fb936bd1d support compiled applescript files. related to #87 2018-06-01 21:12:48 +07:00
f97a3be6d3 resolve confict 2018-06-01 10:50:36 +01:00
3bc26eae82 Update menu loayout 2018-06-01 10:49:06 +01:00
Anton Palgunov
f84463dbb2
Update README.md 2018-06-01 09:27:38 +01:00
46c86a71de Parse AS in any encoding. Groups description 2018-06-01 09:26:07 +01:00
Anton Palgunov
ad70c034da
Merge pull request #90 from ReDetection/support-all-encodings
Read any text encoding
2018-06-01 09:16:34 +01:00
Serg
221565f604 fix #87: read any text encoding 2018-06-01 13:39:26 +07:00
Anton Palgunov
6ca7f11a2b
Merge pull request #88 from Toxblh/startAtLogin
+ start at login
2018-05-31 11:45:42 +01:00
Daniel Apatin
63795b3d41
Update README.md 2018-05-31 13:44:21 +03:00
ad
8a7de99bce + start at login 2018-05-31 13:20:18 +03:00
Anton Palgunov
7f13422805
Merge pull request #85 from ReDetection/fix-open-and-reset
fix broken close/open and toggle control strip
2018-05-26 10:39:01 +01:00
Serg
56d64155ae fix broken close/open and toggle control strip 2018-05-26 13:09:24 +07:00
Daniel Apatin
088f141da3
Update music title right after switch to the next track 2018-05-23 19:24:58 +03:00
091208e536 new gestures 2018-05-22 11:33:15 +01:00
Anton Palgunov
c6f6fbc5cd
Merge pull request #84 from Toxblh/gestures
Gestures for Volume (2 fingers) and Brightness (3 fingers) on ScrollView
2018-05-21 11:01:39 +01:00
ad
3cdf44903b Gestures for Volume (2 fingers) and Brightness (3 fingers) on ScrollView 2018-05-21 12:18:04 +03:00
Anton Palgunov
e13e8598a7
Merge pull request #79 from Toxblh/removeReloadPreset
Remove "Reload Preset" and fix reloading preset
2018-05-18 08:52:43 +01:00
Anton Palgunov
d82c837902
Merge pull request #80 from Toxblh/groupBarItem
+ Groups
2018-05-18 08:49:51 +01:00
ad
d5483cdd03 * group items "decodeIfPresent" -> "decode" 2018-05-18 09:53:15 +03:00
ad
9f2dd277fb * touchbarNeedRefresh 2018-05-18 00:33:14 +03:00
ad
e7c949209d + Groups 2018-05-17 13:47:53 +03:00
ad
42d3d29e0d Remove "Reload Preset" and fix reloading preset 2018-05-17 13:21:58 +03:00
Anton Palgunov
6674b5aef7
Merge pull request #77 from Toxblh/customTitle
+ custom title
2018-05-15 20:45:26 +01:00
ad
f83f331a99 + custom title 2018-05-15 15:11:34 +03:00
Anton Palgunov
cd16c09dbd
Update README.md 2018-05-14 22:35:42 +01:00
Anton Palgunov
4ba9d11494
Update README.md 2018-05-14 22:27:52 +01:00
62a2aa14e1 v0.15 2018-05-14 10:33:17 +01:00
1c0a2f064a upd Readme 2018-05-14 10:32:38 +01:00
Anton Palgunov
23357816b3
Merge pull request #74 from Toxblh/blackListedApps
+ toggle blacklisted app
2018-05-14 09:26:09 +01:00
Anton Palgunov
877dca7587
Merge pull request #64 from Toxblh/musicWidget
+ music widget
2018-05-14 08:13:59 +01:00
Anton Palgunov
fd537bcdfe
Merge pull request #75 from ReDetection/experimental-private-queue
experimental: run applescript on separate serial queue
2018-05-13 19:35:28 +01:00
e852ab58f8 change stip icon 2018-05-12 20:25:14 +01:00
Serg
5c7be625b6 fix warnings, switch track of only first player running 2018-05-13 00:11:47 +07:00
Serg
475b66c956 Merge branch 'master' into musicWidget and fix hidden property usage
# Conflicts:
#	MTMR/CustomButtonTouchBarItem.swift
#	MTMR/TouchBarController.swift
2018-05-12 21:47:13 +07:00
Serg
19f7ff0103 Merge branch 'master' into experimental-private-queue
# Conflicts:
#	MTMR/AppleScriptTouchBarItem.swift
2018-05-12 20:30:36 +07:00
Serg
3bc75b54c0 naive backport of compact map to old swift 2018-05-12 20:19:11 +07:00
Anton Palgunov
3c072923d2
Merge pull request #76 from ReDetection/hide-button
Code cleanup; Hide button property
2018-05-12 12:29:24 +02:00
Serg
b29c74ccca Merge branch 'master' into musicWidget
# Conflicts:
#	MTMR/CustomButtonTouchBarItem.swift
2018-05-12 13:21:25 +07:00
Serg
bbea465500 hide button property, cleanup nasty code 2018-05-12 12:02:45 +07:00
Serg
f7eb49e5de public tap/longtap button action hanglers 2018-05-12 11:41:13 +07:00
Serg
e9c69f6461 experimental: run applescript on separate serial queue 2018-05-12 11:11:53 +07:00
Serg
9e1fcb9a35 incapsulate button image handling 2018-05-12 10:49:16 +07:00
ad
7523605d7a + toggle blacklisted app 2018-05-11 16:31:13 +03:00
Anton Palgunov
b73f8865a9
Merge pull request #73 from Toxblh/toggleControlStrip
+ toggle Control Strip
2018-05-11 13:51:30 +02:00
ad
852df9d6b6 + toggle Control Strip 2018-05-11 14:40:02 +03:00
57e8af90dd close #10 2018-05-11 13:26:52 +02:00
Anton Palgunov
0e48566c74
Merge pull request #72 from ReDetection/fix-button-titles-2
fix button titles and images
2018-05-11 11:35:54 +02:00
Serg
55177a7f90 fix currency widget updating 2018-05-11 09:32:42 +07:00
Serg
d46827832e another try to fix button titles and images 2018-05-11 00:29:05 +07:00
9e10b2d3d1 upd. Build.sh 2018-05-10 10:11:50 +02:00
33efbbebaa switch hidKeys to obj-c realesation 2018-05-10 09:56:04 +02:00
Anton Palgunov
d048ec851e
Merge pull request #65 from ReDetection/fix-width
Fix width for borderless buttons
2018-05-10 09:18:39 +02:00
Anton Palgunov
42ebb91e6b
Merge pull request #66 from ReDetection/fix-taps
Fix taps for borderless buttons
2018-05-10 08:19:40 +02:00
Serg
1da4e2795f fix taps for borderless buttons 2018-05-10 09:31:37 +07:00
Serg
21bc525a04 fix setting width for borderless buttons 2018-05-10 09:02:30 +07:00
ad
b9ef3cdc35 + music widget 2018-05-09 21:34:44 +03:00
Anton Palgunov
9a34d5bca0
Merge pull request #62 from ReDetection/fix-non-bordered-button
Fixes for borderless buttons
2018-05-09 12:22:01 +02:00
Anton Palgunov
4c9820a390
Merge pull request #63 from ReDetection/tests
Example on how to make tests + travis integration
2018-05-09 09:34:20 +02:00
Serg
634beeae8b add travis shield to readme 2018-05-09 14:09:40 +07:00
Serg
3ec2422181 add couple more tests 2018-05-09 14:07:37 +07:00
Serg
a3c7fa7464 replace garbage in the app with garbage in the tests (until apple fix that), enable coverage 2018-05-09 13:34:32 +07:00
Serg
20ed827750 fix that borderless buttons in scrollable area were not centered, fix wrong text color for borderless colored buttons 2018-05-09 12:23:02 +07:00
Serg
85aa2c4f2b original key event type 2018-05-07 09:08:39 +07:00
Serg
b761551384 fix imports 2018-05-07 09:04:33 +07:00
Serg
3854b455a6 workaround travis bug, add xcpretty 2018-05-07 08:42:24 +07:00
Serg
9c803e85cc ensure shared schemes, add travis config 2018-05-07 07:14:14 +07:00
Serg
651258b01f fix building test target, add one more test 2018-05-06 17:13:31 +07:00
Anton Palgunov
02baef80db
Update README.md 2018-05-04 14:10:49 +01:00
cd75be6c26 v0.14 2018-04-30 22:17:04 +01:00
Anton Palgunov
7646c6e89d
Merge pull request #58 from ReDetection/bordered-parameter
WIP: Bordered parameter, background parameter
2018-04-30 19:20:07 +01:00
9c970aff63 if button have only image, change position to .imageonly 2018-04-30 19:19:33 +01:00
Serg
49d7333d5e implement highlight for buttons with no border 2018-04-30 23:18:59 +07:00
Serg
cd69bc341c fix margin for some buttons 2018-04-30 22:54:15 +07:00
Serg
453895f395 fix wrong title while first execution 2018-04-30 18:53:32 +07:00
Serg
12796d6387 fix for colors in non-bordered buttons 2018-04-30 18:40:26 +07:00
Anton Palgunov
f65e72faed
Merge pull request #57 from Toxblh/revert-56-master
Revert "tapAction and longTapAction in config now have nested values"
2018-04-30 11:29:52 +01:00
Anton Palgunov
df535b2893
Revert "tapAction and longTapAction in config now have nested values" 2018-04-30 11:29:42 +01:00
Anton Palgunov
574ca0fb84
Merge pull request #56 from ad/master
tapAction and longTapAction in config now have nested values
2018-04-30 11:08:28 +01:00
ad
4a03ba79ec + background option for button
# Conflicts:
#	MTMR/CustomButtonTouchBarItem.swift
#	MTMR/ItemsParsing.swift
2018-04-30 15:43:55 +07:00
ad
907b79965d + hexColor 2018-04-30 15:26:29 +07:00
Serg
fbec803d47 introduce bordered parameter, restore 0.13 borders 2018-04-30 15:14:27 +07:00
ad
3f9574245a more constants from ev_keymap.h 2018-04-30 10:15:07 +03:00
ad
2bf6b90b04 * fix for brightness 2018-04-30 10:11:22 +03:00
ad
9d596c6adc Merge remote-tracking branch 'upstream/master' 2018-04-30 09:51:42 +03:00
ad
175e9edb8c Merge branch 'nestedActions' 2018-04-30 09:51:26 +03:00
ad
8e58bb179a tap and longTap 2018-04-30 09:50:59 +03:00
Anton Palgunov
d37d9607d3
Merge pull request #55 from ad/master
deinit inputsource observer, button highlight on touch
2018-04-29 23:00:07 +01:00
ad
6b553c4d0d + deinit inputsource observer 2018-04-30 00:57:32 +03:00
ad
788927edb3 + highlighting button background on press 2018-04-30 00:57:09 +03:00
ad
9013779f71 Merge remote-tracking branch 'upstream/master' 2018-04-29 22:51:34 +03:00
a85507c544 upd Readme, upd Default 2018-04-29 19:44:58 +01:00
59aa113995 build.sh upd 2018-04-29 19:27:01 +01:00
Anton Palgunov
9c5a19925b
Merge pull request #54 from Toxblh/fix-leaked-items-and-timers
Fix leaking timers
2018-04-29 18:19:55 +01:00
Serg
2aa602d77d recreate touchbar to clear it's cache (items survive otherwise, still doing their job) 2018-04-29 23:37:09 +07:00
ad
a1b3f13902 Merge remote-tracking branch 'upstream/master' 2018-04-29 14:59:13 +03:00
Anton Palgunov
60384561d2
Merge pull request #53 from Toxblh/fix-warnings
Fix compiler warnings
2018-04-29 12:55:00 +01:00
Serg
77938340d6 fix compiler warnings 2018-04-29 18:03:30 +07:00
ad
93d2f496d0 * first working 2018-04-29 01:18:05 +03:00
ad
0808f0617a * fix 2018-04-28 02:28:09 +03:00
ad
966a924183 Merge remote-tracking branch 'upstream/master' 2018-04-28 02:23:57 +03:00
ad
a648402d04 Merge branch 'master' of https://github.com/ad/MTMR 2018-04-28 02:22:24 +03:00
ad
207af6468e * button highlight on touch 2018-04-28 02:22:05 +03:00
Anton Palgunov
6609a7c806
Merge pull request #49 from ad/master
* NSButtonCell
2018-04-27 23:39:02 +01:00
479190d12b fix center line for apple script 2018-04-27 23:38:13 +01:00
ad
5ed1790601 * scrollView baseline fix 2018-04-27 21:53:27 +03:00
b95f5af95f lead position for image in image+title button 2018-04-27 19:13:55 +01:00
ad
c76027ca9d * fix for button.image size 2018-04-27 20:18:22 +03:00
ad
55dc551516 * fix for longAction 2018-04-27 19:17:19 +03:00
ad
cf5d8bf546 * staticButton image size 2018-04-27 19:03:58 +03:00
ad
8031108fc9 * font baseline like in original touchbar, styled battery widget (time showing as superscript) 2018-04-27 18:46:29 +03:00
ad
d1a577868b * button background 2018-04-27 16:17:40 +03:00
ad
5f02bb2962 * font style 2018-04-27 14:32:07 +03:00
ad
cc1332dd02 * button font color 2018-04-27 14:05:13 +03:00
ad
a0dbfca828 * input source widget image size 2018-04-27 12:55:54 +03:00
ad
76bdf12745 * fixed switching input source 2018-04-26 22:50:38 +03:00
ad
e1268d0d65 * NSButtonCell 2018-04-26 22:36:12 +03:00
9c1e5f5032 Merge branch 'master' of https://github.com/Toxblh/MTMR 2018-04-26 14:54:36 +01:00
88cde9256a v0.13.2 2018-04-26 14:54:24 +01:00
Anton Palgunov
d974f92e3d
brew instruction 2018-04-25 13:48:39 +01:00
4b8ef1cd96 Merge remote-tracking branch 'origin/master' 2018-04-24 22:54:12 +01:00
e65c57dbaa If level a battery less 10% it will be red. 2018-04-24 22:53:56 +01:00
Anton Palgunov
babc8451e0
Merge pull request #48 from jstart/patch-1
Spelling :)
2018-04-24 22:36:20 +01:00
Christopher Truman
7698ea1c8e
Spelling :) 2018-04-24 14:10:18 -07:00
Anton Palgunov
aa95d1a1bd
Merge pull request #46 from ad/master
* trying to terminate app before forceTerminate (to prevent loosing of unsaved data)
2018-04-24 13:36:55 +01:00
ad
3fe6c22068 * trying to terminate app before forceTerminate (to prevent loosing of unsaved data) 2018-04-24 13:19:22 +03:00
5ab19ee27b v0.13.1 2018-04-24 11:14:29 +01:00
Anton Palgunov
ae4b0aa870
Merge pull request #45 from ad/master
Allow multiple instances of currency and weather widgets (different schedulers for one type widget)
2018-04-24 10:59:22 +01:00
ad
1e2043ebda * allow multiple instances of currency and weather widgets (different schedulers for one type widget) 2018-04-24 12:49:36 +03:00
Anton Palgunov
409b8d8b46
Merge pull request #44 from ad/master
* dock widget improvements
2018-04-24 10:47:34 +01:00
ad
8bccbf3834 * dock widget improvements 2018-04-23 20:09:05 +03:00
1b5ae00493 v0.13 2018-04-23 13:09:48 +01:00
22c315d631 Parse JSON with comments. 2018-04-23 13:08:55 +01:00
ffbbafa9d9 Fixed central sort. Close #33 2018-04-23 00:54:29 +01:00
719a212913 disable parse with comments now, need improve speed loading for JSONC 2018-04-22 23:34:32 +01:00
651d883672 + delete button. Close #37 2018-04-22 23:31:05 +01:00
Anton Palgunov
9853e0448f
Merge pull request #41 from ad/master
Switch input source widget
2018-04-22 19:38:22 +01:00
ad
0f41638d0f * file moved to new location 2018-04-22 19:28:23 +03:00
ad
f6e5b3ca09 Merge branch 'inputSourceWidget' 2018-04-22 19:25:49 +03:00
ad
04b821510b + switch input source by tap 2018-04-22 19:22:45 +03:00
ad
d632bd3ee7 + show input source 2018-04-22 17:55:20 +03:00
aa341b818f reorginize project 2018-04-22 15:20:00 +01:00
Anton Palgunov
a513c0083a
Merge pull request #40 from ad/replaceTimers
Weather and Currency timers replaced with NSBackgroundActivityScheduler (more energy efficiency)
2018-04-22 11:42:23 +01:00
ad
683cc4c9f4 Weather and Currency timers replaced with NSBackgroundActivityScheduler (more energy efficiency) 2018-04-22 13:38:16 +03:00
Anton Palgunov
3c171b449a
Merge pull request #39 from ad/stripComments
+ strip comments in config file
2018-04-22 10:17:34 +01:00
ad
b4f62f6be1 + strip comments in config file 2018-04-22 12:15:04 +03:00
Anton Palgunov
8a9b40a030
Merge pull request #38 from ad/master
+ persistent app in dock (press for 0.5 sec), close app (press for 2.0 sec). Reload view if default config changed (write event fired)
2018-04-21 20:16:59 +01:00
ad
3375308386 + reload if default config changed (write event fired) 2018-04-21 20:50:22 +03:00
ad
2fc9dbccd8 Merge remote-tracking branch 'upstream/master' 2018-04-21 17:22:19 +03:00
ad
051a303f96 + persistent app in dock (press for 0.5 sec), close app (press for 2.0 sec) 2018-04-21 16:40:43 +03:00
Anton Palgunov
41a859aea3
Merge pull request #32 from ad/master
Background color parameter moved to CustomButtonTouchBarItem
2018-04-21 13:16:36 +01:00
ad
bb978f2de5 Background color parameter moved to CustomButtonTouchBarItem for future ability to change from config file 2018-04-21 15:01:09 +03:00
Anton Palgunov
9bf42dfb79
Merge pull request #31 from ad/master
Action for battery plugin
2018-04-21 12:08:01 +01:00
ad
641eb18b89 + longAction (action for long press on widgets, now working only with longUrl). TODO: nested options for action/longAction like for image/sourse/actionAppleScript 2018-04-21 10:20:12 +03:00
ad
d76e16b9e6 * action for battery plugin 2018-04-21 08:27:02 +03:00
ad
9abb17ee76 Merge remote-tracking branch 'upstream/master' 2018-04-21 08:19:44 +03:00
4b39b000e0 Upd default. Readme. Fixed reload for central scroller. 2018-04-21 01:43:37 +01:00
9b86ed1dee update Readme 2018-04-21 00:23:48 +01:00
7874433d9b Added Native Battery 2018-04-21 00:22:45 +01:00
d4108d848e merge stash 2018-04-20 22:57:51 +01:00
f78f82893a merged master 2018-04-20 22:56:47 +01:00
05eabdbd0a Added Button mute { "type": "mute", "width": 40, "align": "right" }, #23 2018-04-20 22:54:38 +01:00
4109f6efd4 delete .DS_Store 2018-04-20 22:50:36 +01:00
ad
bbbbf5b5a4 Merge remote-tracking branch 'upstream/master' 2018-04-20 21:01:42 +03:00
Anton Palgunov
8513ed0fa3
Merge pull request #28 from Toxblh/openUrl
Open url action
2018-04-20 18:42:23 +01:00
ad
0e2a958b5c Merge remote-tracking branch 'upstream/master' 2018-04-20 16:26:28 +03:00
Daniel Apatin
ac234aa4bf
Fix for temperature value with decimals 2018-04-20 16:24:00 +03:00
Daniel Apatin
e6d84888ba
Update TouchBarController.swift 2018-04-20 15:21:30 +03:00
Daniel Apatin
2aecb2ffb3
Update ItemsParsing.swift 2018-04-20 15:20:01 +03:00
ad
cd8b595552 - 2018-04-20 14:42:36 +03:00
ad
76934e6ff0 + action for currency, weather, battery
It will allow to process custom action on click by widget
2018-04-20 14:26:26 +03:00
Daniel Apatin
3e35e03bfb
+ action for currency, weather, battery
It will allow to process custom action on click by widget
2018-04-20 14:25:31 +03:00
ad
6f3425f4c3 Merge branch 'master' into actionURL 2018-04-20 14:20:24 +03:00
ad
0c007b7940 + action openURL 2018-04-20 13:53:25 +03:00
ad
a7060b74e4 * weather and currency now is CustomButtonTouchBarItem 2018-04-20 13:42:08 +03:00
Daniel Apatin
92c4eed4ed
Update WeatherBarItem.swift 2018-04-20 13:35:41 +03:00
Daniel Apatin
94d519cd65
Update CurrencyBarItem.swift 2018-04-20 13:35:21 +03:00
Daniel Apatin
7341136897
Update TouchBarController.swift 2018-04-20 13:34:56 +03:00
Daniel Apatin
4459a95691
Merge pull request #27 from Toxblh/ad-patch-weather
+ text and image sets for weather condition  ad committed
2018-04-20 11:19:52 +03:00
Daniel Apatin
7bc8458d8b
Update WeatherBarItem.swift 2018-04-20 11:18:11 +03:00
Daniel Apatin
bfc4dcf22c
Update ItemsParsing.swift 2018-04-20 11:17:29 +03:00
Daniel Apatin
4eb14ea166
Update TouchBarController.swift 2018-04-20 11:16:50 +03:00
ad
c15488b61f + text and image sets for weather condition 2018-04-20 11:13:03 +03:00
33bb3b8d65 Merge remote-tracking branch 'origin/master' into batteryButton 2018-04-19 20:53:41 +01:00
Daniel Apatin
c5a7a3142b
Fixed currency formatting (trailing zero), added colours 2018-04-19 21:54:12 +03:00
ad
5b08861dec * fixed formatting (trailing zero), added colours 2018-04-19 21:50:06 +03:00
ad
82f192a5c0 Merge branch 'weatherWidget' 2018-04-19 19:01:22 +03:00
ad
9230b1c3e6 Merge branch 'currencyWidget' 2018-04-19 19:01:14 +03:00
ad
647e0accea Merge branch 'openPresetFile' 2018-04-19 19:01:04 +03:00
Anton Palgunov
0ce6648bf9
Merge pull request #26 from ad/master
fixed deminiaturization and Finder opening
2018-04-19 16:59:36 +01:00
ad
fe3e647b56 Merge remote-tracking branch 'upstream/master' 2018-04-19 18:54:14 +03:00
ad
fe07ea3b46 * fixed deminiaturization and Finder opening 2018-04-19 18:53:29 +03:00
4b22514969 v0.10 - added open preset, better work reload 2018-04-19 16:23:27 +01:00
Anton Palgunov
03bcafd5ff
Merge pull request #25 from ad/openPresetFile
+ open preset from dialog (not replacing default file)
2018-04-19 16:19:45 +01:00
ad
fdd30413c4 * open dialog at default configs directory 2018-04-19 18:18:54 +03:00
ad
3a57269510 + open preset from dialog (not replacing default file) 2018-04-19 18:01:16 +03:00
Anton Palgunov
e9de35ee37
Merge pull request #24 from ad/currencyWidget
* currency list
2018-04-19 12:35:43 +01:00
Anton Palgunov
e027c640f7
Merge pull request #22 from ad/weatherWidget
Weather widget / volume slider fix / dock shadow
2018-04-19 12:27:44 +01:00
ad
242e2d3c22 * currency list 2018-04-18 22:14:10 +03:00
Daniel Apatin
b3500684d4
Merge pull request #1 from ad/currencyWidget
+ currency widget
2018-04-18 19:53:04 +03:00
ad
42c1ff27c4 + currency widget
{
        "type": "currency",
        "refreshInterval": 600,
        "align": "right",
        "from": "USD",
        "to": "RUB"
    },
2018-04-18 19:50:23 +03:00
ad
4c16321a4a + weather widget 2018-04-18 18:27:28 +03:00
ad
adaed36fbd Merge remote-tracking branch 'upstream/master' 2018-04-18 16:20:36 +03:00
f724a0d355 Draft. Has issues 2018-04-18 09:51:22 +01:00
6b2e06b967 update readme 2018-04-17 18:00:59 +01:00
4663eb9b27 v0.9 dock plugin 2018-04-17 09:35:24 +01:00
ad
7774b1fa5e * add shadow to app scrubber 2018-04-17 10:55:21 +03:00
Anton Palgunov
71ea94590b
Merge pull request #20 from ad/apps
Launched apps (like dock)
2018-04-16 23:15:47 +01:00
ad
20699e9de7 * removed unused code 2018-04-16 23:42:44 +03:00
ad
893e9f6982 * removed local changes 2018-04-16 23:07:15 +03:00
ad
7a3c4811ac * finally 2018-04-16 22:53:34 +03:00
ad
0ce5a49172 * removed buggy "recent apps" 2018-04-16 22:43:06 +03:00
ad
9db9df5641 * code cleanup 2018-04-16 22:37:53 +03:00
ad
69b3f9d238 * removed animation 2018-04-16 15:56:04 +03:00
ad
3d7bcd2d47 * 2018-04-16 15:36:15 +03:00
ad
a37bc94588 Merge remote-tracking branch 'upstream/master' 2018-04-16 11:27:16 +03:00
91ceea564e improve build.sh 2018-04-16 01:17:19 +01:00
ee49e94c73 up version 2018-04-16 00:58:14 +01:00
2f796cc07a Update default preset, now it's more useful and like a demo version. 2018-04-16 00:55:41 +01:00
b2b495b551 A bit better for icon only button, will render without title "Button" 2018-04-16 00:54:21 +01:00
818c32b0ed Reload preset from menu 2018-04-16 00:53:40 +01:00
d15764d41d A bit better Slider UI 2018-04-16 00:53:03 +01:00
9d343a31d3 rotate function for images 2018-04-15 23:17:53 +01:00
0956b4ebcd move CustomSlider to file 2018-04-15 21:52:36 +01:00
9ff68a6948 fix error with uncatching nil, when created slider without image 2018-04-15 21:11:12 +01:00
ad
ac8a05d2e5 + show running apps (like dock) 2018-04-15 22:55:20 +03:00
Anton Palgunov
75ea408370
Merge pull request #19 from ad/master
* prevent crash on return from sleep (removed lockFocus/unlockFocus)
2018-04-15 11:58:50 +01:00
ad
8760185ae7 * prevent crash on return from sleep (removed lockFocus/unlockFocus) 2018-04-15 12:38:07 +03:00
Serg
bd0ffbefe7
spaces in filepath should be escaped 2018-04-14 18:43:08 +07:00
Anton Palgunov
783ed34c0f
Merge pull request #16 from ad/master
* image can be set for slider
2018-04-14 11:12:50 +01:00
ad
37c2e8523c * image can be set for slider 2018-04-14 13:04:46 +03:00
3f2dad52ee upd Readme 2018-04-14 08:18:28 +01:00
Serg
d6b0c0313b
fix a typo 2018-04-14 13:04:56 +07:00
78509fb84d Less spacing in scroll 2018-04-14 02:17:01 +01:00
877c4268fd removed all warnings 2018-04-14 00:02:19 +01:00
0e07f71458 Scripts for create dmg 2018-04-13 22:14:38 +01:00
7100a6e6b8 New version. Layouts 2018-04-13 21:20:45 +01:00
Anton Palgunov
347bad8058
Merge pull request #13 from Toxblh/align
Align (left/center/right)
2018-04-13 21:18:29 +01:00
d72fb0ef7a Update my preset to actual. Fix xcode error 2018-04-13 21:15:02 +01:00
Serg
ccc3a2269b Merge branch 'master' into align 2018-04-14 03:00:58 +07:00
Serg
6d6048c49c build quickfix 2018-04-14 03:00:38 +07:00
Serg
055c5d77ef Merge remote-tracking branch 'ad/master' 2018-04-14 02:54:03 +07:00
Serg
f64c705c0d update example preset, update mine preset 2018-04-14 02:47:05 +07:00
ad
ca2ae30873 + configurable refresh interval for brightness 2018-04-13 22:42:53 +03:00
Serg
00fae08da0 mention change in docs 2018-04-14 02:33:34 +07:00
ad
f09b9155c4 + listener for changes of brightness (timer 0.5 sec)
Can't find any listener
2018-04-13 22:17:12 +03:00
Serg
9ef185d214 remove flexSpace 2018-04-14 02:09:34 +07:00
Serg
12f9220e34 implement scrolling view at the center 2018-04-14 01:58:07 +07:00
Serg
ba753ff331 Merge branch 'master' into align
# Conflicts:
#	MTMR/ItemsParsing.swift
2018-04-14 01:46:44 +07:00
Serg
aba1aea334
Merge pull request #12 from ad/master
+ listener for changes of sytem volume
2018-04-14 01:42:39 +07:00
ad
849b5b6db0 * fixed bug with mute at 0.0 2018-04-13 21:40:34 +03:00
ad
621cb3242b + listener for changes of sytem volume
known bug: sound not mutes at value 0.0
2018-04-13 21:27:59 +03:00
Serg
dd0ecf047d additional parameters as dictionary for easier mapping 2018-04-14 00:53:22 +07:00
Serg
bbc3d9dcfe filter center items, prepare for scrollView 2018-04-14 00:32:12 +07:00
Serg
85785018f4 create all items in advance 2018-04-14 00:23:47 +07:00
Serg
7b9f894bf3 parse align 2018-04-14 00:23:19 +07:00
3a57293206 add brightness icons like original Touch Bar 2018-04-13 17:21:51 +01:00
Serg
9afa9d7470 Merge branch 'tries/button-image'
# Conflicts:
#	MTMR.xcodeproj/project.pbxproj
2018-04-13 22:31:07 +07:00
Anton Palgunov
a86f7eb304
Update README.md 2018-04-13 14:54:44 +01:00
5013262740 v0.6.1 2018-04-13 14:46:38 +01:00
Anton Palgunov
106f297f57
Merge pull request #11 from ad/master
+ volume and brightness sliders
2018-04-13 14:44:39 +01:00
ad
115ea1372c + volume and brightness sliders 2018-04-13 16:25:25 +03:00
f45aad4fad add build.sh 2018-04-13 11:50:27 +01:00
e131f92fb5 del trim() in parser 2018-04-13 09:25:55 +01:00
69a8d81bce del trim() for aScript 2018-04-13 09:22:03 +01:00
Serg
7789407766 no staticImageButton in readme 2018-04-13 14:00:21 +07:00
6c331619d0 Added 2 Support function (trim and resize). Image and title close forever :) resize input image to good size 2018-04-13 01:15:11 +01:00
Anton Palgunov
9393a4e484
Merge pull request #9 from Toxblh/openPreferences
Open preset for editing then you click by Preferences in menu.
2018-04-12 20:07:38 +01:00
4b480dddc3 del the quote 2018-04-12 18:57:02 +01:00
a6f27a1fef open preset for editing then you click by Preferences in menu. 2018-04-12 18:55:54 +01:00
Serg
79eba1a281 image as generic parameter 2018-04-13 00:00:33 +07:00
Serg
2ad89bd8c8
fix a typo 2018-04-12 17:16:48 +07:00
Anton Palgunov
ec990e8147
Merge pull request #7 from Toxblh/example-scripts
Example presets and credits
2018-04-12 10:51:10 +01:00
16d8380830 link to releases 2018-04-12 10:49:37 +01:00
9e43a3b561 changes for Installation part 2018-04-12 10:48:07 +01:00
080bc67c54 upd from release 2018-04-12 10:46:15 +01:00
81131ec390 new screenshot. 2 json author's presets. Instruction. 2018-04-12 10:41:50 +01:00
Serg
cd6054af03 example presets and credits 2018-04-12 14:30:08 +07:00
eee33ee645 v0.6 2018-04-12 07:57:18 +01:00
Anton Palgunov
046f0fc1cf
Merge pull request #6 from Toxblh/cleanup
Cleanup project
2018-04-12 07:34:59 +01:00
Serg
4b7788fff7 some cleanup 2018-04-12 08:58:24 +07:00
Serg
ea5fbf0ee8 action has unified source too 2018-04-12 08:48:24 +07:00
Serg
d0cfa45b5d hacky solution to autohide if title is empty 2018-04-11 23:46:05 +07:00
ed14f34a2d conflict resolved 2018-04-11 17:09:34 +01:00
98d5a6eb0e add staticImageButton for buttons with custom image 2018-04-11 17:03:38 +01:00
Serg
b492b89d2f unified source for scripts (and image data in future) 2018-04-11 22:37:46 +07:00
Serg
ff58329691 displaysleep item 2018-04-11 13:47:09 +07:00
Serg
dd3ab9ac6f no codesign for debug, no UI tests 2018-04-11 07:29:28 +07:00
f08ce7a381 add FlexSpace. A bit refactor structure. Correct loading scripts from preset 2018-04-11 01:07:58 +01:00
Serg
11c8d167c0 custom width 2018-04-11 00:56:14 +07:00
Serg
0ba5984217 shell scripts and sleep implemented 2018-04-10 23:37:20 +07:00
Serg
1584cf979c cleanup extra code 2018-04-10 23:21:38 +07:00
753cfd8a31 add buttons: battery and time with format 2018-04-10 14:07:52 +01:00
34b51b2dd8 a bit fix json lint 2018-04-10 13:23:12 +01:00
Serg
5bda77d0d5 Merge remote-tracking branch 'upstream/master' into preset
# Conflicts:
#	README.md
2018-04-10 19:02:59 +07:00
Serg
2bc8912add weather – example on bundled scripts 2018-04-10 19:01:41 +07:00
Serg
b70c8ca4a3 start to log my dirty code 2018-04-10 18:44:47 +07:00
Serg
2750d00317 media keys implemented 2018-04-10 18:41:25 +07:00
Serg
4fb7cb9b99 cleaner custom actions, potentially able to register from the outside 2018-04-10 18:16:28 +07:00
Serg
651ddbef5c escape button implemented 2018-04-10 17:57:51 +07:00
Serg
fe5705627f missing changes :( 2018-04-10 17:51:21 +07:00
Serg
c0401ce83d exit touchbar implemented 2018-04-10 17:50:34 +07:00
Serg
1c45ca13a3 generic actions sketched 2018-04-10 17:39:07 +07:00
Serg
970fb99e33 interval for apple script 2018-04-10 16:58:52 +07:00
Serg
46f35f4737 preset working 2018-04-10 16:50:41 +07:00
713f4dffc7 added StatusMenu. Del from Dock. 2018-04-10 09:16:05 +01:00
Serg
d0efdef8c2 initial json parsing 2018-04-10 14:13:20 +07:00
65e3d67c32 a bit upd readme 2018-04-10 00:00:34 +01:00
e56a05657e add first badge to readme 😅 2018-04-09 23:25:30 +01:00
4a84532c56 upd roadmap 2018-04-09 23:00:39 +01:00
435439ecd2 Added Haptic feddback on click all buttons 2018-04-09 22:59:02 +01:00
Anton Palgunov
bb0c81ea76
Merge pull request #5 from ReDetection/master
Workaround for apple script
2018-04-09 17:29:14 +01:00
Serg
c022018ec4 workaround for NSAppleScript not being thread safe :( 2018-04-09 23:23:06 +07:00
Anton Palgunov
e10eed17ad
Merge pull request #4 from ReDetection/master
Async apple script
2018-04-09 16:10:37 +01:00
Serg
17e2362ebf async apple script; fix for those who have no helper apps 2018-04-09 22:07:15 +07:00
dd97c706f9 upd Roadmap 2018-04-09 15:39:29 +01:00
002af40756 upd roadmap 2018-04-09 15:37:30 +01:00
285fb1fb85 added sleep button 2018-04-09 15:26:04 +01:00
1e07deedae a bit fix for Icons 2018-04-09 14:25:34 +01:00
a86d31b0ac Added Icon for DockBar 2018-04-09 14:22:30 +01:00
0a0b393fb7 fix brithness keys 2018-04-09 12:38:05 +01:00
af83fa8a72 base added for plugins on appleScript 2018-04-09 12:29:14 +01:00
ab4aef4e2a added weather 2018-04-09 12:22:31 +01:00
e3f5da0294 Different approach 2018-04-09 11:59:44 +01:00
aaa7bc33bc avaliable for >=10.12.2 2018-04-09 11:50:41 +01:00
Anton Palgunov
74d57cdc7d
Merge pull request #3 from ReDetection/master
Basic AppleScript support
2018-04-09 11:42:19 +01:00
Serg
d48d590462 AppleScript and battery items 2018-04-09 17:32:27 +07:00
Serg
b3f461db45 time in separate file, divide bar in two parts 2018-04-09 17:11:52 +07:00
Serg
ee0eab6a76 convenient keycode buttons 2018-04-09 17:05:38 +07:00
320 changed files with 17016 additions and 669 deletions

6
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,6 @@
# These are supported funding model platforms
issuehunt: Toxblh
patreon: toxblh
ko_fi: toxblh
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4

22
.github/workflows/build-test.yml vendored Normal file
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
.github/workflows/publish.yml vendored Normal file
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

5
.gitignore vendored
View File

@ -1,3 +1,5 @@
.DS_Store
# Xcode # Xcode
# #
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
@ -5,6 +7,7 @@
## Build generated ## Build generated
build/ build/
DerivedData/ DerivedData/
Release/
## Various settings ## Various settings
*.pbxuser *.pbxuser
@ -66,3 +69,5 @@ fastlane/report.xml
fastlane/Preview.html fastlane/Preview.html
fastlane/screenshots fastlane/screenshots
fastlane/test_output fastlane/test_output
.vscode

View File

@ -7,63 +7,168 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
36300E83209F040900B31C71 /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
36300E8F20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */; };
36300E9120A2D2B200B31C71 /* BackgroundColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */; };
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */; };
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A778BD20A6C27100B38714 /* GeneralExtensions.swift */; };
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; };
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */; };
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
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 */; };
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */; };
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EC26CC43D40025BDA7 /* CPU.swift */; };
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */; };
5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */; };
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; };
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; };
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; };
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60669B4220AD8FA80074E817 /* GroupBarItem.swift */; };
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; };
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; };
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */; };
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; };
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B002E641216C0E38002774BA /* CoreDisplay.framework */; };
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; };
B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; };
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; };
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; };
B059D624205E04F3006E6B86 /* TouchBarItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* TouchBarItems.swift */; }; B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; };
B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; }; B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; };
B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */; };
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */; };
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126F0217BE19000A98970 /* WidgetProtocol.swift */; };
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08173262135F02B005D4908 /* NightShiftBarItem.swift */; };
B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B08173292135F354005D4908 /* CoreBrightness.framework */; };
B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B081732B213739FE005D4908 /* DnDBarItem.swift */; };
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B252205C7D8000BC04DC /* AppDelegate.swift */; }; B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B252205C7D8000BC04DC /* AppDelegate.swift */; };
B082B255205C7D8000BC04DC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B254205C7D8000BC04DC /* ViewController.swift */; };
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B082B256205C7D8000BC04DC /* Assets.xcassets */; }; B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B082B256205C7D8000BC04DC /* Assets.xcassets */; };
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B082B258205C7D8000BC04DC /* Main.storyboard */; }; B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B082B258205C7D8000BC04DC /* Main.storyboard */; };
B082B266205C7D8000BC04DC /* MTMRTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B265205C7D8000BC04DC /* MTMRTests.swift */; }; B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0846A742220C968000288A7 /* NetworkBarItem.swift */; };
B082B271205C7D8000BC04DC /* MTMRUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B270205C7D8000BC04DC /* MTMRUITests.swift */; }; B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */; };
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */; };
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */; }; B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */; };
B0C1CFCA205C97D30021C862 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1CFC9205C97D30021C862 /* WindowController.swift */; }; B0B17431207D6B590004B740 /* PlaySmart.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17427207D6B580004B740 /* PlaySmart.scpt */; };
B0B17432207D6B590004B740 /* Weather.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17428207D6B580004B740 /* Weather.scpt */; };
B0B17433207D6B590004B740 /* Finder.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17429207D6B580004B740 /* Finder.scpt */; };
B0B17434207D6B590004B740 /* Battery.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742A207D6B580004B740 /* Battery.scpt */; };
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742B207D6B590004B740 /* Spotify.next.scpt */; };
B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742C207D6B590004B740 /* iTunes.next.scpt */; };
B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742D207D6B590004B740 /* Spotify.nowPlaying.scpt */; };
B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742E207D6B590004B740 /* Vox.next.scpt */; };
B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */; };
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */; };
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; };
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; };
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; }; B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
B0F8771D207AD35400D6E430 /* battery.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0F8771C207AD35400D6E430 /* battery.scpt */; }; 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 PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */
B082B262205C7D8000BC04DC /* PBXContainerItemProxy */ = { B00D181E2152F507000806F4 /* CopyFiles */ = {
isa = PBXContainerItemProxy; isa = PBXCopyFilesBuildPhase;
containerPortal = B082B247205C7D8000BC04DC /* Project object */; buildActionMask = 12;
proxyType = 1; dstPath = "";
remoteGlobalIDString = B082B24E205C7D8000BC04DC; dstSubfolderSpec = 10;
remoteInfo = MTMR; files = (
B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
}; };
B082B26D205C7D8000BC04DC /* PBXContainerItemProxy */ = { /* End PBXCopyFilesBuildPhase section */
isa = PBXContainerItemProxy;
containerPortal = B082B247205C7D8000BC04DC /* Project object */;
proxyType = 1;
remoteGlobalIDString = B082B24E205C7D8000BC04DC;
remoteInfo = MTMR;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
36300E85209FD16700B31C71 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; };
36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptDefinitionTests.swift; sourceTree = "<group>"; };
36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundColorTests.swift; sourceTree = "<group>"; };
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewItem.swift; sourceTree = "<group>"; };
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralExtensions.swift; sourceTree = "<group>"; };
36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TECHNICAL_DEBT.md; sourceTree = "<group>"; };
36C2ECD2207B3B1D003CDA33 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 36C2ECD2207B3B1D003CDA33 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTouchBarItem.swift; sourceTree = "<group>"; };
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptTouchBarItem.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>"; };
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>"; };
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>"; };
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPUBarItem.swift; sourceTree = "<group>"; };
4CF222EC26CC43D40025BDA7 /* CPU.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPU.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>"; };
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>"; };
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = "<group>"; };
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; };
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; };
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; };
60669B4220AD8FA80074E817 /* GroupBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBarItem.swift; sourceTree = "<group>"; };
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = "<group>"; };
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = "<group>"; };
60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicBarItem.swift; sourceTree = "<group>"; };
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = "<group>"; };
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; };
B002E641216C0E38002774BA /* CoreDisplay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreDisplay.framework; path = ../../../../../System/Library/Frameworks/CoreDisplay.framework; sourceTree = "<group>"; };
B00D181C2152F4A5000806F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryBarItem.swift; sourceTree = "<group>"; };
B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; };
B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; };
B059D623205E04F3006E6B86 /* TouchBarItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarItems.swift; sourceTree = "<group>"; }; B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = "<group>"; };
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarPrivateApi.h; sourceTree = "<group>"; }; B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarPrivateApi.h; sourceTree = "<group>"; };
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TouchBarPrivateApi-Bridging.h"; sourceTree = "<group>"; }; B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TouchBarPrivateApi-Bridging.h"; sourceTree = "<group>"; };
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = "<group>"; }; B059D62C205F11E8006E6B86 /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = "<group>"; };
B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = "<group>"; };
B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroBarItem.swift; sourceTree = "<group>"; };
B08126F0217BE19000A98970 /* WidgetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetProtocol.swift; sourceTree = "<group>"; };
B08173262135F02B005D4908 /* NightShiftBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightShiftBarItem.swift; sourceTree = "<group>"; };
B08173282135F128005D4908 /* CBBlueLightClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBBlueLightClient.h; sourceTree = "<group>"; };
B08173292135F354005D4908 /* CoreBrightness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBrightness.framework; path = ../../../../../System/Library/PrivateFrameworks/CoreBrightness.framework; sourceTree = "<group>"; };
B081732B213739FE005D4908 /* DnDBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DnDBarItem.swift; sourceTree = "<group>"; };
B082B24F205C7D8000BC04DC /* MTMR.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MTMR.app; sourceTree = BUILT_PRODUCTS_DIR; }; B082B24F205C7D8000BC04DC /* MTMR.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MTMR.app; sourceTree = BUILT_PRODUCTS_DIR; };
B082B252205C7D8000BC04DC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; B082B252205C7D8000BC04DC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B082B254205C7D8000BC04DC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
B082B256205C7D8000BC04DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; B082B256205C7D8000BC04DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B082B259205C7D8000BC04DC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; B082B259205C7D8000BC04DC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
B082B25B205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B082B25B205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B082B25C205C7D8000BC04DC /* MTMR.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MTMR.entitlements; sourceTree = "<group>"; }; B082B25C205C7D8000BC04DC /* MTMR.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MTMR.entitlements; sourceTree = "<group>"; };
B082B261205C7D8000BC04DC /* MTMRTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B082B261205C7D8000BC04DC /* MTMRTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
B082B265205C7D8000BC04DC /* MTMRTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTMRTests.swift; sourceTree = "<group>"; };
B082B267205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B082B267205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B0846A742220C968000288A7 /* NetworkBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkBarItem.swift; sourceTree = "<group>"; };
B082B270205C7D8000BC04DC /* MTMRUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTMRUITests.swift; sourceTree = "<group>"; }; B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
B082B272205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultitouchSupport.framework; path = ../../../../../System/Library/PrivateFrameworks/MultitouchSupport.framework; sourceTree = "<group>"; };
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPress.swift; sourceTree = "<group>"; }; B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPress.swift; sourceTree = "<group>"; };
B0C1CFC9205C97D30021C862 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; }; B0B17427207D6B580004B740 /* PlaySmart.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PlaySmart.scpt; sourceTree = "<group>"; };
B0B17428207D6B580004B740 /* Weather.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Weather.scpt; sourceTree = "<group>"; };
B0B17429207D6B580004B740 /* Finder.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Finder.scpt; sourceTree = "<group>"; };
B0B1742A207D6B580004B740 /* Battery.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Battery.scpt; sourceTree = "<group>"; };
B0B1742B207D6B590004B740 /* Spotify.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Spotify.next.scpt; sourceTree = "<group>"; };
B0B1742C207D6B590004B740 /* iTunes.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.next.scpt; sourceTree = "<group>"; };
B0B1742D207D6B590004B740 /* Spotify.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Spotify.nowPlaying.scpt; sourceTree = "<group>"; };
B0B1742E207D6B590004B740 /* Vox.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.next.scpt; sourceTree = "<group>"; };
B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.nowPlaying.scpt; sourceTree = "<group>"; };
B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.nowPlaying.scpt; sourceTree = "<group>"; };
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportNSTouchBar.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>"; };
B0F8771C207AD35400D6E430 /* battery.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = battery.scpt; 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 */
@ -71,7 +176,11 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */,
B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */,
B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */, B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */,
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */,
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -82,19 +191,16 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
B082B269205C7D8000BC04DC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
B059D62B205F11E8006E6B86 /* Frameworks */ = { B059D62B205F11E8006E6B86 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B002E641216C0E38002774BA /* CoreDisplay.framework */,
B00D181C2152F4A5000806F4 /* Sparkle.framework */,
B08173292135F354005D4908 /* CoreBrightness.framework */,
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */,
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */, B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
); );
name = Frameworks; name = Frameworks;
@ -104,9 +210,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
36C2ECD2207B3B1D003CDA33 /* README.md */, 36C2ECD2207B3B1D003CDA33 /* README.md */,
36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */,
B082B251205C7D8000BC04DC /* MTMR */, B082B251205C7D8000BC04DC /* MTMR */,
B082B264205C7D8000BC04DC /* MTMRTests */, B082B264205C7D8000BC04DC /* MTMRTests */,
B082B26F205C7D8000BC04DC /* MTMRUITests */,
B082B250205C7D8000BC04DC /* Products */, B082B250205C7D8000BC04DC /* Products */,
B059D62B205F11E8006E6B86 /* Frameworks */, B059D62B205F11E8006E6B86 /* Frameworks */,
); );
@ -117,7 +223,6 @@
children = ( children = (
B082B24F205C7D8000BC04DC /* MTMR.app */, B082B24F205C7D8000BC04DC /* MTMR.app */,
B082B261205C7D8000BC04DC /* MTMRTests.xctest */, B082B261205C7D8000BC04DC /* MTMRTests.xctest */,
B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -125,21 +230,32 @@
B082B251205C7D8000BC04DC /* MTMR */ = { B082B251205C7D8000BC04DC /* MTMR */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */, B0B88A07208CD12000A2C160 /* Widgets */,
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */, B0B1743B207D6ED40004B740 /* CBridge */,
B082B252205C7D8000BC04DC /* AppDelegate.swift */, B0B17426207D6AFE0004B740 /* AppleScripts */,
B082B254205C7D8000BC04DC /* ViewController.swift */,
B082B256205C7D8000BC04DC /* Assets.xcassets */, B082B256205C7D8000BC04DC /* Assets.xcassets */,
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
B059D623205E04F3006E6B86 /* TouchBarItems.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
B0C1CFC9205C97D30021C862 /* WindowController.swift */,
B082B258205C7D8000BC04DC /* Main.storyboard */, B082B258205C7D8000BC04DC /* Main.storyboard */,
B082B25B205C7D8000BC04DC /* Info.plist */, B082B25B205C7D8000BC04DC /* Info.plist */,
B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */,
B082B25C205C7D8000BC04DC /* MTMR.entitlements */, B082B25C205C7D8000BC04DC /* MTMR.entitlements */,
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */, 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */, B082B252205C7D8000BC04DC /* AppDelegate.swift */,
B0F8771C207AD35400D6E430 /* battery.scpt */, B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
4CF222EC26CC43D40025BDA7 /* CPU.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
BAF5AB5624317B4300B04904 /* BasicView.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
B05600D22083E9BB00EB218D /* CustomSlider.swift */,
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */,
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */,
); );
path = MTMR; path = MTMR;
sourceTree = "<group>"; sourceTree = "<group>";
@ -147,19 +263,76 @@
B082B264205C7D8000BC04DC /* MTMRTests */ = { B082B264205C7D8000BC04DC /* MTMRTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B082B265205C7D8000BC04DC /* MTMRTests.swift */, 36300E85209FD16700B31C71 /* .travis.yml */,
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */,
B082B267205C7D8000BC04DC /* Info.plist */, B082B267205C7D8000BC04DC /* Info.plist */,
36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */,
36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */,
); );
path = MTMRTests; path = MTMRTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B082B26F205C7D8000BC04DC /* MTMRUITests */ = { B0B17426207D6AFE0004B740 /* AppleScripts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B082B270205C7D8000BC04DC /* MTMRUITests.swift */, 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */,
B082B272205C7D8000BC04DC /* Info.plist */, 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */,
B0B1742A207D6B580004B740 /* Battery.scpt */,
B0B17429207D6B580004B740 /* Finder.scpt */,
B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */,
B0B17427207D6B580004B740 /* PlaySmart.scpt */,
B0B1742B207D6B590004B740 /* Spotify.next.scpt */,
B0B1742D207D6B590004B740 /* Spotify.nowPlaying.scpt */,
B0B1742E207D6B590004B740 /* Vox.next.scpt */,
B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */,
B0B17428207D6B580004B740 /* Weather.scpt */,
); );
path = MTMRUITests; path = AppleScripts;
sourceTree = "<group>";
};
B0B1743B207D6ED40004B740 /* CBridge */ = {
isa = PBXGroup;
children = (
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */,
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */,
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */,
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */,
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */,
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */,
B08173282135F128005D4908 /* CBBlueLightClient.h */,
);
path = CBridge;
sourceTree = "<group>";
};
B0B88A07208CD12000A2C160 /* Widgets */ = {
isa = PBXGroup;
children = (
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */,
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */,
B081732B213739FE005D4908 /* DnDBarItem.swift */,
60669B4220AD8FA80074E817 /* GroupBarItem.swift */,
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */,
60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */,
B0846A742220C968000288A7 /* NetworkBarItem.swift */,
B08173262135F02B005D4908 /* NightShiftBarItem.swift */,
B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */,
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */,
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
);
path = Widgets;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
@ -172,6 +345,8 @@
B082B24B205C7D8000BC04DC /* Sources */, B082B24B205C7D8000BC04DC /* Sources */,
B082B24C205C7D8000BC04DC /* Frameworks */, B082B24C205C7D8000BC04DC /* Frameworks */,
B082B24D205C7D8000BC04DC /* Resources */, B082B24D205C7D8000BC04DC /* Resources */,
B00D181E2152F507000806F4 /* CopyFiles */,
B0679BBF215AE085000FC6B4 /* ShellScript */,
); );
buildRules = ( buildRules = (
); );
@ -193,31 +368,12 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
B082B263205C7D8000BC04DC /* PBXTargetDependency */,
); );
name = MTMRTests; name = MTMRTests;
productName = MTMRTests; productName = MTMRTests;
productReference = B082B261205C7D8000BC04DC /* MTMRTests.xctest */; productReference = B082B261205C7D8000BC04DC /* MTMRTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test"; productType = "com.apple.product-type.bundle.unit-test";
}; };
B082B26B205C7D8000BC04DC /* MTMRUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = B082B27B205C7D8000BC04DC /* Build configuration list for PBXNativeTarget "MTMRUITests" */;
buildPhases = (
B082B268205C7D8000BC04DC /* Sources */,
B082B269205C7D8000BC04DC /* Frameworks */,
B082B26A205C7D8000BC04DC /* Resources */,
);
buildRules = (
);
dependencies = (
B082B26E205C7D8000BC04DC /* PBXTargetDependency */,
);
name = MTMRUITests;
productName = MTMRUITests;
productReference = B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@ -225,12 +381,13 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930; LastUpgradeCheck = 1010;
ORGANIZATIONNAME = "Anton Palgunov"; ORGANIZATIONNAME = "Anton Palgunov";
TargetAttributes = { TargetAttributes = {
B082B24E205C7D8000BC04DC = { B082B24E205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic; LastSwiftMigration = 1020;
ProvisioningStyle = Manual;
SystemCapabilities = { SystemCapabilities = {
com.apple.Sandbox = { com.apple.Sandbox = {
enabled = 0; enabled = 0;
@ -239,13 +396,7 @@
}; };
B082B260205C7D8000BC04DC = { B082B260205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic; LastSwiftMigration = 1020;
TestTargetID = B082B24E205C7D8000BC04DC;
};
B082B26B205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic;
TestTargetID = B082B24E205C7D8000BC04DC;
}; };
}; };
}; };
@ -264,7 +415,6 @@
targets = ( targets = (
B082B24E205C7D8000BC04DC /* MTMR */, B082B24E205C7D8000BC04DC /* MTMR */,
B082B260205C7D8000BC04DC /* MTMRTests */, B082B260205C7D8000BC04DC /* MTMRTests */,
B082B26B205C7D8000BC04DC /* MTMRUITests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -274,9 +424,22 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B0B17434207D6B590004B740 /* Battery.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 */,
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */,
5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */,
B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */,
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */, B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */,
B0F8771D207AD35400D6E430 /* battery.scpt in Resources */, B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */,
B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */,
B0B17433207D6B590004B740 /* Finder.scpt in Resources */,
B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */,
B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */,
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */,
B0B17432207D6B590004B740 /* Weather.scpt in Resources */,
B0B17431207D6B590004B740 /* PlaySmart.scpt in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -287,14 +450,27 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
B082B26A205C7D8000BC04DC /* Resources */ = { /* End PBXResourcesBuildPhase section */
isa = PBXResourcesBuildPhase;
/* Begin PBXShellScriptBuildPhase section */
B0679BBF215AE085000FC6B4 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n";
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
B082B24B205C7D8000BC04DC /* Sources */ = { B082B24B205C7D8000BC04DC /* Sources */ = {
@ -302,12 +478,45 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */, B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
B082B255205C7D8000BC04DC /* ViewController.swift in Sources */, B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
B0C1CFCA205C97D30021C862 /* WindowController.swift in Sources */, 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */,
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */, B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */,
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */,
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
B059D624205E04F3006E6B86 /* TouchBarItems.swift in Sources */, B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */,
B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */,
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */,
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */,
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */,
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */,
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */,
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -315,33 +524,16 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B082B266205C7D8000BC04DC /* MTMRTests.swift in Sources */, 36300E9120A2D2B200B31C71 /* BackgroundColorTests.swift in Sources */,
); 36300E8F20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift in Sources */,
runOnlyForDeploymentPostprocessing = 0; 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */,
}; 36300E83209F040900B31C71 /* SupportHelpers.swift in Sources */,
B082B268205C7D8000BC04DC /* Sources */ = { 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */,
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B082B271205C7D8000BC04DC /* MTMRUITests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B082B263205C7D8000BC04DC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B082B24E205C7D8000BC04DC /* MTMR */;
targetProxy = B082B262205C7D8000BC04DC /* PBXContainerItemProxy */;
};
B082B26E205C7D8000BC04DC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B082B24E205C7D8000BC04DC /* MTMR */;
targetProxy = B082B26D205C7D8000BC04DC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
B082B258205C7D8000BC04DC /* Main.storyboard */ = { B082B258205C7D8000BC04DC /* Main.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -385,7 +577,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -404,7 +595,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.12.2;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@ -457,7 +648,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.12.2;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@ -468,19 +659,20 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/TouchBarPrivateApi-Bridging.h"; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@ -488,19 +680,22 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB; DEVELOPMENT_TEAM = D6D8BR2QNB;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/TouchBarPrivateApi-Bridging.h"; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@ -508,16 +703,12 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB;
INFOPLIST_FILE = MTMRTests/Info.plist; INFOPLIST_FILE = MTMRTests/Info.plist;
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.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MTMR.app/Contents/MacOS/MTMR";
}; };
name = Debug; name = Debug;
}; };
@ -525,48 +716,14 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB; DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = MTMRTests/Info.plist; INFOPLIST_FILE = MTMRTests/Info.plist;
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.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MTMR.app/Contents/MacOS/MTMR";
};
name = Release;
};
B082B27C205C7D8000BC04DC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB;
INFOPLIST_FILE = MTMRUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TEST_TARGET_NAME = MTMR;
};
name = Debug;
};
B082B27D205C7D8000BC04DC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB;
INFOPLIST_FILE = MTMRUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TEST_TARGET_NAME = MTMR;
}; };
name = Release; name = Release;
}; };
@ -600,15 +757,6 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
B082B27B205C7D8000BC04DC /* Build configuration list for PBXNativeTarget "MTMRUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B082B27C205C7D8000BC04DC /* Debug */,
B082B27D205C7D8000BC04DC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = B082B247205C7D8000BC04DC /* Project object */; rootObject = B082B247205C7D8000BC04DC /* Project object */;

View File

@ -0,0 +1,5 @@
<?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">
<plist version="1.0">
<dict/>
</plist>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B260205C7D8000BC04DC"
BuildableName = "MTMRTests.xctest"
BlueprintName = "MTMRTests"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B260205C7D8000BC04DC"
BuildableName = "MTMRTests.xctest"
BlueprintName = "MTMRTests"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -7,21 +7,165 @@
// //
import Cocoa import Cocoa
import Sparkle
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
var isBlockedApp: Bool = false
private var fileSystemSource: DispatchSourceFileSystemObject?
func applicationDidFinishLaunching(_: Notification) {
// Configure Sparkle
SUUpdater.shared().automaticallyDownloadsUpdates = false
SUUpdater.shared().automaticallyChecksForUpdates = true
SUUpdater.shared().checkForUpdatesInBackground()
AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
func applicationDidFinishLaunching(_ aNotification: Notification) {
TouchBarController.shared.setupControlStripPresence() TouchBarController.shared.setupControlStripPresence()
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = #imageLiteral(resourceName: "StatusImage")
}
createMenu()
reloadOnDefaultConfigChanged()
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didActivateApplicationNotification, object: nil)
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_: Notification) {}
// Insert code here to tear down your application
@objc func updateIsBlockedApp() {
if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
} else {
isBlockedApp = false
}
createMenu()
} }
@objc func openPreferences(_: Any?) {
let task = Process()
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let presetPath = appSupportDirectory.appending("/items.json")
task.launchPath = "/usr/bin/open"
task.arguments = [presetPath]
task.launch()
}
@objc func toggleControlStrip(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.showControlStripState = item.state == .off
TouchBarController.shared.resetControlStrip()
}
@objc func toggleBlackListedApp(_: Any?) {
if let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier {
if let index = TouchBarController.shared.blacklistAppIdentifiers.firstIndex(of: appIdentifier) {
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index)
} else {
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier)
}
AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers
TouchBarController.shared.updateActiveApp()
updateIsBlockedApp()
}
}
@objc func toggleHapticFeedback(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.hapticFeedbackState = item.state == .on
}
@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?) {
let dialog = NSOpenPanel()
dialog.title = "Choose a items.json file"
dialog.showsResizeIndicator = true
dialog.showsHiddenFiles = true
dialog.canChooseDirectories = false
dialog.canCreateDirectories = false
dialog.allowsMultipleSelection = false
dialog.allowedFileTypes = ["json"]
dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true)
if dialog.runModal() == .OK, let path = dialog.url?.path {
TouchBarController.shared.reloadPreset(path: path)
}
}
@objc func toggleStartAtLogin(_: Any?) {
LaunchAtLoginController().setLaunchAtLogin(!LaunchAtLoginController().launchAtLogin, for: NSURL.fileURL(withPath: Bundle.main.bundlePath))
createMenu()
}
func createMenu() {
let menu = NSMenu()
let startAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleStartAtLogin(_:)), keyEquivalent: "L")
startAtLogin.state = LaunchAtLoginController().launchAtLogin ? .on : .off
let toggleBlackList = NSMenuItem(title: "Toggle current app in blacklist", action: #selector(toggleBlackListedApp(_:)), keyEquivalent: "B")
toggleBlackList.state = isBlockedApp ? .on : .off
let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
hideControlStrip.state = AppSettings.showControlStripState ? .off : .on
let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H")
hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off
let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "")
multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off
let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
settingSeparator.isEnabled = false
menu.addItem(withTitle: "Preferences", action: #selector(openPreferences(_:)), keyEquivalent: ",")
menu.addItem(withTitle: "Open preset", action: #selector(openPreset(_:)), keyEquivalent: "O")
menu.addItem(withTitle: "Check for Updates...", action: #selector(SUUpdater.checkForUpdates(_:)), keyEquivalent: "").target = SUUpdater.shared()
menu.addItem(NSMenuItem.separator())
menu.addItem(settingSeparator)
menu.addItem(hapticFeedback)
menu.addItem(hideControlStrip)
menu.addItem(toggleBlackList)
menu.addItem(startAtLogin)
menu.addItem(multitouchGestures)
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu
}
func reloadOnDefaultConfigChanged() {
let file = NSURL.fileURL(withPath: standardConfigPath)
let fd = open(file.path, O_EVTONLY)
fileSystemSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue(label: "DefaultConfigChanged"))
fileSystemSource?.setEventHandler(handler: {
print("Config changed, reloading...")
DispatchQueue.main.async {
TouchBarController.shared.reloadPreset(path: file.path)
}
})
fileSystemSource?.setCancelHandler(handler: {
close(fd)
})
fileSystemSource?.resume()
}
} }

33
MTMR/AppSettings.swift Normal file
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()
}
}
}

View File

@ -0,0 +1,97 @@
import Foundation
class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
private var script: NSAppleScript!
private let interval: TimeInterval
private var forceHideConstraint: NSLayoutConstraint!
private let alternativeImages: [String: SourceProtocol]
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) {
self.interval = interval
self.alternativeImages = alternativeImages
super.init(identifier: identifier, title: "")
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
title = "scheduled"
DispatchQueue.appleScriptQueue.async {
guard let script = source.appleScript else {
DispatchQueue.main.async {
self.title = "no script"
}
return
}
self.script = script
DispatchQueue.main.async {
self.isBordered = false
}
var error: NSDictionary?
guard script.compileAndReturnError(&error) else {
#if DEBUG
print(error?.description ?? "unknown error")
#endif
DispatchQueue.main.async {
self.title = "error"
}
return
}
self.refreshAndSchedule()
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
#if DEBUG
print("refresh happened (interval \(interval)), self \(identifier.rawValue))")
#endif
let scriptResult = execute()
DispatchQueue.main.async {
self.title = scriptResult
self.forceHideConstraint.isActive = scriptResult == ""
#if DEBUG
print("did set new script result title \(scriptResult)")
#endif
}
DispatchQueue.appleScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
self?.refreshAndSchedule()
}
}
func updateIcon(iconLabel: String) {
if alternativeImages[iconLabel] != nil {
DispatchQueue.main.async {
self.image = self.alternativeImages[iconLabel]!.image
}
} else {
print("Cannot find icon with label \"\(iconLabel)\"")
}
}
func execute() -> String {
var error: NSDictionary?
let output = script.executeAndReturnError(&error)
if let error = error {
print(error)
return "error"
}
if output.descriptorType == typeAEList {
let arr = Array(1...output.numberOfItems).compactMap({ output.atIndex($0)!.stringValue ?? "" })
if arr.count <= 0 {
return ""
} else if arr.count == 1 {
return arr[0]
} else {
updateIcon(iconLabel: arr[1])
return arr[0]
}
}
return output.stringValue ?? ""
}
}
extension DispatchQueue {
static let appleScriptQueue = DispatchQueue(label: "mtmr.applescript")
}

View File

@ -3,27 +3,27 @@ set percent to word 6 of theList's item 2
set charge to word 7 of theList's item 2 set charge to word 7 of theList's item 2
if (charge = "charging") then if (charge = "charging") then
set iconC to "⚡️" set iconC to "⚡️"
else else
set iconC to "" set iconC to ""
end if end if
set remainingRaw to my split(theList's item 2, " ") set remainingRaw to my split(theList's item 2, " ")
set remainingTime to remainingRaw's item 5 set remainingTime to remainingRaw's item 5
if (remainingTime = "(no") then if (remainingTime = "(no") then
set strTime to " (?)" set strTime to " (?)"
else if (remainingTime = "0:00") then else if (remainingTime = "0:00") then
set strTime to "" set strTime to ""
else else
set strTime to " (" & remainingTime & ")" set strTime to " (" & remainingTime & ")"
end if end if
return iconC & percent & "%" & strTime return iconC & percent & "%" & strTime
to split(someText, delimiter) to split(someText, delimiter)
set AppleScript's text item delimiters to delimiter set AppleScript's text item delimiters to delimiter
set someText to someText's text items set someText to someText's text items
set AppleScript's text item delimiters to {""} set AppleScript's text item delimiters to {""}
return someText return someText
end split end split

View File

@ -0,0 +1,7 @@
tell application "Finder"
if not (exists window 1) then
make new Finder window
set target of front window to path to home folder as string
end if
activate
end tell

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

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 ""

View File

@ -0,0 +1,15 @@
if application "iTunes" is running then
tell application "iTunes" to playpause
end if
if application "Music" is running then
tell application "Music" to playpause
end if
if application "Spotify" is running then
tell application "Spotify" to playpause
end if
if application "VOX" is running then
tell application "VOX" to playpause
end if

View File

@ -0,0 +1,7 @@
if application "Spotify" is running then
tell application "Spotify"
if player state is playing then
next track
end if
end tell
end if

View File

@ -0,0 +1,10 @@
if application "Spotify" is running then
tell application "Spotify"
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 ""

View File

@ -0,0 +1,7 @@
if application "VOX" is running then
tell application "VOX"
if player state is 1 then
next
end if
end tell
end if

View File

@ -0,0 +1,10 @@
if application "VOX" is running then
tell application "VOX"
if player state is 1 then
return (get artist) & " " & (get track)
else
return ""
end if
end tell
end if
return ""

View File

@ -0,0 +1,32 @@
# This script requires two libs. Download them:
# https://itunes.apple.com/ru/app/json-helper-for-applescript/id453114608?l=en&mt=12
# https://itunes.apple.com/ru/app/location-helper-for-applescript/id488536386?mt=12
tell application "Location Helper"
set clocation_coords to get location coordinates
tell application "JSON Helper"
set weather to fetch JSON from "http://api.openweathermap.org/data/2.5/weather?lat=" & item 1 of clocation_coords & "&lon=" & item 2 of clocation_coords & "&units=metric&appid=32c4256d09a4c52b38aecddba7a078f6"
set temp to temp of main of weather as string
set cond_icon to icon of item 1 of weather of weather as string
if cond_icon is in ["01d", "01n"] then
set cond to "☀️"
else if cond_icon is in ["02d", "02n"] then
set cond to "⛅️"
else if cond_icon is in ["03d", "03n", "04d", "04n"] then
set cond to "☁️"
else if cond_icon is in ["09d", "09n"] then
set cond to "🌧"
else if cond_icon is in ["10d", "10n"] then
set cond to "🌦"
else if cond_icon is in ["11d", "11n"] then
set cond to "🌩"
else if cond_icon is in ["13d", "13n"] then
set cond to "❄️"
else if cond_icon is in ["50d", "50n"] then
set cond to "🌫"
else
set cond to ""
end if
set temp_round to round (temp * 1.0)
return cond & " " & temp_round & "°C"
end tell
end tell

View File

@ -0,0 +1,7 @@
if application "iTunes" is running then
tell application "iTunes"
if player state is playing then
next track
end if
end tell
end if

View File

@ -0,0 +1,10 @@
if application "iTunes" is running then
tell application "iTunes"
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 ""

View File

@ -1,53 +1,63 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "mac",
"size" : "16x16", "size" : "16x16",
"idiom" : "mac",
"filename" : "logo-16.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac",
"size" : "16x16", "size" : "16x16",
"idiom" : "mac",
"filename" : "logo-32.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac",
"size" : "32x32", "size" : "32x32",
"idiom" : "mac",
"filename" : "logo-32.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac",
"size" : "32x32", "size" : "32x32",
"idiom" : "mac",
"filename" : "logo-64.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac",
"size" : "128x128", "size" : "128x128",
"idiom" : "mac",
"filename" : "logo-128.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac",
"size" : "128x128", "size" : "128x128",
"idiom" : "mac",
"filename" : "logo-256.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac",
"size" : "256x256", "size" : "256x256",
"idiom" : "mac",
"filename" : "logo-256.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac",
"size" : "256x256", "size" : "256x256",
"idiom" : "mac",
"filename" : "logo-512.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "mac",
"size" : "512x512", "size" : "512x512",
"idiom" : "mac",
"filename" : "logo-512.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "mac",
"size" : "512x512", "size" : "512x512",
"idiom" : "mac",
"filename" : "logo-1024.png",
"scale" : "2x" "scale" : "2x"
} }
], ],

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "StatusImage.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "brightnessDown.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "brightnessUp.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "cpu.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "sun-icon-256.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "39857.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "dnd-off.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "dnd-on.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "KeyboardBrightDown.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "KeyboardBrightUp.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "NightShift.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "NightShiftEnabled.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/> <deployment identifier="macosx"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Application--> <!--Application-->
@ -619,7 +619,7 @@
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE"> <menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections> <connections>
<action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/> <action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa"> <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
@ -676,54 +676,10 @@
</application> </application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="MTMR" customModuleProvider="target"/> <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="MTMR" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="VW7-73-dHf" customClass="SUUpdater"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="75" y="0.0"/> <point key="canvasLocation" x="75" y="0.0"/>
</scene> </scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" customClass="WindowController" customModule="MTMR" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
<connections>
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="MTMR" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="178" height="57"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bso-ZY-Qqn">
<rect key="frame" x="18" y="20" width="142" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="My TouchBar. My rules" id="cmP-Ef-Jrj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="74" y="613"/>
</scene>
</scenes> </scenes>
</document> </document>

107
MTMR/BasicView.swift Normal file
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 {
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP)
} else if position < prevPos {
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN)
}
legacyPrevPositions[fingers] = position
}
}
}
case .ended:
print("gesture ended \(position - prevPositions[fingers]!) \(fingers)")
for item in swipeItems {
item.processEvent(offset: position - prevPositions[fingers]!, fingers: fingers)
}
default:
break
}
}
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
}
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 3, state: sender!.state)
}
@objc func fourfingersHandler(_ sender: NSGestureRecognizer?) {
let position = (sender?.location(in: sender?.view).x)!
self.gestureHandler(position: position, fingers: 4, state: sender!.state)
}
}

View File

@ -0,0 +1,326 @@
//
// ANSIEscapeHelper.h
// AnsiColorsTest
//
// Created by Ali Rantakari on 18.3.09.
//
// Version 0.9.6
//
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <Cocoa/Cocoa.h>
#if !__has_feature(objc_arc)
#warning "This code requires ARC to be enabled."
#endif
// dictionary keys for the SGR code dictionaries that the array
// escapeCodesForString:cleanString: returns contains
#define kAMRCodeDictKey_code @"code"
#define kAMRCodeDictKey_location @"location"
// dictionary keys for the string formatting attribute
// dictionaries that the array attributesForString:cleanString:
// returns contains
#define kAMRAttrDictKey_range @"range"
#define kAMRAttrDictKey_attrName @"attributeName"
#define kAMRAttrDictKey_attrValue @"attributeValue"
/*!
@enum AMR_SGRCode
@abstract SGR (Select Graphic Rendition) ANSI control codes.
*/
typedef enum
{
AMR_SGRCodeNoneOrInvalid = -1,
AMR_SGRCodeAllReset = 0,
AMR_SGRCodeIntensityBold = 1,
AMR_SGRCodeIntensityFaint = 2,
AMR_SGRCodeIntensityNormal = 22,
AMR_SGRCodeItalicOn = 3,
AMR_SGRCodeUnderlineSingle = 4,
AMR_SGRCodeUnderlineDouble = 21,
AMR_SGRCodeUnderlineNone = 24,
AMR_SGRCodeFgBlack = 30,
AMR_SGRCodeFgRed = 31,
AMR_SGRCodeFgGreen = 32,
AMR_SGRCodeFgYellow = 33,
AMR_SGRCodeFgBlue = 34,
AMR_SGRCodeFgMagenta = 35,
AMR_SGRCodeFgCyan = 36,
AMR_SGRCodeFgWhite = 37,
AMR_SGRCodeFgReset = 39,
AMR_SGRCodeBgBlack = 40,
AMR_SGRCodeBgRed = 41,
AMR_SGRCodeBgGreen = 42,
AMR_SGRCodeBgYellow = 43,
AMR_SGRCodeBgBlue = 44,
AMR_SGRCodeBgMagenta = 45,
AMR_SGRCodeBgCyan = 46,
AMR_SGRCodeBgWhite = 47,
AMR_SGRCodeBgReset = 49,
AMR_SGRCodeFgBrightBlack = 90,
AMR_SGRCodeFgBrightRed = 91,
AMR_SGRCodeFgBrightGreen = 92,
AMR_SGRCodeFgBrightYellow = 93,
AMR_SGRCodeFgBrightBlue = 94,
AMR_SGRCodeFgBrightMagenta = 95,
AMR_SGRCodeFgBrightCyan = 96,
AMR_SGRCodeFgBrightWhite = 97,
AMR_SGRCodeBgBrightBlack = 100,
AMR_SGRCodeBgBrightRed = 101,
AMR_SGRCodeBgBrightGreen = 102,
AMR_SGRCodeBgBrightYellow = 103,
AMR_SGRCodeBgBrightBlue = 104,
AMR_SGRCodeBgBrightMagenta = 105,
AMR_SGRCodeBgBrightCyan = 106,
AMR_SGRCodeBgBrightWhite = 107
} AMR_SGRCode;
/*!
@class AMR_ANSIEscapeHelper
@abstract Contains helper methods for dealing with strings
that contain ANSI escape sequences for formatting (colors,
underlining, bold etc.)
*/
@interface AMR_ANSIEscapeHelper : NSObject
/*!
@property defaultStringColor
@abstract The default color used when creating an attributed string (default is black).
*/
@property(copy) NSColor *defaultStringColor;
/*!
@property font
@abstract The font to use when creating string formatting attribute values.
*/
@property(copy) NSFont *font;
/*!
@property ansiColors
@abstract The colors to use for displaying ANSI colors.
@discussion Keys in this dictionary should be NSNumber objects containing SGR code
values from the AMR_SGRCode enum. The corresponding values for these keys
should be NSColor objects. If this property is nil or if it doesn't
contain a key for a specific SGR code, the default color will be used
instead.
*/
@property(retain) NSMutableDictionary *ansiColors;
/*!
@method attributedStringWithANSIEscapedString:
@abstract Returns an attributed string that corresponds both in contents
and formatting to a given string that contains ANSI escape
sequences.
@param aString A String containing ANSI escape sequences
@result An attributed string that mimics as closely as possible
the formatting of the given ANSI-escaped string.
*/
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
/*!
@method ansiEscapedStringWithAttributedString:
@abstract Returns a string containing ANSI escape sequences that corresponds
both in contents and formatting to a given attributed string.
@param aAttributedString An attributed string
@result A string that mimics as closely as possible
the formatting of the given attributed string with
ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
/*!
@method escapeCodesForString:cleanString:
@abstract Returns an array of SGR codes and their locations from a
string containing ANSI escape sequences as well as a "clean"
version of the string (i.e. one without the ANSI escape
sequences.)
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences)
@result An array of NSDictionary objects, each of which has
an NSNumber value for the key "code" (specifying an SGR code) and
another NSNumber value for the key "location" (specifying the
location of the code within aCleanString.)
*/
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method ansiEscapedStringWithCodesAndLocations:cleanString:
@abstract Returns a string containing ANSI escape codes for formatting based
on a string and an array of SGR codes and their locations within
the given string.
@param aCodesArray An array of NSDictionary objects, each of which should have
an NSNumber value for the key "code" (specifying an SGR
code) and another NSNumber value for the key "location"
(specifying the location of this SGR code in aCleanString.)
@param aCleanString The string to which to insert the ANSI escape codes
described in aCodesArray.
@result A string containing ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
/*!
@method attributesForString:cleanString:
@abstract Convert ANSI escape sequences in a string to string formatting attributes.
@discussion Given a string with some ANSI escape sequences in it, this method returns
attributes for formatting the specified string according to those ANSI
escape sequences as well as a "clean" (i.e. free of the escape sequences)
version of this string.
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences.) Pass in NULL if you're not
interested in this.
@result An array containing NSDictionary objects, each of which has keys "range"
(an NSValue containing an NSRange, specifying the range for the
attribute within the "clean" version of aString), "attributeName" (an
NSString) and "attributeValue" (an NSObject). You may use these as
arguments for NSMutableAttributedString's methods for setting the
visual formatting.
*/
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method AMR_SGRCode:endsFormattingIntroducedByCode:
@abstract Whether the occurrence of a given SGR code would end the formatting run
introduced by another SGR code.
@discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
specifying a foreground color would end the formatting run
introduced by a foreground color -specifying SGR code.
@param endCode The SGR code to test as a candidate for ending the formatting run
introduced by startCode
@param startCode The SGR code that has introduced a formatting run
@result YES if the occurrence of endCode would end the formatting run
introduced by startCode, NO otherwise.
*/
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
/*!
@method colorForSGRCode:
@abstract Returns the color to use for displaying a specific ANSI color.
@discussion This method first considers the values set in the ansiColors
property and only then the standard basic colors (NSColor's
redColor, blueColor etc.)
@param code An SGR code that specifies an ANSI color.
@result The color to use for displaying the ANSI color specified by code.
*/
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
/*!
@method AMR_SGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that corresponds to a given color.
@discussion This method matches colors to their equivalent SGR codes
by going through the colors specified in the ansiColors
dictionary, and if ansiColors is null or if a match is
not found there, by comparing the given color to the
standard basic colors (NSColor's redColor, blueColor
etc.) The comparison is done simply by checking for
equality.
@param aColor The color to get a corresponding SGR code for
@param aForeground Whether you want a foreground or background color code
@result SGR code that corresponds with aColor.
*/
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
/*!
@method closestSGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that represents the closest ANSI
color to a given color.
@discussion This method attempts to find the closest ANSI color to
aColor and return its SGR code.
@param color The color to get a closest color SGR code match for
@param foreground Whether you want a foreground or background color code
@result SGR code for the ANSI color that is closest to aColor.
*/
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
@end

View File

@ -0,0 +1,996 @@
//
// ANSIEscapeHelper.m
//
// Created by Ali Rantakari on 18.3.09.
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
todo:
- don't add useless "reset" escape codes to the string in
-ansiEscapedStringWithAttributedString:
*/
#import "AMR_ANSIEscapeHelper.h"
// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
// (add your own CSI:Miami joke here)
#define kANSIEscapeCSI @"\033["
// the end byte of an SGR (Select Graphic Rendition)
// ANSI Escape Sequence
#define kANSIEscapeSGREnd @"m"
// color definition helper macros
#define kBrightColorBrightness 1.0
#define kBrightColorSaturation 0.4
#define kBrightColorAlpha 1.0
#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
// default colors
#define kDefaultANSIColorFgBlack NSColor.blackColor
#define kDefaultANSIColorFgRed NSColor.redColor
#define kDefaultANSIColorFgGreen NSColor.greenColor
#define kDefaultANSIColorFgYellow NSColor.yellowColor
#define kDefaultANSIColorFgBlue NSColor.blueColor
#define kDefaultANSIColorFgMagenta NSColor.magentaColor
#define kDefaultANSIColorFgCyan NSColor.cyanColor
#define kDefaultANSIColorFgWhite NSColor.whiteColor
#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0)
#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0)
#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0)
#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0)
#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0)
#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5)
#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor
#define kDefaultANSIColorBgBlack NSColor.blackColor
#define kDefaultANSIColorBgRed NSColor.redColor
#define kDefaultANSIColorBgGreen NSColor.greenColor
#define kDefaultANSIColorBgYellow NSColor.yellowColor
#define kDefaultANSIColorBgBlue NSColor.blueColor
#define kDefaultANSIColorBgMagenta NSColor.magentaColor
#define kDefaultANSIColorBgCyan NSColor.cyanColor
#define kDefaultANSIColorBgWhite NSColor.whiteColor
#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack
#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed
#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen
#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow
#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue
#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta
#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan
#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite
#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
#define kDefaultForegroundColor NSColor.blackColor
// minimum weight for an NSFont for it to be considered bold
#define kBoldFontMinWeight 9
@implementation AMR_ANSIEscapeHelper
- (id) init
{
if (!(self = [super init]))
return nil;
self.ansiColors = [NSMutableDictionary dictionary];
return self;
}
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
{
if (aString == nil)
return nil;
NSString *cleanString;
NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString:cleanString
attributes:@{
NSFontAttributeName: self.font ?: kDefaultFontSize,
NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
}];
for (NSDictionary *thisAttributeDict in attributesAndRanges)
{
[attributedString
addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
value:thisAttributeDict[kAMRAttrDictKey_attrValue]
range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
];
}
return attributedString;
}
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
{
NSMutableArray *codesAndLocations = [NSMutableArray array];
NSArray *attrNames = @[
NSFontAttributeName, NSForegroundColorAttributeName,
NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
];
for (NSString *thisAttrName in attrNames)
{
NSRange limitRange = NSMakeRange(0, aAttributedString.length);
id attributeValue;
NSRange effectiveRange;
while (limitRange.length > 0)
{
attributeValue = [aAttributedString
attribute:thisAttrName
atIndex:limitRange.location
longestEffectiveRange:&effectiveRange
inRange:limitRange
];
AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
else
thisSGRCode = AMR_SGRCodeFgReset;
}
else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
else
thisSGRCode = AMR_SGRCodeBgReset;
}
else if ([thisAttrName isEqualToString:NSFontAttributeName])
{
// we currently only use NSFontAttributeName for bolding so
// here we assume that the formatting "type" in ANSI SGR
// terms is indeed intensity
if (attributeValue != nil)
thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
else
thisSGRCode = AMR_SGRCodeIntensityNormal;
}
else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
{
if (attributeValue != nil)
{
if ([attributeValue intValue] == NSUnderlineStyleSingle)
thisSGRCode = AMR_SGRCodeUnderlineSingle;
else if ([attributeValue intValue] == NSUnderlineStyleDouble)
thisSGRCode = AMR_SGRCodeUnderlineDouble;
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
{
[codesAndLocations addObject: @{
kAMRCodeDictKey_code: @(thisSGRCode),
kAMRCodeDictKey_location: @(effectiveRange.location),
}];
}
limitRange = NSMakeRange(NSMaxRange(effectiveRange),
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
}
}
return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
}
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSString *cleanString = @"";
// find all escape sequence codes from aString and put them in this array
// along with their start locations within the "clean" version of aString
NSMutableArray *formatCodes = [NSMutableArray array];
NSUInteger aStringLength = aString.length;
NSUInteger coveredLength = 0;
NSRange searchRange = NSMakeRange(0,aStringLength);
NSRange thisEscapeSequenceRange;
do
{
thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
if (thisEscapeSequenceRange.location != NSNotFound)
{
// adjust range's length so that it encompasses the whole ANSI escape sequence
// and not just the Control Sequence Initiator (the "prefix") by finding the
// final byte of the control sequence (one that has an ASCII decimal value
// between 64 and 126.) at the same time, read all formatting codes from inside
// this escape sequence (there may be several, separated by semicolons.)
NSMutableArray *codes = [NSMutableArray array];
unsigned int code = 0;
unsigned int lengthAddition = 1;
NSUInteger thisIndex;
for (;;)
{
thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
if (thisIndex >= aStringLength)
break;
unichar c = [aString characterAtIndex:thisIndex];
if (('0' <= c) && (c <= '9'))
{
int digit = c - '0';
code = (code == 0) ? digit : code*10+digit;
}
// ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
// ("m"). this means that the code value we've just read specifies formatting
// for the output; exactly what we're interested in.
if (c == 'm')
{
[codes addObject:@(code)];
break;
}
else if ((64 <= c) && (c <= 126)) // any other valid final byte
{
[codes removeAllObjects];
break;
}
else if (c == ';') // separates codes within the same sequence
{
[codes addObject:@(code)];
code = 0;
}
lengthAddition++;
}
thisEscapeSequenceRange.length += lengthAddition;
NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
for (NSNumber *codeToAdd in codes)
{
[formatCodes addObject: @{
kAMRCodeDictKey_code: codeToAdd,
kAMRCodeDictKey_location: @(locationInCleanString)
}];
}
NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
if (thisCoveredLength > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
coveredLength += thisCoveredLength;
searchRange.location = NSMaxRange(thisEscapeSequenceRange);
searchRange.length = aStringLength-searchRange.location;
}
}
while(thisEscapeSequenceRange.location != NSNotFound);
if (searchRange.length > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
if (aCleanString)
*aCleanString = cleanString;
return formatCodes;
}
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
{
NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
NSUInteger aCleanStringIndex = 0;
NSUInteger aCleanStringLength = aCleanString.length;
for (NSDictionary *thisCodeDict in codesArray)
{
if (!( thisCodeDict[kAMRCodeDictKey_code] &&
thisCodeDict[kAMRCodeDictKey_location]
))
continue;
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
if (formattingRunStartLocation > aCleanStringLength)
continue;
if (aCleanStringIndex < formattingRunStartLocation)
[retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
aCleanStringIndex = formattingRunStartLocation;
}
if (aCleanStringIndex < aCleanStringLength)
[retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
return retStr;
}
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSMutableArray *attrsAndRanges = [NSMutableArray array];
NSString *cleanString;
NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
// go through all the found escape sequence codes and for each one, create
// the string formatting attribute name and value, find the next escape
// sequence that specifies the end of the formatting run started by
// the currently handled code, and generate a range from the difference
// in those codes' locations within the clean aString.
for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
{
NSDictionary *thisCodeDict = formatCodes[iCode];
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
// the attributed string attribute name for the formatting run introduced
// by this code
NSString *thisAttributeName = nil;
// the attributed string attribute value for this formatting run introduced
// by this code
NSObject *thisAttributeValue = nil;
// set attribute name
switch(thisCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
thisAttributeName = NSForegroundColorAttributeName;
break;
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
thisAttributeName = NSBackgroundColorAttributeName;
break;
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
thisAttributeName = NSFontAttributeName;
break;
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
case AMR_SGRCodeUnderlineNone:
thisAttributeName = NSUnderlineStyleAttributeName;
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
continue;
}
// set attribute value
switch(thisCode)
{
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
case AMR_SGRCodeFgBrightWhite:
thisAttributeValue = [self colorForSGRCode:thisCode];
break;
case AMR_SGRCodeIntensityBold:
{
NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
thisAttributeValue = boldFont;
}
break;
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
{
NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
thisAttributeValue = unboldFont;
}
break;
case AMR_SGRCodeUnderlineSingle:
thisAttributeValue = @(NSUnderlineStyleSingle);
break;
case AMR_SGRCodeUnderlineDouble:
thisAttributeValue = @(NSUnderlineStyleDouble);
break;
case AMR_SGRCodeUnderlineNone:
thisAttributeValue = @(NSUnderlineStyleNone);
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
break;
}
// find the next sequence that specifies the end of this formatting run
NSInteger formattingRunEndLocation = -1;
if (iCode < (formatCodes.count - 1))
{
NSDictionary *thisEndCodeCandidateDict;
unichar thisEndCodeCandidate;
for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
{
thisEndCodeCandidateDict = formatCodes[iEndCode];
thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
{
formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
break;
}
}
}
if (formattingRunEndLocation == -1)
formattingRunEndLocation = cleanString.length;
if (thisAttributeName && thisAttributeValue)
{
[attrsAndRanges addObject:@{
kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
kAMRAttrDictKey_attrName: thisAttributeName,
kAMRAttrDictKey_attrValue: thisAttributeValue,
}];
}
}
if (aCleanString)
*aCleanString = cleanString;
return attrsAndRanges;
}
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
{
switch(startCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
return NO;
}
return NO;
}
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
{
if (self.ansiColors)
{
NSColor *preferredColor = self.ansiColors[@(code)];
if (preferredColor)
return preferredColor;
}
switch(code)
{
case AMR_SGRCodeFgBlack:
return kDefaultANSIColorFgBlack;
case AMR_SGRCodeFgRed:
return kDefaultANSIColorFgRed;
case AMR_SGRCodeFgGreen:
return kDefaultANSIColorFgGreen;
case AMR_SGRCodeFgYellow:
return kDefaultANSIColorFgYellow;
case AMR_SGRCodeFgBlue:
return kDefaultANSIColorFgBlue;
case AMR_SGRCodeFgMagenta:
return kDefaultANSIColorFgMagenta;
case AMR_SGRCodeFgCyan:
return kDefaultANSIColorFgCyan;
case AMR_SGRCodeFgWhite:
return kDefaultANSIColorFgWhite;
case AMR_SGRCodeFgBrightBlack:
return kDefaultANSIColorFgBrightBlack;
case AMR_SGRCodeFgBrightRed:
return kDefaultANSIColorFgBrightRed;
case AMR_SGRCodeFgBrightGreen:
return kDefaultANSIColorFgBrightGreen;
case AMR_SGRCodeFgBrightYellow:
return kDefaultANSIColorFgBrightYellow;
case AMR_SGRCodeFgBrightBlue:
return kDefaultANSIColorFgBrightBlue;
case AMR_SGRCodeFgBrightMagenta:
return kDefaultANSIColorFgBrightMagenta;
case AMR_SGRCodeFgBrightCyan:
return kDefaultANSIColorFgBrightCyan;
case AMR_SGRCodeFgBrightWhite:
return kDefaultANSIColorFgBrightWhite;
case AMR_SGRCodeBgBlack:
return kDefaultANSIColorBgBlack;
case AMR_SGRCodeBgRed:
return kDefaultANSIColorBgRed;
case AMR_SGRCodeBgGreen:
return kDefaultANSIColorBgGreen;
case AMR_SGRCodeBgYellow:
return kDefaultANSIColorBgYellow;
case AMR_SGRCodeBgBlue:
return kDefaultANSIColorBgBlue;
case AMR_SGRCodeBgMagenta:
return kDefaultANSIColorBgMagenta;
case AMR_SGRCodeBgCyan:
return kDefaultANSIColorBgCyan;
case AMR_SGRCodeBgWhite:
return kDefaultANSIColorBgWhite;
case AMR_SGRCodeBgBrightBlack:
return kDefaultANSIColorBgBrightBlack;
case AMR_SGRCodeBgBrightRed:
return kDefaultANSIColorBgBrightRed;
case AMR_SGRCodeBgBrightGreen:
return kDefaultANSIColorBgBrightGreen;
case AMR_SGRCodeBgBrightYellow:
return kDefaultANSIColorBgBrightYellow;
case AMR_SGRCodeBgBrightBlue:
return kDefaultANSIColorBgBrightBlue;
case AMR_SGRCodeBgBrightMagenta:
return kDefaultANSIColorBgBrightMagenta;
case AMR_SGRCodeBgBrightCyan:
return kDefaultANSIColorBgBrightCyan;
case AMR_SGRCodeBgBrightWhite:
return kDefaultANSIColorBgBrightWhite;
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
break;
}
return kDefaultANSIColorFgBlack;
}
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
{
if (self.ansiColors)
{
NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
{
for (NSNumber *thisCode in codesForGivenColor)
{
BOOL thisIsForegroundColor = (thisCode.intValue < 40);
if (aForeground == thisIsForegroundColor)
return thisCode.intValue;
}
}
}
if (aForeground)
{
if ([aColor isEqual:kDefaultANSIColorFgBlack])
return AMR_SGRCodeFgBlack;
else if ([aColor isEqual:kDefaultANSIColorFgRed])
return AMR_SGRCodeFgRed;
else if ([aColor isEqual:kDefaultANSIColorFgGreen])
return AMR_SGRCodeFgGreen;
else if ([aColor isEqual:kDefaultANSIColorFgYellow])
return AMR_SGRCodeFgYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBlue])
return AMR_SGRCodeFgBlue;
else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
return AMR_SGRCodeFgMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgCyan])
return AMR_SGRCodeFgCyan;
else if ([aColor isEqual:kDefaultANSIColorFgWhite])
return AMR_SGRCodeFgWhite;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
return AMR_SGRCodeFgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
return AMR_SGRCodeFgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
return AMR_SGRCodeFgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
return AMR_SGRCodeFgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
return AMR_SGRCodeFgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
return AMR_SGRCodeFgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
return AMR_SGRCodeFgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
return AMR_SGRCodeFgBrightWhite;
}
else
{
if ([aColor isEqual:kDefaultANSIColorBgBlack])
return AMR_SGRCodeBgBlack;
else if ([aColor isEqual:kDefaultANSIColorBgRed])
return AMR_SGRCodeBgRed;
else if ([aColor isEqual:kDefaultANSIColorBgGreen])
return AMR_SGRCodeBgGreen;
else if ([aColor isEqual:kDefaultANSIColorBgYellow])
return AMR_SGRCodeBgYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBlue])
return AMR_SGRCodeBgBlue;
else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
return AMR_SGRCodeBgMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgCyan])
return AMR_SGRCodeBgCyan;
else if ([aColor isEqual:kDefaultANSIColorBgWhite])
return AMR_SGRCodeBgWhite;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
return AMR_SGRCodeBgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
return AMR_SGRCodeBgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
return AMR_SGRCodeBgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
return AMR_SGRCodeBgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
return AMR_SGRCodeBgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
return AMR_SGRCodeBgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
return AMR_SGRCodeBgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
return AMR_SGRCodeBgBrightWhite;
}
return AMR_SGRCodeNoneOrInvalid;
}
// helper struct typedef and a few functions for
// -closestSGRCodeForColor:isForegroundColor:
typedef struct {
CGFloat hue;
CGFloat saturation;
CGFloat brightness;
} AMR_HSB;
AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
{
AMR_HSB outHSB;
outHSB.hue = hue;
outHSB.saturation = saturation;
outHSB.brightness = brightness;
return outHSB;
}
AMR_HSB getHSBFromColor(NSColor *color)
{
CGFloat hue = 0.0;
CGFloat saturation = 0.0;
CGFloat brightness = 0.0;
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
getHue:&hue
saturation:&saturation
brightness:&brightness
alpha:NULL
];
return makeHSB(hue, saturation, brightness);
}
BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
{
return (fabs(first-second)) < maxAbsError;
}
#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
{
if (color == nil)
return AMR_SGRCodeNoneOrInvalid;
AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
return closestColorSGRCode;
AMR_HSB givenColorHSB = getHSBFromColor(color);
CGFloat closestColorHueDiff = FLT_MAX;
CGFloat closestColorSaturationDiff = FLT_MAX;
CGFloat closestColorBrightnessDiff = FLT_MAX;
// (background SGR codes are +10 from foreground ones:)
NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
NSArray *ansiFgColorCodes = @[
@(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
];
for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
{
AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
// comparison depends on hue, saturation and brightness
// (strictly in that order):
if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (hueDiff > closestColorHueDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (saturationDiff > closestColorSaturationDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (brightnessDiff > closestColorBrightnessDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
// If hue (especially hue!), saturation and brightness diffs all
// are equal to some other color, we need to prefer one or the
// other so we'll select the more 'distinctive' color of the
// two (this is *very* subjective, obviously). I basically just
// looked at the hue chart, went through all the points between
// our main ANSI colors and decided which side the middle point
// would lean on. (e.g. the purple color that is exactly between
// the blue and magenta ANSI colors looks more magenta than
// blue to me so I put magenta higher than blue in the list
// below.)
//
// subjective ordering of colors from most to least 'distinctive':
long colorDistinctivenessOrder[6] = {
AMR_SGRCodeFgRed+AMR_SGRCodeShift,
AMR_SGRCodeFgMagenta+AMR_SGRCodeShift,
AMR_SGRCodeFgBlue+AMR_SGRCodeShift,
AMR_SGRCodeFgGreen+AMR_SGRCodeShift,
AMR_SGRCodeFgCyan+AMR_SGRCodeShift,
AMR_SGRCodeFgYellow+AMR_SGRCodeShift
};
for (int i = 0; i < 6; i++)
{
if (colorDistinctivenessOrder[i] == closestColorSGRCode)
break;
else if (colorDistinctivenessOrder[i] == thisSGRCode)
{
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
}
}
}
return closestColorSGRCode;
}
@end

View File

@ -0,0 +1,34 @@
//
// CBBlueLightClient.h
// MTMR
//
// Created by Anton Palgunov on 28/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef struct {
int hour;
int minute;
} Time;
typedef struct {
Time fromTime;
Time toTime;
} Schedule;
typedef struct {
BOOL active;
BOOL enabled;
BOOL sunSchedulePermitted;
int mode;
Schedule schedule;
unsigned long long disableFlags;
} Status;
@interface CBBlueLightClient: NSObject
- (BOOL) setEnabled: (BOOL)enabled;
- (BOOL) setMode: (int)mode;
- (void) getBlueLightStatus: (Status *)status;
@end

View File

@ -0,0 +1,45 @@
//
// DeprecatedCarbonAPI.c
//
// This file is part of TouchDock
// Copyright (C) 2017 Xander Deng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "DeprecatedCarbonAPI.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
CFStringRef kPidKey = CFSTR("pid");
pid_t pidFromASN(void const *asn) {
pid_t pid = -1;
ProcessSerialNumber psn = {kNoProcess, kNoProcess};
if (CFGetTypeID(asn) == _LSASNGetTypeID()) {
_LSASNExtractHighAndLowParts(asn, &psn.highLongOfPSN, &psn.lowLongOfPSN);
CFDictionaryRef processInfo = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
if (processInfo) {
CFNumberRef pidNumber = CFDictionaryGetValue(processInfo, kPidKey);
if (pidNumber) {
CFNumberGetValue(pidNumber, kCFNumberSInt32Type, &pid);
}
CFRelease(processInfo);
}
}
return pid;
}
#pragma GCC diagnostic pop

View File

@ -0,0 +1,27 @@
//
// DeprecatedCarbonAPI.h
//
// This file is part of TouchDock
// Copyright (C) 2017 Xander Deng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#import <Carbon/Carbon.h>
extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(uint32_t sessionID);
extern void _LSASNExtractHighAndLowParts(void const* asn, UInt32* psnHigh, UInt32* psnLow);
extern CFTypeID _LSASNGetTypeID(void);
pid_t pidFromASN(void const *asn);

View File

@ -0,0 +1,35 @@
//
// LaunchAtLoginController.h
//
// Copyright 2011 Tomáš Znamenáček
// Copyright 2010 Ben Clark-Robinson
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the Software),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@import Foundation;
@import CoreServices;
@interface LaunchAtLoginController : NSObject {}
@property(assign) BOOL launchAtLogin;
- (BOOL) willLaunchAtLogin: (NSURL*) itemURL;
- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL;
@end

View File

@ -0,0 +1,128 @@
//
// LaunchAtLoginController.m
//
// Copyright 2011 Tomáš Znamenáček
// Copyright 2010 Ben Clark-Robinson
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the Software),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "LaunchAtLoginController.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
static NSString *const StartAtLoginKey = @"launchAtLogin";
@interface LaunchAtLoginController ()
@property(assign) LSSharedFileListRef loginItems;
@end
@implementation LaunchAtLoginController
@synthesize loginItems;
#pragma mark Change Observing
void sharedFileListDidChange(LSSharedFileListRef inList, void *context)
{
LaunchAtLoginController *self = (__bridge id) context;
[self willChangeValueForKey:StartAtLoginKey];
[self didChangeValueForKey:StartAtLoginKey];
}
#pragma mark Initialization
- (id) init
{
self = [super init];
loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (voidPtr)CFBridgingRetain(self));
return self;
}
- (void) dealloc
{
LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (__bridge void *)(self));
CFRelease(loginItems);
}
#pragma mark Launch List Control
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
LSSharedFileListItemRef copyItemWithURLinFileList(NSURL* wantedURL, LSSharedFileListRef fileList) {
if (wantedURL == NULL || fileList == NULL)
return NULL;
NSArray *listSnapshot = (__bridge_transfer NSArray *)LSSharedFileListCopySnapshot(fileList, NULL);
for(NSUInteger i = 0; i< [listSnapshot count]; i++) {
LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)[listSnapshot objectAtIndex:i];
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef currentItemURL = NULL;
LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
if (currentItemURL && [(__bridge_transfer NSURL*)currentItemURL isEqual:wantedURL]) {
CFRetain(item);
return item;
}
}
return NULL;
}
#pragma clang diagnostic pop
- (BOOL) willLaunchAtLogin: (NSURL*) itemURL
{
return !!copyItemWithURLinFileList(itemURL, loginItems);
}
- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL
{
LSSharedFileListItemRef appItem = copyItemWithURLinFileList(itemURL, loginItems);
if (enabled && !appItem) {
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst,
NULL, NULL, (__bridge CFURLRef)itemURL, NULL, NULL);
} else if (!enabled && appItem) {
LSSharedFileListItemRemove(loginItems, appItem);
}
if (appItem) {
CFRelease(appItem);
}
}
#pragma mark Basic Interface
- (NSURL*) appURL
{
return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
- (void) setLaunchAtLogin: (BOOL) enabled
{
[self willChangeValueForKey:StartAtLoginKey];
[self setLaunchAtLogin:enabled forURL:[self appURL]];
[self didChangeValueForKey:StartAtLoginKey];
}
- (BOOL) launchAtLogin
{
return [self willLaunchAtLogin:[self appURL]];
}
@end
#pragma clang diagnostic pop

View File

@ -0,0 +1,27 @@
//
// TouchBarPrivateApi-Bridging.h
// MTMR
//
// Created by Anton Palgunov on 18/03/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
#import "AMR_ANSIEscapeHelper.h"
#import "TouchBarPrivateApi.h"
#import "TouchBarSupport.h"
#import "DeprecatedCarbonAPI.h"
#import "CBBlueLightClient.h"
#import "LaunchAtLoginController.h"
NS_ASSUME_NONNULL_BEGIN
CF_EXPORT CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID);
CF_EXPORT IOReturn MTActuatorOpen(CFTypeRef actuatorRef);
CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef);
CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3);
CF_EXPORT bool MTActuatorIsOpen(CFTypeRef actuatorRef);
CF_EXPORT void CoreDisplay_Display_SetUserBrightness(int CGDirectDisplayID, double level);
CF_EXPORT double CoreDisplay_Display_GetUserBrightness(int CGDirectDisplayID);
NS_ASSUME_NONNULL_END

View File

@ -17,13 +17,16 @@ extern void DFRSystemModalShowsCloseBoxWhenFrontMost(BOOL);
@interface NSTouchBar (PrivateMethods) @interface NSTouchBar (PrivateMethods)
// presentSystemModalFunctionBar:placement:systemTrayItemIdentifier: // macOS 10.14
+ (void)presentSystemModalTouchBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)presentSystemModalTouchBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)dismissSystemModalTouchBar:(NSTouchBar *)touchBar;
+ (void)minimizeSystemModalTouchBar:(NSTouchBar *)touchBar;
// macOS 10.13
+ (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier; + (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier; + (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)dismissSystemModalFunctionBar:(NSTouchBar *)touchBar; + (void)dismissSystemModalFunctionBar:(NSTouchBar *)touchBar;
+ (void)minimizeSystemModalFunctionBar:(NSTouchBar *)touchBar; + (void)minimizeSystemModalFunctionBar:(NSTouchBar *)touchBar;
@end @end

View File

@ -10,8 +10,6 @@
@interface MediaKeys : NSObject @interface MediaKeys : NSObject
+ (void)decreaseVolume; + (void)HIDPostAuxKey:(UInt8)keyCode;
+ (void)increaseVolume;
+ (void)muteVolume;
@end @end

View File

@ -7,6 +7,7 @@
// //
#import "TouchBarSupport.h" #import "TouchBarSupport.h"
#import <IOKit/hidsystem/ev_keymap.h>
@implementation MediaKeys @implementation MediaKeys
@ -30,7 +31,7 @@ static io_connect_t get_event_driver(void)
} }
static void HIDPostAuxKey( const UInt8 auxKeyCode ) static void HIDReleaseAuxKey( const UInt8 auxKeyCode )
{ {
NXEventData event; NXEventData event;
kern_return_t kr; kern_return_t kr;
@ -51,16 +52,8 @@ static void HIDPostAuxKey( const UInt8 auxKeyCode )
kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE ); kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE );
} }
+ (void)decreaseVolume { + (void)HIDPostAuxKey: (UInt8)keyCode {
HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN); HIDReleaseAuxKey(keyCode);
}
+ (void)increaseVolume {
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP);
}
+ (void)muteVolume {
HIDPostAuxKey(NX_KEYTYPE_MUTE);
} }
@end @end

191
MTMR/CPU.swift Normal file
View File

@ -0,0 +1,191 @@
//
// CPU.swift
// Pods
//
// Created by zixun on 2016/12/5.
// https://github.com/zixun/SystemEye
// MIT License
//
//
import Foundation
private let HOST_CPU_LOAD_INFO_COUNT : mach_msg_type_number_t =
UInt32(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
/// CPU Class
public class CPU: NSObject {
//--------------------------------------------------------------------------
// MARK: OPEN PROPERTY
//--------------------------------------------------------------------------
// /// Number of physical cores on this machine.
// public static var physicalCores: Int {
// get {
// return Int(System.hostBasicInfo.physical_cpu)
// }
// }
//
// /// Number of logical cores on this machine. Will be equal to physicalCores
// /// unless it has hyper-threading, in which case it will be double.
// public static var logicalCores: Int {
// get {
// return Int(System.hostBasicInfo.logical_cpu)
// }
// }
//--------------------------------------------------------------------------
// MARK: OPEN FUNCTIONS
//--------------------------------------------------------------------------
/// Get CPU usage of hole system (system, user, idle, nice). Determined by the delta between
/// the current and last call.
public static func systemUsage() -> (system: Double,
user: Double,
idle: Double,
nice: Double) {
let load = self.hostCPULoadInfo
let userDiff = Double(load.cpu_ticks.0 - loadPrevious.cpu_ticks.0)
let sysDiff = Double(load.cpu_ticks.1 - loadPrevious.cpu_ticks.1)
let idleDiff = Double(load.cpu_ticks.2 - loadPrevious.cpu_ticks.2)
let niceDiff = Double(load.cpu_ticks.3 - loadPrevious.cpu_ticks.3)
let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
let sys = sysDiff / totalTicks * 100.0
let user = userDiff / totalTicks * 100.0
let idle = idleDiff / totalTicks * 100.0
let nice = niceDiff / totalTicks * 100.0
loadPrevious = load
return (sys, user, idle, nice)
}
/// Get CPU usage of application,get from all thread
open class func applicationUsage() -> Double {
let threads = self.threadBasicInfos()
var result : Double = 0.0
threads.forEach { (thread:thread_basic_info) in
if self.flag(thread) {
result += Double.init(thread.cpu_usage) / Double.init(TH_USAGE_SCALE);
}
}
return result * 100
}
//--------------------------------------------------------------------------
// MARK: PRIVATE PROPERTY
//--------------------------------------------------------------------------
/// previous load of cpu
private static var loadPrevious = host_cpu_load_info()
static var hostCPULoadInfo: host_cpu_load_info {
get {
var size = HOST_CPU_LOAD_INFO_COUNT
var hostInfo = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &hostInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
#if DEBUG
if result != KERN_SUCCESS {
fatalError("ERROR - \(#file):\(#function) - kern_result_t = "
+ "\(result)")
}
#endif
return hostInfo
}
}
//--------------------------------------------------------------------------
// MARK: PRIVATE FUNCTION
//--------------------------------------------------------------------------
private class func flag(_ thread:thread_basic_info) -> Bool {
let foo = thread.flags & TH_FLAGS_IDLE
let number = NSNumber.init(value: foo)
return !Bool.init(truncating: number)
}
private class func threadActPointers() -> [thread_act_t] {
var threads_act = [thread_act_t]()
var threads_array: thread_act_array_t? = nil
var count = mach_msg_type_number_t()
let result = task_threads(mach_task_self_, &(threads_array), &count)
guard result == KERN_SUCCESS else {
return threads_act
}
guard let array = threads_array else {
return threads_act
}
for i in 0..<count {
threads_act.append(array[Int(i)])
}
let krsize = count * UInt32.init(MemoryLayout<thread_t>.size)
_ = vm_deallocate(mach_task_self_, vm_address_t(array.pointee), vm_size_t(krsize));
return threads_act
}
private class func threadBasicInfos() -> [thread_basic_info] {
var result = [thread_basic_info]()
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
var basic_info_th: thread_basic_info_t? = nil
for act_t in self.threadActPointers() {
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
let kr = thread_info(act_t ,thread_flavor_t(THREAD_BASIC_INFO),thinfo, thread_info_count);
if (kr != KERN_SUCCESS) {
return [thread_basic_info]();
}
basic_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_basic_info_t in
let int8Ptr = unsafeBitCast(ptr, to: thread_basic_info_t.self)
return int8Ptr
})
if basic_info_th != nil {
result.append(basic_info_th!.pointee)
}
}
return result
}
//TODO: this function is used for get cpu usage of all thread,and this is in developing
private class func threadIdentifierInfos() -> [thread_identifier_info] {
var result = [thread_identifier_info]()
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
var identifier_info_th: thread_identifier_info_t? = nil
for act_t in self.threadActPointers() {
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
let kr = thread_info(act_t ,thread_flavor_t(THREAD_IDENTIFIER_INFO),thinfo, thread_info_count);
if (kr != KERN_SUCCESS) {
return [thread_identifier_info]();
}
identifier_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_identifier_info_t in
let int8Ptr = unsafeBitCast(ptr, to: thread_identifier_info_t.self)
return int8Ptr
})
if identifier_info_th != nil {
result.append(identifier_info_th!.pointee)
}
}
return result
}
}

View File

@ -0,0 +1,340 @@
//
// TouchBarItems.swift
// MTMR
//
// Created by Anton Palgunov on 18/03/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
struct ItemAction {
typealias TriggerClosure = (() -> Void)?
let trigger: Action.Trigger
let closure: TriggerClosure
init(trigger: Action.Trigger, _ closure: TriggerClosure) {
self.trigger = trigger
self.closure = closure
}
}
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
var actions: [ItemAction] = [] {
didSet {
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 longClick: LongPressGestureRecognizer!
private var multiClick: MultiClickGestureRecognizer!
init(identifier: NSTouchBarItem.Identifier, title: String) {
attributedTitle = title.defaultTouchbarAttributedString
super.init(identifier: identifier)
button = CustomHeightButton(title: title, target: nil, action: nil)
longClick = LongPressGestureRecognizer(target: self, action: #selector(handleGestureLong))
longClick.isEnabled = false
longClick.allowedTouchTypes = .direct
longClick.delegate = self
multiClick = MultiClickGestureRecognizer(
target: self,
action: #selector(handleGestureSingleTap),
doubleAction: #selector(handleGestureDoubleTap),
tripleAction: #selector(handleGestureTripleTap)
)
multiClick.allowedTouchTypes = .direct
multiClick.delegate = self
multiClick.isDoubleClickEnabled = false
multiClick.isTripleClickEnabled = false
reinstallButton()
button.attributedTitle = attributedTitle
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var isBordered: Bool = true {
didSet {
reinstallButton()
}
}
var backgroundColor: NSColor? {
didSet {
reinstallButton()
}
}
var title: String {
get {
return attributedTitle.string
}
set {
attributedTitle = newValue.defaultTouchbarAttributedString
}
}
var attributedTitle: NSAttributedString {
didSet {
button?.imagePosition = attributedTitle.length > 0 ? .imageLeading : .imageOnly
button?.attributedTitle = attributedTitle
}
}
var image: NSImage? {
didSet {
button.image = image
}
}
private func reinstallButton() {
let title = button.attributedTitle
let image = button.image
let cell = CustomButtonCell(parentItem: self)
button.cell = cell
if let color = backgroundColor {
cell.isBordered = true
button.bezelColor = color
button.bezelStyle = .rounded
cell.backgroundColor = color
} else {
button.isBordered = isBordered
button.bezelStyle = isBordered ? .rounded : .inline
}
button.imageScaling = .scaleProportionallyDown
button.imageHugsTitle = true
button.attributedTitle = title
button?.imagePosition = title.length > 0 ? .imageLeading : .imageOnly
button.image = image
view = button
view.addGestureRecognizer(longClick)
// view.addGestureRecognizer(singleClick)
view.addGestureRecognizer(multiClick)
finishViewConfiguration()
}
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
{
return false
}
return true
}
func callActions(for trigger: Action.Trigger) {
let itemActions = self.actions.filter { $0.trigger == trigger }
for itemAction in itemActions {
itemAction.closure?()
}
}
@objc func handleGestureSingleTap() {
callActions(for: .singleTap)
}
@objc func handleGestureDoubleTap() {
callActions(for: .doubleTap)
}
@objc func handleGestureTripleTap() {
callActions(for: .tripleTap)
}
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
switch gr.state {
case .possible: // tiny hack because we're calling action manually
callActions(for: .longTap)
break
default:
break
}
}
}
class CustomHeightButton: NSButton {
override var intrinsicContentSize: NSSize {
var size = super.intrinsicContentSize
size.height = 30
return size
}
}
class CustomButtonCell: NSButtonCell {
weak var parentItem: CustomButtonTouchBarItem?
init(parentItem: CustomButtonTouchBarItem) {
super.init(textCell: "")
self.parentItem = parentItem
}
override func highlight(_ flag: Bool, withFrame cellFrame: NSRect, in controlView: NSView) {
super.highlight(flag, withFrame: cellFrame, in: controlView)
if !isBordered {
if flag {
setAttributedTitle(attributedTitle, withColor: .lightGray)
} else if let parentItem = self.parentItem {
attributedTitle = parentItem.attributedTitle
}
}
}
override func drawingRect(forBounds rect: NSRect) -> NSRect {
return rect // need that so content may better fit in button with very limited width
}
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setAttributedTitle(_ title: NSAttributedString, withColor color: NSColor) {
let attrTitle = NSMutableAttributedString(attributedString: title)
attrTitle.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attrTitle.length))
attributedTitle = attrTitle
}
}
// Thanks to https://stackoverflow.com/a/49843893
final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
private let _action: Selector
private let _doubleAction: Selector
private let _tripleAction: Selector
private var _clickCount: Int = 0
public var isDoubleClickEnabled = true
public var isTripleClickEnabled = true
override var action: Selector? {
get {
return nil /// prevent base class from performing any actions
} set {
if newValue != nil { // if they are trying to assign an actual action
fatalError("Only use init(target:action:doubleAction) for assigning actions")
}
}
}
required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) {
_action = action
_doubleAction = doubleAction
_tripleAction = tripleAction
super.init(target: target, action: nil)
}
required init?(coder: NSCoder) {
fatalError("init(target:action:doubleAction:tripleAction) is only support atm")
}
override func touchesBegan(with event: NSEvent) {
HapticFeedback.instance.tap(type: .click)
super.touchesBegan(with: event)
}
override func touchesEnded(with event: NSEvent) {
HapticFeedback.instance.tap(type: .back)
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 {
var recognizeTimeout = 0.4
private var timer: Timer?
override func touchesBegan(with event: NSEvent) {
timerInvalidate()
let touches = event.touches(for: self.view!)
if touches.count == 1 { // to prevent it for built-in two/three-finger gestures
timer = Timer.scheduledTimer(timeInterval: recognizeTimeout, target: self, selector: #selector(self.onTimer), userInfo: nil, repeats: false)
}
super.touchesBegan(with: event)
}
override func touchesMoved(with event: NSEvent) {
timerInvalidate() // to prevent it for built-in two/three-finger gestures
super.touchesMoved(with: event)
}
override func touchesCancelled(with event: NSEvent) {
timerInvalidate()
super.touchesCancelled(with: event)
}
override func touchesEnded(with event: NSEvent) {
timerInvalidate()
super.touchesEnded(with: event)
}
private func timerInvalidate() {
if let timer = timer {
timer.invalidate()
self.timer = nil
}
}
@objc private func onTimer() {
if let target = self.target, let action = self.action {
target.performSelector(onMainThread: action, with: self, waitUntilDone: false)
HapticFeedback.instance.tap(type: .strong)
}
}
deinit {
timerInvalidate()
}
}
extension String {
var defaultTouchbarAttributedString: NSAttributedString {
let attrTitle = NSMutableAttributedString(string: self, attributes: [.foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 15, weight: .regular), .baselineOffset: 1])
attrTitle.setAlignment(.center, range: NSRange(location: 0, length: count))
return attrTitle
}
}

107
MTMR/CustomSlider.swift Normal file
View File

@ -0,0 +1,107 @@
//
// CustomSlider.swift
// MTMR
//
// Created by Anton Palgunov on 15/04/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class CustomSliderCell: NSSliderCell {
var knobImage: NSImage!
private var _currentKnobRect: NSRect!
private var _barRect: NSRect!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
}
init(knob: NSImage?) {
knobImage = knob
super.init()
}
override func drawKnob(_ knobRect: NSRect) {
if knobImage == nil {
super.drawKnob(knobRect)
return
}
_currentKnobRect = knobRect
drawBar(inside: _barRect, flipped: true)
let x = (knobRect.origin.x * (_barRect.size.width - (knobImage.size.width - knobRect.size.width)) / _barRect.size.width) + 1
let y = knobRect.origin.y + 3
knobImage.draw(
at: NSPoint(x: x, y: y),
from: NSZeroRect,
operation: NSCompositingOperation.sourceOver,
fraction: 1
)
}
override func drawBar(inside aRect: NSRect, flipped _: Bool) {
_barRect = aRect
let barRadius = CGFloat(2)
var bgRect = aRect
bgRect.size.height = CGFloat(4)
let bg = NSBezierPath(roundedRect: bgRect, xRadius: barRadius, yRadius: barRadius)
NSColor.lightGray.setFill()
bg.fill()
var activeRect = bgRect
activeRect.size.width = CGFloat((Double(bgRect.size.width) / (maxValue - minValue)) * doubleValue)
let active = NSBezierPath(roundedRect: activeRect, xRadius: barRadius, yRadius: barRadius)
NSColor.darkGray.setFill()
active.fill()
}
}
class CustomSlider: NSSlider {
var currentValue: CGFloat = 0
override func setNeedsDisplay(_ invalidRect: NSRect) {
super.setNeedsDisplay(invalidRect)
}
override func awakeFromNib() {
super.awakeFromNib()
if (cell?.isKind(of: CustomSliderCell.self)) == false {
let cell: CustomSliderCell = CustomSliderCell()
self.cell = cell
}
}
convenience init(knob: NSImage) {
self.init()
cell = CustomSliderCell(knob: knob)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
func knobImage() -> NSImage {
let cell = self.cell as! CustomSliderCell
return cell.knobImage
}
func setKnobImage(image: NSImage) {
let cell = self.cell as! CustomSliderCell
cell.knobImage = image
}
}

View File

@ -0,0 +1,17 @@
import Foundation
#if swift(>=4.1)
// compactMap supported
#else
extension Sequence {
func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
}
#endif
extension String {
var ifNotEmpty: String? {
return count > 0 ? self : nil
}
}

100
MTMR/HapticFeedback.swift Normal file
View File

@ -0,0 +1,100 @@
//
// HapticFeedback.swift
// MTMR
//
// Created by Anton Palgunov on 09/04/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import IOKit
class HapticFeedback {
// Here we have list of possible IDs for Haptic Generator Device. They are not constant
// To find deviceID, you will need IORegistryExplorer app from Additional Tools for Xcode dmg
// which you can download from https://developer.apple.com/download/more/?=Additional%20Tools
// Open IORegistryExplorer app, search for "AppleMultitouchDevice" and get "Multitouch ID"
// or "AppleMultitouchTrackpadHIDEventDriver" and get "mt-device-id"
// There should be programmatic way to get it but I can't find, no docs for macOS :(
private let possibleDeviceIDs: [UInt64] = [
0x200_0000_0100_0000, // MacBook Pro 2016/2017
0x300_0000_8050_0000, // MacBook Pro 2019/2018
0x200_0000_0000_0024, // MacBook Pro (13-inch, M1, 2020)
0x200_0000_0000_0023 // MacBook Pro M1 13-Inch 2020 with 1tb
// 0x300000080500000,
]
// you can get a plist `otool -s __TEXT __tpad_act_plist /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/Current/MultitouchSupport|tail -n +3|awk -F'\t' '{print $2}'|xxd -r -p`
enum HapticType: Int32, CaseIterable {
case back = 1
case click = 2
case weak = 3
case medium = 4
case weakMedium = 5
case strong = 6
case reserved1 = 15
case reserved2 = 16
}
private var actuatorRef: CFTypeRef?
static var instance = HapticFeedback()
// MARK: - Init
private init() {
self.recreateDevice()
}
private func recreateDevice() {
if let actuatorRef = self.actuatorRef {
MTActuatorClose(actuatorRef)
self.actuatorRef = nil // just in case %)
}
guard self.actuatorRef == nil else {
return
}
// Let's find our Haptic device
self.possibleDeviceIDs.forEach {(deviceID) in
let actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
if actuatorRef != nil {
self.actuatorRef = actuatorRef
}
}
}
// MARK: - Tap action
private func getActuatorIfPosible() -> CFTypeRef? {
guard AppSettings.hapticFeedbackState else { return nil }
guard let actuatorRef = self.actuatorRef else {
print("guard actuatorRef == nil (no haptic device found?)")
return nil
}
guard MTActuatorOpen(actuatorRef) == kIOReturnSuccess else {
print("guard MTActuatorOpen")
self.recreateDevice()
return nil
}
return actuatorRef
}
func tap(type: HapticType) {
guard let actuator = getActuatorIfPosible() else { return }
guard MTActuatorActuate(actuator, type.rawValue, 0, 0, 0) == kIOReturnSuccess else {
print("guard MTActuatorActuate")
return
}
guard MTActuatorClose(actuator) == kIOReturnSuccess else {
print("guard MTActuatorClose")
return
}
}
}

View File

@ -17,16 +17,52 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>0.27</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>448</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSAppleEventsUsageDescription</key>
<string>AppleEvents needed for correct work AppleScript</string>
<key>NSAppleMusicUsageDescription</key>
<string>MTMR needs access to Media for work</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>MTMR needs access to Bluetooth for work</string>
<key>NSCalendarsUsageDescription</key>
<string>MTMR needs access to Calendar for work</string>
<key>NSCameraUsageDescription</key>
<string>MTMR needs access to Camera for work</string>
<key>NSHomeKitUsageDescription</key>
<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>
<string>Weather widget need your location for correct work</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSMainStoryboardFile</key> <key>NSMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>MTMR needs access to Photo for work</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSRemindersUsageDescription</key>
<string>MTMR needs access to Reminders for work</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>MTMR needs access to Administation for work</string>
<key>SUFeedURL</key>
<string>https://mtmr.app/appcast.xml</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
<key>kTCCServiceMediaLibrary</key>
<string>MTMR needs access to Music for work</string>
</dict> </dict>
</plist> </plist>

820
MTMR/ItemsParsing.swift Normal file
View File

@ -0,0 +1,820 @@
import AppKit
import Foundation
extension Data {
func barItemDefinitions() -> [BarItemDefinition]? {
return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
}
}
struct BarItemDefinition: Decodable {
let type: ItemType
let actions: [Action]
let legacyAction: LegacyActionType
let legacyLongAction: LegacyLongActionType
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
private enum CodingKeys: String, CodingKey {
case type
case actions
}
init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
self.type = type
self.actions = actions
self.legacyAction = action
self.legacyLongAction = legacyLongAction
self.additionalParameters = additionalParameters
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
let actions = try container.decodeIfPresent([Action].self, forKey: .actions)
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type, actions: actions ?? [])
var additionalParameters = try GeneralParameters(from: decoder).parameters
if let result = try? parametersDecoder(decoder),
case let (itemType, actions, action, longAction, parameters) = result {
parameters.forEach { additionalParameters[$0] = $1 }
self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters)
} else {
self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters)
}
}
}
typealias ParametersDecoder = (Decoder) throws -> (
item: ItemType,
actions: [Action],
legacyAction: LegacyActionType,
legacyLongAction: LegacyLongActionType,
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
)
class SupportedTypesHolder {
private var supportedTypes: [String: ParametersDecoder] = [
"escape": { _ in (
item: .staticButton(title: "esc"),
actions: [
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.align: .align(.left)]
) },
"delete": { _ in (
item: .staticButton(title: "del"),
actions: [
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
) },
"brightnessUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"brightnessDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"illuminationUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"illuminationDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"volumeDown": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"volumeUp": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"mute": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"previous": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"play": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"next": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"sleep": { _ in (
item: .staticButton(title: "☕️"),
actions: [
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
) },
"displaySleep": { _ in (
item: .staticButton(title: "☕️"),
actions: [
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
) },
]
static let sharedInstance = SupportedTypesHolder()
func lookup(by type: String, actions: [Action]) -> ParametersDecoder {
return supportedTypes[type] ?? { decoder in (
item: try ItemType(from: decoder),
actions: actions,
legacyAction: try LegacyActionType(from: decoder),
legacyLongAction: try LegacyLongActionType(from: decoder),
parameters: [:]
) }
}
func register(typename: String, decoder: @escaping ParametersDecoder) {
supportedTypes[typename] = decoder
}
func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) {
register(typename: typename) { _ in
(
item: item,
actions,
legacyAction,
legacyLongAction,
parameters: [:]
)
}
}
}
enum ItemType: Decodable {
case staticButton(title: String)
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double, alternativeImages: [String: SourceProtocol])
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
case battery
case cpu(refreshInterval: Double)
case dock(autoResize: Bool, filter: String?)
case volume
case brightness(refreshInterval: Double)
case weather(interval: Double, units: String, api_key: String, icon_type: String)
case yandexWeather(interval: Double)
case currency(interval: Double, from: String, to: String, full: Bool)
case inputsource
case music(interval: Double, disableMarquee: Bool)
case group(items: [BarItemDefinition])
case nightShift
case dnd
case pomodoro(workTime: Double, restTime: Double)
case network(flip: Bool, units: String)
case darkMode
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool)
private enum CodingKeys: String, CodingKey {
case type
case title
case source
case refreshInterval
case from
case to
case full
case timeZone
case units
case api_key
case icon_type
case formatTemplate
case locale
case image
case url
case longUrl
case items
case workTime
case restTime
case flip
case autoResize
case filter
case disableMarquee
case alternativeImages
case sourceApple
case sourceBash
case direction
case fingers
case minOffset
case maxToShow
}
enum ItemTypeRaw: String, Decodable {
case staticButton
case appleScriptTitledButton
case shellScriptTitledButton
case timeButton
case battery
case cpu
case dock
case volume
case brightness
case weather
case yandexWeather
case currency
case inputsource
case music
case group
case nightShift
case dnd
case pomodoro
case network
case darkMode
case swipe
case upnext
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ItemTypeRaw.self, forKey: .type)
switch type {
case .appleScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:]
self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages)
case .shellScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .shellScriptTitledButton(source: source, refreshInterval: interval)
case .staticButton:
let title = try container.decode(String.self, forKey: .title)
self = .staticButton(title: title)
case .timeButton:
let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm"
let timeZone = try container.decodeIfPresent(String.self, forKey: .timeZone) ?? nil
let locale = try container.decodeIfPresent(String.self, forKey: .locale) ?? nil
self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery:
self = .battery
case .cpu:
let refreshInterval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
self = .cpu(refreshInterval: refreshInterval)
case .dock:
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter)
self = .dock(autoResize: autoResize, filter: filterRegexString)
case .volume:
self = .volume
case .brightness:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
self = .brightness(refreshInterval: interval)
case .weather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric"
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6"
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text"
self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type)
case .yandexWeather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .yandexWeather(interval: interval)
case .currency:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0
let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB"
let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD"
let full = try container.decodeIfPresent(Bool.self, forKey: .full) ?? false
self = .currency(interval: interval, from: from, to: to, full: full)
case .inputsource:
self = .inputsource
case .music:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false
self = .music(interval: interval, disableMarquee: disableMarquee)
case .group:
let items = try container.decode([BarItemDefinition].self, forKey: .items)
self = .group(items: items)
case .nightShift:
self = .nightShift
case .dnd:
self = .dnd
case .pomodoro:
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0
let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime) ?? 600.0
self = .pomodoro(workTime: workTime, restTime: restTime)
case .network:
let flip = try container.decodeIfPresent(Bool.self, forKey: .flip) ?? false
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "dynamic"
self = .network(flip: flip, units: units)
case .darkMode:
self = .darkMode
case .swipe:
let sourceApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple)
let sourceBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash)
let direction = try container.decode(String.self, forKey: .direction)
let fingers = try container.decode(Int.self, forKey: .fingers)
let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0
self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
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)
}
}
}
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 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 CodingKeys: String, CodingKey {
case action
case keycode
case actionAppleScript
case executablePath
case shellArguments
case url
}
private enum ActionTypeRaw: String, Decodable {
case hidKey
case keyPress
case appleScript
case shellScript
case openUrl
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
switch type {
case .some(.hidKey):
let keycode = try container.decode(Int32.self, forKey: .keycode)
self = .hidKey(keycode: keycode)
case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .keycode)
self = .keyPress(keycode: keycode)
case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .actionAppleScript)
self = .appleScript(source: source)
case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl):
let url = try container.decode(String.self, forKey: .url)
self = .openUrl(url: url)
case .none:
self = .none
}
}
}
enum LegacyLongActionType: Decodable {
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 CodingKeys: String, CodingKey {
case longAction
case longKeycode
case longActionAppleScript
case longExecutablePath
case longShellArguments
case longUrl
}
private enum LongActionTypeRaw: String, Decodable {
case hidKey
case keyPress
case appleScript
case shellScript
case openUrl
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction)
switch longType {
case .some(.hidKey):
let keycode = try container.decode(Int32.self, forKey: .longKeycode)
self = .hidKey(keycode: keycode)
case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .longKeycode)
self = .keyPress(keycode: keycode)
case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .longActionAppleScript)
self = .appleScript(source: source)
case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .longExecutablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .longShellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl):
let longUrl = try container.decode(String.self, forKey: .longUrl)
self = .openUrl(url: longUrl)
case .none:
self = .none
}
}
}
enum GeneralParameter {
case width(_: CGFloat)
case image(source: SourceProtocol)
case align(_: Align)
case bordered(_: Bool)
case background(_: NSColor)
case title(_: String)
case matchAppId(_: String)
}
struct GeneralParameters: Decodable {
let parameters: [GeneralParameters.CodingKeys: GeneralParameter]
enum CodingKeys: String, CodingKey {
case width
case image
case align
case bordered
case background
case title
case matchAppId
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:]
if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) {
result[.width] = .width(value)
}
if let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) {
result[.image] = .image(source: imageSource)
}
let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center
result[.align] = .align(align)
if let borderedFlag = try container.decodeIfPresent(Bool.self, forKey: .bordered) {
result[.bordered] = .bordered(borderedFlag)
}
if let backgroundColor = try container.decodeIfPresent(String.self, forKey: .background)?.hexColor {
result[.background] = .background(backgroundColor)
}
if let title = try container.decodeIfPresent(String.self, forKey: .title) {
result[.title] = .title(title)
}
if let matchAppId = try container.decodeIfPresent(String.self, forKey: .matchAppId) {
result[.matchAppId] = .matchAppId(matchAppId)
}
parameters = result
}
}
protocol SourceProtocol {
var data: Data? { get }
var string: String? { get }
var image: NSImage? { get }
var appleScript: NSAppleScript? { get }
}
struct Source: Decodable, SourceProtocol {
let filePath: String?
let base64: String?
let inline: String?
private enum CodingKeys: String, CodingKey {
case filePath
case base64
case inline
}
var data: Data? {
return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData
}
var string: String? {
return inline ?? filePath?.fileString
}
var image: NSImage? {
return data?.image
}
var appleScript: NSAppleScript? {
return filePath?.fileURL.appleScript ?? string?.appleScript
}
private init(filePath: String?, base64: String?, inline: String?) {
self.filePath = filePath
self.base64 = base64
self.inline = inline
}
init(filePath: String) {
self.init(filePath: filePath, base64: nil, inline: nil)
}
}
extension NSImage: SourceProtocol {
var data: Data? {
return nil
}
var string: String? {
return nil
}
var image: NSImage? {
return self
}
var appleScript: NSAppleScript? {
return nil
}
}
extension String {
var base64Data: Data? {
return Data(base64Encoded: self)
}
var fileData: Data? {
return try? Data(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath))
}
var fileString: String? {
var encoding: String.Encoding = .utf8
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)
}
}
extension Data {
var utf8string: String? {
return String(data: self, encoding: .utf8)
}
var image: NSImage? {
return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24))
}
}
enum Align: String, Decodable {
case left
case center
case right
}
extension URL {
var appleScript: NSAppleScript? {
guard FileManager.default.fileExists(atPath: path) else { return nil }
return NSAppleScript(contentsOf: self, error: nil)
}
}

View File

@ -13,8 +13,12 @@ protocol KeyPress {
func send() func send()
} }
struct GenericKeyPress: KeyPress {
var keyCode: CGKeyCode
}
extension KeyPress { extension KeyPress {
func send () { func send() {
let src = CGEventSource(stateID: .hidSystemState) let src = CGEventSource(stateID: .hidSystemState)
let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true) let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true)
let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false) let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false)
@ -25,56 +29,7 @@ extension KeyPress {
} }
} }
struct ESCKeyPress: KeyPress { func HIDPostAuxKey(_ key: Int32) {
let keyCode: CGKeyCode = 53 let key = UInt8(key)
MediaKeys.hidPostAuxKey(key)
} }
struct BrightnessUpPress: KeyPress {
let keyCode: CGKeyCode = 107
}
struct BrightnessDownPress: KeyPress {
let keyCode: CGKeyCode = 113
}
func doKey(_ key: Int, down: Bool) {
let flags = NSEvent.ModifierFlags(rawValue: down ? 0xa00 : 0xb00)
let data1 = (key << 16) | ((down ? 0xa : 0xb) << 8)
let ev = NSEvent.otherEvent(
with: NSEvent.EventType.systemDefined,
location: NSPoint(x:0.0, y:0.0),
modifierFlags: flags,
timestamp: TimeInterval(0),
windowNumber: 0,
context: nil,
// context: 0,
subtype: 8,
data1: data1,
data2: -1
)
let cev = ev!.cgEvent!
cev.post(tap: CGEventTapLocation(rawValue: 0)!)
}
func HIDPostAuxKey(_ key: Int) {
doKey(key, down: true)
doKey(key, down: false)
}
// hidsystem/ev_keymap.h
let NX_KEYTYPE_SOUND_UP = 0
let NX_KEYTYPE_SOUND_DOWN = 1
let NX_KEYTYPE_BRIGHTNESS_UP = 2
let NX_KEYTYPE_BRIGHTNESS_DOWN = 3
let NX_KEYTYPE_PLAY = 16
let NX_KEYTYPE_NEXT = 17
let NX_KEYTYPE_PREVIOUS = 18

20
MTMR/ScrollViewItem.swift Normal file
View File

@ -0,0 +1,20 @@
import Foundation
class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
super.init(identifier: identifier)
let views = items.compactMap { $0.view }
let stackView = NSStackView(views: views)
stackView.spacing = 1
stackView.orientation = .horizontal
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
scrollView.documentView = stackView
view = scrollView
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,113 @@
//
// ShellScriptTouchBarItem.swift
// MTMR
//
// Created by bobr on 08/08/2019.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
private let interval: TimeInterval
private let source: String
private var forceHideConstraint: NSLayoutConstraint!
struct ScriptResult: Decodable {
var title: String?
var image: Source?
}
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
self.interval = interval
self.source = source.string ?? "echo No \"source\""
super.init(identifier: identifier, title: "")
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
DispatchQueue.shellScriptQueue.async {
self.refreshAndSchedule()
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
// Execute script and get result
let scriptResult = execute(source)
var rawTitle: String, image: NSImage?
var json: Bool
do {
let decoder = JSONDecoder()
let result = try decoder.decode(ScriptResult.self, from: scriptResult.data(using: .utf8)!)
json = true
rawTitle = result.title ?? ""
image = result.image?.image
} catch {
json = false
rawTitle = scriptResult
}
// Apply returned text attributes (if they were returned) to our result string
let helper = AMR_ANSIEscapeHelper.init()
helper.defaultStringColor = NSColor.white
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: rawTitle) ?? NSAttributedString(string: ""))
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
let newBackgoundColor: NSColor? = title.length != 0 ? title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor : nil
// Update UI
DispatchQueue.main.async { [weak self, newBackgoundColor] in
if (newBackgoundColor != self?.backgroundColor) { // performance optimization because of reinstallButton
self?.backgroundColor = newBackgoundColor
}
self?.attributedTitle = title
if json {
self?.image = image
}
self?.forceHideConstraint.isActive = scriptResult == ""
}
// Schedule next update
DispatchQueue.shellScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
self?.refreshAndSchedule()
}
}
func execute(_ command: String) -> String {
let task = Process()
if let shell = getenv("SHELL") {
task.launchPath = String.init(cString: shell)
} else {
task.launchPath = "/bin/bash"
}
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
// kill process if it is over update interval
DispatchQueue.main.asyncAfter(deadline: .now() + interval) { [weak task] in
task?.terminate()
}
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
//always wait until task end or you can catch "task still running" error while accessing task.terminationStatus variable
task.waitUntilExit()
if (output == "" && task.terminationStatus != 0) {
output = "error"
}
return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression)
}
}
extension DispatchQueue {
static let shellScriptQueue = DispatchQueue(label: "mtmr.shellscript")
}

104
MTMR/SupportHelpers.swift Normal file
View File

@ -0,0 +1,104 @@
//
// SupportHelpers.swift
// MTMR
//
// Created by Anton Palgunov on 13/04/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import AppKit
import Foundation
extension String {
func trim() -> String {
return trimmingCharacters(in: NSCharacterSet.whitespaces)
}
func stripComments() -> String {
// ((\s|,)\/\*[\s\S]*?\*\/)|(( |, ")\/\/.*)
return replacingOccurrences(of: "((\\s|,)\\/\\*[\\s\\S]*?\\*\\/)|(( |, \\\")\\/\\/.*)", with: "", options: .regularExpression)
}
var hexColor: NSColor? {
let hex = trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.count {
case 3: // RGB (12-bit)
(r, g, b, a) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17, 255)
case 6: // RGB (24-bit)
(r, g, b, a) = (int >> 16, int >> 8 & 0xFF, int & 0xFF, 255)
case 8: // ARGB (32-bit)
(r, g, b, a) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
return nil
}
return NSColor(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}
extension NSImage {
func resize(maxSize: NSSize) -> NSImage {
var ratio: Float = 0.0
let imageWidth = Float(size.width)
let imageHeight = Float(size.height)
let maxWidth = Float(maxSize.width)
let maxHeight = Float(maxSize.height)
// Get ratio (landscape or portrait)
if imageWidth > imageHeight {
// Landscape
ratio = maxWidth / imageWidth
} else {
// Portrait
ratio = maxHeight / imageHeight
}
// Calculate new size based on the ratio
let newWidth = imageWidth * ratio
let newHeight = imageHeight * ratio
// Create a new NSSize object with the newly calculated size
let newSize: NSSize = NSSize(width: Int(newWidth), height: Int(newHeight))
// Cast the NSImage to a CGImage
var imageRect: NSRect = NSMakeRect(0, 0, size.width, size.height)
let imageRef = cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
// Create NSImage from the CGImage using the new size
let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize)
// Return the new image
return imageWithNewSize
}
func rotateByDegreess(degrees: CGFloat) -> NSImage {
var imageBounds = NSZeroRect; imageBounds.size = size
let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform()
transform.rotate(byDegrees: degrees)
pathBounds.transform(using: transform as AffineTransform)
let rotatedBounds: NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, size.width, size.height)
let rotatedImage = NSImage(size: rotatedBounds.size)
// Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform
transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center)
transform.translateX(by: +(NSWidth(rotatedBounds) / 2), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotate(byDegrees: degrees)
// Move the coordinate system bak to normal
transform.translateX(by: -(NSWidth(rotatedBounds) / 2), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image
rotatedImage.lockFocus()
transform.concat()
draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}

View File

@ -0,0 +1,31 @@
//
// ExtendNSTouchBar.swift
// MTMR
//
// Created by Anton Palgunov on 07/06/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
func presentSystemModal(_ touchBar: NSTouchBar!, systemTrayItemIdentifier identifier: NSTouchBarItem.Identifier!) {
if #available(OSX 10.14, *) {
NSTouchBar.presentSystemModalTouchBar(touchBar, systemTrayItemIdentifier: identifier)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, systemTrayItemIdentifier: identifier)
}
}
func presentSystemModal(_ touchBar: NSTouchBar!, placement: Int64, systemTrayItemIdentifier identifier: NSTouchBarItem.Identifier!) {
if #available(OSX 10.14, *) {
NSTouchBar.presentSystemModalTouchBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
}
}
func minimizeSystemModal(_ touchBar: NSTouchBar!) {
if #available(OSX 10.14, *) {
NSTouchBar.minimizeSystemModalTouchBar(touchBar)
} else {
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
}
}

78
MTMR/SwipeItem.swift Normal file
View File

@ -0,0 +1,78 @@
//
// 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()
if let shell = getenv("SHELL") {
task.launchPath = String.init(cString: shell)
} else {
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)")
}
}
}
}
func isEqual(_ object: AnyObject?) -> Bool {
if let object = object as? SwipeItem {
return self.scriptApple?.source as String? == object.scriptApple?.source as String? && self.scriptBash == object.scriptBash && self.direction == object.direction && self.fingers == object.fingers && self.minOffset == object.minOffset
} else {
return false
}
}
}

View File

@ -8,175 +8,576 @@
import Cocoa import Cocoa
class TouchBarController: NSObject, NSTouchBarDelegate { struct ExactItem {
let identifier: NSTouchBarItem.Identifier
let presetItem: BarItemDefinition
}
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let standardConfigPath = appSupportDirectory.appending("/items.json")
extension ItemType {
var identifierBase: String {
switch self {
case .staticButton(title: _):
return "com.toxblh.mtmr.staticButton."
case .appleScriptTitledButton(source: _):
return "com.toxblh.mtmr.appleScriptButton."
case .shellScriptTitledButton(source: _):
return "com.toxblh.mtmr.shellScriptButton."
case .timeButton(formatTemplate: _, timeZone: _, locale: _):
return "com.toxblh.mtmr.timeButton."
case .battery:
return "com.toxblh.mtmr.battery."
case .cpu(refreshInterval: _):
return "com.toxblh.mtmr.cpu."
case .dock(autoResize: _, filter: _):
return "com.toxblh.mtmr.dock"
case .volume:
return "com.toxblh.mtmr.volume"
case .brightness(refreshInterval: _):
return "com.toxblh.mtmr.brightness"
case .weather(interval: _, units: _, api_key: _, icon_type: _):
return "com.toxblh.mtmr.weather"
case .yandexWeather(interval: _):
return "com.toxblh.mtmr.yandexWeather"
case .currency(interval: _, from: _, to: _, full: _):
return "com.toxblh.mtmr.currency"
case .inputsource:
return "com.toxblh.mtmr.inputsource."
case .music(interval: _):
return "com.toxblh.mtmr.music."
case .group(items: _):
return "com.toxblh.mtmr.groupBar."
case .nightShift:
return "com.toxblh.mtmr.nightShift."
case .dnd:
return "com.toxblh.mtmr.dnd."
case .pomodoro(interval: _):
return PomodoroBarItem.identifier
case .network(flip: _):
return NetworkBarItem.identifier
case .darkMode:
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."
}
}
}
extension NSTouchBarItem.Identifier {
static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip")
}
class TouchBarController: NSObject, NSTouchBarDelegate {
static let shared = TouchBarController() static let shared = TouchBarController()
let touchBar = NSTouchBar() var touchBar: NSTouchBar!
var timer = Timer() fileprivate var lastPresetPath = ""
var timeButton: NSButton = NSButton() var jsonItems: [BarItemDefinition] = []
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
var basicView: BasicView?
var swipeItems: [SwipeItem] = []
var blacklistAppIdentifiers: [String] = []
var frontmostApplicationIdentifier: String? {
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
}
private override init() { private override init() {
super.init() super.init()
SupportedTypesHolder.sharedInstance.register(
typename: "exitTouchbar",
item: .staticButton(title: "exit"),
actions: [
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in self?.dismissTouchBar() }))
],
legacyAction: .none,
legacyLongAction: .none
)
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
(
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
guard let `self` = self else { return }
self.reloadPreset(path: self.lastPresetPath)
}))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
}
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
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)
reloadStandardConfig()
}
func createAndUpdatePreset(newJsonItems: [BarItemDefinition]) {
if let oldBar = self.touchBar {
minimizeSystemModal(oldBar)
}
touchBar = NSTouchBar()
jsonItems = newJsonItems
itemDefinitions = [:]
loadItemDefinitions(jsonItems: jsonItems)
updateActiveApp()
}
func didItemsChange(prevItems: [NSTouchBarItem.Identifier: NSTouchBarItem], prevSwipeItems: [SwipeItem]) -> Bool {
var changed = items.count != prevItems.count || swipeItems.count != prevSwipeItems.count
if !changed {
for (item, prevItem) in zip(items, prevItems) {
if item.key != prevItem.key {
changed = true
break
}
}
}
if !changed {
for (swipeItem, prevSwipeItem) in zip(swipeItems, prevSwipeItems) {
if !swipeItem.isEqual(prevSwipeItem) {
changed = true
break
}
}
}
return changed
}
func prepareTouchBar() {
let prevItems = items
let prevSwipeItems = swipeItems
createItems()
let changed = didItemsChange(prevItems: prevItems, prevSwipeItems: prevSwipeItems)
if !changed {
return
}
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier]
})
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
touchBar.delegate = self touchBar.delegate = self
touchBar.defaultItemIdentifiers = [ touchBar.defaultItemIdentifiers = [basicViewIdentifier]
.escButton,
.dismissButton, let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier]
.brightDown, })
.brightUp, let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier]
.prev, })
.play,
.next, basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
.sleep,
.weather,
.volumeDown,
.volumeUp,
.battery,
.time,
]
self.presentTouchBar()
} }
func setupControlStripPresence() { @objc func activeApplicationChanged(_: Notification) {
DFRSystemModalShowsCloseBoxWhenFrontMost(false) updateActiveApp()
let item = NSCustomTouchBarItem(identifier: .controlStripItem)
item.view = NSButton(image: #imageLiteral(resourceName: "Strip"), target: self, action: #selector(presentTouchBar))
NSTouchBarItem.addSystemTrayItem(item)
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
} }
func updateControlStripPresence() {
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
}
@objc private func presentTouchBar() {
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
@objc private func dismissTouchBar() {
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
switch identifier {
case .escButton:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "esc", target: self, action: #selector(handleEsc))
return item
case .dismissButton:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "exit", target: self, action: #selector(dismissTouchBar))
return item
case .brightUp:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "🔆", target: self, action: #selector(handleBrightUp))
return item
case .brightDown:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "🔅", target: self, action: #selector(handleBrightDown))
return item
case .volumeDown: func updateActiveApp() {
let item = NSCustomTouchBarItem(identifier: identifier) if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
item.view = NSButton(title: "🔉", target: self, action: #selector(handleVolumeDown)) dismissTouchBar()
return item } else {
case .volumeUp: prepareTouchBar()
let item = NSCustomTouchBarItem(identifier: identifier) if touchBarContainsAnyItems() {
item.view = NSButton(title: "🔊", target: self, action: #selector(handleVolumeUp)) presentTouchBar()
return item } else {
dismissTouchBar()
case .prev: }
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "", target: self, action: #selector(handlePrev))
return item
case .play:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "", target: self, action: #selector(handlePlay))
return item
case .next:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSButton(title: "", target: self, action: #selector(handleNext))
return item
case .time:
let item = NSCustomTouchBarItem(identifier: identifier)
timeButton = NSButton(title: self.getCurrentTime(), target: self, action: nil)
item.view = timeButton
return item
default:
return nil
} }
} }
func getCurrentTime() -> String { func touchBarContainsAnyItems() -> Bool {
let date = Date() return items.count != 0 || swipeItems.count != 0
}
func reloadStandardConfig() {
let presetPath = standardConfigPath
if !FileManager.default.fileExists(atPath: presetPath),
let defaultPreset = Bundle.main.path(forResource: "defaultPreset", ofType: "json") {
try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath)
}
reloadPreset(path: presetPath)
}
func reloadPreset(path: String) {
lastPresetPath = path
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: [:])]
createAndUpdatePreset(newJsonItems: items)
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("HH:mm") dateFormatter.dateFormat = "HH-mm-ss"
let timestamp = dateFormatter.string(from: date) let time = dateFormatter.string(from: Date())
return timestamp for item in jsonItems {
let identifierString = item.type.identifierBase.appending(time + "--" + UUID().uuidString)
let identifier = NSTouchBarItem.Identifier(identifierString)
itemDefinitions[identifier] = item
if item.align == .left {
leftIdentifiers.append(identifier)
}
if item.align == .right {
rightIdentifiers.append(identifier)
}
if item.align == .center {
centerIdentifiers.append(identifier)
}
}
} }
@objc func updateTime() { func createItems() {
timeButton.title = getCurrentTime() items = [:]
swipeItems = []
for (identifier, definition) in itemDefinitions {
var show = true
if let frontApp = frontmostApplicationIdentifier {
if case let .matchAppId(regexString)? = definition.additionalParameters[.matchAppId] {
let regex = try! NSRegularExpression(pattern: regexString)
let range = NSRange(location: 0, length: frontApp.count)
if regex.firstMatch(in: frontApp, range: range) == nil {
show = false
}
}
}
if show {
let item = createItem(forIdentifier: identifier, definition: definition)
if item is SwipeItem {
swipeItems.append(item as! SwipeItem)
} else {
items[identifier] = item
}
}
}
} }
@objc func handleEsc() { @objc func setupControlStripPresence() {
let sender = ESCKeyPress() DFRSystemModalShowsCloseBoxWhenFrontMost(false)
sender.send() let item = NSCustomTouchBarItem(identifier: .controlStripItem)
item.view = NSButton(image: #imageLiteral(resourceName: "StatusImage"), target: self, action: #selector(presentTouchBar))
NSTouchBarItem.addSystemTrayItem(item)
updateControlStripPresence()
} }
@objc func handleVolumeUp() { func updateControlStripPresence() {
HIDPostAuxKey(Int(NX_KEYTYPE_SOUND_UP)) let showMtmrButtonOnControlStrip = touchBarContainsAnyItems()
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, showMtmrButtonOnControlStrip)
} }
@objc func handleVolumeDown() { @objc private func presentTouchBar() {
HIDPostAuxKey(Int(NX_KEYTYPE_SOUND_DOWN)) if AppSettings.showControlStripState {
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
updateControlStripPresence()
} }
@objc func handleBrightDown() { @objc private func dismissTouchBar() {
// HIDPostAuxKey(Int(NX_KEYTYPE_BRIGHTNESS_DOWN)) if touchBarContainsAnyItems() {
minimizeSystemModal(touchBar)
}
updateControlStripPresence()
}
@objc func resetControlStrip() {
dismissTouchBar()
updateActiveApp()
}
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == basicViewIdentifier {
return basicView
}
return nil
}
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
var barItem: NSTouchBarItem!
switch item.type {
case let .staticButton(title: title):
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages):
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages)
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale):
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery:
barItem = BatteryBarItem(identifier: identifier)
case let .cpu(refreshInterval: refreshInterval):
barItem = CPUBarItem(identifier: identifier, refreshInterval: refreshInterval)
case let .dock(autoResize: autoResize, filter: regexString):
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:
if case let .image(source)? = item.additionalParameters[.image] {
barItem = VolumeViewController(identifier: identifier, image: source.image)
} else {
barItem = VolumeViewController(identifier: identifier)
}
case let .brightness(refreshInterval: interval):
if case let .image(source)? = item.additionalParameters[.image] {
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval, image: source.image)
} else {
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval)
}
case let .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type):
barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key, icon_type: icon_type)
case let .yandexWeather(interval: interval):
barItem = YandexWeatherBarItem(identifier: identifier, interval: interval)
case let .currency(interval: interval, from: from, to: to, full: full):
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full)
case .inputsource:
barItem = InputSourceBarItem(identifier: identifier)
case let .music(interval: interval, disableMarquee: disableMarquee):
barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee)
case let .group(items: items):
barItem = GroupBarItem(identifier: identifier, items: items)
case .nightShift:
barItem = NightShiftBarItem(identifier: identifier)
case .dnd:
barItem = DnDBarItem(identifier: identifier)
case let .pomodoro(workTime: workTime, restTime: restTime):
barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime)
case let .network(flip: flip, units: units):
barItem = NetworkBarItem(identifier: identifier, flip: flip, units: units)
case .darkMode:
barItem = DarkModeBarItem(identifier: identifier)
case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash):
barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
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 {
item.actions.append(ItemAction(trigger: .singleTap, action))
}
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
item.actions.append(ItemAction(trigger: .longTap, longAction))
}
let sender = BrightnessUpPress() if let touchBarItem = barItem as? CustomButtonTouchBarItem {
sender.send() for action in item.actions {
touchBarItem.actions.append(ItemAction(trigger: action.trigger, self.closure(for: action)))
}
}
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
item.isBordered = bordered
}
if case let .background(color)? = item.additionalParameters[.background], let item = barItem as? CustomButtonTouchBarItem {
item.backgroundColor = color
}
if case let .width(value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth {
widthBarItem.setWidth(value: value)
}
if case let .image(source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
item.image = source.image
}
if case let .title(value)? = item.additionalParameters[.title] {
if let item = barItem as? GroupBarItem {
item.collapsedRepresentationLabel = value
} else if let item = barItem as? CustomButtonTouchBarItem {
item.title = value
}
}
return barItem
} }
@objc func handleBrightUp() { func closure(for action: Action) -> (() -> Void)? {
// HIDPostAuxKey(Int(NX_KEYTYPE_BRIGHTNESS_UP)) 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
}
}
let sender = BrightnessDownPress() func action(forItem item: BarItemDefinition) -> (() -> Void)? {
sender.send() switch item.legacyAction {
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 \(item)")
return {}
}
return {
DispatchQueue.appleScriptQueue.async {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(item) ")
}
}
}
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
}
} }
@objc func handlePrev() {
HIDPostAuxKey(Int(NX_KEYTYPE_PREVIOUS))
}
@objc func handlePlay() {
HIDPostAuxKey(Int(NX_KEYTYPE_PLAY))
}
@objc func handleNext() {
HIDPostAuxKey(Int(NX_KEYTYPE_NEXT))
}
// func getBattery() {
// var error: NSDictionary?
// if let scriptObject = NSAppleScript(source: <#T##String#>) {
// if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(
// &error) {
// print(output.stringValue)
// } else if (error != nil) {
// print("error: \(error)")
// }
// }
// }
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
switch item.legacyLongAction {
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 \(item)")
return {}
}
return {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(item) ")
}
}
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
}
}
}
protocol CanSetWidth {
func setWidth(value: CGFloat)
}
extension NSCustomTouchBarItem: CanSetWidth {
func setWidth(value: CGFloat) {
view.widthAnchor.constraint(equalToConstant: value).isActive = true
}
}
extension NSPopoverTouchBarItem: CanSetWidth {
func setWidth(value: CGFloat) {
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
}
}
extension BarItemDefinition {
var align: Align {
if case let .align(result)? = additionalParameters[.align] {
return result
}
return .center
}
} }

View File

@ -1,36 +0,0 @@
//
// TouchBarItems.swift
// MTMR
//
// Created by Anton Palgunov on 18/03/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
extension NSTouchBarItem.Identifier {
static let escButton = NSTouchBarItem.Identifier("com.toxblh.mtmr.escButton")
static let dismissButton = NSTouchBarItem.Identifier("com.toxblh.mtmr.dismissButton")
// Volume
static let volumeUp = NSTouchBarItem.Identifier("com.toxblh.mtmr.volumeUp")
static let volumeDown = NSTouchBarItem.Identifier("com.toxblh.mtmr.volumeDown")
// Brightness
static let brightUp = NSTouchBarItem.Identifier("com.toxblh.mtmr.brightUp")
static let brightDown = NSTouchBarItem.Identifier("com.toxblh.mtmr.brightDown")
// Music
static let prev = NSTouchBarItem.Identifier("com.toxblh.mtmr.prev")
static let next = NSTouchBarItem.Identifier("com.toxblh.mtmr.next")
static let play = NSTouchBarItem.Identifier("com.toxblh.mtmr.play")
// Plugins
static let sleep = NSTouchBarItem.Identifier("com.toxblh.mtmr.sleep")
static let weather = NSTouchBarItem.Identifier("com.toxblh.mtmr.weather")
static let time = NSTouchBarItem.Identifier("com.toxblh.mtmr.time")
static let battery = NSTouchBarItem.Identifier("com.toxblh.mtmr.battery")
static let nowPlaying = NSTouchBarItem.Identifier("com.toxblh.mtmr.nowPlaying")
static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip")
}

View File

@ -1,10 +0,0 @@
//
// TouchBarPrivateApi-Bridging.h
// MTMR
//
// Created by Anton Palgunov on 18/03/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
#import "TouchBarPrivateApi.h"
#import "TouchBarSupport.h"

View File

@ -1,27 +0,0 @@
//
// ViewController.swift
// MTMR
//
// Created by Anton Palgunov on 16/03/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}

View File

@ -0,0 +1,248 @@
//
// AppScrubberTouchBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 18.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem {
private var scrollView = NSScrollView()
private var autoResize: Bool = false
private var widthConstraint: NSLayoutConstraint?
private let filter: NSRegularExpression?
private var persistentAppIdentifiers: [String] = []
private var runningAppsIdentifiers: [String] = []
private var frontmostApplicationIdentifier: String? {
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
}
private var applications: [DockItem] = []
private var items: [DockBarItem] = []
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false, filter: NSRegularExpression? = nil) {
self.filter = filter
super.init(identifier: identifier)
self.autoResize = autoResize
view = scrollView
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)
persistentAppIdentifiers = AppSettings.dockPersistentAppIds
hardReloadItems()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func hardReloadItems() {
applications = launchedApplications()
applications += getDockPersistentAppsList()
reloadData()
softReloadItems()
updateSize()
}
@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() {
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
}
}
func reloadData() {
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 createAppButton(for app: DockItem) -> DockBarItem {
let item = DockBarItem(app)
item.isBordered = false
item.actions.append(contentsOf: [
ItemAction(trigger: .singleTap) { [weak self] in
self?.switchToApp(app: app)
},
ItemAction(trigger: .longTap) { [weak self] in
self?.handleHalfLongPress(item: app)
}
])
item.killAppClosure = {[weak self] in
self?.handleLongPress(item: app)
}
return item
}
public func switchToApp(app: DockItem) {
let bundleIdentifier = app.bundleIdentifier
if bundleIdentifier!.contains("file://") {
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
} else {
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
}
softReloadItems()
// 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
}
//todo
private func handleLongPress(item: DockItem) {
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
if !app.terminate() {
app.forceTerminate()
}
hardReloadItems()
}
}
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] {
runningAppsIdentifiers = []
var returnable: [DockItem] = []
for app in NSWorkspace.shared.runningApplications {
guard app.activationPolicy == NSApplication.ActivationPolicy.regular 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)
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
returnable.append(dockItem)
}
return returnable
}
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage {
if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) {
return NSWorkspace.shared.icon(forFile: appPath)
}
if let path = path {
return NSWorkspace.shared.icon(forFile: path)
}
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
return genericIcon ?? NSImage(size: .zero)
}
public func getDockPersistentAppsList() -> [DockItem] {
var returnable: [DockItem] = []
for bundleIdentifier in persistentAppIdentifiers {
if !runningAppsIdentifiers.contains(bundleIdentifier) {
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier))
returnable.append(dockItem)
}
}
return returnable
}
}
public class DockItem: NSObject {
var bundleIdentifier: String!, icon: NSImage!, pid: Int32!
convenience init(bundleIdentifier: String, icon: NSImage, pid: Int32? = nil) {
self.init()
self.bundleIdentifier = bundleIdentifier
self.icon = icon
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")
}
}

View File

@ -0,0 +1,140 @@
//
// BatteryBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 18/04/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
import IOKit.ps
class BatteryBarItem: CustomButtonTouchBarItem {
private let batteryInfo = BatteryInfo()
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: " ")
batteryInfo.start { [weak self] in
self?.refresh()
}
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refresh() {
attributedTitle = batteryInfo.formattedInfo()
}
deinit {
batteryInfo.stop()
}
}
class BatteryInfo: NSObject {
var current: Int = 0
var timeToEmpty: Int = 0
var timeToFull: Int = 0
var isCharged: Bool = false
var isCharging: Bool = false
var ACPower: String = ""
var timeRemaining: String = ""
var notifyBlock: () -> Void = {}
var loop: CFRunLoopSource?
func start(notifyBlock: @escaping () -> Void) {
self.notifyBlock = notifyBlock
let opaque = Unmanaged.passRetained(self).toOpaque()
let context = UnsafeMutableRawPointer(opaque)
loop = IOPSNotificationCreateRunLoopSource({ context in
guard let ctx = context else {
return
}
let watcher = Unmanaged<BatteryInfo>.fromOpaque(ctx).takeUnretainedValue()
watcher.notifyBlock()
}, context).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
func stop() {
notifyBlock = {}
guard let loop = self.loop else {
return
}
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
self.loop = nil
}
func getPSInfo() {
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
for ps in psList {
if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] {
if let current = psDesc[kIOPSCurrentCapacityKey] as? Int {
self.current = current
}
if let timeToEmpty = psDesc[kIOPSTimeToEmptyKey] as? Int {
self.timeToEmpty = timeToEmpty
}
if let timeToFull = psDesc[kIOPSTimeToFullChargeKey] as? Int {
self.timeToFull = timeToFull
}
if let isCharged = psDesc[kIOPSIsChargedKey] as? Bool {
self.isCharged = isCharged
}
if let isCharging = psDesc[kIOPSIsChargingKey] as? Bool {
self.isCharging = isCharging
}
if let ACPower = psDesc[kIOPSPowerSourceStateKey] as? String {
self.ACPower = ACPower
}
}
}
}
func getFormattedTime(time: Int) -> String {
if time > 0 {
let timeFormatted = NSString(format: " %d:%02d", time / 60, time % 60) as String
return timeFormatted
}
return ""
}
public func formattedInfo() -> NSAttributedString {
var title = ""
getPSInfo()
if ACPower == "AC Power" {
if current < 100 {
title += "⚡️"
}
timeRemaining = getFormattedTime(time: timeToFull)
} else {
timeRemaining = getFormattedTime(time: timeToEmpty)
}
title += String(current) + "%"
var color = NSColor.white
if current <= 10 && ACPower != "AC Power" {
color = NSColor.red
}
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: NSFont.systemFont(ofSize: 15), .baselineOffset: 1])
let newTitleSecond = NSMutableAttributedString(string: timeRemaining as String, attributes: [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular), NSAttributedString.Key.baselineOffset: 7])
newTitle.append(newTitleSecond)
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
return newTitle
}
}

View File

@ -0,0 +1,71 @@
import AppKit
import AVFoundation
import Cocoa
import CoreAudio
class BrightnessViewController: NSCustomTouchBarItem {
private(set) var sliderItem: CustomSlider!
init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) {
super.init(identifier: identifier)
if image == nil {
sliderItem = CustomSlider()
} else {
sliderItem = CustomSlider(knob: image!)
}
sliderItem.target = self
sliderItem.action = #selector(BrightnessViewController.sliderValueChanged(_:))
sliderItem.minValue = 0.0
sliderItem.maxValue = 100.0
sliderItem.floatValue = getBrightness() * 100
view = sliderItem
let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
sliderItem.unbind(NSBindingName.value)
}
@objc func updateBrightnessSlider() {
DispatchQueue.main.async {
self.sliderItem.floatValue = self.getBrightness() * 100
}
}
@objc func sliderValueChanged(_ sender: Any) {
if let sliderItem = sender as? NSSlider {
setBrightness(level: Float32(sliderItem.intValue) / 100.0)
}
}
private func getBrightness() -> Float32 {
if #available(OSX 10.13, *) {
return Float32(CoreDisplay_Display_GetUserBrightness(0))
} else {
var level: Float32 = 0.5
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &level)
return level
}
}
private func setBrightness(level: Float) {
if #available(OSX 10.13, *) {
CoreDisplay_Display_SetUserBrightness(0, Double(level))
} else {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
IODisplaySetFloatParameter(service, 1, kIODisplayBrightnessKey as CFString, level)
IOObjectRelease(service)
}
}
}

View File

@ -0,0 +1,82 @@
//
// CPUBarItem.swift
// MTMR
//
// Created by bobrosoft on 17/08/2021.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class CPUBarItem: CustomButtonTouchBarItem {
private let refreshInterval: TimeInterval
private var refreshQueue: DispatchQueue? = DispatchQueue(label: "mtmr.cpu")
private let defaultSingleTapScript: NSAppleScript! = "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".appleScript
init(identifier: NSTouchBarItem.Identifier, refreshInterval: TimeInterval) {
self.refreshInterval = refreshInterval
super.init(identifier: identifier, title: "")
// Set default image
if self.image == nil {
self.image = #imageLiteral(resourceName: "cpu").resize(maxSize: NSSize(width: 24, height: 24));
}
// Set default action
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
actions.append(ItemAction(
trigger: .singleTap,
defaultTapAction
))
}
refreshAndSchedule()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
DispatchQueue.main.async {
// Get CPU load
let usage = 100 - CPU.systemUsage().idle
guard usage.isFinite else {
return
}
// Choose color based on CPU load
var color: NSColor? = nil
var bgColor: NSColor? = nil
if usage > 70 {
color = .black
bgColor = .yellow
} else if usage > 30 {
color = .yellow
}
// Update layout
let attrTitle = NSMutableAttributedString.init(attributedString: String(format: "%.1f%%", usage).defaultTouchbarAttributedString)
if let color = color {
attrTitle.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attrTitle.length))
}
self.attributedTitle = attrTitle
self.backgroundColor = bgColor
}
refreshQueue?.asyncAfter(deadline: .now() + refreshInterval) { [weak self] in
self?.refreshAndSchedule()
}
}
func defaultTapAction() {
refreshQueue?.async { [weak self] in
self?.defaultSingleTapScript.executeAndReturnError(nil)
}
}
deinit {
refreshQueue?.suspend()
refreshQueue = nil
}
}

View File

@ -0,0 +1,176 @@
//
// CurrencyBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 18.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
import CoreLocation
class CurrencyBarItem: CustomButtonTouchBarItem {
private let activity: NSBackgroundActivityScheduler
private var prefix: String
private var postfix: String
private var from: String
private var to: String
private var decimal: Int
private var decimalValue: Float32!
private var decimalString: String!
private var oldValue: Float32!
private var full: Bool = false
private let currencies = [
"USD": "$",
"EUR": "",
"RUB": "",
"JPY": "¥",
"GBP": "",
"CAD": "$",
"KRW": "",
"CNY": "¥",
"AUD": "$",
"BRL": "R$",
"IDR": "Rp",
"MXN": "$",
"SGD": "$",
"BTC": "฿",
"LTC": "Ł",
"ETH": "Ξ",
"SOL": "",
"DOT": "",
"DOGE": "Ð",
"XMR": "ɱ",
"ADA": "",
"PLN": "",
"UAH": "",
]
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": 3,
"LTC": 2,
"ETH": 2,
"DOT": 3,
"DOGE": 4,
"ADA": 3,
"USDT": 3
]
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval
self.from = from
self.to = to
self.full = full
if let prefix = currencies[from] {
self.prefix = prefix
} else {
prefix = from
}
if let postfix = currencies[to] {
self.postfix = postfix
} else {
postfix = to
}
if let decimal = decimals[to] {
self.decimal = decimal
} else {
decimal = 2
}
super.init(identifier: identifier, title: "")
activity.repeats = true
activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
self.updateCurrency()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateCurrency()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func updateCurrency() {
let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!)
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
var value: Float32!
if let data_array = json["data"] as? [String: AnyObject] {
if let rates = data_array["rates"] as? [String: AnyObject] {
if let item = rates["\(self.to)"] as? String {
value = Float32(item)
}
}
}
if value != nil {
DispatchQueue.main.async {
self.setCurrency(value: value!)
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
}
task.resume()
}
func setCurrency(value: Float32) {
var color = NSColor.white
if let oldValue = self.oldValue {
if oldValue < value {
color = NSColor.green
} else if oldValue > value {
color = NSColor.red
}
}
oldValue = value
decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal))
decimalString = String(decimalValue)
var title = ""
if full {
title = String(format: "%@%@‣%@", prefix, postfix, decimalString)
} else {
title = String(format: "%@%.2f", prefix, value)
}
let regularFont = attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15)
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: regularFont, .baselineOffset: 1])
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
attributedTitle = newTitle
}
deinit {
activity.invalidate()
}
}

View File

@ -0,0 +1,57 @@
import Foundation
class DarkModeBarItem: CustomButtonTouchBarItem, Widget {
static var name: String = "darkmode"
static var identifier: String = "com.toxblh.mtmr.darkmode"
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 24)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DarkModeToggle() })
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func DarkModeToggle() {
DarkMode.isEnabled = !DarkMode.isEnabled
refresh()
}
@objc func refresh() {
image = DarkMode.isEnabled ? #imageLiteral(resourceName: "dark-mode-on") : #imageLiteral(resourceName: "dark-mode-off")
}
}
struct DarkMode {
private static let prefix = "tell application \"System Events\" to tell appearance preferences to"
static var isEnabled: Bool {
get {
return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark"
}
set {
toggle(force: newValue)
}
}
static func toggle(force: Bool? = nil) {
let value = force.map(String.init) ?? "not dark mode"
_ = runAppleScript("\(prefix) set dark mode to \(value)")
}
}
func runAppleScript(_ source: String) -> String? {
return NSAppleScript(source: source)?.executeAndReturnError(nil).stringValue
}

View File

@ -0,0 +1,78 @@
//
// DnDBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 29/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class DnDBarItem: CustomButtonTouchBarItem {
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 32)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DnDToggle() })
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func DnDToggle() {
DoNotDisturb.isEnabled = !DoNotDisturb.isEnabled
refresh()
}
@objc func refresh() {
image = DoNotDisturb.isEnabled ? #imageLiteral(resourceName: "dnd-on") : #imageLiteral(resourceName: "dnd-off")
}
}
public struct DoNotDisturb {
private static let appId = "com.apple.notificationcenterui" as CFString
private static let dndPref = "com.apple.notificationcenterui.dndprefs_changed"
private static func set(_ key: String, value: CFPropertyList?) {
CFPreferencesSetValue(key as CFString, value, appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
}
private static func commitChanges() {
CFPreferencesSynchronize(appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
DistributedNotificationCenter.default().postNotificationName(NSNotification.Name(dndPref), object: nil, userInfo: nil, deliverImmediately: true)
NSRunningApplication.runningApplications(withBundleIdentifier: appId as String).first?.terminate()
}
private static func enable() {
set("dndStart", value: nil)
set("dndEnd", value: nil)
set("doNotDisturb", value: true as CFPropertyList)
set("doNotDisturbDate", value: Date() as CFPropertyList)
commitChanges()
}
private static func disable() {
set("dndStart", value: nil)
set("dndEnd", value: nil)
set("doNotDisturb", value: false as CFPropertyList)
set("doNotDisturbDate", value: nil)
commitChanges()
}
static var isEnabled: Bool {
get {
return CFPreferencesGetAppBooleanValue("doNotDisturb" as CFString, appId, nil)
}
set {
newValue ? enable() : disable()
}
}
}

View File

@ -0,0 +1,100 @@
//
// GroupBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 11.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate {
var jsonItems: [BarItemDefinition]
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
var centerItems: [NSTouchBarItem] = []
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
var scrollArea: NSCustomTouchBarItem?
var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
init(identifier: NSTouchBarItem.Identifier, items: [BarItemDefinition]) {
jsonItems = items
super.init(identifier: identifier)
popoverTouchBar.delegate = self
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {}
@objc override func showPopover(_: Any?) {
itemDefinitions = [:]
items = [:]
leftIdentifiers = []
centerItems = []
rightIdentifiers = []
loadItemDefinitions(jsonItems: jsonItems)
createItems()
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier]
})
centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
TouchBarController.shared.touchBar.delegate = self
TouchBarController.shared.touchBar.defaultItemIdentifiers = []
TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
if AppSettings.showControlStripState {
presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
}
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == centerScrollArea {
return scrollArea
}
guard let item = self.items[identifier],
let definition = self.itemDefinitions[identifier],
definition.align != .center else {
return nil
}
return item
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH-mm-ss"
let time = dateFormatter.string(from: Date())
for item in jsonItems {
let identifierString = item.type.identifierBase.appending(time + "--" + UUID().uuidString)
let identifier = NSTouchBarItem.Identifier(identifierString)
itemDefinitions[identifier] = item
if item.align == .left {
leftIdentifiers.append(identifier)
}
if item.align == .right {
rightIdentifiers.append(identifier)
}
if item.align == .center {
centerIdentifiers.append(identifier)
}
}
}
func createItems() {
for (identifier, definition) in itemDefinitions {
items[identifier] = TouchBarController.shared.createItem(forIdentifier: identifier, definition: definition)
}
}
}

View File

@ -0,0 +1,133 @@
//
// InputSourceBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 22.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class InputSourceBarItem: CustomButtonTouchBarItem {
fileprivate var notificationCenter: CFNotificationCenter
let buttonSize = NSSize(width: 21, height: 21)
init(identifier: NSTouchBarItem.Identifier) {
notificationCenter = CFNotificationCenterGetDistributedCenter()
super.init(identifier: identifier, title: "")
observeIputSourceChangedNotification()
textInputSourceDidChange()
actions.append(ItemAction(trigger: .singleTap) { [weak self] in
self?.switchInputSource()
})
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
CFNotificationCenterRemoveEveryObserver(notificationCenter, UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()))
}
@objc public func textInputSourceDidChange() {
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
var iconImage: NSImage?
if let imageURL = currentSource.iconImageURL,
let image = NSImage(contentsOf: imageURL) {
iconImage = image
} else if let iconRef = currentSource.iconRef {
iconImage = NSImage(iconRef: iconRef)
}
if let iconImage = iconImage {
iconImage.size = buttonSize
image = iconImage
title = ""
} else {
title = currentSource.name
}
}
@objc private func switchInputSource() {
var inputSources: [TISInputSource] = []
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
let inputSourceNSArray = TISCreateInputSourceList(nil, false).takeRetainedValue() as NSArray
let elements = inputSourceNSArray as! [TISInputSource]
inputSources = elements.filter({
$0.category == TISInputSource.Category.keyboardInputSource && $0.isSelectable
})
for item in inputSources {
if item.id != currentSource.id {
TISSelectInputSource(item)
break
}
}
}
@objc public func observeIputSourceChangedNotification() {
let callback: CFNotificationCallback = { _, observer, _, _, _ in
let mySelf = Unmanaged<InputSourceBarItem>.fromOpaque(observer!).takeUnretainedValue()
mySelf.textInputSourceDidChange()
}
CFNotificationCenterAddObserver(notificationCenter,
UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()),
callback,
kTISNotifySelectedKeyboardInputSourceChanged,
nil,
.deliverImmediately)
}
}
extension TISInputSource {
enum Category {
static var keyboardInputSource: String {
return kTISCategoryKeyboardInputSource as String
}
}
private func getProperty(_ key: CFString) -> AnyObject? {
let cfType = TISGetInputSourceProperty(self, key)
if cfType != nil {
return Unmanaged<AnyObject>.fromOpaque(cfType!).takeUnretainedValue()
} else {
return nil
}
}
var id: String {
return getProperty(kTISPropertyInputSourceID) as! String
}
var name: String {
return getProperty(kTISPropertyLocalizedName) as! String
}
var category: String {
return getProperty(kTISPropertyInputSourceCategory) as! String
}
var isSelectable: Bool {
return getProperty(kTISPropertyInputSourceIsSelectCapable) as! Bool
}
var sourceLanguages: [String] {
return getProperty(kTISPropertyInputSourceLanguages) as! [String]
}
var iconImageURL: URL? {
return getProperty(kTISPropertyIconImageURL) as! URL?
}
var iconRef: IconRef? {
return OpaquePointer(TISGetInputSourceProperty(self, kTISPropertyIconRef)) as IconRef?
}
}

View File

@ -0,0 +1,461 @@
//
// MusicBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 05.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
import ScriptingBridge
class MusicBarItem: CustomButtonTouchBarItem {
private enum Player: String {
case Music = "com.apple.Music"
case iTunes = "com.apple.iTunes"
case Spotify = "com.spotify.client"
case VOX = "com.coppertino.Vox"
case Chrome = "com.google.Chrome"
case Safari = "com.apple.Safari"
}
private let playerBundleIdentifiers = [
Player.Music,
Player.iTunes,
Player.Spotify,
Player.VOX,
Player.Chrome,
Player.Safari,
]
private let interval: TimeInterval
private let disableMarquee: Bool
private var songTitle: String?
private var timer: Timer?
private let iconSize = NSSize(width: 21, height: 21)
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, disableMarquee: Bool) {
self.interval = interval
self.disableMarquee = disableMarquee
super.init(identifier: identifier, title: "")
isBordered = false
actions = [
ItemAction(trigger: .singleTap) { [weak self] in self?.playPause() },
ItemAction(trigger: .doubleTap) { [weak self] in self?.previousTrack() },
ItemAction(trigger: .longTap) { [weak self] in self?.nextTrack() }
]
refreshAndSchedule()
}
@objc func marquee() {
let str = title
if str.count > 10 {
let indexFirst = str.index(str.startIndex, offsetBy: 0)
let indexSecond = str.index(str.startIndex, offsetBy: 1)
title = String(str.suffix(from: indexSecond)) + String(str[indexFirst])
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func playPause() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.playpause!()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
mp.playpause!()
return
} else if ident == .Music {
let mp = (musicPlayer as MusicApplication)
mp.playpause!()
return
} else if ident == .VOX {
let mp = (musicPlayer as VoxApplication)
mp.playpause!()
return
} else if ident == .Safari {
// You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use 'do JavaScript'.
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('player-controls__btn_play')[0].click()", in: tab)
return
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('audio_page_player_play')[0].click()", in: tab)
return
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
_ = safariApplication.doJavaScript!("document.getElementById('movie_player').click()", in: tab)
return
}
}
}
}
// else if (ident == .Chrome) {
// let chromeApplication = musicPlayer as GoogleChromeApplication
// let chromeWindows = chromeApplication.windows?().compactMap({ $0 as? GoogleChromeWindow })
// for window in chromeWindows! {
// for tab in window.tabs!() {
// let tab = tab as! GoogleChromeTab
// if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
// chromeApplication.executeJavaScript!(javascript: "document.getElementsByClassName('player-controls__btn_play')[0].click()")
// break
// } else if ((tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))!) {
// chromeApplication.executeJavaScript!(javascript: "document.getElementsByClassName('audio_page_player_ctrl')[0].click()")
// break
// } else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
// chromeApplication.executeJavaScript!(javascript: "alert(document.title)") // , id: tab
// break // document.getElementById('movie_player').click()
// }
// }
// }
// }
break
}
}
}
}
@objc func nextTrack() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.nextTrack!()
updatePlayer()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
mp.nextTrack!()
updatePlayer()
return
} else if ident == .Music {
let mp = (musicPlayer as MusicApplication)
mp.nextTrack!()
updatePlayer()
return
} else if ident == .VOX {
let mp = (musicPlayer as VoxApplication)
mp.next!()
updatePlayer()
return
} else if ident == .Safari {
// You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use 'do JavaScript'.
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('player-controls__btn_next')[0].click()", in: tab)
updatePlayer()
return
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('audio_page_player_next')[0].click()", in: tab)
updatePlayer()
return
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('ytp-next-button')[0].click()", in: tab)
updatePlayer()
return
}
}
}
}
}
}
}
}
@objc func previousTrack() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .Music {
let mp = (musicPlayer as MusicApplication)
mp.previousTrack!()
updatePlayer()
return
}
}
}
}
}
func refreshAndSchedule() {
DispatchQueue.main.async {
self.updatePlayer()
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.refreshAndSchedule()
}
}
}
func updatePlayer() {
var iconUpdated = false
var titleUpdated = false
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
var tempTitle = ""
if ident == .Spotify {
tempTitle = (musicPlayer as SpotifyApplication).title
} else if ident == .iTunes {
tempTitle = (musicPlayer as iTunesApplication).title
} else if ident == .Music {
tempTitle = (musicPlayer as MusicApplication).title
} else if ident == .VOX {
tempTitle = (musicPlayer as VoxApplication).title
} else if ident == .Safari {
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
// if (!(tab.name?.hasSuffix("на Яндекс.Музыке"))!) {
tempTitle = (tab.name)!
break
// }
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
tempTitle = (tab.name)!
break
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
tempTitle = (tab.name)!
break
}
}
}
} else if ident == .Chrome {
let chromeApplication = musicPlayer as GoogleChromeApplication
let chromeWindows = chromeApplication.windows?().compactMap({ $0 as? GoogleChromeWindow })
for window in chromeWindows! {
for tab in window.tabs!() {
let tab = tab as! GoogleChromeTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
if !(tab.title?.hasSuffix("на Яндекс.Музыке"))! {
tempTitle = tab.title!
break
}
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
tempTitle = tab.title!
break
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
tempTitle = tab.title!
break
}
}
}
}
if tempTitle == self.songTitle {
return
} else {
self.songTitle = tempTitle
}
if let songTitle = self.songTitle?.ifNotEmpty {
self.timer?.invalidate()
self.timer = nil
if (disableMarquee) {
self.title = " " + songTitle
} else {
self.title = " " + songTitle + " "
self.timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(self.marquee), userInfo: nil, repeats: true)
}
titleUpdated = true
}
if let _ = tempTitle.ifNotEmpty,
let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: ident.rawValue) {
let image = NSWorkspace.shared.icon(forFile: appPath)
image.size = self.iconSize
self.image = image
iconUpdated = true
}
break
}
}
}
DispatchQueue.main.async {
if !iconUpdated {
self.image = nil
}
if !titleUpdated {
self.title = ""
}
}
}
}
@objc protocol SpotifyApplication {
@objc optional var currentTrack: SpotifyTrack { get }
@objc optional func nextTrack()
@objc optional func previousTrack()
@objc optional func playpause()
}
extension SBApplication: SpotifyApplication {}
@objc protocol SpotifyTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: SpotifyTrack {}
extension SpotifyApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + "" + (t.name ?? "")
}
}
@objc protocol iTunesApplication {
@objc optional var currentTrack: iTunesTrack { get }
@objc optional func playpause()
@objc optional func nextTrack()
@objc optional func previousTrack()
}
extension SBApplication: iTunesApplication {}
@objc protocol iTunesTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: iTunesTrack {}
extension iTunesApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + "" + (t.name ?? "")
}
}
@objc protocol MusicApplication {
@objc optional var currentTrack: MusicTrack { get }
@objc optional func playpause()
@objc optional func nextTrack()
@objc optional func previousTrack()
}
extension SBApplication: MusicApplication {}
@objc protocol MusicTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: MusicTrack {}
extension MusicApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + "" + (t.name ?? "")
}
}
@objc protocol VoxApplication {
@objc optional func playpause()
@objc optional func next()
@objc optional func previous()
@objc optional var track: String { get }
@objc optional var artist: String { get }
}
extension SBApplication: VoxApplication {}
extension VoxApplication {
var title: String {
return (artist ?? "") + "" + (track ?? "")
}
}
@objc public protocol SBObjectProtocol: NSObjectProtocol {
func get() -> Any!
}
@objc public protocol SBApplicationProtocol: SBObjectProtocol {
func activate()
var delegate: SBApplicationDelegate! { get set }
}
@objc public protocol SafariApplication: SBApplicationProtocol {
@objc optional func windows() -> SBElementArray
@objc optional func doJavaScript(_ x: String!, in in_: Any!) -> Any // Applies a string of JavaScript code to a document.
}
extension SBApplication: SafariApplication {}
@objc public protocol SafariWindow: SBObjectProtocol {
@objc optional var name: String { get } // The title of the window.
@objc optional func tabs() -> SBElementArray
// @objc optional var document: SafariDocument { get } // The document whose contents are displayed in the window.
// @objc optional func setCurrentTab(_ currentTab: SafariTab!) // The current tab.
}
extension SBObject: SafariWindow {}
// @objc public protocol SafariDocument: SBObjectProtocol {
// @objc optional var name: String { get } // Its name.
// @objc optional var URL: String { get } // The current URL of the document.
// }
// extension SBObject: SafariDocument {}
@objc public protocol SafariTab: SBObjectProtocol {
@objc optional var URL: String { get } // The current URL of the tab.
@objc optional var name: String { get } // The name of the tab.
}
extension SBObject: SafariTab {}
@objc public protocol GoogleChromeApplication: SBApplicationProtocol {
@objc optional func windows() -> SBElementArray
@objc optional func executeJavaScript(javascript: String!) -> Any // Applies a string of JavaScript code to a document. //, id: Any!
}
extension SBApplication: GoogleChromeApplication {}
@objc public protocol GoogleChromeWindow: SBObjectProtocol {
@objc optional var name: String { get } // The title of the window.
@objc optional func tabs() -> SBElementArray
}
extension SBObject: GoogleChromeWindow {}
@objc public protocol GoogleChromeTab: SBObjectProtocol {
@objc optional var URL: String { get } // The current URL of the tab.
@objc optional var title: String { get } // The name of the tab.
}
extension SBObject: GoogleChromeTab {}

View File

@ -0,0 +1,178 @@
//
// NetworkBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 23/02/2019.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
class NetworkBarItem: CustomButtonTouchBarItem, Widget {
static var name: String = "network"
static var identifier: String = "com.toxblh.mtmr.network"
private let flip: Bool
private let units: String
init(identifier: NSTouchBarItem.Identifier, flip: Bool = false, units: String) {
self.flip = flip
self.units = units
super.init(identifier: identifier, title: " ")
startMonitoringProcess()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startMonitoringProcess() {
var pipe: Pipe
var outputHandle: FileHandle
var bandwidthProcess: Process?
var dSpeed: UInt64?
var uSpeed: UInt64?
var curr: Array<Substring>?
var dataAvailable: NSObjectProtocol?
pipe = Pipe()
bandwidthProcess = Process()
bandwidthProcess?.launchPath = "/usr/bin/env"
bandwidthProcess?.arguments = ["netstat", "-w1", "-l", "en0"]
bandwidthProcess?.standardOutput = pipe
outputHandle = pipe.fileHandleForReading
outputHandle.waitForDataInBackgroundAndNotify(forModes: [RunLoop.Mode.common])
dataAvailable = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NSFileHandleDataAvailable,
object: outputHandle,
queue: nil
) { _ -> Void in
let data = pipe.fileHandleForReading.availableData
if data.count > 0 {
if let str = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
curr = [""]
curr = str
.replacingOccurrences(of: " ", with: " ")
.split(separator: " ")
if curr == nil || (curr?.count)! < 6 {} else {
if Int64(curr![2]) == nil {} else {
dSpeed = UInt64(curr![2])
uSpeed = UInt64(curr![5])
self.setTitle(up: self.getHumanizeSize(speed: uSpeed!), down: self.getHumanizeSize(speed: dSpeed!))
}
}
}
outputHandle.waitForDataInBackgroundAndNotify()
} else if let dataAvailable = dataAvailable {
NotificationCenter.default.removeObserver(dataAvailable)
}
}
var dataReady: NSObjectProtocol?
dataReady = NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: outputHandle,
queue: nil
) { _ -> Void in
print("Task terminated!")
if let observer = dataReady {
NotificationCenter.default.removeObserver(observer)
}
}
bandwidthProcess?.launch()
}
func getHumanizeSize(speed: UInt64) -> String {
let humanText: String
func speedB(speed: UInt64)-> String {
return String(format: "%.0f", Double(speed)) + " B/s"
}
func speedKB(speed: UInt64)-> String {
return String(format: "%.1f", Double(speed) / 1024) + " KB/s"
}
func speedMB(speed: UInt64)-> String {
return String(format: "%.1f", Double(speed) / (1024 * 1024)) + " MB/s"
}
func speedGB(speed: UInt64)-> String {
return String(format: "%.2f", Double(speed) / (1024 * 1024 * 1024)) + " GB/s"
}
switch self.units {
case "B/s":
humanText = speedB(speed: speed)
case "KB/s":
humanText = speedKB(speed: speed)
case "MB/s":
humanText = speedMB(speed: speed)
case "GB/s":
humanText = speedGB(speed: speed)
default:
if speed < 1024 {
humanText = speedB(speed: speed)
} else if speed < (1024 * 1024) {
humanText = speedKB(speed: speed)
} else if speed < (1024 * 1024 * 1024) {
humanText = speedMB(speed: speed)
} else {
humanText = speedGB(speed: speed)
}
}
return humanText
}
func appendUpSpeed(appendString: NSMutableAttributedString, up: String, titleFont: NSFont, newStr: Bool = false) {
appendString.append(NSMutableAttributedString(
string: newStr ? "\n" : "",
attributes: [
NSAttributedString.Key.foregroundColor: NSColor.blue,
NSAttributedString.Key.font: titleFont,
]))
appendString.append(NSMutableAttributedString(
string: up,
attributes: [
NSAttributedString.Key.font: titleFont,
]))
}
func appendDownSpeed(appendString: NSMutableAttributedString, down: String, titleFont: NSFont, newStr: Bool = false) {
appendString.append(NSMutableAttributedString(
string: newStr ? "\n" : "",
attributes: [
NSAttributedString.Key.foregroundColor: NSColor.red,
NSAttributedString.Key.font: titleFont,
]))
appendString.append(NSMutableAttributedString(
string: down,
attributes: [
NSAttributedString.Key.font: titleFont
]))
}
func setTitle(up: String, down: String) {
let titleFont = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: NSFont.Weight.light)
let newTitle: NSMutableAttributedString = NSMutableAttributedString(string: "")
if (self.flip) {
appendUpSpeed(appendString: newTitle, up: up, titleFont: titleFont)
appendDownSpeed(appendString: newTitle, down: down, titleFont: titleFont, newStr: true)
} else {
appendDownSpeed(appendString: newTitle, down: down, titleFont: titleFont)
appendUpSpeed(appendString: newTitle, up: up, titleFont: titleFont, newStr: true)
}
self.attributedTitle = newTitle
}
}

View File

@ -0,0 +1,53 @@
//
// NightShiftBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 28/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class NightShiftBarItem: CustomButtonTouchBarItem {
private let nsclient = CBBlueLightClient()
private var timer: Timer!
private var blueLightStatus: Status {
var status: Status = Status()
nsclient.getBlueLightStatus(&status)
return status
}
private var isNightShiftEnabled: Bool {
return blueLightStatus.enabled.boolValue
}
private func setNightShift(state: Bool) {
nsclient.setEnabled(state)
}
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 28)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.nightShiftAction() })
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func nightShiftAction() {
setNightShift(state: !isNightShiftEnabled)
refresh()
}
@objc func refresh() {
image = isNightShiftEnabled ? #imageLiteral(resourceName: "nightShiftOn") : #imageLiteral(resourceName: "nightShiftOff")
}
}

View File

@ -0,0 +1,127 @@
//
// PomodoroBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 10.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
static let identifier = "com.toxblh.mtmr.pomodoro."
static let name = "pomodoro"
static let decoder: ParametersDecoder = { decoder in
enum CodingKeys: String, CodingKey {
case workTime
case restTime
}
let container = try decoder.container(keyedBy: CodingKeys.self)
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime)
let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime)
return (
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
actions: [],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
)
}
private enum TimeTypes {
case work
case rest
case none
}
private let defaultTitle = "🍅 "
private let workTime: TimeInterval
private let restTime: TimeInterval
private var typeTime: TimeTypes = .none
private var timer: DispatchSourceTimer?
private var timeLeft: Int = 0
private var timeLeftString: String {
return String(format: "%.2i:%.2i", timeLeft / 60, timeLeft % 60)
}
init(identifier: NSTouchBarItem.Identifier, workTime: TimeInterval, restTime: TimeInterval) {
self.workTime = workTime
self.restTime = restTime
super.init(identifier: identifier, title: defaultTitle)
actions.append(contentsOf: [
ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() },
ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() }
])
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
timer?.cancel()
timer = nil
}
@objc func startStopWork() {
typeTime = .work
startStopTimer()
}
@objc func startStopRest() {
typeTime = .rest
startStopTimer()
}
func startStopTimer() {
timer == nil ? start() : reset()
}
private func start() {
timeLeft = Int(typeTime == .work ? workTime : restTime)
let queue: DispatchQueue = DispatchQueue(label: "Timer")
timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
timer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .never)
timer?.setEventHandler(handler: tick)
timer?.resume()
NSSound.beep()
}
private func finish() {
if typeTime != .none {
sendNotification()
}
reset()
}
private func reset() {
typeTime = .none
timer?.cancel()
timer = nil
title = defaultTitle
}
private func tick() {
timeLeft -= 1
DispatchQueue.main.async {
if self.timeLeft >= 0 {
self.title = self.defaultTitle + " " + self.timeLeftString
} else {
self.finish()
}
}
}
private func sendNotification() {
let notification: NSUserNotification = NSUserNotification()
notification.title = "Pomodoro"
notification.informativeText = typeTime == .work ? "it's time to rest your mind!" : "It's time to work!"
notification.soundName = "Submarine"
NSUserNotificationCenter.default.deliver(notification)
}
}

View File

@ -0,0 +1,28 @@
import Cocoa
class TimeTouchBarItem: CustomButtonTouchBarItem {
private let dateFormatter = DateFormatter()
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil, locale: String? = nil) {
dateFormatter.dateFormat = formatTemplate
if let locale = locale {
dateFormatter.locale = Locale(identifier: locale)
}
if let abbr = timeZone {
dateFormatter.timeZone = TimeZone(abbreviation: abbr)
}
super.init(identifier: identifier, title: " ")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
isBordered = false
updateTime()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func updateTime() {
title = dateFormatter.string(from: Date())
}
}

View File

@ -0,0 +1,256 @@
//
// UpNextScrubberTouchBarItems.swift
// MTMR
//
// Created by Connor Meehan on 13/7/20.
// Copyright © 2020 Anton Palgunov. All rights reserved.
//
//
import Foundation
import EventKit
class UpNextScrubberTouchBarItem: NSCustomTouchBarItem {
// Dependencies
private let scrollView = NSScrollView()
private let activity: NSBackgroundActivityScheduler // Update scheduler
private var eventSources : [IUpNextSource] = []
private var items: [UpNextItem] = []
// Settings
private var futureSearchCutoff: Double
private var pastSearchCutoff: Double
private var maxToShow: Int
private var widthConstraint: NSLayoutConstraint?
private var autoResize: Bool = false
/// <#Description#>
/// - Parameters:
/// - identifier: Unique identifier of widget
/// - interval: Update view interval in seconds
/// - from: Relative to current time, how far back we search for events in hours
/// - to: Relative to current time, how far forward we search for events in hours
/// - maxToShow: Which event to show (1 is first, 2 is second, and so on)
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, maxToShow: Int, autoResize: Bool) {
// Initialise member properties
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck")
pastSearchCutoff = from * 3600
futureSearchCutoff = to * 3600
self.maxToShow = maxToShow
self.autoResize = autoResize
UpNextItem.df.dateFormat = "HH:mm"
// Error handling
if (maxToShow <= 0) {
fatalError("Error on UpNext bar item. maxToShow property must be greater than 0.")
}
// Init super
super.init(identifier: identifier)
view = scrollView
// Add event sources
// Can optionally pass an update view callback to an event source to redraw element
self.eventSources.append(UpNextCalenderSource(updateCallback: self.updateView))
// Fallback interactivity via interval
activity.interval = interval
activity.repeats = true
activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
self.updateView()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateView()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateView() -> Void {
items = []
var upcomingEvents = self.getUpcomingEvents()
upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending})
var index = 1
DispatchQueue.main.async {
for event in upcomingEvents {
// Create UpNextItem
let item = UpNextItem(event: event)
item.backgroundColor = self.getBackgroundColor(startDate: event.startDate)
// Bind tap event
item.actions.append(ItemAction(trigger: .singleTap) { [weak self] in
self?.switchToApp(event: event)
})
// Add to view
self.items.append(item)
// Check if should display any more
if (index == self.maxToShow) {
break;
}
index += 1
}
self.reloadData()
self.updateSize()
}
}
private func reloadData() {
let stackView = NSStackView(views: items.compactMap { $0.view })
stackView.spacing = 5
stackView.orientation = .horizontal
let visibleRect = self.scrollView.documentVisibleRect
self.scrollView.documentView = stackView
stackView.scroll(visibleRect.origin)
}
func updateSize() {
if self.autoResize {
self.widthConstraint?.isActive = false
let width = self.scrollView.documentView?.fittingSize.width ?? 0
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
self.widthConstraint!.isActive = true
}
}
private func getUpcomingEvents() -> [UpNextEventModel] {
var upcomingEvents: [UpNextEventModel] = []
// Calculate the range we're going to search for events in
let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff)
let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff)
// Get all events from all sources
for eventSource in self.eventSources {
if (eventSource.hasPermission) {
let events = eventSource.getUpcomingEvents(dateLowerBounds: dateLowerBounds, dateUpperBounds: dateUpperBounds)
upcomingEvents.append(contentsOf: events)
}
}
return upcomingEvents
}
public func switchToApp(event: UpNextEventModel) {
var bundleIdentifier: String
switch(event.sourceType) {
case .iCalendar:
bundleIdentifier = UpNextCalenderSource.bundleIdentifier
}
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
// NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings
}
func getBackgroundColor(startDate: Date) -> NSColor {
let distance = startDate.timeIntervalSinceReferenceDate/60 - Date().timeIntervalSinceReferenceDate/60 // Get time difference in minutes
if (distance < 0 as TimeInterval) { // If it's in the past, draw as blue
return NSColor.systemBlue
} else if (distance < 30 as TimeInterval) { // Less than 30 minutes, backround is red
return NSColor.systemRed
}
return NSColor.clear
}
}
private class UpNextItem : CustomButtonTouchBarItem {
static public let df = DateFormatter()
init(event: UpNextEventModel) {
let identifier = UpNextItem.getIdentifier(event: event)
let title = UpNextItem.getTitle(event: event)
super.init(identifier: NSTouchBarItem.Identifier(rawValue: identifier), title: title)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private static func getTitle(event: UpNextEventModel) -> String {
var title = ""
let startDateString = UpNextItem.df.string(for: event.startDate)
switch event.sourceType {
case .iCalendar:
title = String.init(format: "🗓 %@ - %@ ", event.title, startDateString!)
}
return title
}
private static func getIdentifier(event: UpNextEventModel) -> String {
var identifier : String
switch event.sourceType {
case .iCalendar:
identifier = "com.mtmr.iCalendarEvent"
}
return identifier + "." + event.title
}
}
enum UpNextSourceType {
case iCalendar
}
// Model for events to be displayed in dock
struct UpNextEventModel {
let title: String
let startDate: Date
let sourceType: UpNextSourceType
}
// Interface for any event source
protocol IUpNextSource {
static var bundleIdentifier: String { get }
var hasPermission : Bool { get }
var updateCallback : () -> Void { get set }
init(updateCallback: @escaping () -> Void)
func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel]
}
class UpNextCalenderSource : IUpNextSource {
static public let bundleIdentifier: String = "com.apple.iCal"
public var hasPermission: Bool = false
private var eventStore : EKEventStore
internal var updateCallback: () -> Void
required init(updateCallback: @escaping () -> Void = {}) {
self.updateCallback = updateCallback
eventStore = EKEventStore()
NotificationCenter.default.addObserver(forName: .EKEventStoreChanged, object: eventStore, queue: nil, using: handleUpdate)
let authStatus = EKEventStore.authorizationStatus(for: .event)
if (authStatus != .authorized) {
eventStore.requestAccess(to: .event){ granted, error in
self.hasPermission = granted;
self.handleUpdate()
if(!granted) {
NSLog("Error: MTMR UpNextBarWidget not given calendar access.")
return
}
}
} else {
self.handleUpdate()
}
}
public func handleUpdate() {
self.handleUpdate(note: Notification(name: Notification.Name("refresh view")))
}
public func handleUpdate(note: Notification) {
self.updateCallback()
}
public func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] {
var upcomingEvents: [UpNextEventModel] = []
let calendars = self.eventStore.calendars(for: .event)
let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: calendars)
let events = self.eventStore.events(matching: predicate)
for event in events {
upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate, sourceType: UpNextSourceType.iCalendar))
}
return upcomingEvents
}
}

Some files were not shown because too many files have changed in this diff Show More