mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-11-02 11:18:35 +00:00
Compare commits
44
Commits
v0.23
..
221cc8a176
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
221cc8a176 | ||
|
|
a6b52ebe95 | ||
|
|
a4fee54ae0 | ||
|
|
e9e5a6f739 | ||
|
|
466c0e5f68 | ||
|
|
85e3deac79 | ||
|
|
9e49cf8beb | ||
|
|
d81998862a | ||
|
|
1ae2041d0d | ||
|
|
5eb2d73c94 | ||
|
|
855ddea44e | ||
|
|
eaa26645c5 | ||
|
|
2cdb705b37 | ||
|
|
fa413f2fa2 | ||
|
|
6920664fad | ||
|
|
e9a7b6d32a | ||
|
|
dd23a3bda8 | ||
|
|
f82d7694eb | ||
|
|
94717a5ea3 | ||
|
|
b155cac2b0 | ||
|
|
cd2ec3d032 | ||
|
|
c4928ee382 | ||
|
|
0eef872f2b | ||
|
|
3f78bbe42c | ||
|
|
f2e6959b71 | ||
|
|
820853d300 | ||
|
|
2635e2611f | ||
|
|
e68fa10c42 | ||
|
|
92975cb8e4 | ||
|
|
0b39795bd7 | ||
|
|
d4b950ab64 | ||
|
|
41a9544c3a | ||
|
|
e44ff00f3b | ||
|
|
f378de675e | ||
|
|
66b175d5ba | ||
|
|
59cde098f2 | ||
|
|
f3015df82a | ||
|
|
6629cfd11c | ||
|
|
d6e48c8197 | ||
|
|
0e8dab4677 | ||
|
|
aa73fd1dc3 | ||
|
|
29da70c477 | ||
|
|
aa67dc10b7 | ||
|
|
aaa54e2709 |
@@ -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
@@ -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
@@ -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]}"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
github "stephencelis/SQLite.swift" "0.12.2"
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 }
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} ]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# My touchbar. My rules. [](https://github.com/Toxblh/MTMR/releases) [](https://github.com/Toxblh/MTMR/blob/master/LICENSE) 
|
# My touchbar. My rules. [](https://github.com/Toxblh/MTMR/releases) [](https://github.com/Toxblh/MTMR/blob/master/LICENSE)  
|
||||||
|
|
||||||
<img src="Resources/logo.png" align="right"
|
<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
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user