Compare commits

..
44 Commits
Author SHA1 Message Date
toxblh 221cc8a176 del macos target 2019-11-28 21:50:04 +03:00
toxblh a6b52ebe95 Merge remote-tracking branch 'origin/master' into notification-widget 2019-11-28 21:48:48 +03:00
toxblh a4fee54ae0 merge external code 2019-11-28 21:48:41 +03:00
toxblh e9e5a6f739 fix yandex wheather, for correct lang 2019-11-26 17:33:18 +03:00
toxblh 466c0e5f68 v0.25 2019-11-19 16:19:51 +00:00
Anton Palgunov 85e3deac79 Merge pull request #237 from ReDetection/parser-cleanup
cleanup parser
2019-11-13 15:51:53 +00:00
Anton Palgunov 9e49cf8beb Merge pull request #236 from markrickert/relative-file-paths
Relative file paths
2019-11-12 15:39:37 +00:00
Serg d81998862a cleanup parser: remove other duplicates (they are defined and parsed down below in the ItemType) 2019-11-07 12:09:04 +07:00
Mark Rickert 1ae2041d0d Get rid of all references to /Users/x/ in favor of ~ 2019-11-06 15:03:57 -07:00
Mark Rickert 5eb2d73c94 Add some image filePath documentation. 2019-11-06 14:57:05 -07:00
Mark Rickert 855ddea44e Tests relative file paths and images. 2019-11-06 14:48:26 -07:00
Mark Rickert eaa26645c5 Add ability to use file paths relative to your user directory 2019-11-06 14:48:13 -07:00
Mark Rickert 2cdb705b37 Consolidate the two String extenstions. 2019-11-06 14:43:27 -07:00
Anton Palgunov fa413f2fa2 Merge pull request #234 from ReDetection/dock-improvements
Dock improvements
2019-11-01 15:51:54 +03:00
Serg 6920664fad implement regex filter for dock items 2019-10-27 15:29:52 +07:00
Serg e9a7b6d32a get long tap to kill back into place 2019-10-27 13:01:22 +07:00
Serg dd23a3bda8 don't reload list of apps every time 2019-10-26 22:43:01 +07:00
Serg f82d7694eb rewrite in scrollview; get rid of custom gesture handling 2019-10-26 21:58:23 +07:00
Serg 94717a5ea3 missed a single userdefaults 2019-10-26 17:22:19 +07:00
Anton Palgunov b155cac2b0 Merge pull request #230 from ReDetection/settings-swift-5.1
Multitouch settings & swift 5.1
2019-10-19 15:50:15 +01:00
Serg cd2ec3d032 use property wrappers to have settings in a nice way. now it's the only place for settings key 2019-10-19 00:28:27 +07:00
Serg c4928ee382 enable/disable multitouch gestures 2019-10-19 00:28:27 +07:00
Serg 0eef872f2b Merge pull request #229 from Toxblh/ReDetection-warnings
Redetection warnings
2019-10-18 22:06:29 +07:00
toxblh 3f78bbe42c CI osx_image 11.2 -> 11.1 2019-10-18 12:18:50 +01:00
toxblh f2e6959b71 upd travis-ci 10 to 11.2 2019-10-17 18:27:44 +01:00
Serg 820853d300 little cleanup 2019-10-18 00:05:17 +07:00
Serg 2635e2611f switch to modern build system 2019-10-17 23:40:41 +07:00
Serg e68fa10c42 update swift to 5.0 2019-10-17 23:37:58 +07:00
Serg 92975cb8e4 resolve existing varnings 2019-10-17 22:47:19 +07:00
toxblh 0b39795bd7 v0.24 2019-10-12 13:24:23 +01:00
toxblh d4b950ab64 Merge branch 'ReDetection-disable-haptic' 2019-10-12 13:06:27 +01:00
toxblh 41a9544c3a More consistent settings 2019-10-12 13:04:28 +01:00
Serg e44ff00f3b support for global settings. haptic feedback as the first one
resolves #185
2019-10-12 12:54:48 +07:00
Anton Palgunov f378de675e Merge pull request #219 from gtataranni/master
Activate Finder
2019-10-08 21:41:03 +01:00
Giovanni Tataranni 66b175d5ba Update README.md 2019-10-06 10:54:17 +02:00
Giovanni Tataranni 59cde098f2 Update Finder.scpt
Bring to front existing window, or create new one
2019-10-06 10:52:21 +02:00
Giovanni Tataranni f3015df82a Update finder example script 2019-10-06 10:48:41 +02:00
Anton Palgunov 6629cfd11c Update FUNDING.yml 2019-09-11 12:26:00 +01:00
Anton Palgunov d6e48c8197 Update README.md 2019-09-10 23:41:11 +01:00
Anton Palgunov 0e8dab4677 Update README.md 2019-09-04 12:11:50 +01:00
toxblh aa73fd1dc3 add locale to timeButton 2019-09-04 12:06:53 +01:00
Anton Palgunov 29da70c477 ShellScriptTouchBarItem: fix crash when script returns with error (#209)
ShellScriptTouchBarItem: fix crash when script returns with error
2019-08-29 01:26:52 +01:00
bobrosoft aa67dc10b7 ShellScriptTouchBarItem: fix crash when script returns with error 2019-08-27 14:18:24 +04:00
Anton Palgunov aaa54e2709 Update README.md 2019-08-21 21:46:16 +01:00
31 changed files with 1078 additions and 474 deletions
+2
View File
@@ -1,4 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
issuehunt: Toxblh
patreon: toxblh patreon: toxblh
ko_fi: toxblh ko_fi: toxblh
custom: https://www.paypal.me/toxblh
+1 -1
View File
@@ -54,7 +54,7 @@ playground.xcworkspace
# Carthage # Carthage
# #
# Add this line if you want to avoid checking in source code from Carthage dependencies. # Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts Carthage/Checkouts
Carthage/Build Carthage/Build
+1 -1
View File
@@ -1,6 +1,6 @@
language: swift language: swift
xcode_project: MTMR.xcodeproj xcode_project: MTMR.xcodeproj
xcode_scheme: UnitTests xcode_scheme: UnitTests
osx_image: xcode10 osx_image: xcode11.1
install: gem install xcpretty install: gem install xcpretty
script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}" script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}"
+1
View File
@@ -0,0 +1 @@
github "stephencelis/SQLite.swift" ~> 0.12.0
+1
View File
@@ -0,0 +1 @@
github "stephencelis/SQLite.swift" "0.12.2"
+43 -10
View File
@@ -18,6 +18,7 @@
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; }; 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; }; 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; }; 36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */; };
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; }; 4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; }; 4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; }; 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
@@ -31,10 +32,14 @@
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; }; 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; };
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */; }; 60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */; };
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; }; 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; };
8338290C2360CA31008981D7 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8338290B2360CA31008981D7 /* SQLite.framework */; };
8338290D2360CA3F008981D7 /* SQLite.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 8338290B2360CA31008981D7 /* SQLite.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
839ECD8523600BF500BE2DA5 /* NotificationTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */; };
839ECD882360160100BE2DA5 /* Mail.scpt in Sources */ = {isa = PBXBuildFile; fileRef = 839ECD872360160100BE2DA5 /* Mail.scpt */; };
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B002E641216C0E38002774BA /* CoreDisplay.framework */; }; B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B002E641216C0E38002774BA /* CoreDisplay.framework */; };
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.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, ); }; }; B00D181F2152F521000806F4 /* Sparkle.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; }; B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; };
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.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 */; };
@@ -69,14 +74,16 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
B00D181E2152F507000806F4 /* CopyFiles */ = { B00D181E2152F507000806F4 /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 12; buildActionMask = 12;
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */, B00D181F2152F521000806F4 /* Sparkle.framework in Copy Files */,
8338290D2360CA3F008981D7 /* SQLite.framework in Copy Files */,
); );
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
@@ -94,6 +101,7 @@
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; }; 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; }; 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; }; 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; }; 4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; }; 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; }; 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
@@ -110,6 +118,10 @@
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.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>"; }; 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>"; }; 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = "<group>"; };
8338290B2360CA31008981D7 /* SQLite.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SQLite.framework; path = Carthage/Build/Mac/SQLite.framework; sourceTree = "<group>"; };
839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTouchBarItem.swift; sourceTree = "<group>"; };
839ECD86236015E300BE2DA5 /* Messages.scpt */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Messages.scpt; sourceTree = "<group>"; };
839ECD872360160100BE2DA5 /* Mail.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Mail.scpt; sourceTree = "<group>"; };
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.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>"; }; 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>"; }; B00D181C2152F4A5000806F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
@@ -162,6 +174,7 @@
files = ( files = (
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */, B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */,
B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */, B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */,
8338290C2360CA31008981D7 /* SQLite.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 */, B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */,
@@ -181,6 +194,7 @@
B059D62B205F11E8006E6B86 /* Frameworks */ = { B059D62B205F11E8006E6B86 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8338290B2360CA31008981D7 /* SQLite.framework */,
B002E641216C0E38002774BA /* CoreDisplay.framework */, B002E641216C0E38002774BA /* CoreDisplay.framework */,
B00D181C2152F4A5000806F4 /* Sparkle.framework */, B00D181C2152F4A5000806F4 /* Sparkle.framework */,
B08173292135F354005D4908 /* CoreBrightness.framework */, B08173292135F354005D4908 /* CoreBrightness.framework */,
@@ -225,6 +239,7 @@
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */, 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
B082B252205C7D8000BC04DC /* AppDelegate.swift */, B082B252205C7D8000BC04DC /* AppDelegate.swift */,
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */, B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */, B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */, 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */, 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
@@ -265,6 +280,8 @@
B0B1742E207D6B590004B740 /* Vox.next.scpt */, B0B1742E207D6B590004B740 /* Vox.next.scpt */,
B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */, B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */,
B0B17428207D6B580004B740 /* Weather.scpt */, B0B17428207D6B580004B740 /* Weather.scpt */,
839ECD86236015E300BE2DA5 /* Messages.scpt */,
839ECD872360160100BE2DA5 /* Mail.scpt */,
); );
path = AppleScripts; path = AppleScripts;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -307,6 +324,7 @@
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */,
); );
path = Widgets; path = Widgets;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -321,7 +339,7 @@
B082B24B205C7D8000BC04DC /* Sources */, B082B24B205C7D8000BC04DC /* Sources */,
B082B24C205C7D8000BC04DC /* Frameworks */, B082B24C205C7D8000BC04DC /* Frameworks */,
B082B24D205C7D8000BC04DC /* Resources */, B082B24D205C7D8000BC04DC /* Resources */,
B00D181E2152F507000806F4 /* CopyFiles */, B00D181E2152F507000806F4 /* Copy Files */,
B0679BBF215AE085000FC6B4 /* ShellScript */, B0679BBF215AE085000FC6B4 /* ShellScript */,
); );
buildRules = ( buildRules = (
@@ -362,7 +380,7 @@
TargetAttributes = { TargetAttributes = {
B082B24E205C7D8000BC04DC = { B082B24E205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
ProvisioningStyle = Manual; ProvisioningStyle = Manual;
SystemCapabilities = { SystemCapabilities = {
com.apple.Sandbox = { com.apple.Sandbox = {
@@ -372,7 +390,7 @@
}; };
B082B260205C7D8000BC04DC = { B082B260205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
}; };
}; };
}; };
@@ -474,9 +492,12 @@
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */, B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */,
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */, 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
839ECD882360160100BE2DA5 /* Mail.scpt in Sources */,
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */, 36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */, 60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
839ECD8523600BF500BE2DA5 /* NotificationTouchBarItem.swift in Sources */,
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.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 */,
@@ -633,14 +654,20 @@
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
"\"SQLite\"",
);
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -656,14 +683,20 @@
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
"\"SQLite\"",
);
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -676,7 +709,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -691,7 +724,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict/>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist> </plist>
+36 -23
View File
@@ -25,6 +25,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary) AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
TouchBarController.shared.setupControlStripPresence() TouchBarController.shared.setupControlStripPresence()
HapticFeedbackUpdate()
if let button = statusItem.button { if let button = statusItem.button {
button.image = #imageLiteral(resourceName: "StatusImage") button.image = #imageLiteral(resourceName: "StatusImage")
@@ -40,18 +41,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillTerminate(_: Notification) {} func applicationWillTerminate(_: Notification) {}
@objc func updateIsBlockedApp() { func HapticFeedbackUpdate() {
var blacklistAppIdentifiers: [String] = [] HapticFeedback.shared = AppSettings.hapticFeedbackState ? HapticFeedback() : nil
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 { @objc func updateIsBlockedApp() {
isBlockedApp = blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
} else { } else {
isBlockedApp = false isBlockedApp = false
} }
@@ -67,29 +63,38 @@ class AppDelegate: NSObject, NSApplicationDelegate {
task.launch() task.launch()
} }
@objc func toggleControlStrip(_: Any?) { @objc func toggleControlStrip(_ item: NSMenuItem) {
TouchBarController.shared.showControlStripState = !TouchBarController.shared.showControlStripState item.state = item.state == .on ? .off : .on
AppSettings.showControlStripState = item.state == .off
TouchBarController.shared.resetControlStrip() TouchBarController.shared.resetControlStrip()
createMenu()
} }
@objc func toggleBlackListedApp(_: Any?) { @objc func toggleBlackListedApp(_: Any?) {
let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier if let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier {
if appIdentifier != nil { if let index = TouchBarController.shared.blacklistAppIdentifiers.firstIndex(of: appIdentifier) {
if let index = TouchBarController.shared.blacklistAppIdentifiers.index(of: appIdentifier!) {
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index) TouchBarController.shared.blacklistAppIdentifiers.remove(at: index)
} else { } else {
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier!) TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier)
} }
UserDefaults.standard.set(TouchBarController.shared.blacklistAppIdentifiers, forKey: "com.toxblh.mtmr.blackListedApps") AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers
UserDefaults.standard.synchronize()
TouchBarController.shared.updateActiveApp() TouchBarController.shared.updateActiveApp()
updateIsBlockedApp() updateIsBlockedApp()
} }
} }
@objc func toggleHapticFeedback(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.hapticFeedbackState = item.state == .on
HapticFeedbackUpdate()
}
@objc func toggleMultitouch(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.multitouchGestures = item.state == .on
TouchBarController.shared.scrollArea?.gesturesEnabled = item.state == .on
}
@objc func openPreset(_: Any?) { @objc func openPreset(_: Any?) {
let dialog = NSOpenPanel() let dialog = NSOpenPanel()
@@ -122,7 +127,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
toggleBlackList.state = isBlockedApp ? .on : .off toggleBlackList.state = isBlockedApp ? .on : .off
let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T") let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
hideControlStrip.state = TouchBarController.shared.showControlStripState ? .off : .on hideControlStrip.state = AppSettings.showControlStripState ? .off : .on
let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H")
hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off
let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "")
multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off
let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "") let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
settingSeparator.isEnabled = false settingSeparator.isEnabled = false
@@ -133,9 +144,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(settingSeparator) menu.addItem(settingSeparator)
menu.addItem(hapticFeedback)
menu.addItem(hideControlStrip) menu.addItem(hideControlStrip)
menu.addItem(toggleBlackList) menu.addItem(toggleBlackList)
menu.addItem(startAtLogin) menu.addItem(startAtLogin)
menu.addItem(multitouchGestures)
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu statusItem.menu = menu
+33
View File
@@ -0,0 +1,33 @@
import Foundation
struct AppSettings {
@UserDefault(key: "com.toxblh.mtmr.settings.showControlStrip", defaultValue: false)
static var showControlStripState: Bool
@UserDefault(key: "com.toxblh.mtmr.settings.hapticFeedback", defaultValue: true)
static var hapticFeedbackState: Bool
@UserDefault(key: "com.toxblh.mtmr.settings.multitouchGestures", defaultValue: true)
static var multitouchGestures: Bool
@UserDefault(key: "com.toxblh.mtmr.blackListedApps", defaultValue: [])
static var blacklistedAppIds: [String]
@UserDefault(key: "com.toxblh.mtmr.dock.persistent", defaultValue: [])
static var dockPersistentAppIds: [String]
}
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
}
+4 -2
View File
@@ -1,5 +1,7 @@
tell application "Finder" tell application "Finder"
make new Finder window if not (exists window 1) then
set target of front window to path to home folder as string make new Finder window
set target of front window to path to home folder as string
end if
activate activate
end tell end tell
+10
View File
@@ -0,0 +1,10 @@
if application "Mail" is running then
tell application "Mail"
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 ""
+10
View File
@@ -0,0 +1,10 @@
if application "Messages" is running then
tell application "Messages"
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 ""
+6 -4
View File
@@ -15,6 +15,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
longClick.isEnabled = longTapClosure != nil longClick.isEnabled = longTapClosure != nil
} }
} }
var finishViewConfiguration: ()->() = {}
private var button: NSButton! private var button: NSButton!
private var singleClick: HapticClickGestureRecognizer! private var singleClick: HapticClickGestureRecognizer!
@@ -100,6 +101,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
view.addGestureRecognizer(longClick) view.addGestureRecognizer(longClick)
view.addGestureRecognizer(singleClick) view.addGestureRecognizer(singleClick)
finishViewConfiguration()
} }
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool { func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
@@ -176,18 +178,18 @@ class CustomButtonCell: NSButtonCell {
class HapticClickGestureRecognizer: NSClickGestureRecognizer { class HapticClickGestureRecognizer: NSClickGestureRecognizer {
override func touchesBegan(with event: NSEvent) { override func touchesBegan(with event: NSEvent) {
HapticFeedback.shared.tap(strong: 2) HapticFeedback.shared?.tap(strong: 2)
super.touchesBegan(with: event) super.touchesBegan(with: event)
} }
override func touchesEnded(with event: NSEvent) { override func touchesEnded(with event: NSEvent) {
HapticFeedback.shared.tap(strong: 1) HapticFeedback.shared?.tap(strong: 1)
super.touchesEnded(with: event) super.touchesEnded(with: event)
} }
} }
class LongPressGestureRecognizer: NSPressGestureRecognizer { class LongPressGestureRecognizer: NSPressGestureRecognizer {
private let recognizeTimeout = 0.4 var recognizeTimeout = 0.4
private var timer: Timer? private var timer: Timer?
override func touchesBegan(with event: NSEvent) { override func touchesBegan(with event: NSEvent) {
@@ -226,7 +228,7 @@ class LongPressGestureRecognizer: NSPressGestureRecognizer {
@objc private func onTimer() { @objc private func onTimer() {
if let target = self.target, let action = self.action { if let target = self.target, let action = self.action {
target.performSelector(onMainThread: action, with: self, waitUntilDone: false) target.performSelector(onMainThread: action, with: self, waitUntilDone: false)
HapticFeedback.shared.tap(strong: 6) HapticFeedback.shared?.tap(strong: 6)
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@
import IOKit import IOKit
class HapticFeedback { class HapticFeedback {
static let shared = HapticFeedback() static var shared: HapticFeedback?
// Here we have list of possible IDs for Haptic Generator Device. They are not constant // 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 // To find deviceID, you will need IORegistryExplorer app from Additional Tools for Xcode dmg
+2 -2
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.23</string> <string>0.25</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>275</string> <string>297</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string> <string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
+65 -162
View File
@@ -3,7 +3,7 @@ import Foundation
extension Data { extension Data {
func barItemDefinitions() -> [BarItemDefinition]? { func barItemDefinitions() -> [BarItemDefinition]? {
return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
} }
} }
@@ -56,6 +56,13 @@ class SupportedTypesHolder {
parameters: [.align: .align(.left)] parameters: [.align: .align(.left)]
) }, ) },
"reply": { _ in (
item: .staticButton(title: "Reply"),
action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]),
longAction: .none,
parameters: [.align: .align(.right)]
) },
"delete": { _ in ( "delete": { _ in (
item: .staticButton(title: "del"), item: .staticButton(title: "del"),
action: .keyPress(keycode: 117), action: .keyPress(keycode: 117),
@@ -163,116 +170,6 @@ class SupportedTypesHolder {
) )
}, },
"weather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key; case icon_type }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let units = try container.decodeIfPresent(String.self, forKey: .units)
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key)
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"),
action,
longAction,
parameters: [:]
)
},
"yandexWeather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .yandexWeather(interval: interval ?? 1800.00),
action,
longAction,
parameters: [:]
)
},
"currency": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to; case full }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let from = try container.decodeIfPresent(String.self, forKey: .from)
let to = try container.decodeIfPresent(String.self, forKey: .to)
let full = try container.decodeIfPresent(Bool.self, forKey: .full)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (
item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD", full: full ?? false),
action,
longAction,
parameters: [:]
)
},
"dock": { decoder in
enum CodingKeys: String, CodingKey { case autoResize }
let container = try decoder.container(keyedBy: CodingKeys.self)
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
return (
item: .dock(autoResize: autoResize),
action: .none,
longAction: .none,
parameters: [:]
)
},
"inputsource": { _ in
(
item: .inputsource(),
action: .none,
longAction: .none,
parameters: [:]
)
},
"volume": { decoder in
enum CodingKeys: String, CodingKey { case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (
item: .volume(),
action: .none,
longAction: .none,
parameters: [.image: .image(source: img)]
)
} else {
return (
item: .volume(),
action: .none,
longAction: .none,
parameters: [:]
)
}
},
"brightness": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (
item: .brightness(refreshInterval: interval ?? 0.5),
action: .none,
longAction: .none,
parameters: [.image: .image(source: img)]
)
} else {
return (
item: .brightness(refreshInterval: interval ?? 0.5),
action: .none,
longAction: .none,
parameters: [:]
)
}
},
"sleep": { _ in ( "sleep": { _ in (
item: .staticButton(title: "☕️"), item: .staticButton(title: "☕️"),
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
@@ -287,29 +184,26 @@ class SupportedTypesHolder {
parameters: [:] parameters: [:]
) }, ) },
"music": { decoder in "notification": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee } enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee; case image }
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return ( return (
item: .music(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false), item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
action: .none, action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]),
longAction: .none, longAction: .none,
parameters: [:] parameters: [.image: .image(source: img)]
)
},
"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: [:]
) )
} else {
return (
item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]),
longAction: .none,
parameters: [:]
)
}
}, },
] ]
@@ -344,22 +238,23 @@ enum ItemType: Decodable {
case staticButton(title: String) case staticButton(title: String)
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case timeButton(formatTemplate: String, timeZone: String?) case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
case battery() case battery
case dock(autoResize: Bool) case dock(autoResize: Bool, filter: String?)
case volume() case volume
case brightness(refreshInterval: Double) case brightness(refreshInterval: Double)
case weather(interval: Double, units: String, api_key: String, icon_type: String) case weather(interval: Double, units: String, api_key: String, icon_type: String)
case yandexWeather(interval: Double) case yandexWeather(interval: Double)
case currency(interval: Double, from: String, to: String, full: Bool) case currency(interval: Double, from: String, to: String, full: Bool)
case inputsource() case inputsource
case music(interval: Double, disableMarquee: Bool) case music(interval: Double, disableMarquee: Bool)
case groupBar(items: [BarItemDefinition]) case notification(interval: Double, disableMarquee: Bool)
case nightShift() case group(items: [BarItemDefinition])
case dnd() case nightShift
case dnd
case pomodoro(workTime: Double, restTime: Double) case pomodoro(workTime: Double, restTime: Double)
case network(flip: Bool) case network(flip: Bool)
case darkMode() case darkMode
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type case type
@@ -374,6 +269,7 @@ enum ItemType: Decodable {
case api_key case api_key
case icon_type case icon_type
case formatTemplate case formatTemplate
case locale
case image case image
case url case url
case longUrl case longUrl
@@ -382,6 +278,7 @@ enum ItemType: Decodable {
case restTime case restTime
case flip case flip
case autoResize case autoResize
case filter
case disableMarquee case disableMarquee
} }
@@ -399,7 +296,8 @@ enum ItemType: Decodable {
case currency case currency
case inputsource case inputsource
case music case music
case groupBar case notification
case group
case nightShift case nightShift
case dnd case dnd
case pomodoro case pomodoro
@@ -415,7 +313,7 @@ enum ItemType: Decodable {
let source = try container.decode(Source.self, forKey: .source) let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .appleScriptTitledButton(source: source, refreshInterval: interval) self = .appleScriptTitledButton(source: source, refreshInterval: interval)
case .shellScriptTitledButton: case .shellScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source) let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
@@ -428,17 +326,19 @@ enum ItemType: Decodable {
case .timeButton: case .timeButton:
let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm" let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm"
let timeZone = try container.decodeIfPresent(String.self, forKey: .timeZone) ?? nil let timeZone = try container.decodeIfPresent(String.self, forKey: .timeZone) ?? nil
self = .timeButton(formatTemplate: template, timeZone: timeZone) let locale = try container.decodeIfPresent(String.self, forKey: .locale) ?? nil
self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery: case .battery:
self = .battery() self = .battery
case .dock: case .dock:
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
self = .dock(autoResize: autoResize) let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter)
self = .dock(autoResize: autoResize, filter: filterRegexString)
case .volume: case .volume:
self = .volume() self = .volume
case .brightness: case .brightness:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
@@ -450,7 +350,7 @@ enum ItemType: Decodable {
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6"
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" 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) self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type)
case .yandexWeather: case .yandexWeather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .yandexWeather(interval: interval) self = .yandexWeather(interval: interval)
@@ -463,22 +363,27 @@ enum ItemType: Decodable {
self = .currency(interval: interval, from: from, to: to, full: full) self = .currency(interval: interval, from: from, to: to, full: full)
case .inputsource: case .inputsource:
self = .inputsource() self = .inputsource
case .music: case .music:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false
self = .music(interval: interval, disableMarquee: disableMarquee) self = .music(interval: interval, disableMarquee: disableMarquee)
case .groupBar: case .notification:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false
self = .notification(interval: interval, disableMarquee: disableMarquee)
case .group:
let items = try container.decode([BarItemDefinition].self, forKey: .items) let items = try container.decode([BarItemDefinition].self, forKey: .items)
self = .groupBar(items: items) self = .group(items: items)
case .nightShift: case .nightShift:
self = .nightShift() self = .nightShift
case .dnd: case .dnd:
self = .dnd() self = .dnd
case .pomodoro: case .pomodoro:
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0 let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0
@@ -490,7 +395,7 @@ enum ItemType: Decodable {
self = .network(flip: flip) self = .network(flip: flip)
case .darkMode: case .darkMode:
self = .darkMode() self = .darkMode
} }
} }
} }
@@ -732,12 +637,20 @@ extension String {
} }
var fileData: Data? { var fileData: Data? {
return try? Data(contentsOf: URL(fileURLWithPath: self)) return try? Data(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath))
} }
var fileString: String? { var fileString: String? {
var encoding: String.Encoding = .utf8 var encoding: String.Encoding = .utf8
return try? String(contentsOfFile: self, usedEncoding: &encoding) return try? String(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath), usedEncoding: &encoding)
}
var fileURL: URL {
return URL(fileURLWithPath: (self as NSString).expandingTildeInPath)
}
var appleScript: NSAppleScript? {
return NSAppleScript(source: self)
} }
} }
@@ -757,16 +670,6 @@ enum Align: String, Decodable {
case right case right
} }
extension String {
var fileURL: URL {
return URL(fileURLWithPath: self)
}
var appleScript: NSAppleScript? {
return NSAppleScript(source: self)
}
}
extension URL { extension URL {
var appleScript: NSAppleScript? { var appleScript: NSAppleScript? {
guard FileManager.default.fileExists(atPath: path) else { return nil } guard FileManager.default.fileExists(atPath: path) else { return nil }
+11 -2
View File
@@ -3,6 +3,8 @@ import Foundation
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
var twofingersPrev: CGFloat = 0.0 var twofingersPrev: CGFloat = 0.0
var threefingersPrev: CGFloat = 0.0 var threefingersPrev: CGFloat = 0.0
var twofingers: NSPanGestureRecognizer!
var threefingers: NSPanGestureRecognizer!
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) { init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
super.init(identifier: identifier) super.init(identifier: identifier)
@@ -14,16 +16,23 @@ class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
scrollView.documentView = stackView scrollView.documentView = stackView
view = scrollView view = scrollView
let twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:))) twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
twofingers.allowedTouchTypes = .direct twofingers.allowedTouchTypes = .direct
twofingers.numberOfTouchesRequired = 2 twofingers.numberOfTouchesRequired = 2
view.addGestureRecognizer(twofingers) view.addGestureRecognizer(twofingers)
let threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:))) threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
threefingers.allowedTouchTypes = .direct threefingers.allowedTouchTypes = .direct
threefingers.numberOfTouchesRequired = 3 threefingers.numberOfTouchesRequired = 3
view.addGestureRecognizer(threefingers) view.addGestureRecognizer(threefingers)
} }
var gesturesEnabled = true {
didSet {
twofingers.isEnabled = gesturesEnabled
threefingers.isEnabled = gesturesEnabled
}
}
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
+6 -2
View File
@@ -38,7 +38,7 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: scriptResult) ?? NSAttributedString(string: "")) let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: scriptResult) ?? NSAttributedString(string: ""))
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length)) title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
let newBackgoundColor = title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor let newBackgoundColor: NSColor? = title.length != 0 ? title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor : nil
// Update UI // Update UI
DispatchQueue.main.async { [weak self, newBackgoundColor] in DispatchQueue.main.async { [weak self, newBackgoundColor] in
@@ -65,7 +65,11 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
task.launch() task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile() let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? "" var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
if (output == "" && task.terminationStatus != 0) {
output = "error"
}
return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression) return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression)
} }
+42 -37
View File
@@ -25,13 +25,13 @@ extension ItemType {
return "com.toxblh.mtmr.appleScriptButton." return "com.toxblh.mtmr.appleScriptButton."
case .shellScriptTitledButton(source: _): case .shellScriptTitledButton(source: _):
return "com.toxblh.mtmr.shellScriptButton." return "com.toxblh.mtmr.shellScriptButton."
case .timeButton(formatTemplate: _, timeZone: _): case .timeButton(formatTemplate: _, timeZone: _, locale: _):
return "com.toxblh.mtmr.timeButton." return "com.toxblh.mtmr.timeButton."
case .battery(): case .battery:
return "com.toxblh.mtmr.battery." return "com.toxblh.mtmr.battery."
case .dock(autoResize: _): case .dock(autoResize: _, filter: _):
return "com.toxblh.mtmr.dock" return "com.toxblh.mtmr.dock"
case .volume(): case .volume:
return "com.toxblh.mtmr.volume" return "com.toxblh.mtmr.volume"
case .brightness(refreshInterval: _): case .brightness(refreshInterval: _):
return "com.toxblh.mtmr.brightness" return "com.toxblh.mtmr.brightness"
@@ -41,21 +41,23 @@ extension ItemType {
return "com.toxblh.mtmr.yandexWeather" return "com.toxblh.mtmr.yandexWeather"
case .currency(interval: _, from: _, to: _, full: _): case .currency(interval: _, from: _, to: _, full: _):
return "com.toxblh.mtmr.currency" return "com.toxblh.mtmr.currency"
case .inputsource(): case .inputsource:
return "com.toxblh.mtmr.inputsource." return "com.toxblh.mtmr.inputsource."
case .music(interval: _): case .music(interval: _):
return "com.toxblh.mtmr.music." return "com.toxblh.mtmr.music."
case .groupBar(items: _): case .notification(interval: _):
return "com.toxblh.mtmr.notification."
case .group(items: _):
return "com.toxblh.mtmr.groupBar." return "com.toxblh.mtmr.groupBar."
case .nightShift(items: _): case .nightShift:
return "com.toxblh.mtmr.nightShift." return "com.toxblh.mtmr.nightShift."
case .dnd(items: _): case .dnd:
return "com.toxblh.mtmr.dnd." return "com.toxblh.mtmr.dnd."
case .pomodoro(interval: _): case .pomodoro(interval: _):
return PomodoroBarItem.identifier return PomodoroBarItem.identifier
case .network(flip: _): case .network(flip: _):
return NetworkBarItem.identifier return NetworkBarItem.identifier
case .darkMode(items: _): case .darkMode:
return DarkModeBarItem.identifier return DarkModeBarItem.identifier
} }
} }
@@ -78,22 +80,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
var centerIdentifiers: [NSTouchBarItem.Identifier] = [] var centerIdentifiers: [NSTouchBarItem.Identifier] = []
var centerItems: [NSTouchBarItem] = [] var centerItems: [NSTouchBarItem] = []
var rightIdentifiers: [NSTouchBarItem.Identifier] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = []
var scrollArea: NSCustomTouchBarItem? var scrollArea: ScrollViewItem?
var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) 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 blacklistAppIdentifiers: [String] = []
var frontmostApplicationIdentifier: String? { var frontmostApplicationIdentifier: String? {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil } return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
return frontmostId
} }
private override init() { private override init() {
@@ -107,9 +99,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)]) }), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
} }
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") { blacklistAppIdentifiers = AppSettings.blacklistedAppIds
blacklistAppIdentifiers = blackListed
}
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
@@ -139,6 +129,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
scrollArea?.gesturesEnabled = AppSettings.multitouchGestures
touchBar.delegate = self touchBar.delegate = self
touchBar.defaultItemIdentifiers = [] touchBar.defaultItemIdentifiers = []
@@ -152,7 +143,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
func updateActiveApp() { func updateActiveApp() {
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil { if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
dismissTouchBar() dismissTouchBar()
} else { } else {
presentTouchBar() presentTouchBar()
@@ -215,7 +206,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
@objc private func presentTouchBar() { @objc private func presentTouchBar() {
if showControlStripState { if AppSettings.showControlStripState {
updateControlStripPresence() updateControlStripPresence()
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else { } else {
@@ -255,13 +246,21 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval) barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
case let .shellScriptTitledButton(source: source, refreshInterval: interval): case let .shellScriptTitledButton(source: source, refreshInterval: interval):
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval) barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
case let .timeButton(formatTemplate: template, timeZone: timeZone): case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale):
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone) barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery(): case .battery:
barItem = BatteryBarItem(identifier: identifier) barItem = BatteryBarItem(identifier: identifier)
case let .dock(autoResize: autoResize): case let .dock(autoResize: autoResize, filter: regexString):
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize) if let regexString = regexString {
case .volume: guard let regex = try? NSRegularExpression(pattern: regexString, options: []) else {
barItem = CustomButtonTouchBarItem(identifier: identifier, title: "Bad regex")
break
}
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize, filter: regex)
} else {
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize)
}
case .volume:
if case let .image(source)? = item.additionalParameters[.image] { if case let .image(source)? = item.additionalParameters[.image] {
barItem = VolumeViewController(identifier: identifier, image: source.image) barItem = VolumeViewController(identifier: identifier, image: source.image)
} else { } else {
@@ -279,21 +278,27 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
barItem = YandexWeatherBarItem(identifier: identifier, interval: interval) barItem = YandexWeatherBarItem(identifier: identifier, interval: interval)
case let .currency(interval: interval, from: from, to: to, full: full): case let .currency(interval: interval, from: from, to: to, full: full):
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full) barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full)
case .inputsource(): case .inputsource:
barItem = InputSourceBarItem(identifier: identifier) barItem = InputSourceBarItem(identifier: identifier)
case let .music(interval: interval, disableMarquee: disableMarquee): case let .music(interval: interval, disableMarquee: disableMarquee):
barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee) barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee)
case let .groupBar(items: items): case let .notification(interval: interval, disableMarquee: disableMarquee):
if case let .image(source)? = item.additionalParameters[.image] {
barItem = NotificationBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee, image: source.image)
} else {
barItem = NotificationBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee)
}
case let .group(items: items):
barItem = GroupBarItem(identifier: identifier, items: items) barItem = GroupBarItem(identifier: identifier, items: items)
case .nightShift(): case .nightShift:
barItem = NightShiftBarItem(identifier: identifier) barItem = NightShiftBarItem(identifier: identifier)
case .dnd(): case .dnd:
barItem = DnDBarItem(identifier: identifier) barItem = DnDBarItem(identifier: identifier)
case let .pomodoro(workTime: workTime, restTime: restTime): case let .pomodoro(workTime: workTime, restTime: restTime):
barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime) barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime)
case let .network(flip: flip): case let .network(flip: flip):
barItem = NetworkBarItem(identifier: identifier, flip: flip) barItem = NetworkBarItem(identifier: identifier, flip: flip)
case .darkMode(): case .darkMode:
barItem = DarkModeBarItem(identifier: identifier) barItem = DarkModeBarItem(identifier: identifier)
} }
+142 -167
View File
@@ -7,238 +7,156 @@
import Cocoa import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource { class AppScrubberTouchBarItem: NSCustomTouchBarItem {
private var scrubber: NSScrubber! private var scrollView = NSScrollView()
private var timer: Timer!
private var ticks: Int = 0
private let minTicks: Int = 5
private let maxTicks: Int = 20
private var lastSelected: Int = 0
private var autoResize: Bool = false private var autoResize: Bool = false
private var widthConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint?
private let filter: NSRegularExpression?
private var persistentAppIdentifiers: [String] = [] private var persistentAppIdentifiers: [String] = []
private var runningAppsIdentifiers: [String] = [] private var runningAppsIdentifiers: [String] = []
private var frontmostApplicationIdentifier: String? { private var frontmostApplicationIdentifier: String? {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil } return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
return frontmostId
} }
private var applications: [DockItem] = [] private var applications: [DockItem] = []
private var items: [DockBarItem] = []
convenience override init(identifier: NSTouchBarItem.Identifier) {
self.init(identifier: identifier, autoResize: false)
}
static var iconWidth = 36
static var spacingWidth = 2
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) { init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false, filter: NSRegularExpression? = nil) {
self.filter = filter
super.init(identifier: identifier) super.init(identifier: identifier)
self.autoResize = autoResize self.autoResize = autoResize
view = scrollView
scrubber = NSScrubber()
scrubber.delegate = self
scrubber.dataSource = self
scrubber.mode = .free // .fixed
let layout = NSScrubberFlowLayout()
layout.itemSize = NSSize(width: AppScrubberTouchBarItem.iconWidth, height: 32)
layout.itemSpacing = CGFloat(AppScrubberTouchBarItem.spacingWidth)
scrubber.scrubberLayout = layout
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.showsAdditionalContentIndicators = true
view = scrubber NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(softReloadItems), name: NSWorkspace.didActivateApplicationNotification, object: nil)
scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier")) persistentAppIdentifiers = AppSettings.dockPersistentAppIds
hardReloadItems()
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
if let persistent = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.dock.persistent") {
persistentAppIdentifiers = persistent
}
updateRunningApplication()
} }
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func activeApplicationChanged(n _: Notification) { @objc func hardReloadItems() {
updateRunningApplication() applications = launchedApplications()
}
override func observeValue(forKeyPath _: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
updateRunningApplication()
}
func updateRunningApplication() {
let newApplications = launchedApplications()
let index = newApplications.index {
$0.bundleIdentifier == frontmostApplicationIdentifier
}
applications = newApplications
applications += getDockPersistentAppsList() applications += getDockPersistentAppsList()
scrubber.reloadData() reloadData()
softReloadItems()
updateSize() updateSize()
}
scrubber.selectedIndex = index ?? 0
@objc func softReloadItems() {
let frontMostAppId = self.frontmostApplicationIdentifier
let runningAppsIds = NSWorkspace.shared.runningApplications.map { $0.bundleIdentifier }
for barItem in items {
let bundleId = barItem.dockItem.bundleIdentifier
barItem.isRunning = runningAppsIds.contains(bundleId)
barItem.isFrontmost = frontMostAppId == bundleId
}
} }
func updateSize() { func updateSize() {
if self.autoResize { if self.autoResize {
if let constraint: NSLayoutConstraint = self.widthConstraint { self.widthConstraint?.isActive = false
constraint.isActive = false
self.scrubber.removeConstraint(constraint) let width = self.scrollView.documentView?.fittingSize.width ?? 0
} self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth
self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width))
self.widthConstraint!.isActive = true self.widthConstraint!.isActive = true
} }
} }
public func numberOfItems(for _: NSScrubber) -> Int { func reloadData() {
return applications.count items = applications.map { self.createAppButton(for: $0) }
let stackView = NSStackView(views: items.compactMap { $0.view })
stackView.spacing = 1
stackView.orientation = .horizontal
let visibleRect = self.scrollView.documentVisibleRect
scrollView.documentView = stackView
stackView.scroll(visibleRect.origin)
} }
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { public func createAppButton(for app: DockItem) -> DockBarItem {
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() let item = DockBarItem(app)
item.imageView.imageScaling = .scaleProportionallyDown item.isBordered = false
item.tapClosure = { [weak self] in
let app = applications[index] self?.switchToApp(app: app)
if let icon = app.icon {
item.image = icon
item.image.size = NSSize(width: 26, height: 26)
} }
item.longTapClosure = { [weak self] in
item.removeFromSuperview() self?.handleHalfLongPress(item: app)
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 item.killAppClosure = {[weak self] in
dotView.setFrameOrigin(NSPoint(x: 17, y: 1)) self?.handleLongPress(item: app)
dotView.frame.size = NSSize(width: 3, height: 3) }
item.addSubview(dotView)
return item return item
} }
public func didBeginInteracting(with _: NSScrubber) { public func switchToApp(app: DockItem) {
stopTimer() let bundleIdentifier = app.bundleIdentifier
ticks = 0
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true)
}
@objc private func checkTimer() {
ticks += 1
if ticks == minTicks {
HapticFeedback.shared.tap(strong: 2)
}
if ticks > maxTicks {
stopTimer()
HapticFeedback.shared.tap(strong: 6)
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
lastSelected = 0
}
public func didCancelInteracting(with _: NSScrubber) {
stopTimer()
}
public func didFinishInteracting(with scrubber: NSScrubber) {
stopTimer()
if ticks == 0 {
return
}
if ticks >= minTicks && scrubber.selectedIndex > 0 {
longPress(selected: scrubber.selectedIndex)
return
}
let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier
if bundleIdentifier!.contains("file://") { if bundleIdentifier!.contains("file://") {
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: "")) NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
} else { } else {
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
HapticFeedback.shared.tap(strong: 6)
} }
updateRunningApplication() softReloadItems()
// NB: if you can't open app which on another space, try to check mark // NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application" // "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings // in Mission control settings
} }
private func longPress(selected: Int) { //todo
let bundleIdentifier = applications[selected].bundleIdentifier private func handleLongPress(item: DockItem) {
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
if ticks > maxTicks { if !app.terminate() {
if let processIdentifier = applications[selected].pid { app.forceTerminate()
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
}
} }
} else { hardReloadItems()
HapticFeedback.shared.tap(strong: 6)
if let index = self.persistentAppIdentifiers.index(of: bundleIdentifier!) {
persistentAppIdentifiers.remove(at: index)
} else {
persistentAppIdentifiers.append(bundleIdentifier!)
}
UserDefaults.standard.set(persistentAppIdentifiers, forKey: "com.toxblh.mtmr.dock.persistent")
UserDefaults.standard.synchronize()
} }
ticks = 0
updateRunningApplication()
} }
private func handleHalfLongPress(item: DockItem) {
if let index = self.persistentAppIdentifiers.firstIndex(of: item.bundleIdentifier) {
persistentAppIdentifiers.remove(at: index)
hardReloadItems()
} else {
persistentAppIdentifiers.append(item.bundleIdentifier)
}
AppSettings.dockPersistentAppIds = persistentAppIdentifiers
}
private func launchedApplications() -> [DockItem] { private func launchedApplications() -> [DockItem] {
runningAppsIdentifiers = [] runningAppsIdentifiers = []
var returnable: [DockItem] = [] var returnable: [DockItem] = []
for app in NSWorkspace.shared.runningApplications { for app in NSWorkspace.shared.runningApplications {
guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue } guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue }
guard let bundleIdentifier = app.bundleIdentifier else { continue } guard let bundleIdentifier = app.bundleIdentifier else { continue }
if let filter = self.filter,
let name = app.localizedName,
filter.numberOfMatches(in: name, options: [], range: NSRange(location: 0, length: name.count)) == 0 {
continue
}
runningAppsIdentifiers.append(bundleIdentifier) runningAppsIdentifiers.append(bundleIdentifier)
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier) let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
returnable.append(dockItem) returnable.append(dockItem)
} }
return returnable return returnable
} }
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType _: String? = nil) -> NSImage { public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage {
if bundleIdentifier != nil { if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) {
if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) { return NSWorkspace.shared.icon(forFile: appPath)
return NSWorkspace.shared.icon(forFile: appPath)
}
} }
if path != nil { if let path = path {
return NSWorkspace.shared.icon(forFile: path!) return NSWorkspace.shared.icon(forFile: path)
} }
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns") let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
@@ -269,3 +187,60 @@ public class DockItem: NSObject {
self.pid = pid self.pid = pid
} }
} }
private let iconWidth = 32.0
class DockBarItem: CustomButtonTouchBarItem {
let dotView = NSView(frame: .zero)
let dockItem: DockItem
fileprivate var killGestureRecognizer: LongPressGestureRecognizer!
var killAppClosure: () -> Void = { }
var isRunning = false {
didSet {
redrawDotView()
}
}
var isFrontmost = false {
didSet {
redrawDotView()
}
}
init(_ app: DockItem) {
self.dockItem = app
super.init(identifier: .init(app.bundleIdentifier), title: "")
dotView.wantsLayer = true
image = app.icon
image?.size = NSSize(width: iconWidth, height: iconWidth)
killGestureRecognizer = LongPressGestureRecognizer(target: self, action: #selector(firePanGestureRecognizer))
killGestureRecognizer.allowedTouchTypes = .direct
killGestureRecognizer.recognizeTimeout = 1.5
killGestureRecognizer.minimumPressDuration = 1.5
killGestureRecognizer.isEnabled = isRunning
self.finishViewConfiguration = { [weak self] in
guard let selfie = self else { return }
selfie.dotView.layer?.cornerRadius = 1.5
selfie.view.addSubview(selfie.dotView)
selfie.redrawDotView()
selfie.view.addGestureRecognizer(selfie.killGestureRecognizer)
}
}
func redrawDotView() {
dotView.layer?.backgroundColor = isRunning ? NSColor.white.cgColor : NSColor.clear.cgColor
dotView.frame.size = NSSize(width: isFrontmost ? iconWidth - 14 : 3, height: 3)
dotView.setFrameOrigin(NSPoint(x: 18.0 - Double(dotView.frame.size.width) / 2.0, y: iconWidth - 5))
}
@objc func firePanGestureRecognizer() {
self.killAppClosure()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ struct DarkMode {
static func toggle(force: Bool? = nil) { static func toggle(force: Bool? = nil) {
let value = force.map(String.init) ?? "not dark mode" let value = force.map(String.init) ?? "not dark mode"
runAppleScript("\(prefix) set dark mode to \(value)") _ = runAppleScript("\(prefix) set dark mode to \(value)")
} }
} }
+1 -1
View File
@@ -52,7 +52,7 @@ class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate {
TouchBarController.shared.touchBar.defaultItemIdentifiers = [] TouchBarController.shared.touchBar.defaultItemIdentifiers = []
TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
if TouchBarController.shared.showControlStripState { if AppSettings.showControlStripState {
presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem)
} else { } else {
presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem) presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
+1 -1
View File
@@ -180,7 +180,7 @@ class MusicBarItem: CustomButtonTouchBarItem {
var iconUpdated = false var iconUpdated = false
var titleUpdated = false var titleUpdated = false
for var ident in playerBundleIdentifiers { for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) { if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning { if musicPlayer.isRunning {
var tempTitle = "" var tempTitle = ""
+6 -4
View File
@@ -31,7 +31,7 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
var dSpeed: UInt64? var dSpeed: UInt64?
var uSpeed: UInt64? var uSpeed: UInt64?
var curr: Array<Substring>? var curr: Array<Substring>?
var dataAvailable: NSObjectProtocol! var dataAvailable: NSObjectProtocol?
pipe = Pipe() pipe = Pipe()
bandwidthProcess = Process() bandwidthProcess = Process()
@@ -64,19 +64,21 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
} }
} }
outputHandle.waitForDataInBackgroundAndNotify() outputHandle.waitForDataInBackgroundAndNotify()
} else { } else if let dataAvailable = dataAvailable {
NotificationCenter.default.removeObserver(dataAvailable) NotificationCenter.default.removeObserver(dataAvailable)
} }
} }
var dataReady: NSObjectProtocol! var dataReady: NSObjectProtocol?
dataReady = NotificationCenter.default.addObserver( dataReady = NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification, forName: Process.didTerminateNotification,
object: outputHandle, object: outputHandle,
queue: nil queue: nil
) { _ -> Void in ) { _ -> Void in
print("Task terminated!") print("Task terminated!")
NotificationCenter.default.removeObserver(dataReady) if let observer = dataReady {
NotificationCenter.default.removeObserver(observer)
}
} }
bandwidthProcess?.launch() bandwidthProcess?.launch()
+566
View File
@@ -0,0 +1,566 @@
//
// NotificationsTouchBarItem.swift
// MTMR
//
// Created by Matthew Cox on 10/22/19.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
import Cocoa
import ScriptingBridge
import SQLite
import Contacts
import UserNotifications
class NotificationBarItem: CustomButtonTouchBarItem {
public enum Message: String {
case Messages = "com.apple.iChat"
case Mail = "com.apple.mail"
}
private let notificationIdentifiers = [
Message.Messages,
Message.Mail,
]
private let interval: TimeInterval
private let disableMarquee: Bool
private var messageText: String?
private var messageFrom: String?
private var timer: Timer?
private let iconSize = NSSize(width: 21, height: 21)
private let activity: NSBackgroundActivityScheduler
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, disableMarquee: Bool, image: NSImage? = nil) {
self.interval = interval
self.disableMarquee = disableMarquee
//tapClosure = { [weak self] in self?.DarkModeToggle() }
if image == nil {
//sliderItem = CustomSlider()
} else {
// sliderItem = CustomSlider(knob: image!)
}
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval
super.init(identifier: identifier, title: "⏳")
//var systemBlue: NSColor { set }
isBordered = false
backgroundColor = NSColor.systemRed
//button = item.view as? NSButton else { continue }
//let textRange = NSRange(location: 0, length: button.title.characters.count)
//let titleColor = useCustomColor.state == NSControl.StateValue.on ? NSColor.black : NSColor.white
//let newTitle = NSMutableAttributedString(string: button.title)
//newTitle.addAttribute(NSAttributedStringKey.foregroundColor, value: titleColor, range: textRange)
//newTitle.addAttribute(NSAttributedStringKey.font, value: button.font!, range: textRange)
//newTitle.setAlignment(.center, range: textRange)
//button.attributedTitle = newTitle
//attributedTitle = "test"
//image = NSImage(name: NSUserGroup)
//title = "\(Noti.0)"
// func getDeliveredNotifications(completionHandler: @escaping ([UNNotification]) -> Void) {
// print("")
// }
// let notificationCenter = UNUserNotificationCenter.current()
// notificationCenter.requestAuthorization(options: [.alert, .badge, .provisional]) {}
// UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
// print("Noti")
// print(notifications)
// }
/* let center = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) { (granted, error) in
if granted {
application.registerForRemoteNotifications()
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let actionIdentifier = response.actionIdentifier
let content = response.notification.request.content
print("\(content)")
switch actionIdentifier {
case UNNotificationDismissActionIdentifier: // Notification was dismissed by user
// Do something
completionHandler()
case UNNotificationDefaultActionIdentifier: // App was opened from notification
// Do something
completionHandler()
case "com.usernotificationstutorial.reply":
if let textResponse = response as? UNTextInputNotificationResponse {
let reply = textResponse.userText
// Send reply message
completionHandler()
}
case "com.usernotificationstutorial.delete":
// Delete message
completionHandler()
default:
completionHandler()
}
*/
/*let replyAction = UNTextInputNotificationAction(identifier: "com.usernotificationstutorial.reply", title: "Reply", options: [], textInputButtonTitle: "Send", textInputPlaceholder: "Type your message")
let deleteAction = UNNotificationAction(identifier: "com.usernotificationstutorial.delete", title: "Delete", options: [.authenticationRequired, .destructive])
let category = UNNotificationCategory(identifier: "com.usernotificationstutorial.message", actions: [replyAction, deleteAction], intentIdentifiers: [], options: [])
center.setNotificationCategories([category])
}
userNotificationCenter(center: center)
*/
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 notificationIdentifiers {
if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) {
if messageApplication.isRunning {
if ident == .Messages {
let mp = (messageApplication as MessagesApplication)
mp.playpause!()
return
} else if ident == .Mail {
let mp = (messageApplication as MailApplication)
mp.playpause!()
return
}
break
}
}
}
}
@objc func nextTrack() {
for ident in notificationIdentifiers {
if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) {
if messageApplication.isRunning {
if ident == .Messages {
let mp = (messageApplication as MessagesApplication)
mp.nextTrack!()
updateNotification()
return
} else if ident == .Mail {
let mp = (messageApplication as MailApplication)
mp.nextTrack!()
updateNotification()
return
}
}
}
}
}
func refreshAndSchedule() {
DispatchQueue.main.async {
self.updateNotification()
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.refreshAndSchedule()
}
}
}
func updateNotification() {
var iconUpdated = false
var titleUpdated = false
var notificationbuilder = ""
let name = ""
let message = ""
let durl = ""
let person = ""
let dbpath = ""
let Noti = NotiBar().getAll(name: name, message: message, durl: durl, person: person, dbpath: dbpath)
//let lastNotificationItem = ""
print("\(Noti.0) \(Noti.1) \(Noti.3)")
//icon \(Noti.0)
//Message \(Noti.1)
//button path \(Noti.2)
//Number \(Noti.3)
let fromBox = String(Noti.3)
let messageBox = String(Noti.1)
if fromBox.isEmpty
{
notificationbuilder = ""
}
else
{
notificationbuilder = "\(fromBox) - \(messageBox)"
}
let lastNotificationItem = "\(notificationbuilder)"
for ident in notificationIdentifiers {
if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) {
if messageApplication.isRunning {
var tempTitle = ""
if ident == .Messages {
tempTitle = lastNotificationItem
} else if ident == .Mail {
tempTitle = (messageApplication as MailApplication).title
}
if tempTitle == self.messageText {
return
} else {
self.messageText = tempTitle
}
if let messageText = self.messageText?.ifNotEmpty {
self.timer?.invalidate()
self.timer = nil
if (disableMarquee) {
self.title = " " + messageText
} else {
self.title = " " + messageText + " "
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 = ""
}
}
}
}
class NotiRun {
static func shell(launchPath path: String, arguments args: [String]) -> String {
let task = Process()
task.launchPath = path
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return(output!)
}
}
class NotiBar
{
func getAll(name:String, message:String, durl: String, person:String, dbpath: String) -> (String, String, String, String, String)
{
let res = NotiRun.shell(launchPath: "/usr/bin/getconf", arguments: ["DARWIN_USER_DIR"])
let trimmedres = res.trimmingCharacters(in: .whitespacesAndNewlines)
let db = try! Connection("\(trimmedres)com.apple.notificationcenter/db2/db")
let record = Table("record")
let app = Table("app")
let presented = Expression<Int?>("presented")
let identifier = Expression<String?>("identifier")
let delivered_date = Expression<Int?>("delivered_date")
let app_id = Expression<Int?>("app_id")
// let app_id = Expression<Int64>("app_id")
let data = Expression<Data>("data")
var name = ""
var message = ""
var durl = ""
var person = ""
var dbpath = ""
let timestamp = Int(NSDate().timeIntervalSinceReferenceDate - 86400)
//var image = ""
//var reply = ""
//var dismiss = ""
//var other = ""
let Messages = String("com.apple.iChat")
let Mail = String("com.apple.mail")
for app in try! db.prepare(app.select(app[*]))
{
if ("\(app[identifier]!)" == Messages)
{
}
if ("\(app[identifier]!)" == Mail)
{
}
// Messages
// Mail
}
// print("\(timestamp)")
//for record in try! db.prepare(record.join(app, on: record[app_id] == app[app_id]).select(record[*]).filter(delivered_date > timestamp) ) {
//app_id == 4 ||
for record in try! db.prepare(record.select(record[*]).filter(delivered_date > timestamp && app_id == 5) ) {
guard let notifierout = try? PropertyListSerialization.propertyList(from: record[data], options: [], format: nil) else {
fatalError("failed to deserialize")
}
if let notiDescAppName = (notifierout as AnyObject)["app"]! as? String
{
if let ret = self.getPath(appNamedNew: "\(notiDescAppName)") {
let retfix = ("\(ret)")
let rettrimmed = retfix.trimmingCharacters(in: .whitespacesAndNewlines)
let notifierbuilder = Bundle(url: URL(fileURLWithPath: "\(rettrimmed)"))?.infoDictionary?["CFBundleDisplayName"] as? String ?? Bundle(url: URL(fileURLWithPath: "\(rettrimmed)"))?.infoDictionary?["CFBundleName"] as? String
if notifierbuilder == nil
{
}
else
{
if (notifierbuilder == "Messages" || notifierbuilder == "Mail")
{
if (notifierbuilder == "Messages")
{
name = "💬"
}
else if notifierbuilder == "Mail"
{
name = "✉️"
}
if let notiDescAppMessageTop = (notifierout as AnyObject)["req"]! as? AnyObject
{
if let notiDescAppMail = (notiDescAppMessageTop as AnyObject)["subt"]! as? String
{
message = notiDescAppMail
}
else if let notiDescAppMessage = (notiDescAppMessageTop as AnyObject)["body"]! as? String
{
message = notiDescAppMessage
}
}
if let notiDescAppURLTop = (notifierout as AnyObject)["req"]! as? AnyObject
{
if let notiDescAppURL = (notiDescAppURLTop as AnyObject)["durl"]! as? String
{
durl = notiDescAppURL
}
}
if let notiDescAppPersonTop = (notifierout as AnyObject)["req"]! as? AnyObject
{
if let notiDescAppPerson = (notiDescAppPersonTop as AnyObject)["titl"]! as? String
{
person = notiDescAppPerson
}
// let imagee = getContactImage(imgu: "\(person)")
}
}
else
{
name = notifierbuilder!
}
}
}
}
dbpath = "\(trimmedres)com.apple.notificationcenter/db2/db"
}
//print("\(name)-\(message)-\(durl)-\(person)-\(dbpath)")
return (name, message, durl, person, dbpath)
}
// static func getImage(image: String) -> String
//{
//}
//static func getOptionReply(reply: String) -> String
//{
//}
//static func getOptionDismiss(dismiss: String) -> String
//{
//}
//static func getOptionOther(otNSView()her: String) -> String
//{
//}
func getPath(appNamedNew: String) -> String? {
if let absopath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: appNamedNew) {
return "\(absopath)"
} else {
return ""
}
}
func lastModified(path: String) -> NSDate? {
let fileUrl = NSURL(fileURLWithPath: path)
var modified: AnyObject?
do {
try fileUrl.getResourceValue(&modified, forKey: URLResourceKey.contentModificationDateKey)
return modified as? NSDate
} catch let error as NSError {
print("\(#function) Error: \(error)")
return nil
}
}
}
func getContactImage(imgu:String) -> NSImage?
{
let store = CNContactStore()
//let contactStore = CNContactStore()
let keys = [CNContactPhoneNumbersKey, CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey, CNContactImageDataKey] as! CNKeyDescriptor
// let request = CNContactFetchRequest(keysToFetch: keys)
// try? contactStore.enumerateContacts(with: request) { (contact, error) in
// Do something with contact
//}
do
{
let contacts = try! store.unifiedContacts(matching: CNContact.predicateForContacts(matchingName: imgu), keysToFetch:[keys])
if contacts.count > 0
{
if let image = contacts[0].imageData
{
return NSImage.init(data: image)
}
}
}
return nil
}
@objc protocol MessagesApplication {
@objc optional var currentTrack: SpotifyTrack { get }
@objc optional func nextTrack()
@objc optional func previousTrack()
@objc optional func playpause()
}
extension SBApplication: MessagesApplication {}
@objc protocol MessagesTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: MessagesTrack {}
extension MessagesApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + " — " + (t.name ?? "")
}
}
@objc protocol MailApplication {
@objc optional var currentTrack: MailTrack { get }
@objc optional func playpause()
@objc optional func nextTrack()
@objc optional func previousTrack()
}
extension SBApplication: MailApplication {}
@objc protocol MailTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: MailTrack {}
extension MailApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + " — " + (t.name ?? "")
}
}
+4 -1
View File
@@ -4,8 +4,11 @@ class TimeTouchBarItem: CustomButtonTouchBarItem {
private let dateFormatter = DateFormatter() private let dateFormatter = DateFormatter()
private var timer: Timer! private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil) { init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil, locale: String? = nil) {
dateFormatter.dateFormat = formatTemplate dateFormatter.dateFormat = formatTemplate
if let locale = locale {
dateFormatter.locale = Locale(identifier: locale)
}
if let abbr = timeZone { if let abbr = timeZone {
dateFormatter.timeZone = TimeZone(abbreviation: abbr) dateFormatter.timeZone = TimeZone(abbreviation: abbr)
} }
+25 -25
View File
@@ -16,24 +16,24 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
private var location: CLLocation! private var location: CLLocation!
private var prevLocation: CLLocation! private var prevLocation: CLLocation!
private var manager: CLLocationManager! private var manager: CLLocationManager!
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) { init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval activity.interval = interval
super.init(identifier: identifier, title: "⏳") super.init(identifier: identifier, title: "⏳")
let status = CLLocationManager.authorizationStatus() let status = CLLocationManager.authorizationStatus()
if status == .restricted || status == .denied { if status == .restricted || status == .denied {
print("User permission not given") print("User permission not given")
return return
} }
if !CLLocationManager.locationServicesEnabled() { if !CLLocationManager.locationServicesEnabled() {
print("Location services not enabled") print("Location services not enabled")
return return
} }
activity.repeats = true activity.repeats = true
activity.qualityOfService = .utility activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
@@ -41,70 +41,70 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
completion(NSBackgroundActivityScheduler.Result.finished) completion(NSBackgroundActivityScheduler.Result.finished)
} }
updateWeather() updateWeather()
manager = CLLocationManager() manager = CLLocationManager()
manager.delegate = self manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.startUpdatingLocation() manager.startUpdatingLocation()
tapClosure = tapClosure ?? defaultTapAction tapClosure = tapClosure ?? defaultTapAction
} }
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func updateWeather() { @objc func updateWeather() {
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!) var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
guard error == nil, let response = data?.utf8string else { guard error == nil, let response = data?.utf8string else {
return return
} }
// print(response) // print(response)
var matches: [[String]] var matches: [[String]]
var temperature: String? var temperature: String?
matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<") matches = response.matchingStrings(regex: "fact__temp.*?temp__value.*?>(.*?)<")
temperature = matches.first?.item(at: 1) temperature = matches.first?.item(at: 1)
var icon: String? var icon: String?
matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<") matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<")
icon = matches.first?.item(at: 1) icon = matches.first?.item(at: 1)
if let _ = icon, let test = self.iconsSource[icon!] { if let _ = icon, let test = self.iconsSource[icon!] {
icon = test icon = test
} }
if temperature != nil { if temperature != nil {
DispatchQueue.main.async { DispatchQueue.main.async {
self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)") self.setWeather(text: "\(icon ?? "?") \(temperature!)\(self.unitsStr)")
} }
} }
} }
task.resume() task.resume()
} }
func getWeatherUrl() -> String { func getWeatherUrl() -> String {
if location != nil { if location != nil {
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)" return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)?lang=ru"
} else { } else {
return "https://yandex.ru/pogoda/" // Yandex will try to determine your location by default return "https://yandex.ru/pogoda/?lang=ru" // Yandex will try to determine your location by default
} }
} }
func setWeather(text: String) { func setWeather(text: String) {
title = text title = text
} }
func defaultTapAction() { func defaultTapAction() {
print(getWeatherUrl()) print(getWeatherUrl())
if let url = URL(string: getWeatherUrl()) { if let url = URL(string: getWeatherUrl()) {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last! let lastLocation = locations.last!
location = lastLocation location = lastLocation
@@ -113,15 +113,15 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
} }
prevLocation = lastLocation prevLocation = lastLocation
} }
func locationManager(_: CLLocationManager, didFailWithError error: Error) { func locationManager(_: CLLocationManager, didFailWithError error: Error) {
print(error) print(error)
} }
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) { func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) {
updateWeather() updateWeather()
} }
deinit { deinit {
activity.invalidate() activity.invalidate()
} }
@@ -131,9 +131,9 @@ extension String {
func matchingStrings(regex: String) -> [[String]] { func matchingStrings(regex: String) -> [[String]] {
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
let nsString = self as NSString let nsString = self as NSString
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in return results.map { result in
(0..<result.numberOfRanges).map { (0 ..< result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0)) ? nsString.substring(with: result.range(at: $0))
: "" : ""
@@ -26,6 +26,33 @@ class AppleScriptDefinitionTests: XCTestCase {
XCTAssertEqual(sourceStruct?.filePath, "/ololo/pew") XCTAssertEqual(sourceStruct?.filePath, "/ololo/pew")
} }
// This tests that users can pass paths to files with ~ in them
func testUserPath() {
let buttonNoActionFixture = """
[ { "type": "appleScriptTitledButton", "source": { "filePath": "~/pew" } } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else {
XCTFail()
return
}
let sourceStruct = source as? Source
// gives you a string in the format of file:///Users/your_uer_name/pew
let expandedPath = URL(fileURLWithPath: NSString("~/pew").expandingTildeInPath) as URL
XCTAssertEqual(sourceStruct?.filePath?.fileURL, expandedPath)
}
// This tests that users can pass paths to images with ~ in them
func testUserImagePath() {
let relativeImagePath = """
[ { "filePath": "~/pew/image.png" } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([Source].self, from: relativeImagePath)
// gives you a string in the format of file:///Users/your_uer_name/pew/image.png
let expandedPath = URL(fileURLWithPath: NSString("~/pew/image.png").expandingTildeInPath) as URL
XCTAssertEqual(result?.first?.filePath?.fileURL, expandedPath)
}
func testRefreshInterval() { func testRefreshInterval() {
let buttonNoActionFixture = """ let buttonNoActionFixture = """
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ] [ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" }, "refreshInterval": 305} ]
+15 -9
View File
@@ -1,4 +1,4 @@
# My touchbar. My rules. [![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases) [![license](https://img.shields.io/github/license/Toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/blob/master/LICENSE)![minimal system requirements](https://img.shields.io/badge/required-macOS%2010.12.2-blue.svg) ![travis](https://travis-ci.org/Toxblh/MTMR.svg?branch=master) # My touchbar. My rules. [![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases) [![license](https://img.shields.io/github/license/Toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/blob/master/LICENSE) ![minimal system requirements](https://img.shields.io/badge/required-macOS%2010.12.2-blue.svg) ![travis](https://travis-ci.org/Toxblh/MTMR.svg?branch=master)
<img src="Resources/logo.png" align="right" <img src="Resources/logo.png" align="right"
title="MTMR by Toxblh" width="110" height="110"> title="MTMR by Toxblh" width="110" height="110">
@@ -43,10 +43,7 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
## Examples ## Examples
- [@Toxblh preset](Resources/toxblh.json) [MTMR presets](https://github.com/Toxblh/MTMR-presets)
- [@ReDetection preset](Resources/ReDetection.json)
- [@luongvo209 preset](Resources/luongvo209.json)
- [aadi_vs_anand preset](Resources/aadi_vs_anand.json)
<p align="center"> <p align="center">
<img src="./Resources/Artboard.png" alt="Presets for touchbar" width="800"> <img src="./Resources/Artboard.png" alt="Presets for touchbar" width="800">
@@ -131,9 +128,9 @@ The pre-installed configuration contains less or more than you'll probably want,
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"refreshInterval": 60, //optional "refreshInterval": 60, //optional
"source": { "source": {
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt", "filePath": "~/Library/Application Support/MTMR/iTunes.nowPlaying.scpt",
// or // or
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell", "inline": "tell application \"Finder\"\rif not (exists window 1) then\rmake new Finder window\rset target of front window to path to home folder as string\rend if\ractivate\rend tell",
// or // or
"base64": "StringInbase64" "base64": "StringInbase64"
} }
@@ -159,6 +156,9 @@ Example of "CPU load" button which also changes color based on load value.
}, },
"align": "right", "align": "right",
"image": { "image": {
// Or you can specify a filePath here.
// Images will be resized to 24x24.
// "filePath": "~/myproject/myimage.jpg" // or "/fixed/path/to/the.png"
"base64": "base64":
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC" "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
}, },
@@ -176,7 +176,7 @@ Example of "CPU load" button which also changes color based on load value.
"title": "stats", "title": "stats",
"items": [ "items": [
{ "type": "play" }, { "type": "play" },
{ "type": "mute" }, { "type": "mute" },
... ...
] ]
} }
@@ -197,10 +197,15 @@ To close a group, use the button:
> Attention! Works not all: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations > Attention! Works not all: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
> formatTemplate examples: https://www.datetimeformatter.com/how-to-format-date-time-in-swift/
> locale examples: https://gist.github.com/jacobbubu/1836273
```js ```js
{ {
"type": "timeButton", "type": "timeButton",
"formatTemplate": "dd HH:mm", "formatTemplate": "dd HH:mm",
"locale": "en_GB",
"timeZone": "UTC" "timeZone": "UTC"
} }
``` ```
@@ -286,6 +291,7 @@ To close a group, use the button:
```js ```js
{ {
"type": "dock", "type": "dock",
"filter": "(^Xcode$)|(Safari)|(.*player)",
"autoResize": true "autoResize": true
}, },
``` ```
@@ -312,7 +318,7 @@ To close a group, use the button:
```js ```js
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell" "inline": "tell application \"Finder\"\rif not (exists window 1) then\rmake new Finder window\rset target of front window to path to home folder as string\rend if\ractivate\rend tell",
// "filePath" or "base64" will work as well // "filePath" or "base64" will work as well
}, },
``` ```
File diff suppressed because one or more lines are too long
+10 -10
View File
@@ -3,7 +3,7 @@
{ {
"type": "exitTouchbar", "type": "exitTouchbar",
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/Resources/logo.png" "filePath": "~/git/selfProjects/MTMR/Resources/logo.png"
}, },
"width": 36, "align": "left" }, "width": 36, "align": "left" },
{ "type": "brightnessDown", "width": 36, "align": "left" }, { "type": "brightnessDown", "width": 36, "align": "left" },
@@ -12,46 +12,46 @@
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.next.scpt" "filePath": "~/git/selfProjects/MTMR/MTMR/AppleScripts/iTunes.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/iTunes.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/iTunes.png"
} }
}, },
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.next.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Spotify.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/Spotify.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/Spotify.png"
} }
}, },
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"filePath": "filePath":
"/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.nowPlaying.scpt" "~/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.nowPlaying.scpt"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"filePath": "/Users/toxblh/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.next.scpt" "filePath": "~/git/selfProjects/MTMR/MTMR/AppleScripts/Vox.next.scpt"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"filePath": "/Users/toxblh/git/selfProjects/btt-touchbar-preset/icons/Vox.png" "filePath": "~/git/selfProjects/btt-touchbar-preset/icons/Vox.png"
} }
}, },
{ {