mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-10-31 02:18:35 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e1ae2af61 | ||
|
|
bc11728c2e | ||
|
|
445584bb1b | ||
|
|
b6721f0274 | ||
|
|
75df82a567 | ||
|
|
a1f64028cc | ||
|
|
3fc75ca8f0 | ||
|
|
6d266394a4 | ||
|
|
52758f947d | ||
|
|
502f989417 | ||
|
|
f61550e510 | ||
|
|
a0fc0b33c5 | ||
|
|
42ce95b72e | ||
|
|
642f0807bb | ||
|
|
dbb2f16222 | ||
|
|
3864591777 | ||
|
|
7e0db70fab | ||
|
|
40c684f528 | ||
|
|
093faa2c02 | ||
|
|
aeee9983d1 | ||
|
|
bdfb7118c9 | ||
|
|
878cd346c1 | ||
|
|
712448f207 | ||
|
|
e9e5a6f739 | ||
|
|
466c0e5f68 | ||
|
|
85e3deac79 | ||
|
|
9e49cf8beb | ||
|
|
d81998862a | ||
|
|
1ae2041d0d | ||
|
|
5eb2d73c94 | ||
|
|
855ddea44e | ||
|
|
eaa26645c5 | ||
|
|
2cdb705b37 | ||
|
|
fa413f2fa2 | ||
|
|
6920664fad | ||
|
|
e9a7b6d32a | ||
|
|
dd23a3bda8 | ||
|
|
f82d7694eb | ||
|
|
94717a5ea3 | ||
|
|
b155cac2b0 | ||
|
|
cd2ec3d032 | ||
|
|
c4928ee382 | ||
|
|
0eef872f2b | ||
|
|
3f78bbe42c | ||
|
|
f2e6959b71 | ||
|
|
820853d300 | ||
|
|
2635e2611f | ||
|
|
e68fa10c42 | ||
|
|
92975cb8e4 | ||
|
|
0b39795bd7 |
@@ -0,0 +1,22 @@
|
||||
name: Build-and-test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Run tests
|
||||
run: xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}
|
||||
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Build
|
||||
run: xcodebuild archive -project "MTMR.xcodeproj" -scheme "MTMR" -archivePath Release/App.xcarchive DEVELOPMENT_TEAM="" CODE_SIGN_IDENTITY="" | xcpretty -c && exit ${PIPESTATUS[0]}
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Publish unsign version
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
Build-and-release:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install create-dmg
|
||||
run: npm i -g create-dmg
|
||||
|
||||
- name: Build Archive
|
||||
run: xcodebuild archive -project "MTMR.xcodeproj" -scheme "MTMR" -archivePath Release/App.xcarchive DEVELOPMENT_TEAM="" CODE_SIGN_IDENTITY="" | xcpretty -c && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Build App
|
||||
run: xcodebuild -project "MTMR.xcodeproj" -exportArchive -archivePath Release/App.xcarchive -exportOptionsPlist export-options.plist -exportPath Release | xcpretty -c && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
cd Release
|
||||
create-dmg MTMR.app || true
|
||||
|
||||
- name: GitHub Release
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: Release/MTMR*.dmg
|
||||
@@ -1,6 +0,0 @@
|
||||
language: swift
|
||||
xcode_project: MTMR.xcodeproj
|
||||
xcode_scheme: UnitTests
|
||||
osx_image: xcode10
|
||||
install: gem install xcpretty
|
||||
script: "xcodebuild test -project MTMR.xcodeproj -scheme 'UnitTests' | xcpretty -c && exit ${PIPESTATUS[0]}"
|
||||
@@ -18,9 +18,12 @@
|
||||
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 */; };
|
||||
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */; };
|
||||
5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */; };
|
||||
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; };
|
||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
|
||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
|
||||
@@ -66,6 +69,8 @@
|
||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; };
|
||||
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; };
|
||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
|
||||
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; };
|
||||
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -94,10 +99,13 @@
|
||||
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>"; };
|
||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
|
||||
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.nowPlaying.scpt; sourceTree = "<group>"; };
|
||||
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.next.scpt; sourceTree = "<group>"; };
|
||||
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = "<group>"; };
|
||||
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = "<group>"; };
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -153,6 +161,8 @@
|
||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeBarItem.swift; sourceTree = "<group>"; };
|
||||
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchBarSupport.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -225,10 +235,13 @@
|
||||
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
|
||||
B082B252205C7D8000BC04DC /* AppDelegate.swift */,
|
||||
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
|
||||
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
|
||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
|
||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||
BAF5AB5624317B4300B04904 /* BasicView.swift */,
|
||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
||||
@@ -255,6 +268,8 @@
|
||||
B0B17426207D6AFE0004B740 /* AppleScripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */,
|
||||
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */,
|
||||
B0B1742A207D6B580004B740 /* Battery.scpt */,
|
||||
B0B17429207D6B580004B740 /* Finder.scpt */,
|
||||
B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
|
||||
@@ -362,7 +377,7 @@
|
||||
TargetAttributes = {
|
||||
B082B24E205C7D8000BC04DC = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Manual;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
@@ -372,7 +387,7 @@
|
||||
};
|
||||
B082B260205C7D8000BC04DC = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -404,6 +419,8 @@
|
||||
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */,
|
||||
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */,
|
||||
B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt in Resources */,
|
||||
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */,
|
||||
5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */,
|
||||
B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */,
|
||||
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */,
|
||||
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */,
|
||||
@@ -454,11 +471,13 @@
|
||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
||||
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
|
||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
||||
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */,
|
||||
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
|
||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
||||
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
|
||||
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
||||
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
|
||||
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */,
|
||||
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
|
||||
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
|
||||
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
|
||||
@@ -474,6 +493,7 @@
|
||||
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 */,
|
||||
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
|
||||
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
|
||||
@@ -640,7 +660,7 @@
|
||||
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;
|
||||
};
|
||||
@@ -663,7 +683,7 @@
|
||||
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 +696,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 +711,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>
|
||||
|
||||
+26
-28
@@ -42,21 +42,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationWillTerminate(_: Notification) {}
|
||||
|
||||
func HapticFeedbackUpdate() {
|
||||
HapticFeedback.shared = TouchBarController.shared.hapticFeedbackState ? HapticFeedback() : nil
|
||||
HapticFeedback.shared = AppSettings.hapticFeedbackState ? HapticFeedback() : nil
|
||||
}
|
||||
|
||||
@objc func updateIsBlockedApp() {
|
||||
var blacklistAppIdentifiers: [String] = []
|
||||
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") {
|
||||
blacklistAppIdentifiers = blackListed
|
||||
}
|
||||
var frontmostApplicationIdentifier: String? {
|
||||
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
|
||||
return frontmostId
|
||||
}
|
||||
|
||||
if frontmostApplicationIdentifier != nil {
|
||||
isBlockedApp = blacklistAppIdentifiers.index(of: frontmostApplicationIdentifier!) != nil
|
||||
if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
|
||||
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
|
||||
} else {
|
||||
isBlockedApp = false
|
||||
}
|
||||
@@ -72,33 +63,36 @@ 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(_: Any?) {
|
||||
TouchBarController.shared.hapticFeedbackState = !TouchBarController.shared.hapticFeedbackState
|
||||
@objc func toggleHapticFeedback(_ item: NSMenuItem) {
|
||||
item.state = item.state == .on ? .off : .on
|
||||
AppSettings.hapticFeedbackState = item.state == .on
|
||||
HapticFeedbackUpdate()
|
||||
createMenu()
|
||||
}
|
||||
|
||||
@objc func toggleMultitouch(_ item: NSMenuItem) {
|
||||
item.state = item.state == .on ? .off : .on
|
||||
AppSettings.multitouchGestures = item.state == .on
|
||||
TouchBarController.shared.basicView?.legacyGesturesEnabled = item.state == .on
|
||||
}
|
||||
|
||||
@objc func openPreset(_: Any?) {
|
||||
@@ -133,10 +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 = TouchBarController.shared.hapticFeedbackState ? .on : .off
|
||||
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
|
||||
@@ -151,6 +148,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
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
|
||||
|
||||
@@ -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,9 +4,11 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
private var script: NSAppleScript!
|
||||
private let interval: TimeInterval
|
||||
private var forceHideConstraint: NSLayoutConstraint!
|
||||
private let alternativeImages: [String: SourceProtocol]
|
||||
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) {
|
||||
self.interval = interval
|
||||
self.alternativeImages = alternativeImages
|
||||
super.init(identifier: identifier, title: "⏳")
|
||||
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
||||
title = "scheduled"
|
||||
@@ -57,6 +59,16 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
}
|
||||
}
|
||||
|
||||
func updateIcon(iconLabel: String) {
|
||||
if alternativeImages[iconLabel] != nil {
|
||||
DispatchQueue.main.async {
|
||||
self.image = self.alternativeImages[iconLabel]!.image
|
||||
}
|
||||
} else {
|
||||
print("Cannot find icon with label \"\(iconLabel)\"")
|
||||
}
|
||||
}
|
||||
|
||||
func execute() -> String {
|
||||
var error: NSDictionary?
|
||||
let output = script.executeAndReturnError(&error)
|
||||
@@ -64,6 +76,18 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
print(error)
|
||||
return "error"
|
||||
}
|
||||
if output.descriptorType == typeAEList {
|
||||
let arr = Array(1...output.numberOfItems).compactMap({ output.atIndex($0)!.stringValue ?? "" })
|
||||
|
||||
if arr.count <= 0 {
|
||||
return ""
|
||||
} else if arr.count == 1 {
|
||||
return arr[0]
|
||||
} else {
|
||||
updateIcon(iconLabel: arr[1])
|
||||
return arr[0]
|
||||
}
|
||||
}
|
||||
return output.stringValue ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
if application "Music" is running then
|
||||
tell application "Music"
|
||||
if player state is playing then
|
||||
next track
|
||||
end if
|
||||
end tell
|
||||
end if
|
||||
@@ -0,0 +1,10 @@
|
||||
if application "Music" is running then
|
||||
tell application "Music"
|
||||
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 ""
|
||||
@@ -2,6 +2,10 @@ if application "iTunes" is running then
|
||||
tell application "iTunes" to playpause
|
||||
end if
|
||||
|
||||
if application "Music" is running then
|
||||
tell application "Music" to playpause
|
||||
end if
|
||||
|
||||
if application "Spotify" is running then
|
||||
tell application "Spotify" to playpause
|
||||
end if
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// BasicView.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by Fedor Zaitsev on 3/29/20.
|
||||
// Copyright © 2020 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
var twofingers: NSPanGestureRecognizer!
|
||||
var threefingers: NSPanGestureRecognizer!
|
||||
var fourfingers: NSPanGestureRecognizer!
|
||||
var swipeItems: [SwipeItem] = []
|
||||
var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
||||
|
||||
// legacy gesture positions
|
||||
// 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 legacyGesturesEnabled = false
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) {
|
||||
super.init(identifier: identifier)
|
||||
self.swipeItems = swipeItems
|
||||
let views = items.compactMap { $0.view }
|
||||
let stackView = NSStackView(views: views)
|
||||
stackView.spacing = 1
|
||||
stackView.orientation = .horizontal
|
||||
view = stackView
|
||||
|
||||
twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
||||
twofingers.numberOfTouchesRequired = 2
|
||||
twofingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(twofingers)
|
||||
|
||||
threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
||||
threefingers.numberOfTouchesRequired = 3
|
||||
threefingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(threefingers)
|
||||
|
||||
fourfingers = NSPanGestureRecognizer(target: self, action: #selector(fourfingersHandler(_:)))
|
||||
fourfingers.numberOfTouchesRequired = 4
|
||||
fourfingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(fourfingers)
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func gestureHandler(position: CGFloat, fingers: Int, state: NSGestureRecognizer.State) {
|
||||
switch state {
|
||||
case .began:
|
||||
prevPositions[fingers] = position
|
||||
legacyPrevPositions[fingers] = position
|
||||
case .changed:
|
||||
if self.legacyGesturesEnabled {
|
||||
if fingers == 2 {
|
||||
let prevPos = legacyPrevPositions[fingers]!
|
||||
if ((position - prevPos) > 10) || ((prevPos - position) > 10) {
|
||||
if position > prevPos {
|
||||
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP)
|
||||
} else if position < prevPos {
|
||||
HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN)
|
||||
}
|
||||
legacyPrevPositions[fingers] = position
|
||||
}
|
||||
}
|
||||
if fingers == 3 {
|
||||
let prevPos = legacyPrevPositions[fingers]!
|
||||
if ((position - prevPos) > 15) || ((prevPos - position) > 15) {
|
||||
if position > prevPos {
|
||||
GenericKeyPress(keyCode: CGKeyCode(144)).send()
|
||||
} else if position < prevPos {
|
||||
GenericKeyPress(keyCode: CGKeyCode(145)).send()
|
||||
}
|
||||
legacyPrevPositions[fingers] = position
|
||||
}
|
||||
}
|
||||
}
|
||||
case .ended:
|
||||
print("gesture ended \(position - prevPositions[fingers]!) \(fingers)")
|
||||
for item in swipeItems {
|
||||
item.processEvent(offset: position - prevPositions[fingers]!, fingers: fingers)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
|
||||
}
|
||||
|
||||
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) {
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
self.gestureHandler(position: position, fingers: 3, state: sender!.state)
|
||||
}
|
||||
|
||||
@objc func fourfingersHandler(_ sender: NSGestureRecognizer?) {
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
self.gestureHandler(position: position, fingers: 4, state: sender!.state)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -187,7 +189,7 @@ class HapticClickGestureRecognizer: NSClickGestureRecognizer {
|
||||
}
|
||||
|
||||
class LongPressGestureRecognizer: NSPressGestureRecognizer {
|
||||
private let recognizeTimeout = 0.4
|
||||
var recognizeTimeout = 0.4
|
||||
private var timer: Timer?
|
||||
|
||||
override func touchesBegan(with event: NSEvent) {
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.23.1</string>
|
||||
<string>0.26</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>294</string>
|
||||
<string>401</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
@@ -39,7 +39,7 @@
|
||||
<key>NSHomeKitUsageDescription</key>
|
||||
<string>MTMR needs access to HomeKit for work</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018 Anton Palgunov. All rights reserved.</string>
|
||||
<string>Copyright © 2018 - 2020 Anton Palgunov. All rights reserved.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Weather widget need your location for correct work</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
|
||||
+49
-166
@@ -163,116 +163,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,30 +177,6 @@ class SupportedTypesHolder {
|
||||
parameters: [:]
|
||||
) },
|
||||
|
||||
"music": { decoder in
|
||||
enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee }
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
|
||||
let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee)
|
||||
return (
|
||||
item: .music(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false),
|
||||
action: .none,
|
||||
longAction: .none,
|
||||
parameters: [:]
|
||||
)
|
||||
},
|
||||
|
||||
"group": { decoder in
|
||||
enum CodingKeys: CodingKey { case items }
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let items = try container.decode([BarItemDefinition].self, forKey: .items)
|
||||
return (
|
||||
item: .groupBar(items: items),
|
||||
action: .none,
|
||||
longAction: .none,
|
||||
parameters: [:]
|
||||
)
|
||||
},
|
||||
]
|
||||
|
||||
static let sharedInstance = SupportedTypesHolder()
|
||||
@@ -342,24 +208,25 @@ class SupportedTypesHolder {
|
||||
|
||||
enum ItemType: Decodable {
|
||||
case staticButton(title: String)
|
||||
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double, alternativeImages: [String: SourceProtocol])
|
||||
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 group(items: [BarItemDefinition])
|
||||
case nightShift
|
||||
case dnd
|
||||
case pomodoro(workTime: Double, restTime: Double)
|
||||
case network(flip: Bool)
|
||||
case darkMode()
|
||||
case darkMode
|
||||
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
@@ -383,7 +250,14 @@ enum ItemType: Decodable {
|
||||
case restTime
|
||||
case flip
|
||||
case autoResize
|
||||
case filter
|
||||
case disableMarquee
|
||||
case alternativeImages
|
||||
case sourceApple
|
||||
case sourceBash
|
||||
case direction
|
||||
case fingers
|
||||
case minOffset
|
||||
}
|
||||
|
||||
enum ItemTypeRaw: String, Decodable {
|
||||
@@ -400,12 +274,13 @@ enum ItemType: Decodable {
|
||||
case currency
|
||||
case inputsource
|
||||
case music
|
||||
case groupBar
|
||||
case group
|
||||
case nightShift
|
||||
case dnd
|
||||
case pomodoro
|
||||
case network
|
||||
case darkMode
|
||||
case swipe
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@@ -415,7 +290,8 @@ enum ItemType: Decodable {
|
||||
case .appleScriptTitledButton:
|
||||
let source = try container.decode(Source.self, forKey: .source)
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
||||
self = .appleScriptTitledButton(source: source, refreshInterval: interval)
|
||||
let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:]
|
||||
self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages)
|
||||
|
||||
case .shellScriptTitledButton:
|
||||
let source = try container.decode(Source.self, forKey: .source)
|
||||
@@ -433,14 +309,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
|
||||
@@ -465,22 +342,22 @@ 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 .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 +369,15 @@ enum ItemType: Decodable {
|
||||
self = .network(flip: flip)
|
||||
|
||||
case .darkMode:
|
||||
self = .darkMode()
|
||||
self = .darkMode
|
||||
|
||||
case .swipe:
|
||||
let sourceApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple)
|
||||
let sourceBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash)
|
||||
let direction = try container.decode(String.self, forKey: .direction)
|
||||
let fingers = try container.decode(Int.self, forKey: .fingers)
|
||||
let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0
|
||||
self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -734,12 +619,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 +652,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 }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
var twofingersPrev: CGFloat = 0.0
|
||||
var threefingersPrev: CGFloat = 0.0
|
||||
class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
|
||||
super.init(identifier: identifier)
|
||||
@@ -13,63 +11,10 @@ class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
|
||||
scrollView.documentView = stackView
|
||||
view = scrollView
|
||||
|
||||
let twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
||||
twofingers.allowedTouchTypes = .direct
|
||||
twofingers.numberOfTouchesRequired = 2
|
||||
view.addGestureRecognizer(twofingers)
|
||||
|
||||
let threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
||||
threefingers.allowedTouchTypes = .direct
|
||||
threefingers.numberOfTouchesRequired = 3
|
||||
view.addGestureRecognizer(threefingers)
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) { // Volume
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
|
||||
switch sender!.state {
|
||||
case .began:
|
||||
twofingersPrev = position
|
||||
case .changed:
|
||||
if ((position - twofingersPrev) > 10) || ((twofingersPrev - position) > 10) {
|
||||
if position > twofingersPrev {
|
||||
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP)
|
||||
} else if position < twofingersPrev {
|
||||
HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN)
|
||||
}
|
||||
twofingersPrev = position
|
||||
}
|
||||
case .ended:
|
||||
twofingersPrev = 0.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) { // Brightness
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
|
||||
switch sender!.state {
|
||||
case .began:
|
||||
threefingersPrev = position
|
||||
case .changed:
|
||||
if ((position - threefingersPrev) > 15) || ((threefingersPrev - position) > 15) {
|
||||
if position > threefingersPrev {
|
||||
GenericKeyPress(keyCode: CGKeyCode(144)).send()
|
||||
} else if position < threefingersPrev {
|
||||
GenericKeyPress(keyCode: CGKeyCode(145)).send()
|
||||
}
|
||||
threefingersPrev = position
|
||||
}
|
||||
case .ended:
|
||||
threefingersPrev = 0.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +62,19 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
|
||||
// kill process if it is over update interval
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + interval) { [weak task] in
|
||||
task?.terminate()
|
||||
}
|
||||
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
var output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
|
||||
|
||||
//always wait until task end or you can catch "task still running" error while accessing task.terminationStatus variable
|
||||
task.waitUntilExit()
|
||||
if (output == "" && task.terminationStatus != 0) {
|
||||
output = "error"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// SwipeItem.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by Fedor Zaitsev on 3/29/20.
|
||||
// Copyright © 2020 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Foundation
|
||||
|
||||
class SwipeItem: NSCustomTouchBarItem {
|
||||
private var scriptApple: NSAppleScript?
|
||||
private var scriptBash: String?
|
||||
private var direction: String
|
||||
private var fingers: Int
|
||||
private var minOffset: Float
|
||||
init?(identifier: NSTouchBarItem.Identifier, direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) {
|
||||
self.direction = direction
|
||||
self.fingers = fingers
|
||||
self.scriptBash = sourceBash?.string
|
||||
self.scriptApple = sourceApple?.appleScript
|
||||
self.minOffset = minOffset
|
||||
super.init(identifier: identifier)
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func processEvent(offset: CGFloat, fingers: Int) {
|
||||
if direction == "right" && Float(offset) > self.minOffset && self.fingers == fingers {
|
||||
self.execute()
|
||||
}
|
||||
if direction == "left" && Float(offset) < -self.minOffset && self.fingers == fingers {
|
||||
self.execute()
|
||||
}
|
||||
}
|
||||
|
||||
func execute() {
|
||||
if scriptApple != nil {
|
||||
DispatchQueue.appleScriptQueue.async {
|
||||
var error: NSDictionary?
|
||||
self.scriptApple?.executeAndReturnError(&error)
|
||||
if let error = error {
|
||||
print("SwipeItem apple script error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if scriptBash != nil {
|
||||
DispatchQueue.shellScriptQueue.async {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
task.arguments = ["-c", self.scriptBash!]
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
|
||||
|
||||
if (task.terminationStatus != 0) {
|
||||
print("SwipeItem bash script error. Status: \(task.terminationStatus)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,22 +41,24 @@ 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 .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
|
||||
case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _):
|
||||
return "com.toxblh.mtmr.swipe."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,33 +78,14 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
|
||||
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||
var centerItems: [NSTouchBarItem] = []
|
||||
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||
var scrollArea: NSCustomTouchBarItem?
|
||||
var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||
|
||||
var showControlStripState: Bool {
|
||||
get {
|
||||
return UserDefaults.standard.bool(forKey: "com.toxblh.mtmr.settings.showControlStrip")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "com.toxblh.mtmr.settings.showControlStrip")
|
||||
}
|
||||
}
|
||||
|
||||
var hapticFeedbackState: Bool {
|
||||
get {
|
||||
return UserDefaults.standard.bool(forKey: "com.toxblh.mtmr.settings.hapticFeedback")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "com.toxblh.mtmr.settings.hapticFeedback")
|
||||
}
|
||||
}
|
||||
var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
|
||||
var basicView: BasicView?
|
||||
var swipeItems: [SwipeItem] = []
|
||||
|
||||
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() {
|
||||
@@ -116,10 +99,8 @@ 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)
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||
@@ -135,23 +116,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
jsonItems = newJsonItems
|
||||
itemDefinitions = [:]
|
||||
items = [:]
|
||||
leftIdentifiers = []
|
||||
centerItems = []
|
||||
rightIdentifiers = []
|
||||
|
||||
loadItemDefinitions(jsonItems: jsonItems)
|
||||
createItems()
|
||||
|
||||
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
|
||||
centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||
scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||
|
||||
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||
|
||||
touchBar.delegate = self
|
||||
touchBar.defaultItemIdentifiers = []
|
||||
touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
|
||||
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
||||
|
||||
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
|
||||
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
||||
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
||||
|
||||
updateActiveApp()
|
||||
}
|
||||
@@ -161,7 +148,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()
|
||||
@@ -207,7 +194,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
|
||||
func createItems() {
|
||||
for (identifier, definition) in itemDefinitions {
|
||||
items[identifier] = createItem(forIdentifier: identifier, definition: definition)
|
||||
let item = createItem(forIdentifier: identifier, definition: definition)
|
||||
if item is SwipeItem {
|
||||
swipeItems.append(item as! SwipeItem)
|
||||
} else {
|
||||
items[identifier] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +216,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
|
||||
@objc private func presentTouchBar() {
|
||||
if showControlStripState {
|
||||
if AppSettings.showControlStripState {
|
||||
updateControlStripPresence()
|
||||
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
|
||||
} else {
|
||||
@@ -243,16 +235,11 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
|
||||
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||
if identifier == centerScrollArea {
|
||||
return scrollArea
|
||||
if identifier == basicViewIdentifier {
|
||||
return basicView
|
||||
}
|
||||
|
||||
guard let item = self.items[identifier],
|
||||
let definition = self.itemDefinitions[identifier],
|
||||
definition.align != .center else {
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
return nil
|
||||
}
|
||||
|
||||
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
||||
@@ -260,16 +247,24 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
switch item.type {
|
||||
case let .staticButton(title: title):
|
||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
||||
case let .appleScriptTitledButton(source: source, refreshInterval: interval):
|
||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
||||
case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages):
|
||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages)
|
||||
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
|
||||
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 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)
|
||||
@@ -288,22 +283,24 @@ 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 .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)
|
||||
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)
|
||||
}
|
||||
|
||||
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
private var postfix: String
|
||||
private var from: String
|
||||
private var to: String
|
||||
private var decimal: Int
|
||||
private var decimalValue: Float32!
|
||||
private var decimalString: String!
|
||||
private var oldValue: Float32!
|
||||
private var full: Bool = false
|
||||
|
||||
@@ -32,11 +35,29 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
"IDR": "Rp",
|
||||
"MXN": "$",
|
||||
"SGD": "$",
|
||||
"CHF": "Fr.",
|
||||
"BTC": "฿",
|
||||
"LTC": "Ł",
|
||||
"ETH": "Ξ",
|
||||
]
|
||||
private let decimals = [
|
||||
"USD": 4,
|
||||
"EUR": 4,
|
||||
"RUB": 2,
|
||||
"JPY": 2,
|
||||
"GBP": 4,
|
||||
"CAD": 4,
|
||||
"KRW": 4,
|
||||
"CNY": 4,
|
||||
"AUD": 4,
|
||||
"BRL": 4,
|
||||
"IDR": 1,
|
||||
"MXN": 2,
|
||||
"SGD": 4,
|
||||
"CHF": 4,
|
||||
"BTC": 2,
|
||||
"LTC": 2,
|
||||
"ETH": 2,
|
||||
]
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) {
|
||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||
@@ -57,6 +78,14 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
postfix = to
|
||||
}
|
||||
|
||||
|
||||
if let decimal = decimals[to] {
|
||||
self.decimal = decimal
|
||||
} else {
|
||||
decimal = 2
|
||||
}
|
||||
|
||||
|
||||
super.init(identifier: identifier, title: "⏳")
|
||||
|
||||
activity.repeats = true
|
||||
@@ -114,10 +143,12 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
}
|
||||
|
||||
oldValue = value
|
||||
decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal))
|
||||
decimalString = String(decimalValue)
|
||||
|
||||
var title = ""
|
||||
if full {
|
||||
title = String(format: "%@‣%.2f%@", prefix, value, postfix)
|
||||
title = String(format: "%@%@‣%@", prefix, postfix, decimalString)
|
||||
} else {
|
||||
title = String(format: "%@%.2f", prefix, value)
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,6 +11,7 @@ import ScriptingBridge
|
||||
|
||||
class MusicBarItem: CustomButtonTouchBarItem {
|
||||
private enum Player: String {
|
||||
case Music = "com.apple.Music"
|
||||
case iTunes = "com.apple.iTunes"
|
||||
case Spotify = "com.spotify.client"
|
||||
case VOX = "com.coppertino.Vox"
|
||||
@@ -19,6 +20,7 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
}
|
||||
|
||||
private let playerBundleIdentifiers = [
|
||||
Player.Music,
|
||||
Player.iTunes,
|
||||
Player.Spotify,
|
||||
Player.VOX,
|
||||
@@ -71,6 +73,10 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
let mp = (musicPlayer as iTunesApplication)
|
||||
mp.playpause!()
|
||||
return
|
||||
} else if ident == .Music {
|
||||
let mp = (musicPlayer as MusicApplication)
|
||||
mp.playpause!()
|
||||
return
|
||||
} else if ident == .VOX {
|
||||
let mp = (musicPlayer as VoxApplication)
|
||||
mp.playpause!()
|
||||
@@ -134,6 +140,11 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
mp.nextTrack!()
|
||||
updatePlayer()
|
||||
return
|
||||
} else if ident == .Music {
|
||||
let mp = (musicPlayer as MusicApplication)
|
||||
mp.nextTrack!()
|
||||
updatePlayer()
|
||||
return
|
||||
} else if ident == .VOX {
|
||||
let mp = (musicPlayer as VoxApplication)
|
||||
mp.next!()
|
||||
@@ -180,7 +191,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 = ""
|
||||
@@ -188,6 +199,8 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
tempTitle = (musicPlayer as SpotifyApplication).title
|
||||
} else if ident == .iTunes {
|
||||
tempTitle = (musicPlayer as iTunesApplication).title
|
||||
} else if ident == .Music {
|
||||
tempTitle = (musicPlayer as MusicApplication).title
|
||||
} else if ident == .VOX {
|
||||
tempTitle = (musicPlayer as VoxApplication).title
|
||||
} else if ident == .Safari {
|
||||
@@ -321,6 +334,29 @@ extension iTunesApplication {
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol MusicApplication {
|
||||
@objc optional var currentTrack: MusicTrack { get }
|
||||
@objc optional func playpause()
|
||||
@objc optional func nextTrack()
|
||||
@objc optional func previousTrack()
|
||||
}
|
||||
|
||||
extension SBApplication: MusicApplication {}
|
||||
|
||||
@objc protocol MusicTrack {
|
||||
@objc optional var artist: String { get }
|
||||
@objc optional var name: String { get }
|
||||
}
|
||||
|
||||
extension SBObject: MusicTrack {}
|
||||
|
||||
extension MusicApplication {
|
||||
var title: String {
|
||||
guard let t = currentTrack else { return "" }
|
||||
return (t.artist ?? "") + " — " + (t.name ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol VoxApplication {
|
||||
@objc optional func playpause()
|
||||
@objc optional func next()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -35,7 +35,7 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
||||
case none
|
||||
}
|
||||
|
||||
private let defaultTitle = "🍅"
|
||||
private let defaultTitle = "🍅 "
|
||||
private let workTime: TimeInterval
|
||||
private let restTime: TimeInterval
|
||||
private var typeTime: TimeTypes = .none
|
||||
|
||||
@@ -12,28 +12,31 @@ import CoreLocation
|
||||
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
||||
private let activity: NSBackgroundActivityScheduler
|
||||
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": "🌫"
|
||||
]
|
||||
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 +44,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 +116,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 +134,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))
|
||||
: ""
|
||||
|
||||
@@ -28,6 +28,26 @@
|
||||
}
|
||||
},
|
||||
|
||||
// Music
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
"source": {
|
||||
"inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rreturn (get artist of current track) & \" – \" & (get name of current track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
|
||||
},
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
|
||||
},
|
||||
"longAction": "appleScript",
|
||||
"longActionAppleScript": {
|
||||
"inline": "if application \"Music\" is running then\rtell application \"Music\"\rif player state is playing then\rprev track\rend if\rend tell\rend if\r"
|
||||
},
|
||||
"refreshInterval": 2,
|
||||
"image": {
|
||||
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADAUExURUdwTOVVZCzB+3qc0gkDBgEBAgcKEwAAAA4EBP5aVU2V95iJv7V3rtBOvH5W6jaOyclScKZGX3wuQCMuUqZN7+NQYXtDvFd9/sxYni2z6UhBhyhvnIp7sf38/PXz9ePm69/k6fHv8/j29+3q7/v6+ufq7uvu8fTw8+1egOFki/dbboVj/HNy/T+j/dNtnEul81vC8Vmf3OeRqOBVofK4xZfF7sDb7PLe6LxU1KKK79PL6vrW3fh4g5Zi4bi16daa0A3Qc90AAAAddFJOUwD3/v0uOlYNG/z+/v7998OYYztt/Le2/eDqi5jEo2rNTwAABMtJREFUWMPdl1tjqjoQhauC0Hqvta26JVxUQBQFRPBS/f//as8kISBqd/twXs5KjBDyrZkEFfP09D+WoigqFxz+mlbVXncweEZVBoNuD1x+E1vtMVZoURn01J/mob52KRNuj4k5mZjJcRueFotFpfv6EwdVRfy0tSdXSranheN0/zkRSB7x42Q6udExdBy3972Fog4o/kBg4X6bhPJaeX7eTqeT6b0MJtPpdOu6n49XQgX+dOQ8vpWEvYn/2AHiL052PpoROcsOtND17ztQ3rwTuCQz9O/moKiMf6BkG/puKBzurKQ64PmbU2bDzUxk3Uql4lZcl3Vpvt9VbxLoLZwjY7E1WcNZoB0XpbELie/3Sg6KVHG2jGPs1LTCE2UXFfgIgtBgyq8d/E/pehJq1zmZGc7kAPsMX4Ec932T25uX5vUklFcHJlDU1OT4wllkvOtn9lrSbF7dCUggNEtaMOXhQZq4WkpBcksJQBCOnyjvM4P8KqQgFW9BFJka2NMKB1gw+VMxvN9smnwI1EuzpxS+g9FWYySjsTpOtnq+H162iW01m/wyXLUPzT/5HKQoSjQmU8vE6TAElvWggbhuNRpScQa002bVtJmBCz9qNusWBkJmoyHmoHajC4yybVujhR26mJVha7lDo2FrhnA4N0aq+BpE24zjgsMoEsfU0AADaKCwemiIRZA+o6N9oygyMi/EAWk0DMNgFvCmN/5IwqCV3PCGzzIwbINrzgwykVq2iorUalm2UTZotXKWqVYz5uBjzDUoxrxWyzKQWy061LZsNIJ3PAMDIcbVauwdGmxrNblgYNnCgStN54ylBSsYoAxerwwgJsCWTS0sepym0Mdp1gYBw5lmwgDXIDEoaeHLYE36BafzuQWFQ9RASM/XQPpMD5YQ2gA/AwPArQJyZWDsgo64C+/pBRkDG4s31hdmwFNAGz1mBjPukBs8qdSgLDBA1LJm1lw/14IgWAdAQ5nhax4HY/FR7qfpHQMPUChzS0c6eFmv17MZo7HZBP3MQJHTNCnzxPMgOFUteAGtMwMmfRPIijBgc+AmNK6+9zw+9Iw05YsG8aaT/7Kro7eUcoSWw3n/1W57SxgGOZCYxl+VDDabcf6LpNTf3g6IQ4XY7TbiyyUBmhqsViswWK02cE7ITIdmtxnWlcJz6f1tPyMsha+2R4UGyJPdywotwGSHOKHdm+IMYA40BRhtkXOb42DAh8crppjw8CyB4nMBlvFtD/0WSfZebkDHguLNar2JdyTXptqRrx6OmMKZkISQc4Yv9yyDXHiiEx1qXL1OAFdhBJPAAQeRQDEiRZEm+kwnu2p1XHo64yQ8j47bL1kCZ87pDKWxuW4mQJ9O9ba31xE5Y/rnA4VoTCJQwvnyBNgk+pkDi8sSJjlKRPxhX7r3Lytz0LPMi1H1Qv7VuzwuAzh4h1ukKFi/YV9+9E8THZbne2Ezxd/xsNGQ6u+wgoeH4SH9Tl367t+2Ko/acA8Oj/DhWP7X/30Zkvj4WMYlj10MOISXf7DlkPvvH6g43u0oCzDS1U5f/sHWC3d7cn3UAQf4HeHfwxXQY4yu/HTDKNXro3Gngw4vw2FnPKrXJfUXu0fqIdeFZOnXm08FTRSxcf391pW7oNGT8vRf6i9jqljwYzAm6AAAAABJRU5ErkJggg=="
|
||||
}
|
||||
},
|
||||
|
||||
// iTunes
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
|
||||
@@ -6,7 +6,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
||||
[ { "type": "appleScriptTitledButton", "source": { "inline": "tell everything fine" } } ]
|
||||
""".data(using: .utf8)!
|
||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||
guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else {
|
||||
guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class AppleScriptDefinitionTests: XCTestCase {
|
||||
[ { "type": "appleScriptTitledButton", "source": { "filePath": "/ololo/pew" } } ]
|
||||
""".data(using: .utf8)!
|
||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||
guard case .appleScriptTitledButton(let source, _)? = result?.first?.type else {
|
||||
guard case .appleScriptTitledButton(let source, _, _)? = result?.first?.type else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
@@ -26,12 +26,39 @@ 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} ]
|
||||
""".data(using: .utf8)!
|
||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
|
||||
guard case .appleScriptTitledButton(_, 305)? = result?.first?.type else {
|
||||
guard case .appleScriptTitledButton(_, 305, _)? = result?.first?.type else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,11 +102,31 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
- appleScriptTitledButton
|
||||
- shellScriptTitledButton
|
||||
|
||||
## Gestures on central part:
|
||||
## Gestures
|
||||
|
||||
By default you can enable basic gestures from application menu (status bar -> MTMR icon -> Volume/Brightness gestures):
|
||||
- two finger slide: change you Volume
|
||||
- three finger slide: change you Brightness
|
||||
|
||||
### Custom gestures
|
||||
|
||||
You can add custom actions for two/three/four finger swipes. To do it, you need to use `swipe` type:
|
||||
|
||||
```json
|
||||
"type": "swipe",
|
||||
"fingers": 2, // number of fingers required (2,3 or 4)
|
||||
"direction": "right", // direction of swipe (right/left)
|
||||
"minOffset" 10, // optional: minimal required offset for gesture to emit event
|
||||
"sourceApple": { // optional: apple script to run
|
||||
"inline": "beep"
|
||||
},
|
||||
"sourceBash": { // optional: bash script to run
|
||||
"inline": "touch /Users/lobster/test"
|
||||
}
|
||||
```
|
||||
|
||||
You may create as many `swipe` objects in the preset as you want.
|
||||
|
||||
## Built-in slider types:
|
||||
|
||||
- brightness
|
||||
@@ -128,15 +148,42 @@ 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\"\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"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
> Note: appleScriptTitledButton can change its icon. To do it, you need to do the following things:
|
||||
1. Declarate dictionary of icons in `alternativeImages` field
|
||||
2. Make you script return array of two values - `{"TITLE", "IMAGE_LABEL"}`
|
||||
3. Make sure that your `IMAGE_LABEL` is declared in `alternativeImages` field
|
||||
|
||||
Example:
|
||||
```js
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
"source": {
|
||||
"inline": "if (random number from 1 to 2) = 1 then\n\tset val to {\"title\", \"play\"}\nelse\n\tset val to {\"title\", \"pause\"}\nend if\nreturn val"
|
||||
},
|
||||
"refreshInterval": 1,
|
||||
"image": {
|
||||
"base64": "iVBORw0KGgoAAAANSUhEUgA..."
|
||||
},
|
||||
"alternativeImages": {
|
||||
"play": {
|
||||
"base64": "iVBORw0KGgoAAAANSUhEUgAAAAAA..."
|
||||
},
|
||||
"pause": {
|
||||
"base64": "iVBORw0KGgoAAAANSUhEUgAAAIAA..."
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
#### `shellScriptTitledButton`
|
||||
> Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting)
|
||||
> Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color.
|
||||
@@ -156,6 +203,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"
|
||||
},
|
||||
@@ -173,7 +223,7 @@ Example of "CPU load" button which also changes color based on load value.
|
||||
"title": "stats",
|
||||
"items": [
|
||||
{ "type": "play" },
|
||||
{ "type": "mute" },
|
||||
{ "type": "mute" },
|
||||
...
|
||||
]
|
||||
}
|
||||
@@ -288,6 +338,7 @@ To close a group, use the button:
|
||||
```js
|
||||
{
|
||||
"type": "dock",
|
||||
"filter": "(^Xcode$)|(Safari)|(.*player)",
|
||||
"autoResize": true
|
||||
},
|
||||
```
|
||||
@@ -371,6 +422,31 @@ If you want to longPress for some operations, it is similar to the configuration
|
||||
"bordered": "false" // "true" or "false"
|
||||
```
|
||||
|
||||
- `background` allow to specify you button background color
|
||||
|
||||
```js
|
||||
"background": "#FF0000",
|
||||
```
|
||||
by using background with color "#000000" and bordered == false you can create button without gray background but with background when the button is pressed
|
||||
|
||||
- `title` specify button title
|
||||
|
||||
```js
|
||||
"title": "hello"
|
||||
```
|
||||
|
||||
- `image` specify button icon
|
||||
|
||||
```js
|
||||
"image": {
|
||||
//Can be either of those
|
||||
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdB...."
|
||||
//or
|
||||
"filePath": "~/img.png"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Roadmap
|
||||
|
||||
- [x] Create the first prototype with TouchBar in Storyboard
|
||||
@@ -399,7 +475,7 @@ Settings:
|
||||
- [x] Startup at login
|
||||
- [ ] Show on/off in Dock
|
||||
- [ ] Show on/off in StatusBar
|
||||
- [ ] On/off Haptic Feedback
|
||||
- [ x] On/off Haptic Feedback
|
||||
|
||||
Maybe:
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+10
-10
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# INSTALL xcpretty: sudo gem install xcpretty
|
||||
|
||||
NAME='MTMR'
|
||||
|
||||
rm -r Release 2>/dev/null
|
||||
|
||||
xcodebuild archive \
|
||||
-scheme "$NAME" \
|
||||
-archivePath Release/App.xcarchive
|
||||
-archivePath Release/App.xcarchive | xcpretty -c
|
||||
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-archivePath Release/App.xcarchive \
|
||||
-exportOptionsPlist export-options.plist \
|
||||
-exportPath Release
|
||||
-exportPath Release | xcpretty -c
|
||||
|
||||
cd Release
|
||||
rm -r App.xcarchive
|
||||
@@ -20,7 +22,7 @@ NAME_DMG="${NAME}.app"
|
||||
echo $NAME_DMG
|
||||
create-dmg $NAME_DMG
|
||||
|
||||
DATE=`date +"%a, %d %b %Y %H:%M:%S %z"`
|
||||
DATE=`LC_ALL=en_US.utf8 date +"%a, %d %b %Y %H:%M:%S %z"`
|
||||
BUILD=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${NAME}.app/Contents/Info.plist`
|
||||
VERSION=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${NAME}.app/Contents/Info.plist`
|
||||
MINIMUM=`/usr/libexec/PlistBuddy -c "Print LSMinimumSystemVersion" ${NAME}.app/Contents/Info.plist`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# INSTALL xcpretty: sudo gem install xcpretty
|
||||
|
||||
NAME='MTMR'
|
||||
killall $NAME
|
||||
@@ -11,13 +12,13 @@ rm -r Release 2>/dev/null
|
||||
|
||||
xcodebuild archive \
|
||||
-scheme "$NAME" \
|
||||
-archivePath Release/App.xcarchive
|
||||
-archivePath Release/App.xcarchive | xcpretty
|
||||
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-archivePath Release/App.xcarchive \
|
||||
-exportOptionsPlist export-options.plist \
|
||||
-exportPath Release
|
||||
-exportPath Release | xcpretty
|
||||
|
||||
cd Release
|
||||
rm -r App.xcarchive
|
||||
|
||||
Reference in New Issue
Block a user