Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95271dba0e | ||
|
|
67aaa2abf4 | ||
|
|
58e4160649 | ||
|
|
77846d0436 | ||
|
|
bc66b375d3 | ||
|
|
ce8004641d | ||
|
|
155e48a693 | ||
|
|
76a7d59fe5 | ||
|
|
6a85bea5b5 | ||
|
|
000b825ec9 | ||
|
|
3432e24a55 | ||
|
|
705d0a64b5 | ||
|
|
9f0944b06b | ||
|
|
efc52293a8 | ||
|
|
7b853b5d47 | ||
|
|
439246e85b | ||
|
|
c254ee430d | ||
|
|
cfcda6e46f | ||
|
|
144ff9cf79 | ||
|
|
91a4e5bded | ||
|
|
68dfcddb29 | ||
|
|
82ec231700 | ||
|
|
ed261c2349 | ||
|
|
f06d1f4329 | ||
|
|
3dabd598f4 | ||
|
|
0a09ea7117 | ||
|
|
02cf911336 | ||
|
|
d831069025 | ||
|
|
33206ab457 | ||
|
|
c6c808369f | ||
|
|
cbad06ac07 | ||
|
|
64171d5c51 | ||
|
|
5504e5d640 | ||
|
|
164295820b | ||
|
|
ae469bb92a | ||
|
|
57b5129135 | ||
|
|
7cc72de66f | ||
|
|
2e5db4ffa0 | ||
|
|
8a73fe01e4 | ||
|
|
5fbb2bafc6 | ||
|
|
5b6b6dfa56 | ||
|
|
65e5a52383 | ||
|
|
03304bd030 | ||
|
|
f264425ba7 | ||
|
|
c377b9494c | ||
|
|
f5b36ec012 | ||
|
|
999c4cf6e2 | ||
|
|
b8d5d0a8a5 | ||
|
|
6bb5aade07 | ||
|
|
b6bcc31456 | ||
|
|
9d09646742 | ||
|
|
55f988e393 | ||
|
|
2f4cf9e5f6 | ||
|
|
327d38e47d | ||
|
|
cd380b289d | ||
|
|
6a26b2f5ca | ||
|
|
d7186aac18 | ||
|
|
cef07dc965 | ||
|
|
471c0c5d57 | ||
|
|
5b93525806 | ||
|
|
b87bc632ab | ||
|
|
1763a24942 | ||
|
|
627e6951c1 | ||
|
|
918054df78 | ||
|
|
25fb0d4f04 | ||
|
|
61a357ffc3 | ||
|
|
f4116a95df | ||
|
|
c952505c1c | ||
|
|
f10b58b996 | ||
|
|
88a8ae3ae8 | ||
|
|
d783448567 | ||
|
|
595ae165d3 | ||
|
|
da406713dd | ||
|
|
938b83f37e | ||
|
|
a8a085df1e | ||
|
|
b188f45c8e | ||
|
|
3319c5d45a | ||
|
|
6174d14925 | ||
|
|
c8197239d7 | ||
|
|
d2854e3dae | ||
|
|
4f9c3258ae | ||
|
|
dfeda1e874 | ||
|
|
82f82039ae | ||
|
|
324c3f711e | ||
|
|
a81d6fc595 | ||
|
|
3bde1fe4b1 | ||
|
|
7c9dd26eb0 | ||
|
|
40fa61fcb5 | ||
|
|
5a8117ac7f | ||
|
|
a606767ddc | ||
|
|
6badc37dc3 | ||
|
|
4497c7102c | ||
|
|
b6c6bb945b | ||
|
|
3e399d14c2 | ||
|
|
624c8ac6d8 | ||
|
|
ea68ce33c1 | ||
|
|
744ea067db | ||
|
|
8064ab759c | ||
|
|
556c28df85 | ||
|
|
e68f930efa | ||
|
|
647ac0ba20 | ||
|
|
780a8ba81e | ||
|
|
71f5030fa6 | ||
|
|
f435c0b530 | ||
|
|
e278482807 | ||
|
|
51d246bf7f | ||
|
|
c1cd402241 | ||
|
|
c496156cf2 | ||
|
|
154ea09e98 | ||
|
|
3b493fd5be | ||
|
|
b718b1c9dd | ||
|
|
acc248a579 | ||
|
|
59581ffdc8 | ||
|
|
238b15b6a4 | ||
|
|
732fd5c5b5 | ||
|
|
f4b8e1f39a | ||
|
|
e988951ff9 | ||
|
|
807290ff0c | ||
|
|
0775ea0412 | ||
|
|
1d1d666986 | ||
|
|
560d8ed508 | ||
|
|
06d6f06adf | ||
|
|
0fafe3912e | ||
|
|
319241d4d4 | ||
|
|
61d5e8f77e | ||
|
|
e62530d7ab | ||
|
|
43675027d5 | ||
|
|
b70c040111 | ||
|
|
e795f77ba1 | ||
|
|
37b0462bc1 | ||
|
|
d2d33dee3b | ||
|
|
c0fc40b1c9 | ||
|
|
47a2027443 | ||
|
|
b3c1d6b323 | ||
|
|
a15cc6506f | ||
|
|
35a6ceae07 | ||
|
|
be1c439867 | ||
|
|
9f84b38084 | ||
|
|
86954a7981 | ||
|
|
8554dfeb5e | ||
|
|
bd2cd6d0b7 | ||
|
|
06ecdfe016 | ||
|
|
85d54e4f53 | ||
|
|
b368a21f62 | ||
|
|
644b15ffcd | ||
|
|
b0b10074f8 | ||
|
|
2023ab29f3 | ||
|
|
9a265887cf | ||
|
|
62e87c53f5 | ||
|
|
dbab502d14 | ||
|
|
ae79e8b90e | ||
|
|
d7eddb8484 | ||
|
|
457fa9481e | ||
|
|
69e8e6eefd | ||
|
|
ffc101b455 | ||
|
|
bbe9a1f10e | ||
|
|
71051d7ad0 | ||
|
|
e5136d5991 | ||
|
|
09d9f9618f | ||
|
|
534dbc2284 | ||
|
|
9e0f103ba6 | ||
|
|
c6b6f7bbd5 | ||
|
|
eee4082a1a | ||
|
|
10d94d65e2 | ||
|
|
7de87ad029 | ||
|
|
ad27d26c86 | ||
|
|
ecbd2beb48 | ||
|
|
0aed9a8680 | ||
|
|
1f764d539a | ||
|
|
e95c707009 | ||
|
|
1914dbb08f | ||
|
|
34074a1b9b | ||
|
|
e07ee1e263 | ||
|
|
9228eaffc8 | ||
|
|
f89871d294 | ||
|
|
8e35d38b59 | ||
|
|
17e023d2d2 | ||
|
|
dc94857ac4 | ||
|
|
ec228fddde | ||
|
|
958f93f440 | ||
|
|
d03b16eb89 | ||
|
|
ea573882a0 | ||
|
|
7fb936bd1d | ||
|
|
f97a3be6d3 | ||
|
|
3bc26eae82 | ||
|
|
f84463dbb2 | ||
|
|
46c86a71de | ||
|
|
ad70c034da | ||
|
|
221565f604 | ||
|
|
6ca7f11a2b | ||
|
|
63795b3d41 | ||
|
|
8a7de99bce | ||
|
|
7f13422805 | ||
|
|
56d64155ae | ||
|
|
088f141da3 | ||
|
|
091208e536 | ||
|
|
c6f6fbc5cd | ||
|
|
3cdf44903b | ||
|
|
e13e8598a7 | ||
|
|
d82c837902 | ||
|
|
d5483cdd03 | ||
|
|
9f2dd277fb | ||
|
|
e7c949209d | ||
|
|
42d3d29e0d | ||
|
|
6674b5aef7 | ||
|
|
f83f331a99 | ||
|
|
cd16c09dbd | ||
|
|
4ba9d11494 | ||
|
|
62a2aa14e1 | ||
|
|
1c0a2f064a | ||
|
|
23357816b3 | ||
|
|
877dca7587 | ||
|
|
fd537bcdfe | ||
|
|
e852ab58f8 | ||
|
|
5c7be625b6 | ||
|
|
475b66c956 | ||
|
|
19f7ff0103 | ||
|
|
3bc75b54c0 | ||
|
|
3c072923d2 | ||
|
|
b29c74ccca | ||
|
|
bbea465500 | ||
|
|
f7eb49e5de | ||
|
|
e9c69f6461 | ||
|
|
9e1fcb9a35 | ||
|
|
7523605d7a | ||
|
|
b73f8865a9 | ||
|
|
852df9d6b6 | ||
|
|
57e8af90dd | ||
|
|
0e48566c74 | ||
|
|
55177a7f90 | ||
|
|
d46827832e | ||
|
|
9e10b2d3d1 | ||
|
|
33efbbebaa | ||
|
|
d048ec851e | ||
|
|
42ebb91e6b | ||
|
|
1da4e2795f | ||
|
|
21bc525a04 | ||
|
|
b9ef3cdc35 | ||
|
|
9a34d5bca0 | ||
|
|
4c9820a390 | ||
|
|
634beeae8b | ||
|
|
3ec2422181 | ||
|
|
a3c7fa7464 | ||
|
|
20ed827750 | ||
|
|
85aa2c4f2b | ||
|
|
b761551384 | ||
|
|
3854b455a6 | ||
|
|
9c803e85cc | ||
|
|
651258b01f | ||
|
|
02baef80db | ||
|
|
cd75be6c26 | ||
|
|
7646c6e89d | ||
|
|
9c970aff63 | ||
|
|
49d7333d5e | ||
|
|
cd69bc341c | ||
|
|
453895f395 | ||
|
|
12796d6387 | ||
|
|
f65e72faed | ||
|
|
df535b2893 | ||
|
|
574ca0fb84 | ||
|
|
4a03ba79ec | ||
|
|
907b79965d | ||
|
|
fbec803d47 | ||
|
|
3f9574245a | ||
|
|
2bf6b90b04 | ||
|
|
9d596c6adc | ||
|
|
175e9edb8c | ||
|
|
8e58bb179a | ||
|
|
d37d9607d3 | ||
|
|
6b553c4d0d | ||
|
|
788927edb3 | ||
|
|
9013779f71 | ||
|
|
a85507c544 | ||
|
|
59aa113995 | ||
|
|
9c5a19925b | ||
|
|
2aa602d77d | ||
|
|
a1b3f13902 | ||
|
|
60384561d2 | ||
|
|
77938340d6 | ||
|
|
93d2f496d0 | ||
|
|
0808f0617a | ||
|
|
966a924183 | ||
|
|
a648402d04 | ||
|
|
207af6468e | ||
|
|
6609a7c806 | ||
|
|
479190d12b | ||
|
|
5ed1790601 | ||
|
|
b95f5af95f | ||
|
|
c76027ca9d | ||
|
|
55dc551516 | ||
|
|
cf5d8bf546 | ||
|
|
8031108fc9 | ||
|
|
d1a577868b | ||
|
|
5f02bb2962 | ||
|
|
cc1332dd02 | ||
|
|
a0dbfca828 | ||
|
|
76bdf12745 | ||
|
|
e1268d0d65 | ||
|
|
9c1e5f5032 | ||
|
|
88cde9256a | ||
|
|
d974f92e3d | ||
|
|
4b8ef1cd96 | ||
|
|
e65c57dbaa | ||
|
|
babc8451e0 | ||
|
|
7698ea1c8e | ||
|
|
aa95d1a1bd | ||
|
|
3fe6c22068 | ||
|
|
5ab19ee27b | ||
|
|
ae4b0aa870 | ||
|
|
1e2043ebda | ||
|
|
409b8d8b46 | ||
|
|
8bccbf3834 | ||
|
|
1b5ae00493 | ||
|
|
22c315d631 | ||
|
|
ffbbafa9d9 | ||
|
|
719a212913 | ||
|
|
651d883672 | ||
|
|
9853e0448f | ||
|
|
0f41638d0f | ||
|
|
f6e5b3ca09 | ||
|
|
04b821510b | ||
|
|
d632bd3ee7 | ||
|
|
aa341b818f | ||
|
|
a513c0083a | ||
|
|
683cc4c9f4 | ||
|
|
3c171b449a | ||
|
|
b4f62f6be1 | ||
|
|
8a9b40a030 | ||
|
|
3375308386 | ||
|
|
2fc9dbccd8 | ||
|
|
051a303f96 | ||
|
|
41a859aea3 | ||
|
|
bb978f2de5 | ||
|
|
9bf42dfb79 | ||
|
|
641eb18b89 | ||
|
|
d76e16b9e6 | ||
|
|
9abb17ee76 | ||
|
|
4b39b000e0 | ||
|
|
9b86ed1dee | ||
|
|
7874433d9b | ||
|
|
d4108d848e | ||
|
|
f78f82893a | ||
|
|
05eabdbd0a | ||
|
|
4109f6efd4 | ||
|
|
bbbbf5b5a4 | ||
|
|
8513ed0fa3 | ||
|
|
0e2a958b5c | ||
|
|
ac234aa4bf | ||
|
|
e6d84888ba | ||
|
|
2aecb2ffb3 | ||
|
|
cd8b595552 | ||
|
|
76934e6ff0 | ||
|
|
3e35e03bfb | ||
|
|
6f3425f4c3 | ||
|
|
0c007b7940 | ||
|
|
a7060b74e4 | ||
|
|
92c4eed4ed | ||
|
|
94d519cd65 | ||
|
|
7341136897 | ||
|
|
4459a95691 | ||
|
|
7bc8458d8b | ||
|
|
bfc4dcf22c | ||
|
|
4eb14ea166 | ||
|
|
c15488b61f | ||
|
|
33bb3b8d65 | ||
|
|
c5a7a3142b | ||
|
|
5b08861dec | ||
|
|
82f192a5c0 | ||
|
|
9230b1c3e6 | ||
|
|
647e0accea | ||
|
|
0ce6648bf9 | ||
|
|
fe3e647b56 | ||
|
|
fe07ea3b46 | ||
|
|
4b22514969 | ||
|
|
03bcafd5ff | ||
|
|
fdd30413c4 | ||
|
|
3a57269510 | ||
|
|
e9de35ee37 | ||
|
|
e027c640f7 | ||
|
|
242e2d3c22 | ||
|
|
b3500684d4 | ||
|
|
42c1ff27c4 | ||
|
|
4c16321a4a | ||
|
|
adaed36fbd | ||
|
|
f724a0d355 | ||
|
|
6b2e06b967 | ||
|
|
4663eb9b27 | ||
|
|
7774b1fa5e | ||
|
|
71ea94590b | ||
|
|
20699e9de7 | ||
|
|
893e9f6982 | ||
|
|
7a3c4811ac | ||
|
|
0ce5a49172 | ||
|
|
9db9df5641 | ||
|
|
69b3f9d238 | ||
|
|
3d7bcd2d47 | ||
|
|
a37bc94588 | ||
|
|
91ceea564e | ||
|
|
ee49e94c73 | ||
|
|
2f796cc07a | ||
|
|
b2b495b551 | ||
|
|
818c32b0ed | ||
|
|
d15764d41d | ||
|
|
9d343a31d3 | ||
|
|
0956b4ebcd | ||
|
|
9ff68a6948 | ||
|
|
ac8a05d2e5 | ||
|
|
75ea408370 | ||
|
|
8760185ae7 | ||
|
|
bd0ffbefe7 | ||
|
|
783ed34c0f | ||
|
|
37c2e8523c | ||
|
|
3f2dad52ee | ||
|
|
d6b0c0313b | ||
|
|
78509fb84d | ||
|
|
877c4268fd | ||
|
|
0e07f71458 | ||
|
|
7100a6e6b8 | ||
|
|
347bad8058 | ||
|
|
d72fb0ef7a | ||
|
|
ccc3a2269b | ||
|
|
6d6048c49c | ||
|
|
055c5d77ef | ||
|
|
f64c705c0d | ||
|
|
ca2ae30873 | ||
|
|
00fae08da0 | ||
|
|
f09b9155c4 | ||
|
|
9ef185d214 | ||
|
|
12f9220e34 | ||
|
|
ba753ff331 | ||
|
|
aba1aea334 | ||
|
|
849b5b6db0 | ||
|
|
621cb3242b | ||
|
|
dd0ecf047d | ||
|
|
bbc3d9dcfe | ||
|
|
85785018f4 | ||
|
|
7b9f894bf3 | ||
|
|
3a57293206 | ||
|
|
9afa9d7470 | ||
|
|
a86f7eb304 | ||
|
|
5013262740 | ||
|
|
106f297f57 | ||
|
|
115ea1372c | ||
|
|
f45aad4fad | ||
|
|
e131f92fb5 | ||
|
|
69a8d81bce | ||
|
|
7789407766 | ||
|
|
6c331619d0 | ||
|
|
9393a4e484 | ||
|
|
4b480dddc3 | ||
|
|
a6f27a1fef | ||
|
|
79eba1a281 | ||
|
|
2ad89bd8c8 | ||
|
|
ec990e8147 | ||
|
|
16d8380830 | ||
|
|
9e43a3b561 | ||
|
|
080bc67c54 | ||
|
|
81131ec390 | ||
|
|
cd6054af03 | ||
|
|
eee33ee645 | ||
|
|
046f0fc1cf | ||
|
|
4b7788fff7 | ||
|
|
ea5fbf0ee8 | ||
|
|
d0cfa45b5d | ||
|
|
ed14f34a2d | ||
|
|
98d5a6eb0e | ||
|
|
b492b89d2f | ||
|
|
ff58329691 | ||
|
|
dd3ab9ac6f | ||
|
|
f08ce7a381 | ||
|
|
11c8d167c0 | ||
|
|
0ba5984217 | ||
|
|
1584cf979c | ||
|
|
753cfd8a31 | ||
|
|
34b51b2dd8 | ||
|
|
5bda77d0d5 | ||
|
|
2bc8912add | ||
|
|
b70c8ca4a3 | ||
|
|
2750d00317 | ||
|
|
4fb7cb9b99 | ||
|
|
651ddbef5c | ||
|
|
fe5705627f | ||
|
|
c0401ce83d | ||
|
|
1c45ca13a3 | ||
|
|
970fb99e33 | ||
|
|
46f35f4737 | ||
|
|
d0efdef8c2 |
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
patreon: toxblh
|
||||||
|
ko_fi: toxblh
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
language: swift
|
||||||
|
xcode_project: MTMR.xcodeproj
|
||||||
|
xcode_scheme: UnitTests
|
||||||
|
osx_image: xcode10
|
||||||
|
install: gem install xcpretty
|
||||||
|
script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}"
|
||||||
@@ -7,73 +7,147 @@
|
|||||||
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 */; };
|
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; };
|
||||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.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 */; };
|
||||||
|
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
|
||||||
|
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 */; };
|
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */; };
|
||||||
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */; };
|
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 */; };
|
||||||
B0A8BF9E207B84160086F74D /* weather.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0A8BF9D207B84160086F74D /* weather.scpt */; };
|
B0B17431207D6B590004B740 /* PlaySmart.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17427207D6B580004B740 /* PlaySmart.scpt */; };
|
||||||
B0C1CFCA205C97D30021C862 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1CFC9205C97D30021C862 /* WindowController.swift */; };
|
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 */; };
|
|
||||||
/* 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>"; };
|
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>"; };
|
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>"; };
|
||||||
|
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; 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>"; };
|
|
||||||
B082B272205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
|
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
|
||||||
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultitouchSupport.framework; path = ../../../../../System/Library/PrivateFrameworks/MultitouchSupport.framework; 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>"; };
|
||||||
B0A8BF9D207B84160086F74D /* weather.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = weather.scpt; sourceTree = "<group>"; };
|
B0B17427207D6B580004B740 /* PlaySmart.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PlaySmart.scpt; sourceTree = "<group>"; };
|
||||||
B0C1CFC9205C97D30021C862 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; 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>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -81,8 +155,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 */,
|
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */,
|
||||||
|
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -93,19 +170,15 @@
|
|||||||
);
|
);
|
||||||
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 */,
|
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */,
|
||||||
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
|
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
|
||||||
);
|
);
|
||||||
@@ -116,9 +189,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 */,
|
||||||
);
|
);
|
||||||
@@ -129,7 +202,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>";
|
||||||
@@ -137,25 +209,27 @@
|
|||||||
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 */,
|
|
||||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
|
||||||
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.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 */,
|
||||||
B0A8BF9D207B84160086F74D /* weather.scpt */,
|
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
||||||
|
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
||||||
|
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||||
|
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
||||||
|
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
||||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
||||||
|
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
|
||||||
|
B05600D22083E9BB00EB218D /* CustomSlider.swift */,
|
||||||
|
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */,
|
||||||
|
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */,
|
||||||
);
|
);
|
||||||
path = MTMR;
|
path = MTMR;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -163,19 +237,70 @@
|
|||||||
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 */,
|
B0B1742A207D6B580004B740 /* Battery.scpt */,
|
||||||
B082B272205C7D8000BC04DC /* Info.plist */,
|
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 = (
|
||||||
|
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 */,
|
||||||
|
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 */,
|
||||||
|
);
|
||||||
|
path = Widgets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
@@ -188,6 +313,8 @@
|
|||||||
B082B24B205C7D8000BC04DC /* Sources */,
|
B082B24B205C7D8000BC04DC /* Sources */,
|
||||||
B082B24C205C7D8000BC04DC /* Frameworks */,
|
B082B24C205C7D8000BC04DC /* Frameworks */,
|
||||||
B082B24D205C7D8000BC04DC /* Resources */,
|
B082B24D205C7D8000BC04DC /* Resources */,
|
||||||
|
B00D181E2152F507000806F4 /* CopyFiles */,
|
||||||
|
B0679BBF215AE085000FC6B4 /* ShellScript */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -209,31 +336,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 */
|
||||||
@@ -241,12 +349,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 = 1000;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.Sandbox = {
|
com.apple.Sandbox = {
|
||||||
enabled = 0;
|
enabled = 0;
|
||||||
@@ -255,13 +364,7 @@
|
|||||||
};
|
};
|
||||||
B082B260205C7D8000BC04DC = {
|
B082B260205C7D8000BC04DC = {
|
||||||
CreatedOnToolsVersion = 9.2;
|
CreatedOnToolsVersion = 9.2;
|
||||||
ProvisioningStyle = Automatic;
|
LastSwiftMigration = 1000;
|
||||||
TestTargetID = B082B24E205C7D8000BC04DC;
|
|
||||||
};
|
|
||||||
B082B26B205C7D8000BC04DC = {
|
|
||||||
CreatedOnToolsVersion = 9.2;
|
|
||||||
ProvisioningStyle = Automatic;
|
|
||||||
TestTargetID = B082B24E205C7D8000BC04DC;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -280,7 +383,6 @@
|
|||||||
targets = (
|
targets = (
|
||||||
B082B24E205C7D8000BC04DC /* MTMR */,
|
B082B24E205C7D8000BC04DC /* MTMR */,
|
||||||
B082B260205C7D8000BC04DC /* MTMRTests */,
|
B082B260205C7D8000BC04DC /* MTMRTests */,
|
||||||
B082B26B205C7D8000BC04DC /* MTMRUITests */,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -290,10 +392,20 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B0A8BF9E207B84160086F74D /* weather.scpt in Resources */,
|
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 */,
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
@@ -304,14 +416,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 */ = {
|
||||||
@@ -319,15 +444,37 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
||||||
|
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
|
||||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
||||||
B082B255205C7D8000BC04DC /* ViewController.swift in Sources */,
|
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
|
||||||
B0C1CFCA205C97D30021C862 /* WindowController.swift in Sources */,
|
|
||||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
||||||
|
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
|
||||||
|
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
||||||
|
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.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 */,
|
||||||
|
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 */,
|
||||||
|
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
||||||
|
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
||||||
|
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
||||||
|
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
||||||
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
|
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
|
||||||
|
B08173272135F02B005D4908 /* NightShiftBarItem.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 */,
|
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
|
||||||
|
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
|
||||||
|
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
|
||||||
|
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
|
||||||
|
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -335,33 +482,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;
|
||||||
@@ -405,7 +535,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;
|
||||||
@@ -488,19 +617,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 = 4.2;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -508,19 +638,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 = 4.2;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -528,16 +661,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 = 4.2;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MTMR.app/Contents/MacOS/MTMR";
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -545,48 +674,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 = 4.2;
|
||||||
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;
|
||||||
};
|
};
|
||||||
@@ -620,15 +715,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 */;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>BuildSystemType</key>
|
||||||
|
<string>Original</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -7,37 +7,158 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import Sparkle
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
|
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) {
|
|
||||||
// Insert code here to initialize your application
|
|
||||||
TouchBarController.shared.setupControlStripPresence()
|
TouchBarController.shared.setupControlStripPresence()
|
||||||
|
|
||||||
if let button = statusItem.button {
|
if let button = statusItem.button {
|
||||||
button.image = #imageLiteral(resourceName: "StatusImage")
|
button.image = #imageLiteral(resourceName: "StatusImage")
|
||||||
}
|
}
|
||||||
createMenu()
|
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() {
|
||||||
|
var blacklistAppIdentifiers: [String] = []
|
||||||
|
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") {
|
||||||
|
blacklistAppIdentifiers = blackListed
|
||||||
|
}
|
||||||
|
var frontmostApplicationIdentifier: String? {
|
||||||
|
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
|
||||||
|
return frontmostId
|
||||||
|
}
|
||||||
|
|
||||||
|
if frontmostApplicationIdentifier != nil {
|
||||||
|
isBlockedApp = blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil
|
||||||
|
} else {
|
||||||
|
isBlockedApp = false
|
||||||
|
}
|
||||||
|
createMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func testFn(_ sender: Any?) {
|
@objc func openPreferences(_: Any?) {
|
||||||
let quoteText = "You click on menu"
|
let task = Process()
|
||||||
print(quoteText)
|
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(_: Any?) {
|
||||||
|
TouchBarController.shared.showControlStripState = !TouchBarController.shared.showControlStripState
|
||||||
|
TouchBarController.shared.resetControlStrip()
|
||||||
|
createMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleBlackListedApp(_: Any?) {
|
||||||
|
let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier
|
||||||
|
if appIdentifier != nil {
|
||||||
|
if let index = TouchBarController.shared.blacklistAppIdentifiers.index(of: appIdentifier!) {
|
||||||
|
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index)
|
||||||
|
} else {
|
||||||
|
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier!)
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDefaults.standard.set(TouchBarController.shared.blacklistAppIdentifiers, forKey: "com.toxblh.mtmr.blackListedApps")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
|
||||||
|
TouchBarController.shared.updateActiveApp()
|
||||||
|
updateIsBlockedApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {
|
func createMenu() {
|
||||||
let menu = NSMenu()
|
let menu = NSMenu()
|
||||||
menu.addItem(withTitle: "Preferences", action: #selector(testFn(_:)), keyEquivalent: ",")
|
|
||||||
|
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 = TouchBarController.shared.showControlStripState ? .off : .on
|
||||||
|
|
||||||
|
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(hideControlStrip)
|
||||||
|
menu.addItem(toggleBlackList)
|
||||||
|
menu.addItem(startAtLogin)
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
||||||
statusItem.menu = menu
|
statusItem.menu = menu
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class AppleScriptTouchBarItem: NSCustomTouchBarItem {
|
class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||||
private let script: NSAppleScript!
|
private var script: NSAppleScript!
|
||||||
private let button = NSButton(title: "", target: nil, action: nil)
|
|
||||||
private let interval: TimeInterval
|
private let interval: TimeInterval
|
||||||
|
private var forceHideConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
init?(identifier: NSTouchBarItem.Identifier, appleScript: String, interval: TimeInterval) {
|
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
|
||||||
guard let script = NSAppleScript(source: appleScript) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.script = script
|
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
super.init(identifier: identifier)
|
super.init(identifier: identifier, title: "⏳")
|
||||||
self.view = button
|
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
||||||
button.bezelColor = .clear
|
title = "scheduled"
|
||||||
button.title = "compile"
|
DispatchQueue.appleScriptQueue.async {
|
||||||
DispatchQueue.main.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?
|
var error: NSDictionary?
|
||||||
guard script.compileAndReturnError(&error) else {
|
guard script.compileAndReturnError(&error) else {
|
||||||
print(error?.description ?? "unknown error")
|
#if DEBUG
|
||||||
|
print(error?.description ?? "unknown error")
|
||||||
|
#endif
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.button.title = "compile error"
|
self.title = "error"
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -28,17 +36,23 @@ class AppleScriptTouchBarItem: NSCustomTouchBarItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshAndSchedule() {
|
func refreshAndSchedule() {
|
||||||
print("refresh happened")
|
#if DEBUG
|
||||||
let scriptResult = self.execute()
|
print("refresh happened (interval \(interval)), self \(identifier.rawValue))")
|
||||||
|
#endif
|
||||||
|
let scriptResult = execute()
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.button.title = scriptResult
|
self.title = scriptResult
|
||||||
|
self.forceHideConstraint.isActive = scriptResult == ""
|
||||||
|
#if DEBUG
|
||||||
|
print("did set new script result title \(scriptResult)")
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
|
DispatchQueue.appleScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
|
||||||
self?.refreshAndSchedule()
|
self?.refreshAndSchedule()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +64,10 @@ class AppleScriptTouchBarItem: NSCustomTouchBarItem {
|
|||||||
print(error)
|
print(error)
|
||||||
return "error"
|
return "error"
|
||||||
}
|
}
|
||||||
return output.stringValue ?? "empty value"
|
return output.stringValue ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DispatchQueue {
|
||||||
|
static let appleScriptQueue = DispatchQueue(label: "mtmr.applescript")
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
tell application "Finder"
|
||||||
|
make new Finder window
|
||||||
|
set target of front window to path to home folder as string
|
||||||
|
activate
|
||||||
|
end tell
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
if application "iTunes" is running then
|
||||||
|
tell application "iTunes" 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
|
||||||
@@ -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
|
||||||
@@ -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 ""
|
||||||
@@ -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
|
||||||
@@ -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 ""
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 ""
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 7.8 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -1,9 +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="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Application-->
|
<!--Application-->
|
||||||
@@ -620,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">
|
||||||
@@ -677,33 +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>
|
||||||
<!--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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
@@ -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, ¤tItemURL, 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
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
#import "TouchBarPrivateApi.h"
|
#import "TouchBarPrivateApi.h"
|
||||||
#import "TouchBarSupport.h"
|
#import "TouchBarSupport.h"
|
||||||
|
#import "DeprecatedCarbonAPI.h"
|
||||||
|
#import "CBBlueLightClient.h"
|
||||||
|
#import "LaunchAtLoginController.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@@ -17,4 +20,7 @@ CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef);
|
|||||||
CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3);
|
CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3);
|
||||||
CF_EXPORT bool MTActuatorIsOpen(CFTypeRef actuatorRef);
|
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
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -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
|
||||||
@@ -10,8 +10,6 @@
|
|||||||
|
|
||||||
@interface MediaKeys : NSObject
|
@interface MediaKeys : NSObject
|
||||||
|
|
||||||
+ (void)decreaseVolume;
|
+ (void)HIDPostAuxKey:(UInt8)keyCode;
|
||||||
+ (void)increaseVolume;
|
|
||||||
+ (void)muteVolume;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
//
|
||||||
|
// TouchBarItems.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Anton Palgunov on 18/03/2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||||
|
var tapClosure: (() -> Void)?
|
||||||
|
var longTapClosure: (() -> Void)?
|
||||||
|
|
||||||
|
private var button: NSButton!
|
||||||
|
private var singleClick: HapticClickGestureRecognizer!
|
||||||
|
private var longClick: NSPressGestureRecognizer!
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
||||||
|
attributedTitle = title.defaultTouchbarAttributedString
|
||||||
|
|
||||||
|
super.init(identifier: identifier)
|
||||||
|
button = CustomHeightButton(title: title, target: nil, action: nil)
|
||||||
|
|
||||||
|
longClick = NSPressGestureRecognizer(target: self, action: #selector(handleGestureLong))
|
||||||
|
longClick.allowedTouchTypes = .direct
|
||||||
|
longClick.delegate = self
|
||||||
|
|
||||||
|
singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
|
||||||
|
singleClick.allowedTouchTypes = .direct
|
||||||
|
singleClick.delegate = self
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
||||||
|
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
|
||||||
|
switch gr.state {
|
||||||
|
case .ended:
|
||||||
|
tapClosure?()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
||||||
|
switch gr.state {
|
||||||
|
case .began:
|
||||||
|
if let closure = self.longTapClosure {
|
||||||
|
HapticFeedback.shared.tap(strong: 2)
|
||||||
|
closure()
|
||||||
|
} else if let closure = self.tapClosure {
|
||||||
|
HapticFeedback.shared.tap(strong: 6)
|
||||||
|
closure()
|
||||||
|
print("long click")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HapticClickGestureRecognizer: NSClickGestureRecognizer {
|
||||||
|
override func touchesBegan(with event: NSEvent) {
|
||||||
|
HapticFeedback.shared.tap(strong: 2)
|
||||||
|
super.touchesBegan(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(with event: NSEvent) {
|
||||||
|
HapticFeedback.shared.tap(strong: 1)
|
||||||
|
super.touchesEnded(with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,9 +9,23 @@
|
|||||||
import IOKit
|
import IOKit
|
||||||
|
|
||||||
class HapticFeedback {
|
class HapticFeedback {
|
||||||
|
static let shared = 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"
|
||||||
|
// 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
|
||||||
|
0x300000080500000 // MacBook Pro 2019 (possibly 2018 as well)
|
||||||
|
]
|
||||||
|
private var correctDeviceID: UInt64?
|
||||||
private var actuatorRef: CFTypeRef?
|
private var actuatorRef: CFTypeRef?
|
||||||
private var deviceID: UInt64 = 0x200000001000000
|
|
||||||
private var error: IOReturn = 0
|
init() {
|
||||||
|
recreateDevice()
|
||||||
|
}
|
||||||
|
|
||||||
// Don't know how to do strong is enum one of
|
// Don't know how to do strong is enum one of
|
||||||
// 1 like back Click
|
// 1 like back Click
|
||||||
@@ -24,35 +38,52 @@ class HapticFeedback {
|
|||||||
// 16 nothing
|
// 16 nothing
|
||||||
// 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`
|
// 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`
|
||||||
|
|
||||||
func tap(strong:Int32) -> Void {
|
func tap(strong: Int32) {
|
||||||
let actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
|
guard correctDeviceID != nil, actuatorRef != nil else {
|
||||||
|
print("guard actuatorRef == nil (no haptic device found?)")
|
||||||
// TODO: Need to fix warning
|
|
||||||
guard actuatorRef != nil else {
|
|
||||||
print("guard actuatorRef == nil")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
error = MTActuatorOpen(actuatorRef)
|
var result: IOReturn
|
||||||
guard error == kIOReturnSuccess else {
|
|
||||||
|
result = MTActuatorOpen(actuatorRef!)
|
||||||
|
guard result == kIOReturnSuccess else {
|
||||||
print("guard MTActuatorOpen")
|
print("guard MTActuatorOpen")
|
||||||
|
recreateDevice()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
error = MTActuatorActuate(actuatorRef, strong, 0, 0.0, 0.0)
|
result = MTActuatorActuate(actuatorRef!, strong, 0, 0, 0)
|
||||||
guard error == kIOReturnSuccess else {
|
guard result == kIOReturnSuccess else {
|
||||||
print("guard MTActuatorActuate")
|
print("guard MTActuatorActuate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
error = MTActuatorClose(actuatorRef)
|
result = MTActuatorClose(actuatorRef!)
|
||||||
guard error == kIOReturnSuccess else {
|
guard result == kIOReturnSuccess else {
|
||||||
print("guard MTActuatorClose")
|
print("guard MTActuatorClose")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
private func recreateDevice() {
|
||||||
|
if let actuatorRef = actuatorRef {
|
||||||
|
MTActuatorClose(actuatorRef)
|
||||||
|
self.actuatorRef = nil // just in case %)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let correctDeviceID = correctDeviceID {
|
||||||
|
actuatorRef = MTActuatorCreateFromDeviceID(correctDeviceID).takeRetainedValue()
|
||||||
|
} else {
|
||||||
|
// Let's find our Haptic device
|
||||||
|
possibleDeviceIDs.forEach {(deviceID) in
|
||||||
|
guard correctDeviceID == nil else {return}
|
||||||
|
actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
|
||||||
|
|
||||||
|
if actuatorRef != nil {
|
||||||
|
correctDeviceID = deviceID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>LSUIElement</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -19,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.22</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>270</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 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>
|
||||||
|
|||||||
@@ -0,0 +1,768 @@
|
|||||||
|
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 action: ActionType
|
||||||
|
let longAction: LongActionType
|
||||||
|
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case type
|
||||||
|
}
|
||||||
|
|
||||||
|
init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
||||||
|
self.type = type
|
||||||
|
self.action = action
|
||||||
|
self.longAction = longAction
|
||||||
|
self.additionalParameters = additionalParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let type = try container.decode(String.self, forKey: .type)
|
||||||
|
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
|
||||||
|
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||||
|
|
||||||
|
if let result = try? parametersDecoder(decoder),
|
||||||
|
case let (itemType, action, longAction, parameters) = result {
|
||||||
|
parameters.forEach { additionalParameters[$0] = $1 }
|
||||||
|
self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters)
|
||||||
|
} else {
|
||||||
|
self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias ParametersDecoder = (Decoder) throws -> (
|
||||||
|
item: ItemType,
|
||||||
|
action: ActionType,
|
||||||
|
longAction: LongActionType,
|
||||||
|
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
|
)
|
||||||
|
|
||||||
|
class SupportedTypesHolder {
|
||||||
|
private var supportedTypes: [String: ParametersDecoder] = [
|
||||||
|
"escape": { _ in (
|
||||||
|
item: .staticButton(title: "esc"),
|
||||||
|
action: .keyPress(keycode: 53),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.align: .align(.left)]
|
||||||
|
) },
|
||||||
|
|
||||||
|
"delete": { _ in (
|
||||||
|
item: .staticButton(title: "del"),
|
||||||
|
action: .keyPress(keycode: 117),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
) },
|
||||||
|
|
||||||
|
"brightnessUp": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .keyPress(keycode: 144),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"brightnessDown": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .keyPress(keycode: 145),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"illuminationUp": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"illuminationDown": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"volumeDown": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"volumeUp": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"mute": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_MUTE),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"previous": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"play": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_PLAY),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"next": { _ in
|
||||||
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
||||||
|
return (
|
||||||
|
item: .staticButton(title: ""),
|
||||||
|
action: .hidKey(keycode: NX_KEYTYPE_NEXT),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: imageParameter]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"weather": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key; case icon_type }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
let units = try container.decodeIfPresent(String.self, forKey: .units)
|
||||||
|
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key)
|
||||||
|
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type)
|
||||||
|
let action = try ActionType(from: decoder)
|
||||||
|
let longAction = try LongActionType(from: decoder)
|
||||||
|
return (
|
||||||
|
item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"),
|
||||||
|
action,
|
||||||
|
longAction,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"yandexWeather": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
let action = try ActionType(from: decoder)
|
||||||
|
let longAction = try LongActionType(from: decoder)
|
||||||
|
return (
|
||||||
|
item: .yandexWeather(interval: interval ?? 1800.00),
|
||||||
|
action,
|
||||||
|
longAction,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"currency": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to; case full }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
let from = try container.decodeIfPresent(String.self, forKey: .from)
|
||||||
|
let to = try container.decodeIfPresent(String.self, forKey: .to)
|
||||||
|
let full = try container.decodeIfPresent(Bool.self, forKey: .full)
|
||||||
|
let action = try ActionType(from: decoder)
|
||||||
|
let longAction = try LongActionType(from: decoder)
|
||||||
|
return (
|
||||||
|
item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD", full: full ?? false),
|
||||||
|
action,
|
||||||
|
longAction,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"dock": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case autoResize }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
||||||
|
return (
|
||||||
|
item: .dock(autoResize: autoResize),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"inputsource": { _ in
|
||||||
|
(
|
||||||
|
item: .inputsource(),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"volume": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case image }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
|
||||||
|
return (
|
||||||
|
item: .volume(),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: .image(source: img)]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
item: .volume(),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"brightness": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval; case image }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
|
||||||
|
return (
|
||||||
|
item: .brightness(refreshInterval: interval ?? 0.5),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: .image(source: img)]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
item: .brightness(refreshInterval: interval ?? 0.5),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"sleep": { _ in (
|
||||||
|
item: .staticButton(title: "☕️"),
|
||||||
|
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
) },
|
||||||
|
|
||||||
|
"displaySleep": { _ in (
|
||||||
|
item: .staticButton(title: "☕️"),
|
||||||
|
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
) },
|
||||||
|
|
||||||
|
"music": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee)
|
||||||
|
return (
|
||||||
|
item: .music(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
"group": { decoder in
|
||||||
|
enum CodingKeys: CodingKey { case items }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let items = try container.decode([BarItemDefinition].self, forKey: .items)
|
||||||
|
return (
|
||||||
|
item: .groupBar(items: items),
|
||||||
|
action: .none,
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
static let sharedInstance = SupportedTypesHolder()
|
||||||
|
|
||||||
|
func lookup(by type: String) -> ParametersDecoder {
|
||||||
|
return supportedTypes[type] ?? { decoder in (
|
||||||
|
item: try ItemType(from: decoder),
|
||||||
|
action: try ActionType(from: decoder),
|
||||||
|
longAction: try LongActionType(from: decoder),
|
||||||
|
parameters: [:]
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(typename: String, decoder: @escaping ParametersDecoder) {
|
||||||
|
supportedTypes[typename] = decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
|
||||||
|
register(typename: typename) { _ in
|
||||||
|
(
|
||||||
|
item: item,
|
||||||
|
action,
|
||||||
|
longAction,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemType: Decodable {
|
||||||
|
case staticButton(title: String)
|
||||||
|
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||||
|
case timeButton(formatTemplate: String, timeZone: String?)
|
||||||
|
case battery()
|
||||||
|
case dock(autoResize: Bool)
|
||||||
|
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 groupBar(items: [BarItemDefinition])
|
||||||
|
case nightShift()
|
||||||
|
case dnd()
|
||||||
|
case pomodoro(workTime: Double, restTime: Double)
|
||||||
|
case network(flip: Bool)
|
||||||
|
case darkMode()
|
||||||
|
|
||||||
|
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 image
|
||||||
|
case url
|
||||||
|
case longUrl
|
||||||
|
case items
|
||||||
|
case workTime
|
||||||
|
case restTime
|
||||||
|
case flip
|
||||||
|
case autoResize
|
||||||
|
case disableMarquee
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemTypeRaw: String, Decodable {
|
||||||
|
case staticButton
|
||||||
|
case appleScriptTitledButton
|
||||||
|
case timeButton
|
||||||
|
case battery
|
||||||
|
case dock
|
||||||
|
case volume
|
||||||
|
case brightness
|
||||||
|
case weather
|
||||||
|
case yandexWeather
|
||||||
|
case currency
|
||||||
|
case inputsource
|
||||||
|
case music
|
||||||
|
case groupBar
|
||||||
|
case nightShift
|
||||||
|
case dnd
|
||||||
|
case pomodoro
|
||||||
|
case network
|
||||||
|
case darkMode
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
self = .appleScriptTitledButton(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
|
||||||
|
self = .timeButton(formatTemplate: template, timeZone: timeZone)
|
||||||
|
|
||||||
|
case .battery:
|
||||||
|
self = .battery()
|
||||||
|
|
||||||
|
case .dock:
|
||||||
|
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
||||||
|
self = .dock(autoResize: autoResize)
|
||||||
|
|
||||||
|
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 .groupBar:
|
||||||
|
let items = try container.decode([BarItemDefinition].self, forKey: .items)
|
||||||
|
self = .groupBar(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
|
||||||
|
self = .network(flip: flip)
|
||||||
|
|
||||||
|
case .darkMode:
|
||||||
|
self = .darkMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ActionType: 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 LongActionType: 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GeneralParameters: Decodable {
|
||||||
|
let parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case width
|
||||||
|
case image
|
||||||
|
case align
|
||||||
|
case bordered
|
||||||
|
case background
|
||||||
|
case title
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileString: String? {
|
||||||
|
var encoding: String.Encoding = .utf8
|
||||||
|
return try? String(contentsOfFile: self, usedEncoding: &encoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String {
|
||||||
|
var fileURL: URL {
|
||||||
|
return URL(fileURLWithPath: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
var appleScript: NSAppleScript? {
|
||||||
|
return NSAppleScript(source: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
var appleScript: NSAppleScript? {
|
||||||
|
guard FileManager.default.fileExists(atPath: path) else { return nil }
|
||||||
|
return NSAppleScript(contentsOf: self, error: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = 113
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BrightnessDownPress: KeyPress {
|
|
||||||
let keyCode: CGKeyCode = 107
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||||
|
var twofingersPrev: CGFloat = 0.0
|
||||||
|
var threefingersPrev: CGFloat = 0.0
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
let twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
||||||
|
twofingers.allowedTouchTypes = .direct
|
||||||
|
twofingers.numberOfTouchesRequired = 2
|
||||||
|
view.addGestureRecognizer(twofingers)
|
||||||
|
|
||||||
|
let threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
||||||
|
threefingers.allowedTouchTypes = .direct
|
||||||
|
threefingers.numberOfTouchesRequired = 3
|
||||||
|
view.addGestureRecognizer(threefingers)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) { // Volume
|
||||||
|
let position = (sender?.location(in: sender?.view).x)!
|
||||||
|
|
||||||
|
switch sender!.state {
|
||||||
|
case .began:
|
||||||
|
twofingersPrev = position
|
||||||
|
case .changed:
|
||||||
|
if ((position - twofingersPrev) > 10) || ((twofingersPrev - position) > 10) {
|
||||||
|
if position > twofingersPrev {
|
||||||
|
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP)
|
||||||
|
} else if position < twofingersPrev {
|
||||||
|
HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN)
|
||||||
|
}
|
||||||
|
twofingersPrev = position
|
||||||
|
}
|
||||||
|
case .ended:
|
||||||
|
twofingersPrev = 0.0
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) { // Brightness
|
||||||
|
let position = (sender?.location(in: sender?.view).x)!
|
||||||
|
|
||||||
|
switch sender!.state {
|
||||||
|
case .began:
|
||||||
|
threefingersPrev = position
|
||||||
|
case .changed:
|
||||||
|
if ((position - threefingersPrev) > 15) || ((threefingersPrev - position) > 15) {
|
||||||
|
if position > threefingersPrev {
|
||||||
|
GenericKeyPress(keyCode: CGKeyCode(144)).send()
|
||||||
|
} else if position < threefingersPrev {
|
||||||
|
GenericKeyPress(keyCode: CGKeyCode(145)).send()
|
||||||
|
}
|
||||||
|
threefingersPrev = position
|
||||||
|
}
|
||||||
|
case .ended:
|
||||||
|
threefingersPrev = 0.0
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import Cocoa
|
|
||||||
|
|
||||||
class TimeTouchBarItem: NSCustomTouchBarItem {
|
|
||||||
private let dateFormatter = DateFormatter()
|
|
||||||
private var timer: Timer!
|
|
||||||
private let button = NSButton(title: "", target: nil, action: nil)
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String) {
|
|
||||||
dateFormatter.setLocalizedDateFormatFromTemplate(formatTemplate)
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
|
|
||||||
self.view = button
|
|
||||||
button.bezelColor = .clear
|
|
||||||
updateTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func updateTime() {
|
|
||||||
button.title = self.dateFormatter.string(from: Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,45 +8,204 @@
|
|||||||
|
|
||||||
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 .timeButton(formatTemplate: _, timeZone: _):
|
||||||
|
return "com.toxblh.mtmr.timeButton."
|
||||||
|
case .battery():
|
||||||
|
return "com.toxblh.mtmr.battery."
|
||||||
|
case .dock(autoResize: _):
|
||||||
|
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 .groupBar(items: _):
|
||||||
|
return "com.toxblh.mtmr.groupBar."
|
||||||
|
case .nightShift(items: _):
|
||||||
|
return "com.toxblh.mtmr.nightShift."
|
||||||
|
case .dnd(items: _):
|
||||||
|
return "com.toxblh.mtmr.dnd."
|
||||||
|
case .pomodoro(interval: _):
|
||||||
|
return PomodoroBarItem.identifier
|
||||||
|
case .network(flip: _):
|
||||||
|
return NetworkBarItem.identifier
|
||||||
|
case .darkMode(items: _):
|
||||||
|
return DarkModeBarItem.identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
fileprivate var lastPresetPath = ""
|
||||||
|
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))
|
||||||
|
|
||||||
|
var showControlStripState: Bool {
|
||||||
|
get {
|
||||||
|
return UserDefaults.standard.bool(forKey: "com.toxblh.mtmr.settings.showControlStrip")
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
UserDefaults.standard.set(newValue, forKey: "com.toxblh.mtmr.settings.showControlStrip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blacklistAppIdentifiers: [String] = []
|
||||||
|
var frontmostApplicationIdentifier: String? {
|
||||||
|
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
|
||||||
|
return frontmostId
|
||||||
|
}
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
touchBar.delegate = self
|
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar() }), longAction: .none)
|
||||||
touchBar.defaultItemIdentifiers = [
|
|
||||||
.escButton,
|
|
||||||
.dismissButton,
|
|
||||||
|
|
||||||
.brightDown,
|
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
|
||||||
.brightUp,
|
(item: .staticButton(title: ""), action: .custom(closure: { [weak self] in
|
||||||
|
guard let `self` = self else { return }
|
||||||
|
self.reloadPreset(path: self.lastPresetPath)
|
||||||
|
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
||||||
|
}
|
||||||
|
|
||||||
.flexibleSpace,
|
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") {
|
||||||
|
blacklistAppIdentifiers = blackListed
|
||||||
|
}
|
||||||
|
|
||||||
.prev,
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
||||||
.play,
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
|
||||||
.next,
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||||
|
|
||||||
.sleep,
|
reloadStandardConfig()
|
||||||
.weather,
|
|
||||||
|
|
||||||
.volumeDown,
|
|
||||||
.volumeUp,
|
|
||||||
.battery,
|
|
||||||
.time,
|
|
||||||
]
|
|
||||||
self.presentTouchBar()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupControlStripPresence() {
|
func createAndUpdatePreset(newJsonItems: [BarItemDefinition]) {
|
||||||
|
if let oldBar = self.touchBar {
|
||||||
|
minimizeSystemModal(oldBar)
|
||||||
|
}
|
||||||
|
touchBar = NSTouchBar()
|
||||||
|
jsonItems = newJsonItems
|
||||||
|
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)
|
||||||
|
|
||||||
|
touchBar.delegate = self
|
||||||
|
touchBar.defaultItemIdentifiers = []
|
||||||
|
touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
|
||||||
|
|
||||||
|
updateActiveApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func activeApplicationChanged(_: Notification) {
|
||||||
|
updateActiveApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateActiveApp() {
|
||||||
|
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil {
|
||||||
|
dismissTouchBar()
|
||||||
|
} else {
|
||||||
|
presentTouchBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"), action: .none, longAction: .none, additionalParameters: [:])]
|
||||||
|
createAndUpdatePreset(newJsonItems: items)
|
||||||
|
}
|
||||||
|
|
||||||
|
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] = createItem(forIdentifier: identifier, definition: definition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func setupControlStripPresence() {
|
||||||
DFRSystemModalShowsCloseBoxWhenFrontMost(false)
|
DFRSystemModalShowsCloseBoxWhenFrontMost(false)
|
||||||
let item = NSCustomTouchBarItem(identifier: .controlStripItem)
|
let item = NSCustomTouchBarItem(identifier: .controlStripItem)
|
||||||
item.view = NSButton(image: #imageLiteral(resourceName: "Strip"), target: self, action: #selector(presentTouchBar))
|
item.view = NSButton(image: #imageLiteral(resourceName: "StatusImage"), target: self, action: #selector(presentTouchBar))
|
||||||
NSTouchBarItem.addSystemTrayItem(item)
|
NSTouchBarItem.addSystemTrayItem(item)
|
||||||
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
|
updateControlStripPresence()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateControlStripPresence() {
|
func updateControlStripPresence() {
|
||||||
@@ -54,79 +213,216 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func presentTouchBar() {
|
@objc private func presentTouchBar() {
|
||||||
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
|
if showControlStripState {
|
||||||
|
updateControlStripPresence()
|
||||||
|
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
|
||||||
|
} else {
|
||||||
|
presentSystemModal(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func dismissTouchBar() {
|
@objc private func dismissTouchBar() {
|
||||||
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
|
minimizeSystemModal(touchBar)
|
||||||
|
updateControlStripPresence()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func goToSleep() {
|
@objc func resetControlStrip() {
|
||||||
let task = Process()
|
dismissTouchBar()
|
||||||
task.launchPath = "/usr/bin/pmset"
|
presentTouchBar()
|
||||||
task.arguments = ["sleepnow"]
|
|
||||||
task.launch()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||||
switch identifier {
|
if identifier == centerScrollArea {
|
||||||
case .escButton:
|
return scrollArea
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "esc", key: ESCKeyPress())
|
}
|
||||||
case .dismissButton:
|
|
||||||
let item = NSCustomTouchBarItem(identifier: identifier)
|
|
||||||
item.view = NSButton(title: "exit", target: self, action: #selector(dismissTouchBar))
|
|
||||||
return item
|
|
||||||
|
|
||||||
case .brightUp:
|
guard let item = self.items[identifier],
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "🔆", key: BrightnessUpPress())
|
let definition = self.itemDefinitions[identifier],
|
||||||
case .brightDown:
|
definition.align != .center else {
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "🔅", key: BrightnessDownPress())
|
return nil
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
case .volumeDown:
|
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "🔉", HIDKeycode: NX_KEYTYPE_SOUND_DOWN)
|
var barItem: NSTouchBarItem!
|
||||||
case .volumeUp:
|
switch item.type {
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "🔊", HIDKeycode: NX_KEYTYPE_SOUND_UP)
|
case let .staticButton(title: title):
|
||||||
|
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
||||||
|
case let .appleScriptTitledButton(source: source, refreshInterval: interval):
|
||||||
|
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
||||||
|
case let .timeButton(formatTemplate: template, timeZone: timeZone):
|
||||||
|
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone)
|
||||||
|
case .battery():
|
||||||
|
barItem = BatteryBarItem(identifier: identifier)
|
||||||
|
case let .dock(autoResize: autoResize):
|
||||||
|
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 .groupBar(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):
|
||||||
|
barItem = NetworkBarItem(identifier: identifier, flip: flip)
|
||||||
|
case .darkMode():
|
||||||
|
barItem = DarkModeBarItem(identifier: identifier)
|
||||||
|
}
|
||||||
|
|
||||||
case .weather:
|
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||||
let url = Bundle.main.url(forResource: "weather", withExtension: "scpt")!
|
item.tapClosure = action
|
||||||
let script = try! String.init(contentsOf: url)
|
}
|
||||||
return AppleScriptTouchBarItem(identifier: identifier, appleScript: script, interval: 600)
|
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||||
|
item.longTapClosure = longAction
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
case .sleep:
|
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
let item = NSCustomTouchBarItem(identifier: identifier)
|
switch item.action {
|
||||||
item.view = NSButton(title: "☕️", target: self, action: #selector(goToSleep))
|
case let .hidKey(keycode: keycode):
|
||||||
return item
|
return { HIDPostAuxKey(keycode) }
|
||||||
|
case let .keyPress(keycode: keycode):
|
||||||
case .prev:
|
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "⏪", HIDKeycode: NX_KEYTYPE_PREVIOUS)
|
case let .appleScript(source: source):
|
||||||
case .play:
|
guard let appleScript = source.appleScript else {
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "⏯", HIDKeycode: NX_KEYTYPE_PLAY)
|
print("cannot create apple script for item \(item)")
|
||||||
case .next:
|
return {}
|
||||||
return CustomButtonTouchBarItem(identifier: identifier, title: "⏩", HIDKeycode: NX_KEYTYPE_NEXT)
|
}
|
||||||
|
return {
|
||||||
case .battery:
|
DispatchQueue.appleScriptQueue.async {
|
||||||
let url = Bundle.main.url(forResource: "battery", withExtension: "scpt")!
|
var error: NSDictionary?
|
||||||
let script = try! String.init(contentsOf: url)
|
appleScript.executeAndReturnError(&error)
|
||||||
return AppleScriptTouchBarItem(identifier: identifier, appleScript: script, interval: 60)
|
if let error = error {
|
||||||
case .time:
|
print("error \(error) when handling \(item) ")
|
||||||
return TimeTouchBarItem(identifier: identifier, formatTemplate: "HH:mm")
|
}
|
||||||
|
}
|
||||||
default:
|
}
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
|
switch item.longAction {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CustomButtonTouchBarItem {
|
protocol CanSetWidth {
|
||||||
convenience init(identifier: NSTouchBarItem.Identifier, title: String, HIDKeycode: Int) {
|
func setWidth(value: CGFloat)
|
||||||
self.init(identifier: identifier, title: title) { _ in
|
}
|
||||||
HIDPostAuxKey(HIDKeycode)
|
|
||||||
}
|
extension NSCustomTouchBarItem: CanSetWidth {
|
||||||
|
func setWidth(value: CGFloat) {
|
||||||
|
view.widthAnchor.constraint(equalToConstant: value).isActive = true
|
||||||
}
|
}
|
||||||
convenience init(identifier: NSTouchBarItem.Identifier, title: String, key: KeyPress) {
|
}
|
||||||
self.init(identifier: identifier, title: title) { _ in
|
|
||||||
key.send()
|
extension BarItemDefinition {
|
||||||
|
var align: Align {
|
||||||
|
if case let .align(result)? = additionalParameters[.align] {
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
return .center
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +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")
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomButtonTouchBarItem: NSCustomTouchBarItem {
|
|
||||||
let tapClosure: (NSCustomTouchBarItem) -> ()
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping (NSCustomTouchBarItem) -> ()) {
|
|
||||||
self.tapClosure = callback
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
self.view = NSButton(title: title, target: self, action: #selector(didTapped))
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func didTapped() {
|
|
||||||
self.tapClosure(self)
|
|
||||||
let hf: HapticFeedback = HapticFeedback()
|
|
||||||
hf.tap(strong: 6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
//
|
||||||
|
// AppScrubberTouchBarItem.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Daniel Apatin on 18.04.2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
|
||||||
|
private var scrubber: NSScrubber!
|
||||||
|
|
||||||
|
private var timer: Timer!
|
||||||
|
private var ticks: Int = 0
|
||||||
|
private let minTicks: Int = 5
|
||||||
|
private let maxTicks: Int = 20
|
||||||
|
private var lastSelected: Int = 0
|
||||||
|
private var autoResize: Bool = false
|
||||||
|
private var widthConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
|
private var persistentAppIdentifiers: [String] = []
|
||||||
|
private var runningAppsIdentifiers: [String] = []
|
||||||
|
|
||||||
|
private var frontmostApplicationIdentifier: String? {
|
||||||
|
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
|
||||||
|
return frontmostId
|
||||||
|
}
|
||||||
|
|
||||||
|
private var applications: [DockItem] = []
|
||||||
|
|
||||||
|
convenience override init(identifier: NSTouchBarItem.Identifier) {
|
||||||
|
self.init(identifier: identifier, autoResize: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var iconWidth = 36
|
||||||
|
static var spacingWidth = 2
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) {
|
||||||
|
super.init(identifier: identifier)
|
||||||
|
self.autoResize = autoResize
|
||||||
|
|
||||||
|
scrubber = NSScrubber()
|
||||||
|
scrubber.delegate = self
|
||||||
|
scrubber.dataSource = self
|
||||||
|
scrubber.mode = .free // .fixed
|
||||||
|
let layout = NSScrubberFlowLayout()
|
||||||
|
layout.itemSize = NSSize(width: AppScrubberTouchBarItem.iconWidth, height: 32)
|
||||||
|
layout.itemSpacing = CGFloat(AppScrubberTouchBarItem.spacingWidth)
|
||||||
|
scrubber.scrubberLayout = layout
|
||||||
|
scrubber.selectionBackgroundStyle = .roundedBackground
|
||||||
|
scrubber.showsAdditionalContentIndicators = true
|
||||||
|
|
||||||
|
view = scrubber
|
||||||
|
|
||||||
|
scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"))
|
||||||
|
|
||||||
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
||||||
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
|
||||||
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||||
|
|
||||||
|
if let persistent = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.dock.persistent") {
|
||||||
|
persistentAppIdentifiers = persistent
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRunningApplication()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func activeApplicationChanged(n _: Notification) {
|
||||||
|
updateRunningApplication()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath _: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
|
||||||
|
updateRunningApplication()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRunningApplication() {
|
||||||
|
let newApplications = launchedApplications()
|
||||||
|
|
||||||
|
let index = newApplications.index {
|
||||||
|
$0.bundleIdentifier == frontmostApplicationIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
applications = newApplications
|
||||||
|
applications += getDockPersistentAppsList()
|
||||||
|
scrubber.reloadData()
|
||||||
|
updateSize()
|
||||||
|
|
||||||
|
scrubber.selectedIndex = index ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSize() {
|
||||||
|
if self.autoResize {
|
||||||
|
if let constraint: NSLayoutConstraint = self.widthConstraint {
|
||||||
|
constraint.isActive = false
|
||||||
|
self.scrubber.removeConstraint(constraint)
|
||||||
|
}
|
||||||
|
let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth
|
||||||
|
self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width))
|
||||||
|
self.widthConstraint!.isActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func numberOfItems(for _: NSScrubber) -> Int {
|
||||||
|
return applications.count
|
||||||
|
}
|
||||||
|
|
||||||
|
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
|
||||||
|
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView()
|
||||||
|
item.imageView.imageScaling = .scaleProportionallyDown
|
||||||
|
|
||||||
|
let app = applications[index]
|
||||||
|
|
||||||
|
if let icon = app.icon {
|
||||||
|
item.image = icon
|
||||||
|
item.image.size = NSSize(width: 26, height: 26)
|
||||||
|
}
|
||||||
|
|
||||||
|
item.removeFromSuperview()
|
||||||
|
|
||||||
|
let dotView = NSView(frame: .zero)
|
||||||
|
dotView.wantsLayer = true
|
||||||
|
if runningAppsIdentifiers.contains(app.bundleIdentifier!) {
|
||||||
|
dotView.layer?.backgroundColor = NSColor.white.cgColor
|
||||||
|
} else {
|
||||||
|
dotView.layer?.backgroundColor = NSColor.black.cgColor
|
||||||
|
}
|
||||||
|
dotView.layer?.cornerRadius = 1.5
|
||||||
|
dotView.setFrameOrigin(NSPoint(x: 17, y: 1))
|
||||||
|
dotView.frame.size = NSSize(width: 3, height: 3)
|
||||||
|
item.addSubview(dotView)
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didBeginInteracting(with _: NSScrubber) {
|
||||||
|
stopTimer()
|
||||||
|
ticks = 0
|
||||||
|
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func checkTimer() {
|
||||||
|
ticks += 1
|
||||||
|
|
||||||
|
if ticks == minTicks {
|
||||||
|
HapticFeedback.shared.tap(strong: 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ticks > maxTicks {
|
||||||
|
stopTimer()
|
||||||
|
HapticFeedback.shared.tap(strong: 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopTimer() {
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
lastSelected = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didCancelInteracting(with _: NSScrubber) {
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didFinishInteracting(with scrubber: NSScrubber) {
|
||||||
|
stopTimer()
|
||||||
|
|
||||||
|
if ticks == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ticks >= minTicks && scrubber.selectedIndex > 0 {
|
||||||
|
longPress(selected: scrubber.selectedIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier
|
||||||
|
if bundleIdentifier!.contains("file://") {
|
||||||
|
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
|
||||||
|
} else {
|
||||||
|
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
||||||
|
HapticFeedback.shared.tap(strong: 6)
|
||||||
|
}
|
||||||
|
updateRunningApplication()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
private func longPress(selected: Int) {
|
||||||
|
let bundleIdentifier = applications[selected].bundleIdentifier
|
||||||
|
|
||||||
|
if ticks > maxTicks {
|
||||||
|
if let processIdentifier = applications[selected].pid {
|
||||||
|
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
|
||||||
|
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HapticFeedback.shared.tap(strong: 6)
|
||||||
|
if let index = self.persistentAppIdentifiers.index(of: bundleIdentifier!) {
|
||||||
|
persistentAppIdentifiers.remove(at: index)
|
||||||
|
} else {
|
||||||
|
persistentAppIdentifiers.append(bundleIdentifier!)
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDefaults.standard.set(persistentAppIdentifiers, forKey: "com.toxblh.mtmr.dock.persistent")
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
ticks = 0
|
||||||
|
updateRunningApplication()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func 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 }
|
||||||
|
|
||||||
|
runningAppsIdentifiers.append(bundleIdentifier)
|
||||||
|
|
||||||
|
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
|
||||||
|
returnable.append(dockItem)
|
||||||
|
}
|
||||||
|
return returnable
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType _: String? = nil) -> NSImage {
|
||||||
|
if bundleIdentifier != nil {
|
||||||
|
if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) {
|
||||||
|
return NSWorkspace.shared.icon(forFile: appPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
//
|
||||||
|
// 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 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": "$",
|
||||||
|
"CHF": "Fr.",
|
||||||
|
"BTC": "฿",
|
||||||
|
"LTC": "Ł",
|
||||||
|
"ETH": "Ξ",
|
||||||
|
]
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
var title = ""
|
||||||
|
if full {
|
||||||
|
title = String(format: "%@‣%.2f%@", prefix, value, postfix)
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
tapClosure = { [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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
tapClosure = { [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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 TouchBarController.shared.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// 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()
|
||||||
|
tapClosure = { [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?
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
//
|
||||||
|
// 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 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.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
|
||||||
|
|
||||||
|
tapClosure = { [weak self] in self?.playPause() }
|
||||||
|
longTapClosure = { [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 == .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 == .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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 var 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 == .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 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 {}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, flip: Bool = false) {
|
||||||
|
self.flip = flip
|
||||||
|
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 {
|
||||||
|
NotificationCenter.default.removeObserver(dataAvailable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataReady: NSObjectProtocol!
|
||||||
|
dataReady = NotificationCenter.default.addObserver(
|
||||||
|
forName: Process.didTerminateNotification,
|
||||||
|
object: outputHandle,
|
||||||
|
queue: nil
|
||||||
|
) { _ -> Void in
|
||||||
|
print("Task terminated!")
|
||||||
|
NotificationCenter.default.removeObserver(dataReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
bandwidthProcess?.launch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHumanizeSize(speed: UInt64) -> String {
|
||||||
|
let humanText: String
|
||||||
|
|
||||||
|
if speed < 1024 {
|
||||||
|
humanText = String(format: "%.0f", Double(speed)) + " B/s"
|
||||||
|
} else if speed < (1024 * 1024) {
|
||||||
|
humanText = String(format: "%.1f", Double(speed) / 1024) + " KB/s"
|
||||||
|
} else if speed < (1024 * 1024 * 1024) {
|
||||||
|
humanText = String(format: "%.1f", Double(speed) / (1024 * 1024)) + " MB/s"
|
||||||
|
} else {
|
||||||
|
humanText = String(format: "%.2f", Double(speed) / (1024 * 1024 * 1024)) + " GB/s"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
tapClosure = { [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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// 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),
|
||||||
|
action: .none,
|
||||||
|
longAction: .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)
|
||||||
|
tapClosure = { [weak self] in self?.startStopWork() }
|
||||||
|
longTapClosure = { [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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class TimeTouchBarItem: CustomButtonTouchBarItem {
|
||||||
|
private let dateFormatter = DateFormatter()
|
||||||
|
private var timer: Timer!
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil) {
|
||||||
|
dateFormatter.dateFormat = formatTemplate
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import AppKit
|
||||||
|
import AVFoundation
|
||||||
|
import Cocoa
|
||||||
|
import CoreAudio
|
||||||
|
|
||||||
|
class VolumeViewController: NSCustomTouchBarItem {
|
||||||
|
private(set) var sliderItem: CustomSlider!
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
|
||||||
|
super.init(identifier: identifier)
|
||||||
|
|
||||||
|
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||||
|
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||||
|
mScope: kAudioDevicePropertyScopeOutput,
|
||||||
|
mElement: kAudioObjectPropertyElementMaster
|
||||||
|
)
|
||||||
|
|
||||||
|
AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
|
||||||
|
|
||||||
|
if image == nil {
|
||||||
|
sliderItem = CustomSlider()
|
||||||
|
} else {
|
||||||
|
sliderItem = CustomSlider(knob: image!)
|
||||||
|
}
|
||||||
|
sliderItem.target = self
|
||||||
|
sliderItem.action = #selector(VolumeViewController.sliderValueChanged(_:))
|
||||||
|
sliderItem.minValue = 0.0
|
||||||
|
sliderItem.maxValue = 100.0
|
||||||
|
sliderItem.floatValue = getInputGain() * 100
|
||||||
|
|
||||||
|
view = sliderItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func audioObjectPropertyListenerBlock(numberAddresses _: UInt32, addresses _: UnsafePointer<AudioObjectPropertyAddress>) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.sliderItem.floatValue = self.getInputGain() * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
sliderItem.unbind(NSBindingName.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func sliderValueChanged(_ sender: Any) {
|
||||||
|
if let sliderItem = sender as? NSSlider {
|
||||||
|
_ = setInputGain(Float32(sliderItem.intValue) / 100.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var defaultDeviceID: AudioObjectID {
|
||||||
|
var deviceID: AudioObjectID = AudioObjectID(0)
|
||||||
|
var size: UInt32 = UInt32(MemoryLayout<AudioObjectID>.size)
|
||||||
|
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||||
|
address.mSelector = AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice)
|
||||||
|
address.mScope = AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal)
|
||||||
|
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||||
|
AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID)
|
||||||
|
return deviceID
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getInputGain() -> Float32 {
|
||||||
|
var volume: Float32 = 0.5
|
||||||
|
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume))
|
||||||
|
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||||
|
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||||
|
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||||
|
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||||
|
AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume)
|
||||||
|
return volume
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setInputGain(_ volume: Float32) -> OSStatus {
|
||||||
|
var inputVolume: Float32 = volume
|
||||||
|
|
||||||
|
if inputVolume == 0.0 {
|
||||||
|
_ = setMute(mute: 1)
|
||||||
|
} else {
|
||||||
|
_ = setMute(mute: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: inputVolume))
|
||||||
|
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||||
|
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||||
|
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||||
|
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||||
|
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setMute(mute: Int) -> OSStatus {
|
||||||
|
var muteVal: Int = mute
|
||||||
|
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||||
|
address.mSelector = AudioObjectPropertySelector(kAudioDevicePropertyMute)
|
||||||
|
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: muteVal))
|
||||||
|
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||||
|
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||||
|
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &muteVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
//
|
||||||
|
// WeatherBarItem.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Daniel Apatin on 18.04.2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
|
class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
||||||
|
private let activity: NSBackgroundActivityScheduler
|
||||||
|
private var units: String
|
||||||
|
private var api_key: String
|
||||||
|
private var units_str = "°F"
|
||||||
|
private var prev_location: CLLocation!
|
||||||
|
private var location: CLLocation!
|
||||||
|
private let iconsImages = ["01d": "☀️", "01n": "☀️", "02d": "⛅️", "02n": "⛅️", "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️", "09d": "⛅️", "09n": "⛅️", "10d": "🌦", "10n": "🌦", "11d": "🌩", "11n": "🌩", "13d": "❄️", "13n": "❄️", "50d": "🌫", "50n": "🌫"]
|
||||||
|
private let iconsText = ["01d": "☀", "01n": "☀", "02d": "☁", "02n": "☁", "03d": "☁", "03n": "☁", "04d": "☁", "04n": "☁", "09d": "☂", "09n": "☂", "10d": "☂", "10n": "☂", "11d": "☈", "11n": "☈", "13d": "☃", "13n": "☃", "50d": "♨", "50n": "♨"]
|
||||||
|
private var iconsSource: Dictionary<String, String>
|
||||||
|
|
||||||
|
private var manager: CLLocationManager!
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String, icon_type: String? = "text") {
|
||||||
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||||
|
activity.interval = interval
|
||||||
|
self.units = units
|
||||||
|
self.api_key = api_key
|
||||||
|
|
||||||
|
if self.units == "metric" {
|
||||||
|
units_str = "°C"
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.units == "imperial" {
|
||||||
|
units_str = "°F"
|
||||||
|
}
|
||||||
|
|
||||||
|
if icon_type == "images" {
|
||||||
|
iconsSource = iconsImages
|
||||||
|
} else {
|
||||||
|
iconsSource = iconsText
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init(identifier: identifier, title: "⏳")
|
||||||
|
|
||||||
|
let status = CLLocationManager.authorizationStatus()
|
||||||
|
if status == .restricted || status == .denied {
|
||||||
|
print("User permission not given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !CLLocationManager.locationServicesEnabled() {
|
||||||
|
print("Location services not enabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.repeats = true
|
||||||
|
activity.qualityOfService = .utility
|
||||||
|
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||||
|
self.updateWeather()
|
||||||
|
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||||
|
}
|
||||||
|
updateWeather()
|
||||||
|
|
||||||
|
manager = CLLocationManager()
|
||||||
|
manager.delegate = self
|
||||||
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
|
manager.startUpdatingLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func updateWeather() {
|
||||||
|
if location != nil {
|
||||||
|
let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(units)&appid=\(api_key)")!)
|
||||||
|
|
||||||
|
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]
|
||||||
|
// print(json)
|
||||||
|
var temperature: Int!
|
||||||
|
var condition_icon = ""
|
||||||
|
|
||||||
|
if let main = json["main"] as? [String: AnyObject] {
|
||||||
|
if let temp = main["temp"] as? Double {
|
||||||
|
temperature = Int(temp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let weather = json["weather"] as? NSArray, let item = weather[0] as? NSDictionary {
|
||||||
|
let icon = item["icon"] as! String
|
||||||
|
if let test = self.iconsSource[icon] {
|
||||||
|
condition_icon = test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if temperature != nil {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.setWeather(text: "\(condition_icon) \(temperature!)\(self.units_str)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch let jsonError {
|
||||||
|
print(jsonError.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWeather(text: String) {
|
||||||
|
title = text
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||||
|
let lastLocation = locations.last!
|
||||||
|
location = lastLocation
|
||||||
|
if prev_location == nil {
|
||||||
|
updateWeather()
|
||||||
|
}
|
||||||
|
prev_location = lastLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didFailWithError error: Error) {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) {
|
||||||
|
// print("inside didChangeAuthorization ");
|
||||||
|
updateWeather()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// WidgetProtocol.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Anton Palgunov on 20/10/2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
protocol Widget {
|
||||||
|
static var name: String { get }
|
||||||
|
static var identifier: String { get }
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// YandexWeatherBarItem.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by bobrosoft on 22/07/2019.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
|
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
||||||
|
private let activity: NSBackgroundActivityScheduler
|
||||||
|
private let unitsStr = "°C"
|
||||||
|
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
|
||||||
|
private var location: CLLocation!
|
||||||
|
private var prevLocation: CLLocation!
|
||||||
|
private var manager: CLLocationManager!
|
||||||
|
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
||||||
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||||
|
activity.interval = interval
|
||||||
|
|
||||||
|
super.init(identifier: identifier, title: "⏳")
|
||||||
|
|
||||||
|
let status = CLLocationManager.authorizationStatus()
|
||||||
|
if status == .restricted || status == .denied {
|
||||||
|
print("User permission not given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !CLLocationManager.locationServicesEnabled() {
|
||||||
|
print("Location services not enabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.repeats = true
|
||||||
|
activity.qualityOfService = .utility
|
||||||
|
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||||
|
self.updateWeather()
|
||||||
|
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||||
|
}
|
||||||
|
updateWeather()
|
||||||
|
|
||||||
|
manager = CLLocationManager()
|
||||||
|
manager.delegate = self
|
||||||
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
|
manager.startUpdatingLocation()
|
||||||
|
|
||||||
|
tapClosure = tapClosure ?? defaultTapAction
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func updateWeather() {
|
||||||
|
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
|
||||||
|
urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
|
||||||
|
guard error == nil, let response = data?.utf8string else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// print(response)
|
||||||
|
|
||||||
|
var matches: [[String]]
|
||||||
|
var temperature: String?
|
||||||
|
matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<")
|
||||||
|
temperature = matches.first?.item(at: 1)
|
||||||
|
|
||||||
|
var icon: String?
|
||||||
|
matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<")
|
||||||
|
icon = matches.first?.item(at: 1)
|
||||||
|
if let _ = icon, let test = self.iconsSource[icon!] {
|
||||||
|
icon = test
|
||||||
|
}
|
||||||
|
|
||||||
|
if temperature != nil {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWeatherUrl() -> String {
|
||||||
|
if location != nil {
|
||||||
|
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)"
|
||||||
|
} else {
|
||||||
|
return "https://yandex.ru/pogoda/" // Yandex will try to determine your location by default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWeather(text: String) {
|
||||||
|
title = text
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTapAction() {
|
||||||
|
print(getWeatherUrl())
|
||||||
|
if let url = URL(string: getWeatherUrl()) {
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||||
|
let lastLocation = locations.last!
|
||||||
|
location = lastLocation
|
||||||
|
if prevLocation == nil {
|
||||||
|
updateWeather()
|
||||||
|
}
|
||||||
|
prevLocation = lastLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didFailWithError error: Error) {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) {
|
||||||
|
updateWeather()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func matchingStrings(regex: String) -> [[String]] {
|
||||||
|
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
|
||||||
|
let nsString = self as NSString
|
||||||
|
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
|
||||||
|
return results.map { result in
|
||||||
|
(0..<result.numberOfRanges).map {
|
||||||
|
result.range(at: $0).location != NSNotFound
|
||||||
|
? nsString.substring(with: result.range(at: $0))
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
func item(at index: Int) -> Element? {
|
||||||
|
return indices.contains(index) ? self[index] : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// WindowController.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 17/03/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class WindowController: NSWindowController {
|
|
||||||
|
|
||||||
override func windowDidLoad() {
|
|
||||||
super.windowDidLoad()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "escape",
|
||||||
|
"width": 64,
|
||||||
|
"align": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dnd",
|
||||||
|
"align": "left",
|
||||||
|
"width": 38
|
||||||
|
},
|
||||||
|
{ "type": "brightnessDown", "width": 32, "bordered": false, "align": "left" },
|
||||||
|
{ "type": "brightnessUp", "width": 32, "bordered": false, "align": "left" },
|
||||||
|
|
||||||
|
// Spotify
|
||||||
|
{
|
||||||
|
"type": "appleScriptTitledButton",
|
||||||
|
"source": {
|
||||||
|
"inline": "if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||||
|
},
|
||||||
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline": "if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
|
||||||
|
},
|
||||||
|
"refreshInterval": 1,
|
||||||
|
"image": {
|
||||||
|
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUdwTB3WXx3UXh3VXx7XYBkXFRpVLRyURmIaeAQAAAAEdFJOUwDDO3fSqUUkAAABbklEQVRIx61VbW6DMAztoAeYNA7ApB6gkzhAWS24wIAL0HABaK6/pHFNEhy8TXu/kPzkPD/8cTj8K7KPAqB+K5NhQPCUrABCXe7HOUYYZxgVRLiG8RfY4DUgFFtC7cffAfZTFBwBdhWEKfgEq4ocEjgj4ZQifO6/QG9kkETp1dDeVWfRKx3XYSW0LoqY5kCElXDrQkyeCCuh6WL0M4nIWQIyzqixdfKU1koFDKvyCA8NJMzU4xiD+b4kfHRpsIyKc6hBwjVptFHVY51EMAINNDFGJITKDNQcdpX74Hz0CQ3rY5qwMp4EIxrlafzrsYZ2Veb0DkRgfNCUok4Y1fqEijfyi2b8RE9beWqa48Y/uvCNMcH9btfUi+/CGLR1vhL6Zz9N/vBlaCU+7lwY/cmJ67Ryen/2tj23PLqJBodZH8vgj544vOL4pxfI5acrSFxi8hrkU9TSKr78ZpnL50A8KPJJEo+afBblwyqf5j/iGys5j6ScrST2AAAAAElFTkSuQmCC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// iTunes
|
||||||
|
{
|
||||||
|
"type": "appleScriptTitledButton",
|
||||||
|
"source": {
|
||||||
|
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||||
|
},
|
||||||
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
|
||||||
|
},
|
||||||
|
"longAction": "appleScript",
|
||||||
|
"longActionAppleScript": {
|
||||||
|
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rprev track\rend if\rend tell\rend if\r"
|
||||||
|
},
|
||||||
|
"refreshInterval": 2,
|
||||||
|
"image": {
|
||||||
|
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADAUExURUdwTOVVZCzB+3qc0gkDBgEBAgcKEwAAAA4EBP5aVU2V95iJv7V3rtBOvH5W6jaOyclScKZGX3wuQCMuUqZN7+NQYXtDvFd9/sxYni2z6UhBhyhvnIp7sf38/PXz9ePm69/k6fHv8/j29+3q7/v6+ufq7uvu8fTw8+1egOFki/dbboVj/HNy/T+j/dNtnEul81vC8Vmf3OeRqOBVofK4xZfF7sDb7PLe6LxU1KKK79PL6vrW3fh4g5Zi4bi16daa0A3Qc90AAAAddFJOUwD3/v0uOlYNG/z+/v7998OYYztt/Le2/eDqi5jEo2rNTwAABMtJREFUWMPdl1tjqjoQhauC0Hqvta26JVxUQBQFRPBS/f//as8kISBqd/twXs5KjBDyrZkEFfP09D+WoigqFxz+mlbVXncweEZVBoNuD1x+E1vtMVZoURn01J/mob52KRNuj4k5mZjJcRueFotFpfv6EwdVRfy0tSdXSranheN0/zkRSB7x42Q6udExdBy3972Fog4o/kBg4X6bhPJaeX7eTqeT6b0MJtPpdOu6n49XQgX+dOQ8vpWEvYn/2AHiL052PpoROcsOtND17ztQ3rwTuCQz9O/moKiMf6BkG/puKBzurKQ64PmbU2bDzUxk3Uql4lZcl3Vpvt9VbxLoLZwjY7E1WcNZoB0XpbELie/3Sg6KVHG2jGPs1LTCE2UXFfgIgtBgyq8d/E/pehJq1zmZGc7kAPsMX4Ec932T25uX5vUklFcHJlDU1OT4wllkvOtn9lrSbF7dCUggNEtaMOXhQZq4WkpBcksJQBCOnyjvM4P8KqQgFW9BFJka2NMKB1gw+VMxvN9smnwI1EuzpxS+g9FWYySjsTpOtnq+H162iW01m/wyXLUPzT/5HKQoSjQmU8vE6TAElvWggbhuNRpScQa002bVtJmBCz9qNusWBkJmoyHmoHajC4yybVujhR26mJVha7lDo2FrhnA4N0aq+BpE24zjgsMoEsfU0AADaKCwemiIRZA+o6N9oygyMi/EAWk0DMNgFvCmN/5IwqCV3PCGzzIwbINrzgwykVq2iorUalm2UTZotXKWqVYz5uBjzDUoxrxWyzKQWy061LZsNIJ3PAMDIcbVauwdGmxrNblgYNnCgStN54ylBSsYoAxerwwgJsCWTS0sepym0Mdp1gYBw5lmwgDXIDEoaeHLYE36BafzuQWFQ9RASM/XQPpMD5YQ2gA/AwPArQJyZWDsgo64C+/pBRkDG4s31hdmwFNAGz1mBjPukBs8qdSgLDBA1LJm1lw/14IgWAdAQ5nhax4HY/FR7qfpHQMPUChzS0c6eFmv17MZo7HZBP3MQJHTNCnzxPMgOFUteAGtMwMmfRPIijBgc+AmNK6+9zw+9Iw05YsG8aaT/7Kro7eUcoSWw3n/1W57SxgGOZCYxl+VDDabcf6LpNTf3g6IQ4XY7TbiyyUBmhqsViswWK02cE7ITIdmtxnWlcJz6f1tPyMsha+2R4UGyJPdywotwGSHOKHdm+IMYA40BRhtkXOb42DAh8crppjw8CyB4nMBlvFtD/0WSfZebkDHguLNar2JdyTXptqRrx6OmMKZkISQc4Yv9yyDXHiiEx1qXL1OAFdhBJPAAQeRQDEiRZEm+kwnu2p1XHo64yQ8j47bL1kCZ87pDKWxuW4mQJ9O9ba31xE5Y/rnA4VoTCJQwvnyBNgk+pkDi8sSJjlKRPxhX7r3Lytz0LPMi1H1Qv7VuzwuAzh4h1ukKFi/YV9+9E8THZbne2Ezxd/xsNGQ6u+wgoeH4SH9Tl367t+2Ko/acA8Oj/DhWP7X/30Zkvj4WMYlj10MOISXf7DlkPvvH6g43u0oCzDS1U5f/sHWC3d7cn3UAQf4HeHfwxXQY4yu/HTDKNXro3Gngw4vw2FnPKrXJfUXu0fqIdeFZOnXm08FTRSxcf391pW7oNGT8vRf6i9jqljwYzAm6AAAAABJRU5ErkJggg=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "type": "displaySleep", "width": 40, "align": "right", "bordered": false },
|
||||||
|
{
|
||||||
|
"type": "weather",
|
||||||
|
"align": "right",
|
||||||
|
"icon_type": "images",
|
||||||
|
"api_key": "ca93a0bb8cdb428552660d83249e4bc9",
|
||||||
|
"bordered": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "volumeDown",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "right",
|
||||||
|
"width": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "volumeUp",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "right",
|
||||||
|
"width": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "play",
|
||||||
|
"align": "right",
|
||||||
|
"width": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "battery",
|
||||||
|
"align": "right",
|
||||||
|
"bordered": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeButton",
|
||||||
|
"formatTemplate": "HH:mm",
|
||||||
|
"align": "right",
|
||||||
|
"bordered": false,
|
||||||
|
"longAction": "shellScript",
|
||||||
|
"longExecutablePath": "/usr/bin/pmset",
|
||||||
|
"longShellArguments": ["sleepnow"]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIGRzCCBDkGByqGSM44BAEwggQsAoICAQDYdwbPwVzbYmnGvpiuwcMuHbpwBymv
|
||||||
|
X+yc6OmFgZNRX39k9T3KyPNwRA8izqsdLsGQe66WxU+HYcVymUt6AWyFYQy4izUN
|
||||||
|
LNO90jqrcpQgES/5n0losdU/uYsATIo098RrGKB8pX5aPU/LavuQQCwyUcqSyRxs
|
||||||
|
p1SSyfj8gkyyFA8wnVmK/xe+O9xVbFzmcxPecwAVW9NIhCjSxTWZJyo5ocg/rf6G
|
||||||
|
dK+MmlrkgSEinXsUs8bR0FqW/3abQXDeAzKrvOTImyvdHb78usLPOopM05j9w3Dq
|
||||||
|
jHj6LdVFf3lAdg7pP0EJYkpuCyJnMQP3BDrewTkIVgmg8nrjR9ZCMSTdNIFTQwqp
|
||||||
|
jfTNh6vUdhPWt4bqMNRT3GbjMBaUNNVt7h0tsrND1nNFUS+PifoJCogqUAcQII//
|
||||||
|
U5lvfC01KxfyOYL1yzNCrq5tZFWqcgm9ko0fOf7c/vFrAQAX7+g5CkDB0DZIdwiq
|
||||||
|
66FDWewudzp3fe9EZB1/NrNX0Dh03uAGc/zCZjLODSqDyrmsG8p9x1uC0fyoFkfm
|
||||||
|
Itjm7nQtcPNdC782QJnDF9c/T1gbaJVMOFvRFiiWbP8U9w9dd5aJZpm3LtDEwT+X
|
||||||
|
ehLmi5n41L0fnh71kSQ63hmu26vLhfbFC+pTMelWnej7d1bNVujdsyqmuKqiv2qC
|
||||||
|
l9izOglpi5G16wIhAIuuCaGHwNtZPE8kw5TIxamUQaBR6ypUgc23Bmq3w6sNAoIC
|
||||||
|
ABaip4l8PU8nlvbA0PshCTTxTokiG8e7qnZRcp3nS/3Fm8QEQlZ15qgsuAym82F/
|
||||||
|
pBYN7ACR+KnePmHLJyIdBYDceW3FS2UcDvA1OZuNbnJwor8RggYfusKSM5AmULwQ
|
||||||
|
qZh42wVQ3NXaJMfGVr8sLH9M6GKgygj7D0Fv7bkDuntIo/Lh9n1q/Y9h8JU0O38r
|
||||||
|
JAiUrJ+q/rNo+8OeNGPadP3W9Slq81JjgSLfMpLDBOEpCEG4Lk6nmbhc45VhUG4+
|
||||||
|
uREm50/ow4PqnW4rr1WDRgjeSGptvxXh8OsLehQcl9ochYyS2PFywfmDCzA81fsg
|
||||||
|
hFAy8BICWB6/rC61+H8+8m0exUfTnzIC6RFderrymq7kbfRwYkh5etRjvEWEdgeI
|
||||||
|
WrJxLiHktDs72bAX9ujwJdXvqJy9SYVy+yWr8fY8vfXtLtZ7KI7gZu4q5tHxCA3s
|
||||||
|
zNK67yjxb6Zpjap/UP4iKyHnTy8DZ26120Jy4oN80xzQzNHfEzOxidQ54pVRH8uA
|
||||||
|
9B5MqySrNOuViAkJCa7LaPu2EEs8tLmiCa4gksaQj3Kdi+j5Icl2A/gkgIcqwyvW
|
||||||
|
VX/8ulRyrRRwVjxuJPNV3UwMANdNmRKWouVO1YLmS5XiBiYj25Tyo1IXH9c+AREB
|
||||||
|
4/gByGkWS9+x0JBAPZ8k4F7wsP73td/zckLaxwDB+7woA4ICBgACggIBAM5ntyMa
|
||||||
|
GTSdchuDYXIwH4iC72tXYgDIXBdLuVWGiOHeo25FFoXyrFVTm31sinzingQiAvXL
|
||||||
|
nlzTT8cdN2oWpOPNG14BzPPxSmRUzXJvgope4/Xp38eELi6DJvpMaAgSIBo+EAP+
|
||||||
|
b6vZO4Bcy3ExW+hMKGnbaRn90tQM7NIEbk+2/j75Z5ihNNQ6y+5JzcDqgacnP3fC
|
||||||
|
mwDkcOQtQyZrR3VByd6ZoV0dpqHapLU/mQwD34Qo34u9TwY0NFJRuz21aJ10EPNf
|
||||||
|
hnlsWtKGhijY79RplZRHdFtR5Y0e3Y3N9s5B5KM6qmyGJP7UyCfG70vrDGrBAei5
|
||||||
|
VupQIiDGw9UVP55wtuJridjTWTCKAhP+UUXaZhu1STYnQmfejU90bkRyggnd9g0E
|
||||||
|
bBixfxa+whskGWHG4pHzfpZVpkbceTdXnF5ieWG4jHzob82OwyTJ9yffWDr0eNlj
|
||||||
|
6vhral8ANHCqbx2vI8foU4XbKURSQxe93eLc+o1+sNlEexH+wg645BeVjI5Zaumg
|
||||||
|
GMcdiwbFdId3cCBOOwvo2KIlSI/DdwSJ9NCgCF2NJCpuZJy2HliCrDegymTf+Y18
|
||||||
|
Gt81NhNe9B8rB7slmX8XU7RlSdmMhNRm9fLSKGADJzhdwSPnaJxQ+AP6QgBeJWLf
|
||||||
|
35sZE2RHp4aEb9WceYHpjz8orPiLHJ/7OPbZ
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
class AppleScriptDefinitionTests: XCTestCase {
|
||||||
|
func testInline() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" } } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(source.string, "tell everything fine")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPath() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "appleScriptTitledButton", "source": { "filePath": "/ololo/pew" } } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let sourceStruct = source as? Source
|
||||||
|
XCTAssertEqual(sourceStruct?.filePath, "/ololo/pew")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRefreshInterval() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case .appleScriptTitledButton(_, 305)? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
class BackgroundColorTests: XCTestCase {
|
||||||
|
func testOpaque() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "staticButton", "title": "Pew", "background": "#FF0000" } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case let .background(color)? = result?.first?.additionalParameters[.background] else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(color, .red)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAlpha() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "staticButton", "title": "Pew", "background": "#FF000080" } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case let .background(color)? = result?.first?.additionalParameters[.background] else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(color.alphaComponent, 0.5, accuracy: 0.01)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//
|
|
||||||
// MTMRTests.swift
|
|
||||||
// MTMRTests
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 16/03/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
@testable import MTMR
|
|
||||||
|
|
||||||
class MTMRTests: XCTestCase {
|
|
||||||
|
|
||||||
override func setUp() {
|
|
||||||
super.setUp()
|
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDown() {
|
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
super.tearDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testExample() {
|
|
||||||
// This is an example of a functional test case.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPerformanceExample() {
|
|
||||||
// This is an example of a performance test case.
|
|
||||||
self.measure {
|
|
||||||
// Put the code you want to measure the time of here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
class ParseConfig: XCTestCase {
|
||||||
|
func testButtonNoAction() {
|
||||||
|
let buttonNoActionFixture = """
|
||||||
|
[ { "type": "staticButton", "title": "Pew" } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||||
|
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .none? = result?.first?.action else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testButtonKeyCodeAction() {
|
||||||
|
let buttonKeycodeFixture = """
|
||||||
|
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||||
|
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .hidKey(keycode: 123)? = result?.first?.action else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPredefinedItem() {
|
||||||
|
let buttonKeycodeFixture = """
|
||||||
|
[ { "type": "escape" } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||||
|
guard case .staticButton("esc")? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExtendedWidthForPredefinedItem() {
|
||||||
|
let buttonKeycodeFixture = """
|
||||||
|
[ { "type": "escape", "width": 110}, ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||||
|
guard case .staticButton("esc")? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .width(110)? = result?.first?.additionalParameters[.width] else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>BNDL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//
|
|
||||||
// MTMRUITests.swift
|
|
||||||
// MTMRUITests
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 16/03/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
class MTMRUITests: XCTestCase {
|
|
||||||
|
|
||||||
override func setUp() {
|
|
||||||
super.setUp()
|
|
||||||
|
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
|
||||||
|
|
||||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
|
||||||
continueAfterFailure = false
|
|
||||||
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
|
|
||||||
XCUIApplication().launch()
|
|
||||||
|
|
||||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDown() {
|
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
super.tearDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testExample() {
|
|
||||||
// Use recording to get started writing UI tests.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,345 @@
|
|||||||
|
# My touchbar. My rules. [](https://github.com/Toxblh/MTMR/releases) [](https://github.com/Toxblh/MTMR/blob/master/LICENSE) 
|
||||||
|
|
||||||
|
<img src="Resources/logo.png" align="right"
|
||||||
|
title="MTMR by Toxblh" width="110" height="110">
|
||||||
|
|
||||||
|
_The TouchBar Customization App for your MacBook Pro_
|
||||||
|
|
||||||
|
My idea is to create a platform for creating plugins to customize the TouchBar. I very much like BTT and having a full custom TouchBar (my BTT preset), and I wanted to create it. It's my first Swift project for MacOS :)
|
||||||
|
|
||||||
|
**Share your presets [here](https://github.com/Toxblh/MTMR-presets)**
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="Resources/logo.png" width="120">
|
<img src="./Resources/aaaaa-acc6-17fee7572ed0.png" alt="Mackbook with touchbar" width="800">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# My TouchBar. My rules
|
|
||||||
[](https://github.com/Toxblh/MTMR/releases)
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="Resources/TouchBar-v0.3.png">
|
<a href="https://discord.gg/CmNcDuQ"><img height="20px" src="https://camo.githubusercontent.com/88f53948f291c54736bf08f5fd7b037a848dfc62/68747470733a2f2f646973636f72646170702e636f6d2f6173736574732f30376463613830613130326434313439653937333664346231363263666636662e69636f"> Discord</a>
|
||||||
|
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :)
|
<p align="center"><a href="https://www.paypal.me/toxblh/10" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
||||||
|
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
||||||
|
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
||||||
|
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
||||||
|
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=my-touchbar-my-rules-mtmr&theme=light" alt="My TouchBar My Rules (MTMR)" height="36px" style="max-width:100%">
|
||||||
|
</a></p>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- Download lastest [release](https://github.com/Toxblh/MTMR/releases) (.dmg) from github
|
||||||
|
- Or via Homebrew `brew cask install mtmr`
|
||||||
|
- [Dario Prski](https://medium.com/@urdigitalpulse) has written a [fantastic article on medium](https://medium.com/@urdigitalpulse/customise-your-macbook-pro-touch-bar-966998e606b5) that goes into more detail on installing MTMR
|
||||||
|
|
||||||
|
**On first install** you need to allow access for MTMR in Accessibility otherwise buttons like <kbd>Esc</kbd>, <kbd>Volume</kbd>, <kbd>Brightness</kbd> and other system keys won't work
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="450" alt="screenshot 2019-02-24 at 23 19 20" src="https://user-images.githubusercontent.com/2198153/53307057-2b078200-388c-11e9-8212-8c2b1aff0aa6.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- [@Toxblh preset](Resources/toxblh.json)
|
||||||
|
- [@ReDetection preset](Resources/ReDetection.json)
|
||||||
|
- [@luongvo209 preset](Resources/luongvo209.json)
|
||||||
|
- [aadi_vs_anand preset](Resources/aadi_vs_anand.json)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="./Resources/Artboard.png" alt="Presets for touchbar" width="800">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
MTMR preferences are stored under `~/Library/Application\ Support/MTMR/items.json`.
|
||||||
|
|
||||||
|
The pre-installed configuration contains less or more than you'll probably want, try to configure:
|
||||||
|
|
||||||
|
## Built-in button types:
|
||||||
|
|
||||||
|
> Buttons
|
||||||
|
|
||||||
|
- escape
|
||||||
|
- exitTouchbar
|
||||||
|
- brightnessUp
|
||||||
|
- brightnessDown
|
||||||
|
- illuminationUp (keyboard illumination)
|
||||||
|
- illuminationDown (keyboard illumination)
|
||||||
|
- volumeDown
|
||||||
|
- volumeUp
|
||||||
|
- mute
|
||||||
|
|
||||||
|
> Native Plugins
|
||||||
|
|
||||||
|
- battery
|
||||||
|
- currency
|
||||||
|
- weather
|
||||||
|
- yandexWeather
|
||||||
|
- inputsource
|
||||||
|
- music (tap for pause, longTap for next)
|
||||||
|
- dock (half-long click to open app, full-long click to kill app)
|
||||||
|
- nightShift
|
||||||
|
- dnd (Don't disturb)
|
||||||
|
- darkMode
|
||||||
|
- pomodoro
|
||||||
|
- network
|
||||||
|
|
||||||
|
> Media Keys
|
||||||
|
|
||||||
|
- previous
|
||||||
|
- play
|
||||||
|
- next
|
||||||
|
|
||||||
|
> AppleScript plugins
|
||||||
|
|
||||||
|
- sleep
|
||||||
|
- displaySleep
|
||||||
|
|
||||||
|
## Gestures on central part:
|
||||||
|
|
||||||
|
- two finger slide: change you Volume
|
||||||
|
- three finger slide: change you Brightness
|
||||||
|
|
||||||
|
## Built-in slider types:
|
||||||
|
|
||||||
|
- brightness
|
||||||
|
- volume
|
||||||
|
|
||||||
|
### You can also make custom buttons using these types
|
||||||
|
|
||||||
|
- `staticButton`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"type": "staticButton",
|
||||||
|
"title": "esc",
|
||||||
|
```
|
||||||
|
|
||||||
|
- `appleScriptTitledButton`
|
||||||
|
|
||||||
|
```js
|
||||||
|
"type": "appleScriptTitledButton",
|
||||||
|
"refreshInterval": 60, //optional
|
||||||
|
"source": {
|
||||||
|
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt",
|
||||||
|
// or
|
||||||
|
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell",
|
||||||
|
// or
|
||||||
|
"base64": "StringInbase64"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- `timeButton`
|
||||||
|
|
||||||
|
```js
|
||||||
|
"type": "timeButton",
|
||||||
|
"formatTemplate": "HH:mm" //optional
|
||||||
|
```
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "group",
|
||||||
|
"align": "center",
|
||||||
|
"bordered": true,
|
||||||
|
"title": "stats",
|
||||||
|
"items": [
|
||||||
|
{ "type": "play" }, { "type": "mute" }, ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To close a group, use the button:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"type": "close",
|
||||||
|
"width": 64
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Native plugins
|
||||||
|
|
||||||
|
#### `timeButton`
|
||||||
|
|
||||||
|
> Attention! Works not all: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "timeButton",
|
||||||
|
"formatTemplate": "dd HH:mm",
|
||||||
|
"timeZone": "UTC"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `weather`
|
||||||
|
|
||||||
|
> Provider: https://openweathermap.org \
|
||||||
|
> Note: you need to register on https://openweathermap.org to get your API key \
|
||||||
|
> Note: you may need to wait for near 20 mins until your API key will be activated by Openweathermap \
|
||||||
|
> Note: you need to allow using "Location Services" in your Mac OS "Security & Privacy" settings for MTMR
|
||||||
|
|
||||||
|
```js
|
||||||
|
"type": "weather",
|
||||||
|
"refreshInterval": 600, // in seconds
|
||||||
|
"units": "metric", // or imperial
|
||||||
|
"icon_type": "text" // or images
|
||||||
|
"api_key": "" // you can get the key on openweather
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `yandexWeather` (experimental)
|
||||||
|
|
||||||
|
> Provider: https://yandex.ru/pogoda. One click to open up weather forecast in your browser. \
|
||||||
|
> Note: you need to allow using "Location Services" in your Mac OS "Security & Privacy" settings for MTMR
|
||||||
|
|
||||||
|
```js
|
||||||
|
"type": "yandexWeather",
|
||||||
|
"refreshInterval": 600 // in seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `currency`
|
||||||
|
|
||||||
|
> Provider: https://coinbase.com
|
||||||
|
|
||||||
|
```js
|
||||||
|
"type": "currency",
|
||||||
|
"refreshInterval": 600, // in seconds
|
||||||
|
"align": "right",
|
||||||
|
"from": "BTC",
|
||||||
|
"to": "USD",
|
||||||
|
"full": true // £‣1.29$
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `music`
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "music",
|
||||||
|
"align": "center",
|
||||||
|
"width": 80, // Optional
|
||||||
|
"bordered": false, // Optional
|
||||||
|
"refreshInterval": 2, // in seconds. Optional. Default 5 seconds
|
||||||
|
"disableMarquee": true // to disable marquee effect. Optional. Default false
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `pomodoro`
|
||||||
|
|
||||||
|
> Pomodoro plugin. One click to start the work timer, longclick to start the rest timer. Click in progress for reset.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "pomodoro",
|
||||||
|
"workTime": 1200, // set time work in seconds. Default 1500 (25 min)
|
||||||
|
"restTime": 600 // set time rest in seconds. Default 300 (5 min)
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `network`
|
||||||
|
|
||||||
|
> Network plugin. The plugin to show usage a network
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "network",
|
||||||
|
"flip": true
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `dock`
|
||||||
|
|
||||||
|
> Dock plugin
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "dock",
|
||||||
|
"autoResize": true
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Actions:
|
||||||
|
|
||||||
|
- `hidKey`
|
||||||
|
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
||||||
|
|
||||||
|
```json
|
||||||
|
"action": "hidKey",
|
||||||
|
"keycode": 53,
|
||||||
|
```
|
||||||
|
|
||||||
|
- `keyPress`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"action": "keyPress",
|
||||||
|
"keycode": 1,
|
||||||
|
```
|
||||||
|
|
||||||
|
- `appleScript`
|
||||||
|
|
||||||
|
```js
|
||||||
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell"
|
||||||
|
// "filePath" or "base64" will work as well
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- `shellScript`
|
||||||
|
|
||||||
|
```js
|
||||||
|
"action": "shellScript",
|
||||||
|
"executablePath": "/usr/bin/pmset",
|
||||||
|
"shellArguments": ["sleepnow"], // optional
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- `openUrl`
|
||||||
|
|
||||||
|
```js
|
||||||
|
"action": "openUrl",
|
||||||
|
"url": "https://google.com",
|
||||||
|
```
|
||||||
|
|
||||||
|
## LongActions
|
||||||
|
|
||||||
|
If you want to longPress for some operations, it is similar to the configuration for Actions but with additional parameters, for example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
"longAction": "hidKey",
|
||||||
|
"longKeycode": 53,
|
||||||
|
```
|
||||||
|
|
||||||
|
- longAction
|
||||||
|
- longKeycode
|
||||||
|
- longActionAppleScript
|
||||||
|
- longExecutablePath
|
||||||
|
- longShellArguments
|
||||||
|
- longUrl
|
||||||
|
|
||||||
|
## Additional parameters:
|
||||||
|
|
||||||
|
- `width` restrict how much room a particular button will take
|
||||||
|
|
||||||
|
```json
|
||||||
|
"width": 34
|
||||||
|
```
|
||||||
|
|
||||||
|
- `align` can stick the item to the side. default is center
|
||||||
|
|
||||||
|
```js
|
||||||
|
"align": "left" // "left", "right" or "center"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `bordered` you can do button without border
|
||||||
|
|
||||||
|
```js
|
||||||
|
"bordered": "false" // "true" or "false"
|
||||||
|
```
|
||||||
|
|
||||||
### Roadmap
|
### Roadmap
|
||||||
|
|
||||||
- [x] Create the first prototype with TouchBar in Storyboard
|
- [x] Create the first prototype with TouchBar in Storyboard
|
||||||
- [x] Put in stripe menu on startup the application
|
- [x] Put in stripe menu on startup the application
|
||||||
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
||||||
@@ -23,17 +350,30 @@ My the idea is to create the program like a platform for plugins for customizati
|
|||||||
- [x] Add icon and menu in StatusBar
|
- [x] Add icon and menu in StatusBar
|
||||||
- [x] Hide from Dock
|
- [x] Hide from Dock
|
||||||
- [x] Status menu: "preferences", "quit"
|
- [x] Status menu: "preferences", "quit"
|
||||||
- [ ] JSON or another approch for save preset and plugins maybe in `~/mtmr`
|
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
|
||||||
- [ ] Layout: [always left, NSSliderView for center, always right]
|
- [x] Custom buttons size, actions by click
|
||||||
- [ ] Custom buttons size, actions by click in settings
|
- [x] Layout: [always left, NSSliderView for center, always right]
|
||||||
- [ ] System for autoupdate (maybe https://sparkle-project.org/)
|
- [x] System for autoupdate (https://sparkle-project.org/)
|
||||||
|
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
||||||
|
- [ ] Custom settings for paddings and margins for buttons
|
||||||
|
- [ ] XPC Service for scripts
|
||||||
|
- [ ] UI for settings
|
||||||
|
- [ ] Import config from BTT
|
||||||
|
|
||||||
Settings:
|
Settings:
|
||||||
- [ ] Intarface for plugins and export like presets
|
|
||||||
- [ ] Startup at login
|
- [ ] Interface for plugins and export like presets
|
||||||
|
- [x] Startup at login
|
||||||
- [ ] Show on/off in Dock
|
- [ ] Show on/off in Dock
|
||||||
- [ ] Show on/off in StatusBar
|
- [ ] Show on/off in StatusBar
|
||||||
- [ ] On/off Haptic Feedback
|
- [ ] On/off Haptic Feedback
|
||||||
|
|
||||||
Maybe:
|
Maybe:
|
||||||
- [ ] Refactoring the application on packages (AppleScript, JavaScript? and Swift?)
|
|
||||||
|
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Built by [@Toxblh](https://patreon.com/toxblh) and [@ReDetection](http://patreon.com/ReDetection).
|
||||||
|
|
||||||
|
[](https://github.com/igrigorik/ga-beacon)
|
||||||
|
|||||||
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 583 KiB |
@@ -0,0 +1,129 @@
|
|||||||
|
[
|
||||||
|
{ "type": "escape", "align": "left" },
|
||||||
|
// --- iTunes ---
|
||||||
|
{
|
||||||
|
"type": "appleScriptTitledButton",
|
||||||
|
"source": {
|
||||||
|
"inline":
|
||||||
|
"if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||||
|
},
|
||||||
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline":
|
||||||
|
"if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
|
||||||
|
},
|
||||||
|
"refreshInterval": 1,
|
||||||
|
"image": {
|
||||||
|
"base64":
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADAUExURUdwTOVVZCzB+3qc0gkDBgEBAgcKEwAAAA4EBP5aVU2V95iJv7V3rtBOvH5W6jaOyclScKZGX3wuQCMuUqZN7+NQYXtDvFd9/sxYni2z6UhBhyhvnIp7sf38/PXz9ePm69/k6fHv8/j29+3q7/v6+ufq7uvu8fTw8+1egOFki/dbboVj/HNy/T+j/dNtnEul81vC8Vmf3OeRqOBVofK4xZfF7sDb7PLe6LxU1KKK79PL6vrW3fh4g5Zi4bi16daa0A3Qc90AAAAddFJOUwD3/v0uOlYNG/z+/v7998OYYztt/Le2/eDqi5jEo2rNTwAABMtJREFUWMPdl1tjqjoQhauC0Hqvta26JVxUQBQFRPBS/f//as8kISBqd/twXs5KjBDyrZkEFfP09D+WoigqFxz+mlbVXncweEZVBoNuD1x+E1vtMVZoURn01J/mob52KRNuj4k5mZjJcRueFotFpfv6EwdVRfy0tSdXSranheN0/zkRSB7x42Q6udExdBy3972Fog4o/kBg4X6bhPJaeX7eTqeT6b0MJtPpdOu6n49XQgX+dOQ8vpWEvYn/2AHiL052PpoROcsOtND17ztQ3rwTuCQz9O/moKiMf6BkG/puKBzurKQ64PmbU2bDzUxk3Uql4lZcl3Vpvt9VbxLoLZwjY7E1WcNZoB0XpbELie/3Sg6KVHG2jGPs1LTCE2UXFfgIgtBgyq8d/E/pehJq1zmZGc7kAPsMX4Ec932T25uX5vUklFcHJlDU1OT4wllkvOtn9lrSbF7dCUggNEtaMOXhQZq4WkpBcksJQBCOnyjvM4P8KqQgFW9BFJka2NMKB1gw+VMxvN9smnwI1EuzpxS+g9FWYySjsTpOtnq+H162iW01m/wyXLUPzT/5HKQoSjQmU8vE6TAElvWggbhuNRpScQa002bVtJmBCz9qNusWBkJmoyHmoHajC4yybVujhR26mJVha7lDo2FrhnA4N0aq+BpE24zjgsMoEsfU0AADaKCwemiIRZA+o6N9oygyMi/EAWk0DMNgFvCmN/5IwqCV3PCGzzIwbINrzgwykVq2iorUalm2UTZotXKWqVYz5uBjzDUoxrxWyzKQWy061LZsNIJ3PAMDIcbVauwdGmxrNblgYNnCgStN54ylBSsYoAxerwwgJsCWTS0sepym0Mdp1gYBw5lmwgDXIDEoaeHLYE36BafzuQWFQ9RASM/XQPpMD5YQ2gA/AwPArQJyZWDsgo64C+/pBRkDG4s31hdmwFNAGz1mBjPukBs8qdSgLDBA1LJm1lw/14IgWAdAQ5nhax4HY/FR7qfpHQMPUChzS0c6eFmv17MZo7HZBP3MQJHTNCnzxPMgOFUteAGtMwMmfRPIijBgc+AmNK6+9zw+9Iw05YsG8aaT/7Kro7eUcoSWw3n/1W57SxgGOZCYxl+VDDabcf6LpNTf3g6IQ4XY7TbiyyUBmhqsViswWK02cE7ITIdmtxnWlcJz6f1tPyMsha+2R4UGyJPdywotwGSHOKHdm+IMYA40BRhtkXOb42DAh8crppjw8CyB4nMBlvFtD/0WSfZebkDHguLNar2JdyTXptqRrx6OmMKZkISQc4Yv9yyDXHiiEx1qXL1OAFdhBJPAAQeRQDEiRZEm+kwnu2p1XHo64yQ8j47bL1kCZ87pDKWxuW4mQJ9O9ba31xE5Y/rnA4VoTCJQwvnyBNgk+pkDi8sSJjlKRPxhX7r3Lytz0LPMi1H1Qv7VuzwuAzh4h1ukKFi/YV9+9E8THZbne2Ezxd/xsNGQ6u+wgoeH4SH9Tl367t+2Ko/acA8Oj/DhWP7X/30Zkvj4WMYlj10MOISXf7DlkPvvH6g43u0oCzDS1U5f/sHWC3d7cn3UAQf4HeHfwxXQY4yu/HTDKNXro3Gngw4vw2FnPKrXJfUXu0fqIdeFZOnXm08FTRSxcf391pW7oNGT8vRf6i9jqljwYzAm6AAAAABJRU5ErkJggg=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- VOX ---
|
||||||
|
// {
|
||||||
|
// "type": "appleScriptTitledButton",
|
||||||
|
// "source": {
|
||||||
|
// "inline":
|
||||||
|
// "if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rreturn (get artist) & \" – \" & (get track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||||
|
// },
|
||||||
|
// "action": "appleScript",
|
||||||
|
// "actionAppleScript": {
|
||||||
|
// "inline":
|
||||||
|
// "if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rnext\rend if\rend tell\rend if\r"
|
||||||
|
// },
|
||||||
|
// "refreshInterval": 1,
|
||||||
|
// "image": {
|
||||||
|
// "base64":
|
||||||
|
// "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADAUExURUdwTHd7gqeqtKClrd/k7n+Di7e7xI6TnNHX4cPI0Xp9hJibpOfs9u/1/vX7/rq/yZiborK3wEpOUxgYFlFXYiIhHycpLD9ESjI0NgsLCj09O4SKk19lcbBkALhoAHJ3gT8rAzMvJJugqKhcAL5xAOSxB4I/AJ5SAGpweXuAiZJJAG8zAeXr9WNiYM+NANqeAMd9APHACfr9/e7JPrO5w6mrsA4bKC0gCHhtTyAYBFcpAZduKK2JP/fost/Eb5SUlFFfi9AAAAASdFJOUwDJE9FgWjb+/vyHppTXtbn9mDupvZ0AAAdoSURBVFjDlZcLQ6O6FoWrtgI+Owl5EGgp2DrEgnV6wD4c7f//V3ftAE7nzOj17NGxpayPtXfSZGcw+CjOh1c3N9eIm5ur4fngv8Xp8KZE+F2U5bq8GZ5+WU5qP6jzsRdTeF7e1AEoN8Ovya/LMsg9mxZJErpIksJarwnK8nr4FblfQ52EDOH0zL1KUuvVfnn9eTVOryCPoVZQuGj/FOApMAhx9Uktzq/L2ksT3FuQhIVVVVRVRa+SogA1KbzgExPD0s/xdBOmKdOqODR95IdEaZba0LAkbfzyg0rA/pgekxZKV4cmtxXTEmFUEufNodIKYJjLkcbf9UEM90mqZJU3qeJRxDkXAv/hlbJNXkmVJjCBNP5CGPq1TZSyiWB5XomIi+woRBSJKj8YUVgFQ4E//FMfQM8s40VTyIhDpLVRNIJKaWLwSNqm4mEMl/YPwrkP/9AbER+UkxtWbbcvLrbbkBlCcDa23BABHn4bi1PSM2a1PFgZwbwJty+TybSLyeRlyxQlIuNY6FgpDKd/PB+uXP2tFHEheCtvhW3Q65dtiEyQRixMrBjG4uo4gTpNFApvU9Kr6sWp77qY3BFj8lLBBJdpykOLwaqPkgioAEnFi1RCz7Z3uJ2U37sgCBB3W5ZJLm0VJQmVIXgfgSAvQpYK1ACDx7bTOT2dlA8uWsYEl0EQ3KDUKcOECvqRCGgGpApXf9M/PNx3QQwyMZ9uGWZWiGelsFAHvQFUMEx4WAku2HZC+k6+vF/ip0U4wmSrBJcFZgss9xZuYYAlWhaGC1PhMb1++R6EaAl3lRFcFUI5C7duDlAFwpCzSogsfCH93fdWvm+jRYCASs5fwkyIinGsVrBAc2FYxwVLlKhgQG3n8+kv/WwjhNhsZvveQ5+EqrhKWOHVlMNtgyUkhHkpdPjy1CZA+pl4bkOIfWcBQ/EEC1xWWmKxsi6HussA9WUwQBV0BmbPz3y32+x2nD8/z/okpvM5LAjGeEg51JiFtQeAEqHhWUIGJq0B0m9mLjYdgSxMnQVhGHIIkcP5YNjEAGjJJDfF5AnzzRnY/9K3hP3SjSUAk0oLzaQOwyJGEb41FgWVuOIymHYZCKfPGFaxzBHEcQ5cYrXD3LHNt8E3WkdDbpBY8vMdMHsW0Jui9v26MCDAggNMAfgZoghGdIDbPMXewY0RWfHzqQMsN2QgC/MAkVcZWZi1DuYAJJlQBlVM0vx2cDZOw4QAPEt/AcTzDgkkh3w8Hh8SPZvtKIc2haefKapo2mE4G5x5nQOuLQA0jwggCcAqF1SFnZQEwEwCwGZ0P+sAXQqa69gBJvgeLCUBhMS6arSWogV0NWwB2gHG5KAggDYO0OXQATghJP4QIPsFiJHCEYB2YgBE5gBzNxEyjVHYcOxKKDfquRE6u39wFXh6dADJaccG4HLsANIIEb89PnaEvSYLG2kYAG0G+3YMnh4fDxbfe4wkAS47gAAyShsA2iSWUgsiYHfJMCF2ZKDVA9AkET4WbhgvB6OxxeoiuZZRVT/2BFjQfOemMX53qNn+e5fA42OtAOCYiYXNR4PznL4LJoLA1G8rQjjCTCmxc9+GzU4oNXvonr9avTUZPc7QdyHH0j4mgIqQVZQHq1VvAgRKf7MRKISZPWAG0ONXq1Vg6WYahMIbYz24pJ6ECYIyf7EiBBGm3/da0c6KX/ifTnv9P74iu66G3iUAI4+KoCNpeNS8rha9ifnkfmlcLO+n816+WL3mEVVcY2ew4xEtqsgBjUWEQkWsfFssWhdgPM0xoBg8iKl2JF8s3kpngCtMo3jsNljkUDAmYQFVWC9+tAgHeQ93AR/8WKw9d6MMWWFdBn0OJsJsjERZLn4QomOsWmmrpk9Kn0eYxpGiMfBG7dZ0FrcW8B2N1PoV9zkEUehn1b2hy68lxg/F0owVaXzW7Y0j6kwZuiqhUIaW8M54F9Olcq0irF0R1vAkfTdAFmwaKhjDUgfCur39n+NwF9ZrFhnMWm5YiM737L0/GHkxtYD4RKIWILz1+jf86/Vv69I4fSTRTNojAxgIdPUgCE7VjXhzhHAEJ1/nUUSl4qTHKeDyuMnCycCzjiCwyUYsWK9fofzhxIC8rtcBi2SlqPeEnu7/rekGwcutwhTHhKoYHlWvEa8u6FVN17D/Or3Ncfu/Wu5R7I2bmNECRiYYRpuaYtKiBa+0u0j9K7aguMFZZvTvVvUCFuoce5ygSmA9xlcRDR8iyzaZqVq50Coc1zBw8WezfBEfmqBGX+8QXLs1HY9tV3aMMeQSp4A6aP6qdx4aNHyMWnxBHbpwzTIW9vadkGTf/1BPdYhxsqrR7zuGoD4/6pp+iKVkaY3zWGxHHx55TuJxXZa1RSmwIyAyeoGVlf4wG9CBLD755Nx1ehHHub9e+3lBpxUh+0DuuO4ef/H5AXR0gkpg+EofjQed1ej8VsTuEk6T8cno/54cRyc2zms3Bcr+4Esvazz9C/IOYWlESNseoWtU3n5V3tYCDEA8tAce5j3enFyc/scT/Pno4qSLi9HHhf8f8eOIRf9lFswAAAAASUVORK5CYII="
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
{ "type": "dock", "width": 200, "align": "left"},
|
||||||
|
//{ "type": "button", "title": "alfred"},
|
||||||
|
//{ "type": "volume", "width": 160, "align": "right"},
|
||||||
|
{
|
||||||
|
"type": "group",
|
||||||
|
"align": "right",
|
||||||
|
"bordered": false,
|
||||||
|
"title": "Media",
|
||||||
|
"items": [
|
||||||
|
{"type": "close", "bordered": false, "align": "left"},
|
||||||
|
{
|
||||||
|
"type": "brightnessDown",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "left",
|
||||||
|
"width": 36
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "brightness",
|
||||||
|
"width": 200,
|
||||||
|
"align": "left",
|
||||||
|
"image": {
|
||||||
|
"base64":
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURffLOPfLNyaSVzUAAAACdFJOU/kBxOqnWgAAAbJJREFUSMfVljtyhDAQBVulQCFH4CgcDR1NR9ERFBKoeA5GfGddtkNvwFINFKP5tED22+Zxwviv6QVKfIEc/iNoF5gkpLIeYI8SUp4PsAUJiekADQntF6isQjvxCTrhAJlFqMMBeIH9BMsD7DAb2BhvYbIyNAOCZIWqYKGTpDZJFQu9EKVd44RxQRq3IrULWD62C8wSssWUZEsR0k6wcDOrJZmoBpMKI+s5qkBQCQOUJADVOECdOsDS0gDbsgHMfT4rVwHSrZQFIN5ABka8BgDgAeZ+BztBgvUEnSgVlhNsTFJjvoF5HAZorBpdYKAiSRbqNyBIUr6AjZMdPwO72R40MElS+wZUWA+wQ6LAYkFvdIhkmA+wQSDDdIAGAZ6A34H0x0fca11gBZZsIHSIfnE/5+NjCn/OuiuUB+/aunZwDeNayjXdTpDN0wlY+r1PfWu75nfj8RogN2JuCN2Y5qgMwTI0wGPUnQw6Qarx0sVNKA5Mn6VUL22lIbZoYitDbPmlvocc9Umfl2D7adz1reC3pF8av4m+DCenp/ndZuG3E7fhuC3pH2+vnz8V3MfE+bnxBTXuuIMTrLWHAAAAAElFTkSuQmCC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "brightnessUp",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "left",
|
||||||
|
"width": 36
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "volumeDown",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "left",
|
||||||
|
"width": 36,
|
||||||
|
},
|
||||||
|
{ "type": "volume", "width": 200, "align": "left"},
|
||||||
|
{
|
||||||
|
"type": "volumeUp",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "left",
|
||||||
|
"width": 36
|
||||||
|
},
|
||||||
|
{"type": "previous", "bordered": false, "align": "center"},
|
||||||
|
//{"type": "play", "bordered": false, "align": "center"},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "appleScriptTitledButton",
|
||||||
|
"align": "center",
|
||||||
|
"source": {
|
||||||
|
"inline":
|
||||||
|
"if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||||
|
},
|
||||||
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline":
|
||||||
|
"if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rpause\rend if\rend tell\rend if\r"
|
||||||
|
},
|
||||||
|
"refreshInterval": 0.0001,
|
||||||
|
"image": {
|
||||||
|
"base64":
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUdwTB3WXx3UXh3VXx7XYBkXFRpVLRyURmIaeAQAAAAEdFJOUwDDO3fSqUUkAAABbklEQVRIx61VbW6DMAztoAeYNA7ApB6gkzhAWS24wIAL0HABaK6/pHFNEhy8TXu/kPzkPD/8cTj8K7KPAqB+K5NhQPCUrABCXe7HOUYYZxgVRLiG8RfY4DUgFFtC7cffAfZTFBwBdhWEKfgEq4ocEjgj4ZQifO6/QG9kkETp1dDeVWfRKx3XYSW0LoqY5kCElXDrQkyeCCuh6WL0M4nIWQIyzqixdfKU1koFDKvyCA8NJMzU4xiD+b4kfHRpsIyKc6hBwjVptFHVY51EMAINNDFGJITKDNQcdpX74Hz0CQ3rY5qwMp4EIxrlafzrsYZ2Veb0DkRgfNCUok4Y1fqEijfyi2b8RE9beWqa48Y/uvCNMcH9btfUi+/CGLR1vhL6Zz9N/vBlaCU+7lwY/cmJ67Ryen/2tj23PLqJBodZH8vgj544vOL4pxfI5acrSFxi8hrkU9TSKr78ZpnL50A8KPJJEo+afBblwyqf5j/iGys5j6ScrST2AAAAAElFTkSuQmCC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"type": "next", "bordered": false, "align": "center"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "inputsource", "align": "right", },
|
||||||
|
{ "type": "mute", "bordered": false, "align": "right"},
|
||||||
|
{ "type": "dnd", "align": "right"},
|
||||||
|
{ "type": "nightShift", "align": "right"},
|
||||||
|
{ "type": "sleep", "align": "right", "bordered": false},
|
||||||
|
{ "type": "currency",
|
||||||
|
"refreshInterval": 600, // in seconds
|
||||||
|
"bordered": false,
|
||||||
|
"align": "right",
|
||||||
|
"from": "USD",
|
||||||
|
"to": "HKD",},
|
||||||
|
{
|
||||||
|
"type": "pomodoro",
|
||||||
|
"bordered": false,
|
||||||
|
"align": "right",
|
||||||
|
"workTime": 1800, // set time work in seconds. Default 1500 (25 min)
|
||||||
|
"restTime": 600, // set time rest in seconds. Default 300 (5 min)
|
||||||
|
},
|
||||||
|
{ "type": "weather", "refreshInterval": 60, "units": "metric", "align": "right", "bordered": false, "icon_type": "images", "api_key": "84645702688e83a35e2549ca77f73369"},
|
||||||
|
{ "type": "battery", "align": "right", "bordered": false },
|
||||||
|
{ "type": "timeButton", "align": "right", "formatTemplate": "E MMM d h:mm a", "bordered": false }
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 14 KiB |