Compare commits

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