mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-11-01 11:08:34 +00:00
Compare commits
20
Commits
v0.26
...
e5bb90249f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5bb90249f | ||
|
|
3e82676008 | ||
|
|
a2ad47c7ba | ||
|
|
bbe901a572 | ||
|
|
54eaa3fd9f | ||
|
|
6660bb2d8f | ||
|
|
7a1800252c | ||
|
|
588e6ae09b | ||
|
|
87141e381b | ||
|
|
14282b86a9 | ||
|
|
810cdeed36 | ||
|
|
a65613acaf | ||
|
|
aa69d5f592 | ||
|
|
2f00c9ffb3 | ||
|
|
1def53878d | ||
|
|
3e5fa14494 | ||
|
|
2e2f556daf | ||
|
|
221cc8a176 | ||
|
|
a6b52ebe95 | ||
|
|
a4fee54ae0 |
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
issuehunt: Toxblh
|
issuehunt: Toxblh
|
||||||
patreon: toxblh
|
patreon: toxblh
|
||||||
ko_fi: toxblh
|
ko_fi: toxblh
|
||||||
custom: https://www.paypal.me/toxblh
|
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4
|
||||||
|
|||||||
+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
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
github "stephencelis/SQLite.swift" "0.12.2"
|
||||||
@@ -34,10 +34,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 */; };
|
||||||
@@ -71,17 +75,20 @@
|
|||||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
|
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
|
||||||
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; };
|
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; };
|
||||||
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; };
|
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; };
|
||||||
|
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
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 */
|
||||||
@@ -118,6 +125,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>"; };
|
||||||
@@ -163,6 +174,7 @@
|
|||||||
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = "<group>"; };
|
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = "<group>"; };
|
||||||
BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = "<group>"; };
|
BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = "<group>"; };
|
||||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = "<group>"; };
|
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = "<group>"; };
|
||||||
|
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextScrubberTouchBarItem.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -172,6 +184,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 */,
|
||||||
@@ -191,6 +204,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 */,
|
||||||
@@ -280,6 +294,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>";
|
||||||
@@ -322,6 +338,8 @@
|
|||||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
|
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
|
||||||
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
||||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
||||||
|
839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */,
|
||||||
|
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
|
||||||
);
|
);
|
||||||
path = Widgets;
|
path = Widgets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -336,7 +354,7 @@
|
|||||||
B082B24B205C7D8000BC04DC /* Sources */,
|
B082B24B205C7D8000BC04DC /* Sources */,
|
||||||
B082B24C205C7D8000BC04DC /* Frameworks */,
|
B082B24C205C7D8000BC04DC /* Frameworks */,
|
||||||
B082B24D205C7D8000BC04DC /* Resources */,
|
B082B24D205C7D8000BC04DC /* Resources */,
|
||||||
B00D181E2152F507000806F4 /* CopyFiles */,
|
B00D181E2152F507000806F4 /* Copy Files */,
|
||||||
B0679BBF215AE085000FC6B4 /* ShellScript */,
|
B0679BBF215AE085000FC6B4 /* ShellScript */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
@@ -488,6 +506,7 @@
|
|||||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
||||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
||||||
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
||||||
|
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */,
|
||||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
||||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
||||||
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
||||||
@@ -495,8 +514,10 @@
|
|||||||
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
|
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
|
||||||
36FEF872235A1CFC00A0ABCE /* AppSettings.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 */,
|
||||||
@@ -653,9 +674,15 @@
|
|||||||
"$(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 = "";
|
||||||
@@ -676,9 +703,15 @@
|
|||||||
"$(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 = "";
|
||||||
|
|||||||
@@ -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 ""
|
||||||
+11
-11
@@ -15,41 +15,41 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
|||||||
var fourfingers: NSPanGestureRecognizer!
|
var fourfingers: NSPanGestureRecognizer!
|
||||||
var swipeItems: [SwipeItem] = []
|
var swipeItems: [SwipeItem] = []
|
||||||
var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
||||||
|
|
||||||
// legacy gesture positions
|
// legacy gesture positions
|
||||||
// by legacy I mean gestures to increse/decrease volume/brigtness which can be checked from app menu
|
// by legacy I mean gestures to increse/decrease volume/brigtness which can be checked from app menu
|
||||||
var legacyPrevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
var legacyPrevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
||||||
var legacyGesturesEnabled = false
|
var legacyGesturesEnabled = false
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) {
|
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) {
|
||||||
super.init(identifier: identifier)
|
super.init(identifier: identifier)
|
||||||
self.swipeItems = swipeItems
|
self.swipeItems = swipeItems
|
||||||
let views = items.compactMap { $0.view }
|
let views = items.compactMap { $0.view }
|
||||||
let stackView = NSStackView(views: views)
|
let stackView = NSStackView(views: views)
|
||||||
stackView.spacing = 1
|
stackView.spacing = 8
|
||||||
stackView.orientation = .horizontal
|
stackView.orientation = .horizontal
|
||||||
view = stackView
|
view = stackView
|
||||||
|
|
||||||
twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
||||||
twofingers.numberOfTouchesRequired = 2
|
twofingers.numberOfTouchesRequired = 2
|
||||||
twofingers.allowedTouchTypes = .direct
|
twofingers.allowedTouchTypes = .direct
|
||||||
view.addGestureRecognizer(twofingers)
|
view.addGestureRecognizer(twofingers)
|
||||||
|
|
||||||
threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
||||||
threefingers.numberOfTouchesRequired = 3
|
threefingers.numberOfTouchesRequired = 3
|
||||||
threefingers.allowedTouchTypes = .direct
|
threefingers.allowedTouchTypes = .direct
|
||||||
view.addGestureRecognizer(threefingers)
|
view.addGestureRecognizer(threefingers)
|
||||||
|
|
||||||
fourfingers = NSPanGestureRecognizer(target: self, action: #selector(fourfingersHandler(_:)))
|
fourfingers = NSPanGestureRecognizer(target: self, action: #selector(fourfingersHandler(_:)))
|
||||||
fourfingers.numberOfTouchesRequired = 4
|
fourfingers.numberOfTouchesRequired = 4
|
||||||
fourfingers.allowedTouchTypes = .direct
|
fourfingers.allowedTouchTypes = .direct
|
||||||
view.addGestureRecognizer(fourfingers)
|
view.addGestureRecognizer(fourfingers)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureHandler(position: CGFloat, fingers: Int, state: NSGestureRecognizer.State) {
|
func gestureHandler(position: CGFloat, fingers: Int, state: NSGestureRecognizer.State) {
|
||||||
switch state {
|
switch state {
|
||||||
case .began:
|
case .began:
|
||||||
@@ -72,9 +72,9 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
|||||||
let prevPos = legacyPrevPositions[fingers]!
|
let prevPos = legacyPrevPositions[fingers]!
|
||||||
if ((position - prevPos) > 15) || ((prevPos - position) > 15) {
|
if ((position - prevPos) > 15) || ((prevPos - position) > 15) {
|
||||||
if position > prevPos {
|
if position > prevPos {
|
||||||
GenericKeyPress(keyCode: CGKeyCode(144)).send()
|
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP)
|
||||||
} else if position < prevPos {
|
} else if position < prevPos {
|
||||||
GenericKeyPress(keyCode: CGKeyCode(145)).send()
|
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN)
|
||||||
}
|
}
|
||||||
legacyPrevPositions[fingers] = position
|
legacyPrevPositions[fingers] = position
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
|
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
|
||||||
let position = (sender?.location(in: sender?.view).x)!
|
let position = (sender?.location(in: sender?.view).x)!
|
||||||
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
|
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
|
||||||
|
|||||||
@@ -8,18 +8,32 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
|
struct ItemAction {
|
||||||
|
typealias TriggerClosure = (() -> Void)?
|
||||||
|
|
||||||
|
let trigger: Action.Trigger
|
||||||
|
let closure: TriggerClosure
|
||||||
|
|
||||||
|
init(trigger: Action.Trigger, _ closure: TriggerClosure) {
|
||||||
|
self.trigger = trigger
|
||||||
|
self.closure = closure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||||
var tapClosure: (() -> Void)?
|
|
||||||
var longTapClosure: (() -> Void)? {
|
var actions: [ItemAction] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
longClick.isEnabled = longTapClosure != nil
|
multiClick.isDoubleClickEnabled = actions.filter({ $0.trigger == .doubleTap }).count > 0
|
||||||
|
multiClick.isTripleClickEnabled = actions.filter({ $0.trigger == .tripleTap }).count > 0
|
||||||
|
longClick.isEnabled = actions.filter({ $0.trigger == .longTap }).count > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var finishViewConfiguration: ()->() = {}
|
var finishViewConfiguration: ()->() = {}
|
||||||
|
|
||||||
private var button: NSButton!
|
private var button: NSButton!
|
||||||
private var singleClick: HapticClickGestureRecognizer!
|
|
||||||
private var longClick: LongPressGestureRecognizer!
|
private var longClick: LongPressGestureRecognizer!
|
||||||
|
private var multiClick: MultiClickGestureRecognizer!
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
||||||
attributedTitle = title.defaultTouchbarAttributedString
|
attributedTitle = title.defaultTouchbarAttributedString
|
||||||
@@ -31,10 +45,17 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
longClick.isEnabled = false
|
longClick.isEnabled = false
|
||||||
longClick.allowedTouchTypes = .direct
|
longClick.allowedTouchTypes = .direct
|
||||||
longClick.delegate = self
|
longClick.delegate = self
|
||||||
|
|
||||||
singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
|
multiClick = MultiClickGestureRecognizer(
|
||||||
singleClick.allowedTouchTypes = .direct
|
target: self,
|
||||||
singleClick.delegate = self
|
action: #selector(handleGestureSingleTap),
|
||||||
|
doubleAction: #selector(handleGestureDoubleTap),
|
||||||
|
tripleAction: #selector(handleGestureTripleTap)
|
||||||
|
)
|
||||||
|
multiClick.allowedTouchTypes = .direct
|
||||||
|
multiClick.delegate = self
|
||||||
|
multiClick.isDoubleClickEnabled = false
|
||||||
|
multiClick.isTripleClickEnabled = false
|
||||||
|
|
||||||
reinstallButton()
|
reinstallButton()
|
||||||
button.attributedTitle = attributedTitle
|
button.attributedTitle = attributedTitle
|
||||||
@@ -100,33 +121,43 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
|||||||
view = button
|
view = button
|
||||||
|
|
||||||
view.addGestureRecognizer(longClick)
|
view.addGestureRecognizer(longClick)
|
||||||
view.addGestureRecognizer(singleClick)
|
// view.addGestureRecognizer(singleClick)
|
||||||
|
view.addGestureRecognizer(multiClick)
|
||||||
finishViewConfiguration()
|
finishViewConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
||||||
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick
|
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
|
||||||
|| gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it
|
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
|
||||||
{
|
{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
|
func callActions(for trigger: Action.Trigger) {
|
||||||
switch gr.state {
|
let itemActions = self.actions.filter { $0.trigger == trigger }
|
||||||
case .ended:
|
for itemAction in itemActions {
|
||||||
tapClosure?()
|
itemAction.closure?()
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureSingleTap() {
|
||||||
|
callActions(for: .singleTap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureDoubleTap() {
|
||||||
|
callActions(for: .doubleTap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleGestureTripleTap() {
|
||||||
|
callActions(for: .tripleTap)
|
||||||
|
}
|
||||||
|
|
||||||
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
||||||
switch gr.state {
|
switch gr.state {
|
||||||
case .possible: // tiny hack because we're calling action manually
|
case .possible: // tiny hack because we're calling action manually
|
||||||
(self.longTapClosure ?? self.tapClosure)?()
|
callActions(for: .longTap)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@@ -176,15 +207,78 @@ class CustomButtonCell: NSButtonCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HapticClickGestureRecognizer: NSClickGestureRecognizer {
|
// Thanks to https://stackoverflow.com/a/49843893
|
||||||
|
final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
|
||||||
|
|
||||||
|
private let _action: Selector
|
||||||
|
private let _doubleAction: Selector
|
||||||
|
private let _tripleAction: Selector
|
||||||
|
private var _clickCount: Int = 0
|
||||||
|
|
||||||
|
public var isDoubleClickEnabled = true
|
||||||
|
public var isTripleClickEnabled = true
|
||||||
|
|
||||||
|
override var action: Selector? {
|
||||||
|
get {
|
||||||
|
return nil /// prevent base class from performing any actions
|
||||||
|
} set {
|
||||||
|
if newValue != nil { // if they are trying to assign an actual action
|
||||||
|
fatalError("Only use init(target:action:doubleAction) for assigning actions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) {
|
||||||
|
_action = action
|
||||||
|
_doubleAction = doubleAction
|
||||||
|
_tripleAction = tripleAction
|
||||||
|
super.init(target: target, action: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(target:action:doubleAction:tripleAction) is only support atm")
|
||||||
|
}
|
||||||
|
|
||||||
override func touchesBegan(with event: NSEvent) {
|
override func touchesBegan(with event: NSEvent) {
|
||||||
HapticFeedback.shared?.tap(strong: 2)
|
HapticFeedback.shared?.tap(strong: 2)
|
||||||
super.touchesBegan(with: event)
|
super.touchesBegan(with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(with event: NSEvent) {
|
override func touchesEnded(with event: NSEvent) {
|
||||||
HapticFeedback.shared?.tap(strong: 1)
|
HapticFeedback.shared?.tap(strong: 1)
|
||||||
super.touchesEnded(with: event)
|
super.touchesEnded(with: event)
|
||||||
|
_clickCount += 1
|
||||||
|
|
||||||
|
var delayThreshold: TimeInterval // fine tune this as needed
|
||||||
|
|
||||||
|
guard isDoubleClickEnabled || isTripleClickEnabled else {
|
||||||
|
_ = target?.perform(_action)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTripleClickEnabled) {
|
||||||
|
delayThreshold = 0.4
|
||||||
|
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
|
||||||
|
if _clickCount == 3 {
|
||||||
|
_ = target?.perform(_tripleAction)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delayThreshold = 0.3
|
||||||
|
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
|
||||||
|
if _clickCount == 2 {
|
||||||
|
_ = target?.perform(_doubleAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func _resetAndPerformActionIfNecessary() {
|
||||||
|
if _clickCount == 1 {
|
||||||
|
_ = target?.perform(_action)
|
||||||
|
}
|
||||||
|
if isTripleClickEnabled && _clickCount == 2 {
|
||||||
|
_ = target?.perform(_doubleAction)
|
||||||
|
}
|
||||||
|
_clickCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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.26</string>
|
<string>0.27</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>401</string>
|
<string>434</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>
|
||||||
|
|||||||
+233
-51
@@ -3,47 +3,52 @@ import Foundation
|
|||||||
|
|
||||||
extension Data {
|
extension Data {
|
||||||
func barItemDefinitions() -> [BarItemDefinition]? {
|
func barItemDefinitions() -> [BarItemDefinition]? {
|
||||||
return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
|
return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BarItemDefinition: Decodable {
|
struct BarItemDefinition: Decodable {
|
||||||
let type: ItemType
|
let type: ItemType
|
||||||
let action: ActionType
|
let actions: [Action]
|
||||||
let longAction: LongActionType
|
let legacyAction: LegacyActionType
|
||||||
|
let legacyLongAction: LegacyLongActionType
|
||||||
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type
|
case type
|
||||||
|
case actions
|
||||||
}
|
}
|
||||||
|
|
||||||
init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
||||||
self.type = type
|
self.type = type
|
||||||
self.action = action
|
self.actions = actions
|
||||||
self.longAction = longAction
|
self.legacyAction = action
|
||||||
|
self.legacyLongAction = legacyLongAction
|
||||||
self.additionalParameters = additionalParameters
|
self.additionalParameters = additionalParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
let type = try container.decode(String.self, forKey: .type)
|
let type = try container.decode(String.self, forKey: .type)
|
||||||
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
|
let actions = try container.decodeIfPresent([Action].self, forKey: .actions)
|
||||||
|
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type, actions: actions ?? [])
|
||||||
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||||
|
|
||||||
if let result = try? parametersDecoder(decoder),
|
if let result = try? parametersDecoder(decoder),
|
||||||
case let (itemType, action, longAction, parameters) = result {
|
case let (itemType, actions, action, longAction, parameters) = result {
|
||||||
parameters.forEach { additionalParameters[$0] = $1 }
|
parameters.forEach { additionalParameters[$0] = $1 }
|
||||||
self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters)
|
self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters)
|
||||||
} else {
|
} else {
|
||||||
self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
|
self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias ParametersDecoder = (Decoder) throws -> (
|
typealias ParametersDecoder = (Decoder) throws -> (
|
||||||
item: ItemType,
|
item: ItemType,
|
||||||
action: ActionType,
|
actions: [Action],
|
||||||
longAction: LongActionType,
|
legacyAction: LegacyActionType,
|
||||||
|
legacyLongAction: LegacyLongActionType,
|
||||||
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,15 +56,28 @@ class SupportedTypesHolder {
|
|||||||
private var supportedTypes: [String: ParametersDecoder] = [
|
private var supportedTypes: [String: ParametersDecoder] = [
|
||||||
"escape": { _ in (
|
"escape": { _ in (
|
||||||
item: .staticButton(title: "esc"),
|
item: .staticButton(title: "esc"),
|
||||||
action: .keyPress(keycode: 53),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.align: .align(.left)]
|
parameters: [.align: .align(.left)]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
|
"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),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
@@ -67,8 +85,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .keyPress(keycode: 144),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -77,8 +98,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .keyPress(keycode: 145),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -87,8 +111,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -97,8 +124,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -107,8 +137,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -117,8 +150,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -127,8 +163,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_MUTE),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -137,8 +176,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -147,8 +189,11 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_PLAY),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -157,35 +202,66 @@ class SupportedTypesHolder {
|
|||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
||||||
return (
|
return (
|
||||||
item: .staticButton(title: ""),
|
item: .staticButton(title: ""),
|
||||||
action: .hidKey(keycode: NX_KEYTYPE_NEXT),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [.image: imageParameter]
|
parameters: [.image: imageParameter]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
"sleep": { _ in (
|
"sleep": { _ in (
|
||||||
item: .staticButton(title: "☕️"),
|
item: .staticButton(title: "☕️"),
|
||||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
"displaySleep": { _ in (
|
"displaySleep": { _ in (
|
||||||
item: .staticButton(title: "☕️"),
|
item: .staticButton(title: "☕️"),
|
||||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]),
|
actions: [
|
||||||
longAction: .none,
|
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) },
|
) },
|
||||||
|
|
||||||
|
"notification": { decoder in
|
||||||
|
enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee; case image }
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||||
|
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee)
|
||||||
|
if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
|
||||||
|
return (
|
||||||
|
item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
|
||||||
|
action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [.image: .image(source: img)]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
|
||||||
|
action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]),
|
||||||
|
longAction: .none,
|
||||||
|
parameters: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
static let sharedInstance = SupportedTypesHolder()
|
static let sharedInstance = SupportedTypesHolder()
|
||||||
|
|
||||||
func lookup(by type: String) -> ParametersDecoder {
|
func lookup(by type: String, actions: [Action]) -> ParametersDecoder {
|
||||||
return supportedTypes[type] ?? { decoder in (
|
return supportedTypes[type] ?? { decoder in (
|
||||||
item: try ItemType(from: decoder),
|
item: try ItemType(from: decoder),
|
||||||
action: try ActionType(from: decoder),
|
actions: actions,
|
||||||
longAction: try LongActionType(from: decoder),
|
legacyAction: try LegacyActionType(from: decoder),
|
||||||
|
legacyLongAction: try LegacyLongActionType(from: decoder),
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
) }
|
) }
|
||||||
}
|
}
|
||||||
@@ -194,12 +270,13 @@ class SupportedTypesHolder {
|
|||||||
supportedTypes[typename] = decoder
|
supportedTypes[typename] = decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
|
func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) {
|
||||||
register(typename: typename) { _ in
|
register(typename: typename) { _ in
|
||||||
(
|
(
|
||||||
item: item,
|
item: item,
|
||||||
action,
|
actions,
|
||||||
longAction,
|
legacyAction,
|
||||||
|
legacyLongAction,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -220,6 +297,7 @@ enum ItemType: Decodable {
|
|||||||
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 notification(interval: Double, disableMarquee: Bool)
|
||||||
case group(items: [BarItemDefinition])
|
case group(items: [BarItemDefinition])
|
||||||
case nightShift
|
case nightShift
|
||||||
case dnd
|
case dnd
|
||||||
@@ -227,6 +305,7 @@ enum ItemType: Decodable {
|
|||||||
case network(flip: Bool)
|
case network(flip: Bool)
|
||||||
case darkMode
|
case darkMode
|
||||||
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
|
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
|
||||||
|
case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool)
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type
|
case type
|
||||||
@@ -258,6 +337,7 @@ enum ItemType: Decodable {
|
|||||||
case direction
|
case direction
|
||||||
case fingers
|
case fingers
|
||||||
case minOffset
|
case minOffset
|
||||||
|
case maxToShow
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ItemTypeRaw: String, Decodable {
|
enum ItemTypeRaw: String, Decodable {
|
||||||
@@ -274,6 +354,7 @@ enum ItemType: Decodable {
|
|||||||
case currency
|
case currency
|
||||||
case inputsource
|
case inputsource
|
||||||
case music
|
case music
|
||||||
|
case notification
|
||||||
case group
|
case group
|
||||||
case nightShift
|
case nightShift
|
||||||
case dnd
|
case dnd
|
||||||
@@ -281,6 +362,7 @@ enum ItemType: Decodable {
|
|||||||
case network
|
case network
|
||||||
case darkMode
|
case darkMode
|
||||||
case swipe
|
case swipe
|
||||||
|
case upnext
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
@@ -292,7 +374,7 @@ enum ItemType: Decodable {
|
|||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
||||||
let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:]
|
let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:]
|
||||||
self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages)
|
self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages)
|
||||||
|
|
||||||
case .shellScriptTitledButton:
|
case .shellScriptTitledButton:
|
||||||
let source = try container.decode(Source.self, forKey: .source)
|
let source = try container.decode(Source.self, forKey: .source)
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
||||||
@@ -329,7 +411,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)
|
||||||
@@ -349,6 +431,11 @@ enum ItemType: Decodable {
|
|||||||
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 .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:
|
case .group:
|
||||||
let items = try container.decode([BarItemDefinition].self, forKey: .items)
|
let items = try container.decode([BarItemDefinition].self, forKey: .items)
|
||||||
self = .group(items: items)
|
self = .group(items: items)
|
||||||
@@ -370,7 +457,7 @@ enum ItemType: Decodable {
|
|||||||
|
|
||||||
case .darkMode:
|
case .darkMode:
|
||||||
self = .darkMode
|
self = .darkMode
|
||||||
|
|
||||||
case .swipe:
|
case .swipe:
|
||||||
let sourceApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple)
|
let sourceApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple)
|
||||||
let sourceBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash)
|
let sourceBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash)
|
||||||
@@ -378,11 +465,106 @@ enum ItemType: Decodable {
|
|||||||
let fingers = try container.decode(Int.self, forKey: .fingers)
|
let fingers = try container.decode(Int.self, forKey: .fingers)
|
||||||
let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0
|
let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0
|
||||||
self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
||||||
|
|
||||||
|
case .upnext:
|
||||||
|
let from = try container.decodeIfPresent(Double.self, forKey: .from) ?? 0 // Lower bounds of period of time in hours to search for events
|
||||||
|
let to = try container.decodeIfPresent(Double.self, forKey: .to) ?? 12 // Upper bounds of period of time in hours to search for events
|
||||||
|
let maxToShow = try container.decodeIfPresent(Int.self, forKey: .maxToShow) ?? 3 // 1 indexed array. Get the 1st, 2nd, 3rd event to display multiple notifications
|
||||||
|
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
||||||
|
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 60.0
|
||||||
|
self = .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActionType: Decodable {
|
struct FailableDecodable<Base : Decodable> : Decodable {
|
||||||
|
|
||||||
|
let base: Base?
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
self.base = try? container.decode(Base.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Action: Decodable {
|
||||||
|
enum Trigger: String, Decodable {
|
||||||
|
case singleTap
|
||||||
|
case doubleTap
|
||||||
|
case tripleTap
|
||||||
|
case longTap
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Value {
|
||||||
|
case none
|
||||||
|
case hidKey(keycode: Int32)
|
||||||
|
case keyPress(keycode: Int)
|
||||||
|
case appleScript(source: SourceProtocol)
|
||||||
|
case shellScript(executable: String, parameters: [String])
|
||||||
|
case custom(closure: () -> Void)
|
||||||
|
case openUrl(url: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ActionTypeRaw: String, Decodable {
|
||||||
|
case hidKey
|
||||||
|
case keyPress
|
||||||
|
case appleScript
|
||||||
|
case shellScript
|
||||||
|
case openUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case trigger
|
||||||
|
case action
|
||||||
|
case keycode
|
||||||
|
case actionAppleScript
|
||||||
|
case executablePath
|
||||||
|
case shellArguments
|
||||||
|
case url
|
||||||
|
}
|
||||||
|
|
||||||
|
let trigger: Trigger
|
||||||
|
let value: Value
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
trigger = try container.decode(Trigger.self, forKey: .trigger)
|
||||||
|
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
|
||||||
|
|
||||||
|
switch type {
|
||||||
|
case .some(.hidKey):
|
||||||
|
let keycode = try container.decode(Int32.self, forKey: .keycode)
|
||||||
|
value = .hidKey(keycode: keycode)
|
||||||
|
|
||||||
|
case .some(.keyPress):
|
||||||
|
let keycode = try container.decode(Int.self, forKey: .keycode)
|
||||||
|
value = .keyPress(keycode: keycode)
|
||||||
|
|
||||||
|
case .some(.appleScript):
|
||||||
|
let source = try container.decode(Source.self, forKey: .actionAppleScript)
|
||||||
|
value = .appleScript(source: source)
|
||||||
|
|
||||||
|
case .some(.shellScript):
|
||||||
|
let executable = try container.decode(String.self, forKey: .executablePath)
|
||||||
|
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
|
||||||
|
value = .shellScript(executable: executable, parameters: parameters)
|
||||||
|
|
||||||
|
case .some(.openUrl):
|
||||||
|
let url = try container.decode(String.self, forKey: .url)
|
||||||
|
value = .openUrl(url: url)
|
||||||
|
case .none:
|
||||||
|
value = .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(trigger: Trigger, value: Value) {
|
||||||
|
self.trigger = trigger
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LegacyActionType: Decodable {
|
||||||
case none
|
case none
|
||||||
case hidKey(keycode: Int32)
|
case hidKey(keycode: Int32)
|
||||||
case keyPress(keycode: Int)
|
case keyPress(keycode: Int)
|
||||||
@@ -440,7 +622,7 @@ enum ActionType: Decodable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LongActionType: Decodable {
|
enum LegacyLongActionType: Decodable {
|
||||||
case none
|
case none
|
||||||
case hidKey(keycode: Int32)
|
case hidKey(keycode: Int32)
|
||||||
case keyPress(keycode: Int)
|
case keyPress(keycode: Int)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ extension ItemType {
|
|||||||
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 .notification(interval: _):
|
||||||
|
return "com.toxblh.mtmr.notification."
|
||||||
case .group(items: _):
|
case .group(items: _):
|
||||||
return "com.toxblh.mtmr.groupBar."
|
return "com.toxblh.mtmr.groupBar."
|
||||||
case .nightShift:
|
case .nightShift:
|
||||||
@@ -59,6 +61,8 @@ extension ItemType {
|
|||||||
return DarkModeBarItem.identifier
|
return DarkModeBarItem.identifier
|
||||||
case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _):
|
case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _):
|
||||||
return "com.toxblh.mtmr.swipe."
|
return "com.toxblh.mtmr.swipe."
|
||||||
|
case .upnext(from: _, to: _, maxToShow: _, autoResize: _):
|
||||||
|
return "com.connorgmeehan.mtmrup.next."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,17 +94,32 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar() }), longAction: .none)
|
SupportedTypesHolder.sharedInstance.register(
|
||||||
|
typename: "exitTouchbar",
|
||||||
|
item: .staticButton(title: "exit"),
|
||||||
|
actions: [
|
||||||
|
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in self?.dismissTouchBar() }))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none
|
||||||
|
)
|
||||||
|
|
||||||
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
|
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
|
||||||
(item: .staticButton(title: ""), action: .custom(closure: { [weak self] in
|
(
|
||||||
guard let `self` = self else { return }
|
item: .staticButton(title: ""),
|
||||||
self.reloadPreset(path: self.lastPresetPath)
|
actions: [
|
||||||
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
|
||||||
|
guard let `self` = self else { return }
|
||||||
|
self.reloadPreset(path: self.lastPresetPath)
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
|
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
|
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
|
||||||
|
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.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)
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||||
@@ -123,20 +142,20 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||||
items[identifier]
|
items[identifier]
|
||||||
})
|
})
|
||||||
|
|
||||||
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||||
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||||
|
|
||||||
touchBar.delegate = self
|
touchBar.delegate = self
|
||||||
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
||||||
|
|
||||||
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||||
items[identifier]
|
items[identifier]
|
||||||
})
|
})
|
||||||
let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||||
items[identifier]
|
items[identifier]
|
||||||
})
|
})
|
||||||
|
|
||||||
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
||||||
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
||||||
|
|
||||||
@@ -168,7 +187,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
|
|
||||||
func reloadPreset(path: String) {
|
func reloadPreset(path: String) {
|
||||||
lastPresetPath = path
|
lastPresetPath = path
|
||||||
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
|
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: [:])]
|
||||||
createAndUpdatePreset(newJsonItems: items)
|
createAndUpdatePreset(newJsonItems: items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +284,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
} else {
|
} else {
|
||||||
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize)
|
barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize)
|
||||||
}
|
}
|
||||||
case .volume:
|
case .volume:
|
||||||
if case let .image(source)? = item.additionalParameters[.image] {
|
if case let .image(source)? = item.additionalParameters[.image] {
|
||||||
barItem = VolumeViewController(identifier: identifier, image: source.image)
|
barItem = VolumeViewController(identifier: identifier, image: source.image)
|
||||||
} else {
|
} else {
|
||||||
@@ -287,6 +306,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
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 .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):
|
case let .group(items: items):
|
||||||
barItem = GroupBarItem(identifier: identifier, items: items)
|
barItem = GroupBarItem(identifier: identifier, items: items)
|
||||||
case .nightShift:
|
case .nightShift:
|
||||||
@@ -301,13 +326,21 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
barItem = DarkModeBarItem(identifier: identifier)
|
barItem = DarkModeBarItem(identifier: identifier)
|
||||||
case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash):
|
case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash):
|
||||||
barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
||||||
|
case let .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize):
|
||||||
|
barItem = UpNextScrubberTouchBarItem(identifier: identifier, interval: 60, from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||||
item.tapClosure = action
|
item.actions.append(ItemAction(trigger: .singleTap, action))
|
||||||
}
|
}
|
||||||
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||||
item.longTapClosure = longAction
|
item.actions.append(ItemAction(trigger: .longTap, longAction))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let touchBarItem = barItem as? CustomButtonTouchBarItem {
|
||||||
|
for action in item.actions {
|
||||||
|
touchBarItem.actions.append(ItemAction(trigger: action.trigger, self.closure(for: action)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
|
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
|
||||||
item.isBordered = bordered
|
item.isBordered = bordered
|
||||||
@@ -330,9 +363,53 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
}
|
}
|
||||||
return barItem
|
return barItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closure(for action: Action) -> (() -> Void)? {
|
||||||
|
switch action.value {
|
||||||
|
case let .hidKey(keycode: keycode):
|
||||||
|
return { HIDPostAuxKey(keycode) }
|
||||||
|
case let .keyPress(keycode: keycode):
|
||||||
|
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
|
||||||
|
case let .appleScript(source: source):
|
||||||
|
guard let appleScript = source.appleScript else {
|
||||||
|
print("cannot create apple script for item \(action)")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
DispatchQueue.appleScriptQueue.async {
|
||||||
|
var error: NSDictionary?
|
||||||
|
appleScript.executeAndReturnError(&error)
|
||||||
|
if let error = error {
|
||||||
|
print("error \(error) when handling \(action) ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .shellScript(executable: executable, parameters: parameters):
|
||||||
|
return {
|
||||||
|
let task = Process()
|
||||||
|
task.launchPath = executable
|
||||||
|
task.arguments = parameters
|
||||||
|
task.launch()
|
||||||
|
}
|
||||||
|
case let .openUrl(url: url):
|
||||||
|
return {
|
||||||
|
if let url = URL(string: url), NSWorkspace.shared.open(url) {
|
||||||
|
#if DEBUG
|
||||||
|
print("URL was successfully opened")
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
print("error", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .custom(closure: closure):
|
||||||
|
return closure
|
||||||
|
case .none:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
switch item.action {
|
switch item.legacyAction {
|
||||||
case let .hidKey(keycode: keycode):
|
case let .hidKey(keycode: keycode):
|
||||||
return { HIDPostAuxKey(keycode) }
|
return { HIDPostAuxKey(keycode) }
|
||||||
case let .keyPress(keycode: keycode):
|
case let .keyPress(keycode: keycode):
|
||||||
@@ -376,7 +453,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||||
switch item.longAction {
|
switch item.legacyLongAction {
|
||||||
case let .hidKey(keycode: keycode):
|
case let .hidKey(keycode: keycode):
|
||||||
return { HIDPostAuxKey(keycode) }
|
return { HIDPostAuxKey(keycode) }
|
||||||
case let .keyPress(keycode: keycode):
|
case let .keyPress(keycode: keycode):
|
||||||
@@ -428,6 +505,12 @@ extension NSCustomTouchBarItem: CanSetWidth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NSPopoverTouchBarItem: CanSetWidth {
|
||||||
|
func setWidth(value: CGFloat) {
|
||||||
|
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension BarItemDefinition {
|
extension BarItemDefinition {
|
||||||
var align: Align {
|
var align: Align {
|
||||||
if case let .align(result)? = additionalParameters[.align] {
|
if case let .align(result)? = additionalParameters[.align] {
|
||||||
|
|||||||
@@ -82,12 +82,14 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem {
|
|||||||
public func createAppButton(for app: DockItem) -> DockBarItem {
|
public func createAppButton(for app: DockItem) -> DockBarItem {
|
||||||
let item = DockBarItem(app)
|
let item = DockBarItem(app)
|
||||||
item.isBordered = false
|
item.isBordered = false
|
||||||
item.tapClosure = { [weak self] in
|
item.actions.append(contentsOf: [
|
||||||
self?.switchToApp(app: app)
|
ItemAction(trigger: .singleTap) { [weak self] in
|
||||||
}
|
self?.switchToApp(app: app)
|
||||||
item.longTapClosure = { [weak self] in
|
},
|
||||||
self?.handleHalfLongPress(item: app)
|
ItemAction(trigger: .longTap) { [weak self] in
|
||||||
}
|
self?.handleHalfLongPress(item: app)
|
||||||
|
}
|
||||||
|
])
|
||||||
item.killAppClosure = {[weak self] in
|
item.killAppClosure = {[weak self] in
|
||||||
self?.handleLongPress(item: app)
|
self?.handleLongPress(item: app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class DarkModeBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 24)
|
setWidth(value: 24)
|
||||||
|
|
||||||
tapClosure = { [weak self] in self?.DarkModeToggle() }
|
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DarkModeToggle() })
|
||||||
|
|
||||||
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class DnDBarItem: CustomButtonTouchBarItem {
|
|||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 32)
|
setWidth(value: 32)
|
||||||
|
|
||||||
tapClosure = { [weak self] in self?.DnDToggle() }
|
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DnDToggle() })
|
||||||
|
|
||||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ class InputSourceBarItem: CustomButtonTouchBarItem {
|
|||||||
|
|
||||||
observeIputSourceChangedNotification()
|
observeIputSourceChangedNotification()
|
||||||
textInputSourceDidChange()
|
textInputSourceDidChange()
|
||||||
tapClosure = { [weak self] in
|
|
||||||
|
actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
||||||
self?.switchInputSource()
|
self?.switchInputSource()
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
|
|
||||||
super.init(identifier: identifier, title: "⏳")
|
super.init(identifier: identifier, title: "⏳")
|
||||||
isBordered = false
|
isBordered = false
|
||||||
|
|
||||||
tapClosure = { [weak self] in self?.playPause() }
|
actions = [
|
||||||
longTapClosure = { [weak self] in self?.nextTrack() }
|
ItemAction(trigger: .singleTap) { [weak self] in self?.playPause() },
|
||||||
|
ItemAction(trigger: .doubleTap) { [weak self] in self?.previousTrack() },
|
||||||
|
ItemAction(trigger: .longTap) { [weak self] in self?.nextTrack() }
|
||||||
|
]
|
||||||
|
|
||||||
refreshAndSchedule()
|
refreshAndSchedule()
|
||||||
}
|
}
|
||||||
@@ -177,6 +180,31 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func previousTrack() {
|
||||||
|
for ident in playerBundleIdentifiers {
|
||||||
|
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
|
||||||
|
if musicPlayer.isRunning {
|
||||||
|
if ident == .Spotify {
|
||||||
|
let mp = (musicPlayer as SpotifyApplication)
|
||||||
|
mp.previousTrack!()
|
||||||
|
updatePlayer()
|
||||||
|
return
|
||||||
|
} else if ident == .iTunes {
|
||||||
|
let mp = (musicPlayer as iTunesApplication)
|
||||||
|
mp.previousTrack!()
|
||||||
|
updatePlayer()
|
||||||
|
return
|
||||||
|
} else if ident == .Music {
|
||||||
|
let mp = (musicPlayer as MusicApplication)
|
||||||
|
mp.previousTrack!()
|
||||||
|
updatePlayer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func refreshAndSchedule() {
|
func refreshAndSchedule() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class NightShiftBarItem: CustomButtonTouchBarItem {
|
|||||||
super.init(identifier: identifier, title: "")
|
super.init(identifier: identifier, title: "")
|
||||||
isBordered = false
|
isBordered = false
|
||||||
setWidth(value: 28)
|
setWidth(value: 28)
|
||||||
|
|
||||||
tapClosure = { [weak self] in self?.nightShiftAction() }
|
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.nightShiftAction() })
|
||||||
|
|
||||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
|
|||||||
@@ -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 ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,9 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
|
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
|
||||||
action: .none,
|
actions: [],
|
||||||
longAction: .none,
|
legacyAction: .none,
|
||||||
|
legacyLongAction: .none,
|
||||||
parameters: [:]
|
parameters: [:]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,8 +51,10 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
|||||||
self.workTime = workTime
|
self.workTime = workTime
|
||||||
self.restTime = restTime
|
self.restTime = restTime
|
||||||
super.init(identifier: identifier, title: defaultTitle)
|
super.init(identifier: identifier, title: defaultTitle)
|
||||||
tapClosure = { [weak self] in self?.startStopWork() }
|
actions.append(contentsOf: [
|
||||||
longTapClosure = { [weak self] in self?.startStopRest() }
|
ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() },
|
||||||
|
ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() }
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
//
|
||||||
|
// UpNextScrubberTouchBarItems.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Connor Meehan on 13/7/20.
|
||||||
|
// Copyright © 2020 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import EventKit
|
||||||
|
|
||||||
|
class UpNextScrubberTouchBarItem: NSCustomTouchBarItem {
|
||||||
|
// Dependencies
|
||||||
|
private let scrollView = NSScrollView()
|
||||||
|
private let activity: NSBackgroundActivityScheduler // Update scheduler
|
||||||
|
private var eventSources : [IUpNextSource] = []
|
||||||
|
private var items: [UpNextItem] = []
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
private var futureSearchCutoff: Double
|
||||||
|
private var pastSearchCutoff: Double
|
||||||
|
private var maxToShow: Int
|
||||||
|
private var widthConstraint: NSLayoutConstraint?
|
||||||
|
private var autoResize: Bool = false
|
||||||
|
|
||||||
|
/// <#Description#>
|
||||||
|
/// - Parameters:
|
||||||
|
/// - identifier: Unique identifier of widget
|
||||||
|
/// - interval: Update view interval in seconds
|
||||||
|
/// - from: Relative to current time, how far back we search for events in hours
|
||||||
|
/// - to: Relative to current time, how far forward we search for events in hours
|
||||||
|
/// - maxToShow: Which event to show (1 is first, 2 is second, and so on)
|
||||||
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, maxToShow: Int, autoResize: Bool) {
|
||||||
|
// Initialise member properties
|
||||||
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck")
|
||||||
|
pastSearchCutoff = from * 3600
|
||||||
|
futureSearchCutoff = to * 3600
|
||||||
|
self.maxToShow = maxToShow
|
||||||
|
self.autoResize = autoResize
|
||||||
|
UpNextItem.df.dateFormat = "HH:mm"
|
||||||
|
// Error handling
|
||||||
|
if (maxToShow <= 0) {
|
||||||
|
fatalError("Error on UpNext bar item. maxToShow property must be greater than 0.")
|
||||||
|
}
|
||||||
|
// Init super
|
||||||
|
super.init(identifier: identifier)
|
||||||
|
view = scrollView
|
||||||
|
// Add event sources
|
||||||
|
// Can optionally pass an update view callback to an event source to redraw element
|
||||||
|
self.eventSources.append(UpNextCalenderSource(updateCallback: self.updateView))
|
||||||
|
// Fallback interactivity via interval
|
||||||
|
activity.interval = interval
|
||||||
|
activity.repeats = true
|
||||||
|
activity.qualityOfService = .utility
|
||||||
|
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||||
|
self.updateView()
|
||||||
|
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||||
|
}
|
||||||
|
updateView()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateView() -> Void {
|
||||||
|
items = []
|
||||||
|
var upcomingEvents = self.getUpcomingEvents()
|
||||||
|
upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending})
|
||||||
|
var index = 1
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
for event in upcomingEvents {
|
||||||
|
// Create UpNextItem
|
||||||
|
let item = UpNextItem(event: event)
|
||||||
|
item.backgroundColor = self.getBackgroundColor(startDate: event.startDate)
|
||||||
|
// Bind tap event
|
||||||
|
item.actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
||||||
|
self?.switchToApp(event: event)
|
||||||
|
})
|
||||||
|
// Add to view
|
||||||
|
self.items.append(item)
|
||||||
|
// Check if should display any more
|
||||||
|
if (index == self.maxToShow) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
self.reloadData()
|
||||||
|
self.updateSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadData() {
|
||||||
|
let stackView = NSStackView(views: items.compactMap { $0.view })
|
||||||
|
stackView.spacing = 5
|
||||||
|
stackView.orientation = .horizontal
|
||||||
|
let visibleRect = self.scrollView.documentVisibleRect
|
||||||
|
self.scrollView.documentView = stackView
|
||||||
|
stackView.scroll(visibleRect.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSize() {
|
||||||
|
if self.autoResize {
|
||||||
|
self.widthConstraint?.isActive = false
|
||||||
|
|
||||||
|
let width = self.scrollView.documentView?.fittingSize.width ?? 0
|
||||||
|
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
|
||||||
|
self.widthConstraint!.isActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func getUpcomingEvents() -> [UpNextEventModel] {
|
||||||
|
var upcomingEvents: [UpNextEventModel] = []
|
||||||
|
|
||||||
|
// Calculate the range we're going to search for events in
|
||||||
|
let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff)
|
||||||
|
let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff)
|
||||||
|
|
||||||
|
// Get all events from all sources
|
||||||
|
for eventSource in self.eventSources {
|
||||||
|
if (eventSource.hasPermission) {
|
||||||
|
let events = eventSource.getUpcomingEvents(dateLowerBounds: dateLowerBounds, dateUpperBounds: dateUpperBounds)
|
||||||
|
upcomingEvents.append(contentsOf: events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return upcomingEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
public func switchToApp(event: UpNextEventModel) {
|
||||||
|
var bundleIdentifier: String
|
||||||
|
switch(event.sourceType) {
|
||||||
|
case .iCalendar:
|
||||||
|
bundleIdentifier = UpNextCalenderSource.bundleIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
||||||
|
|
||||||
|
// NB: if you can't open app which on another space, try to check mark
|
||||||
|
// "When switching to an application, switch to a Space with open windows for the application"
|
||||||
|
// in Mission control settings
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func getBackgroundColor(startDate: Date) -> NSColor {
|
||||||
|
let distance = startDate.timeIntervalSinceReferenceDate/60 - Date().timeIntervalSinceReferenceDate/60 // Get time difference in minutes
|
||||||
|
if (distance < 0 as TimeInterval) { // If it's in the past, draw as blue
|
||||||
|
return NSColor.systemBlue
|
||||||
|
} else if (distance < 30 as TimeInterval) { // Less than 30 minutes, backround is red
|
||||||
|
return NSColor.systemRed
|
||||||
|
}
|
||||||
|
return NSColor.clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpNextItem : CustomButtonTouchBarItem {
|
||||||
|
static public let df = DateFormatter()
|
||||||
|
|
||||||
|
init(event: UpNextEventModel) {
|
||||||
|
let identifier = UpNextItem.getIdentifier(event: event)
|
||||||
|
let title = UpNextItem.getTitle(event: event)
|
||||||
|
super.init(identifier: NSTouchBarItem.Identifier(rawValue: identifier), title: title)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getTitle(event: UpNextEventModel) -> String {
|
||||||
|
var title = ""
|
||||||
|
let startDateString = UpNextItem.df.string(for: event.startDate)
|
||||||
|
switch event.sourceType {
|
||||||
|
case .iCalendar:
|
||||||
|
title = String.init(format: "🗓 %@ - %@ ", event.title, startDateString!)
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getIdentifier(event: UpNextEventModel) -> String {
|
||||||
|
var identifier : String
|
||||||
|
switch event.sourceType {
|
||||||
|
case .iCalendar:
|
||||||
|
identifier = "com.mtmr.iCalendarEvent"
|
||||||
|
}
|
||||||
|
return identifier + "." + event.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UpNextSourceType {
|
||||||
|
case iCalendar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model for events to be displayed in dock
|
||||||
|
struct UpNextEventModel {
|
||||||
|
let title: String
|
||||||
|
let startDate: Date
|
||||||
|
let sourceType: UpNextSourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Interface for any event source
|
||||||
|
protocol IUpNextSource {
|
||||||
|
static var bundleIdentifier: String { get }
|
||||||
|
var hasPermission : Bool { get }
|
||||||
|
var updateCallback : () -> Void { get set }
|
||||||
|
|
||||||
|
init(updateCallback: @escaping () -> Void)
|
||||||
|
func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel]
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpNextCalenderSource : IUpNextSource {
|
||||||
|
static public let bundleIdentifier: String = "com.apple.iCal"
|
||||||
|
|
||||||
|
public var hasPermission: Bool = false
|
||||||
|
private var eventStore : EKEventStore
|
||||||
|
internal var updateCallback: () -> Void
|
||||||
|
|
||||||
|
required init(updateCallback: @escaping () -> Void = {}) {
|
||||||
|
self.updateCallback = updateCallback
|
||||||
|
eventStore = EKEventStore()
|
||||||
|
NotificationCenter.default.addObserver(forName: .EKEventStoreChanged, object: eventStore, queue: nil, using: handleUpdate)
|
||||||
|
let authStatus = EKEventStore.authorizationStatus(for: .event)
|
||||||
|
if (authStatus != .authorized) {
|
||||||
|
eventStore.requestAccess(to: .event){ granted, error in
|
||||||
|
self.hasPermission = granted;
|
||||||
|
self.handleUpdate()
|
||||||
|
if(!granted) {
|
||||||
|
NSLog("Error: MTMR UpNextBarWidget not given calendar access.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.handleUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public func handleUpdate() {
|
||||||
|
self.handleUpdate(note: Notification(name: Notification.Name("refresh view")))
|
||||||
|
}
|
||||||
|
public func handleUpdate(note: Notification) {
|
||||||
|
self.updateCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] {
|
||||||
|
var upcomingEvents: [UpNextEventModel] = []
|
||||||
|
let calendars = self.eventStore.calendars(for: .event)
|
||||||
|
let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: calendars)
|
||||||
|
let events = self.eventStore.events(matching: predicate)
|
||||||
|
for event in events {
|
||||||
|
upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate, sourceType: UpNextSourceType.iCalendar))
|
||||||
|
}
|
||||||
|
return upcomingEvents
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,12 +13,13 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
private let activity: NSBackgroundActivityScheduler
|
private let activity: NSBackgroundActivityScheduler
|
||||||
private let unitsStr = "°C"
|
private let unitsStr = "°C"
|
||||||
private let iconsSource = [
|
private let iconsSource = [
|
||||||
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь с грозой": "⛈", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
||||||
"Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫"
|
"Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Thunderstorm with rain": "⛈", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫"
|
||||||
]
|
]
|
||||||
private var location: CLLocation!
|
private var location: CLLocation!
|
||||||
private var prevLocation: CLLocation!
|
private var prevLocation: CLLocation!
|
||||||
private var manager: CLLocationManager!
|
private var manager: CLLocationManager!
|
||||||
|
private var updateWeatherTask: URLSessionDataTask?
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
||||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||||
@@ -49,8 +50,13 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
manager.delegate = self
|
manager.delegate = self
|
||||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
manager.startUpdatingLocation()
|
manager.startUpdatingLocation()
|
||||||
|
|
||||||
tapClosure = tapClosure ?? defaultTapAction
|
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
|
||||||
|
actions.append(ItemAction(
|
||||||
|
trigger: .singleTap,
|
||||||
|
defaultTapAction
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
@@ -61,7 +67,8 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
|
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
|
||||||
urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format
|
urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
|
updateWeatherTask?.cancel()
|
||||||
|
updateWeatherTask = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
|
||||||
guard error == nil, let response = data?.utf8string else {
|
guard error == nil, let response = data?.utf8string else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -86,12 +93,12 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resume()
|
updateWeatherTask?.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWeatherUrl() -> String {
|
func getWeatherUrl() -> String {
|
||||||
if location != nil {
|
if location != nil {
|
||||||
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)?lang=ru"
|
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&lang=ru"
|
||||||
} else {
|
} else {
|
||||||
return "https://yandex.ru/pogoda/?lang=ru" // 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .none? = result?.first?.action else {
|
guard result?.first?.actions.count == 0 else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,29 @@ class ParseConfig: XCTestCase {
|
|||||||
|
|
||||||
func testButtonKeyCodeAction() {
|
func testButtonKeyCodeAction() {
|
||||||
let buttonKeycodeFixture = """
|
let buttonKeycodeFixture = """
|
||||||
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ]
|
[ { "type": "staticButton", "title": "Pew", "actions": [ { "trigger": "singleTap", "action": "hidKey", "keycode": 123 } ] } ]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||||
guard case .staticButton("Pew")? = result?.first?.type else {
|
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .hidKey(keycode: 123)? = result?.first?.action else {
|
guard case .hidKey(keycode: 123)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testButtonKeyCodeLegacyAction() {
|
||||||
|
let buttonKeycodeFixture = """
|
||||||
|
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||||
|
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -40,7 +55,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -55,7 +70,7 @@ class ParseConfig: XCTestCase {
|
|||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
|
|||||||
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><a href="https://www.paypal.me/toxblh/10" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
<p align="center"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
||||||
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
||||||
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
||||||
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
||||||
@@ -84,6 +84,7 @@ The pre-installed configuration contains less or more than you'll probably want,
|
|||||||
- darkMode
|
- darkMode
|
||||||
- pomodoro
|
- pomodoro
|
||||||
- network
|
- network
|
||||||
|
- upnext (Calendar events)
|
||||||
|
|
||||||
> Media Keys
|
> Media Keys
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ You can add custom actions for two/three/four finger swipes. To do it, you need
|
|||||||
"type": "swipe",
|
"type": "swipe",
|
||||||
"fingers": 2, // number of fingers required (2,3 or 4)
|
"fingers": 2, // number of fingers required (2,3 or 4)
|
||||||
"direction": "right", // direction of swipe (right/left)
|
"direction": "right", // direction of swipe (right/left)
|
||||||
"minOffset" 10, // optional: minimal required offset for gesture to emit event
|
"minOffset": 10, // optional: minimal required offset for gesture to emit event
|
||||||
"sourceApple": { // optional: apple script to run
|
"sourceApple": { // optional: apple script to run
|
||||||
"inline": "beep"
|
"inline": "beep"
|
||||||
},
|
},
|
||||||
@@ -197,10 +198,15 @@ Example of "CPU load" button which also changes color based on load value.
|
|||||||
"source": {
|
"source": {
|
||||||
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
|
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
|
||||||
},
|
},
|
||||||
"action": "appleScript",
|
"actions": [
|
||||||
"actionAppleScript": {
|
{
|
||||||
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
|
"trigger": "singleTap",
|
||||||
},
|
"action": "appleScript",
|
||||||
|
"actionAppleScript": {
|
||||||
|
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"align": "right",
|
"align": "right",
|
||||||
"image": {
|
"image": {
|
||||||
// Or you can specify a filePath here.
|
// Or you can specify a filePath here.
|
||||||
@@ -343,8 +349,46 @@ To close a group, use the button:
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `upnext`
|
||||||
|
|
||||||
|
> Calender next event plugin
|
||||||
|
Displays upcoming events from MacOS Calendar. Does not display current event.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"type": "upnext",
|
||||||
|
"from": 0, // Lower bound of search range for next event in hours. Default 0 (current time)(can be negative to view events in the past)
|
||||||
|
"to": 12, // Upper bounds of search range for next event in hours. Default 12 (12 hours in the future)
|
||||||
|
"maxToShow": 3 // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events)
|
||||||
|
"autoResize": false // If true, widget will expand to display all events. Default false (scrollable view within "width")
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Actions:
|
## Actions:
|
||||||
|
|
||||||
|
### Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"trigger": "singleTap",
|
||||||
|
"action": "hidKey",
|
||||||
|
"keycode": 53
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Triggers:
|
||||||
|
|
||||||
|
- `singleTap`
|
||||||
|
- `doubleTap`
|
||||||
|
- `tripleTap`
|
||||||
|
- `longTap`
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
- `hidKey`
|
- `hidKey`
|
||||||
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
||||||
|
|
||||||
@@ -386,22 +430,6 @@ To close a group, use the button:
|
|||||||
"url": "https://google.com",
|
"url": "https://google.com",
|
||||||
```
|
```
|
||||||
|
|
||||||
## LongActions
|
|
||||||
|
|
||||||
If you want to longPress for some operations, it is similar to the configuration for Actions but with additional parameters, for example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
"longAction": "hidKey",
|
|
||||||
"longKeycode": 53,
|
|
||||||
```
|
|
||||||
|
|
||||||
- longAction
|
|
||||||
- longKeycode
|
|
||||||
- longActionAppleScript
|
|
||||||
- longExecutablePath
|
|
||||||
- longShellArguments
|
|
||||||
- longUrl
|
|
||||||
|
|
||||||
## Additional parameters:
|
## Additional parameters:
|
||||||
|
|
||||||
- `width` restrict how much room a particular button will take
|
- `width` restrict how much room a particular button will take
|
||||||
@@ -446,40 +474,21 @@ by using background with color "#000000" and bordered == false you can create bu
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
### Roadmap
|
#### If you can't open preferences:
|
||||||
|
- Opening another program which can't edit text
|
||||||
|
1. Open Terminal.app
|
||||||
|
2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press <kbd>Enter</kbd>
|
||||||
|
|
||||||
- [x] Create the first prototype with TouchBar in Storyboard
|
|
||||||
- [x] Put in stripe menu on startup the application
|
|
||||||
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
|
||||||
- [x] Time in touchbar!
|
|
||||||
- [x] First the weather plugin
|
|
||||||
- [x] Find how to open full-screen TouchBar without the cross and stripe menu
|
|
||||||
- [x] Find how to add haptic feedback
|
|
||||||
- [x] Add icon and menu in StatusBar
|
|
||||||
- [x] Hide from Dock
|
|
||||||
- [x] Status menu: "preferences", "quit"
|
|
||||||
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
|
|
||||||
- [x] Custom buttons size, actions by click
|
|
||||||
- [x] Layout: [always left, NSSliderView for center, always right]
|
|
||||||
- [x] System for autoupdate (https://sparkle-project.org/)
|
|
||||||
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
|
||||||
- [ ] Custom settings for paddings and margins for buttons
|
|
||||||
- [ ] XPC Service for scripts
|
|
||||||
- [ ] UI for settings
|
|
||||||
- [ ] Import config from BTT
|
|
||||||
|
|
||||||
Settings:
|
#### Buttons or gestures doesn't work:
|
||||||
|
- "After the last update my mtmr is not working anymore!"
|
||||||
|
- "Buttons sometimes do not trigger action"
|
||||||
|
- "ESC don't work"
|
||||||
|
- "Gestures don't work"
|
||||||
|
|
||||||
- [ ] Interface for plugins and export like presets
|
Re-tick or check a tick for access 🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
|
||||||
- [x] Startup at login
|
|
||||||
- [ ] Show on/off in Dock
|
|
||||||
- [ ] Show on/off in StatusBar
|
|
||||||
- [ x] On/off Haptic Feedback
|
|
||||||
|
|
||||||
Maybe:
|
|
||||||
|
|
||||||
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,38 @@
|
|||||||
* try move away from enums when parse preset – enums are hard to extend
|
* try move away from enums when parse preset – enums are hard to extend
|
||||||
* find better way to hide bar items
|
* find better way to hide bar items
|
||||||
* extract bar items creating from TouchBarController to separate class, cover with tests
|
* extract bar items creating from TouchBarController to separate class, cover with tests
|
||||||
|
|
||||||
|
|
||||||
|
### Roadmap
|
||||||
|
|
||||||
|
- [x] Create the first prototype with TouchBar in Storyboard
|
||||||
|
- [x] Put in stripe menu on startup the application
|
||||||
|
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
||||||
|
- [x] Time in touchbar!
|
||||||
|
- [x] First the weather plugin
|
||||||
|
- [x] Find how to open full-screen TouchBar without the cross and stripe menu
|
||||||
|
- [x] Find how to add haptic feedback
|
||||||
|
- [x] Add icon and menu in StatusBar
|
||||||
|
- [x] Hide from Dock
|
||||||
|
- [x] Status menu: "preferences", "quit"
|
||||||
|
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
|
||||||
|
- [x] Custom buttons size, actions by click
|
||||||
|
- [x] Layout: [always left, NSSliderView for center, always right]
|
||||||
|
- [x] System for autoupdate (https://sparkle-project.org/)
|
||||||
|
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
||||||
|
- [ ] Custom settings for paddings and margins for buttons
|
||||||
|
- [ ] XPC Service for scripts
|
||||||
|
- [ ] UI for settings
|
||||||
|
- [ ] Import config from BTT
|
||||||
|
|
||||||
|
Settings:
|
||||||
|
|
||||||
|
- [ ] Interface for plugins and export like presets
|
||||||
|
- [x] Startup at login
|
||||||
|
- [ ] Show on/off in Dock
|
||||||
|
- [ ] Show on/off in StatusBar
|
||||||
|
- [x] On/off Haptic Feedback
|
||||||
|
|
||||||
|
Maybe:
|
||||||
|
|
||||||
|
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ echo "<?xml version=\"1.0\" standalone=\"yes\"?>
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Homebrew https://github.com/Homebrew/homebrew-cask/edit/master/Casks/mtmr.rb"
|
echo "Homebrew https://github.com/Homebrew/homebrew-cask/edit/master/Casks/mtmr.rb"
|
||||||
echo ""
|
echo ""
|
||||||
echo " version '${VERSION}'"
|
echo " version \"${VERSION}\""
|
||||||
echo " sha256 '${SHA256}'"
|
echo " sha256 \"${SHA256}\""
|
||||||
echo ""
|
echo ""
|
||||||
echo "Update MTMR v${VERSION}"
|
echo "Update MTMR v${VERSION}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user