1
0
mirror of https://github.com/Toxblh/MTMR.git synced 2026-01-12 01:48:38 +00:00

Compare commits

..

No commits in common. "master" and "v0.13.2" have entirely different histories.

291 changed files with 1266 additions and 14982 deletions

6
.github/FUNDING.yml vendored
View File

@ -1,6 +0,0 @@
# These are supported funding model platforms
issuehunt: Toxblh
patreon: toxblh
ko_fi: toxblh
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4

View File

@ -1,22 +0,0 @@
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]}

View File

@ -1,41 +0,0 @@
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

2
.gitignore vendored
View File

@ -69,5 +69,3 @@ fastlane/report.xml
fastlane/Preview.html fastlane/Preview.html
fastlane/screenshots fastlane/screenshots
fastlane/test_output fastlane/test_output
.vscode

View File

@ -7,54 +7,29 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
36300E83209F040900B31C71 /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
36300E8F20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */; };
36300E9120A2D2B200B31C71 /* BackgroundColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */; };
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */; }; 368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */; };
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A778BD20A6C27100B38714 /* GeneralExtensions.swift */; };
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; }; 36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; };
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */; }; 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */; };
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; }; 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; }; 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; }; 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; }; 36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */; };
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */; };
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EC26CC43D40025BDA7 /* CPU.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 */; }; 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; }; 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; };
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; }; 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; };
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60669B4220AD8FA80074E817 /* GroupBarItem.swift */; };
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; }; 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; };
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; }; 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; };
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */; };
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; }; 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; };
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B002E641216C0E38002774BA /* CoreDisplay.framework */; };
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; };
B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; }; B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; };
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; }; B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; };
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; };
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; }; B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; };
B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; }; B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; };
B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */; };
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */; };
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126F0217BE19000A98970 /* WidgetProtocol.swift */; };
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08173262135F02B005D4908 /* NightShiftBarItem.swift */; };
B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B08173292135F354005D4908 /* CoreBrightness.framework */; };
B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B081732B213739FE005D4908 /* DnDBarItem.swift */; };
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B252205C7D8000BC04DC /* AppDelegate.swift */; }; B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B252205C7D8000BC04DC /* AppDelegate.swift */; };
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B082B256205C7D8000BC04DC /* Assets.xcassets */; }; B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B082B256205C7D8000BC04DC /* Assets.xcassets */; };
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B082B258205C7D8000BC04DC /* Main.storyboard */; }; B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B082B258205C7D8000BC04DC /* Main.storyboard */; };
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0846A742220C968000288A7 /* NetworkBarItem.swift */; };
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */; }; B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */; };
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */; }; B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */; };
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */; }; B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */; };
@ -68,33 +43,21 @@
B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742E207D6B590004B740 /* Vox.next.scpt */; }; B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742E207D6B590004B740 /* Vox.next.scpt */; };
B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */; }; B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */; };
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */; }; B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */; };
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 */; }; 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 */; };
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXContainerItemProxy section */
B00D181E2152F507000806F4 /* CopyFiles */ = { B082B262205C7D8000BC04DC /* PBXContainerItemProxy */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXContainerItemProxy;
buildActionMask = 12; containerPortal = B082B247205C7D8000BC04DC /* Project object */;
dstPath = ""; proxyType = 1;
dstSubfolderSpec = 10; remoteGlobalIDString = B082B24E205C7D8000BC04DC;
files = ( remoteInfo = MTMR;
B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXCopyFilesBuildPhase section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
36300E85209FD16700B31C71 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; };
36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptDefinitionTests.swift; sourceTree = "<group>"; };
36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundColorTests.swift; sourceTree = "<group>"; };
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewItem.swift; sourceTree = "<group>"; }; 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewItem.swift; sourceTree = "<group>"; };
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralExtensions.swift; sourceTree = "<group>"; };
36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TECHNICAL_DEBT.md; sourceTree = "<group>"; }; 36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TECHNICAL_DEBT.md; sourceTree = "<group>"; };
36C2ECD2207B3B1D003CDA33 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 36C2ECD2207B3B1D003CDA33 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTouchBarItem.swift; sourceTree = "<group>"; }; 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTouchBarItem.swift; sourceTree = "<group>"; };
@ -102,30 +65,15 @@
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; }; 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; }; 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; }; 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
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>"; };
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPUBarItem.swift; sourceTree = "<group>"; };
4CF222EC26CC43D40025BDA7 /* CPU.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPU.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>"; }; 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = "<group>"; }; 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = "<group>"; };
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; }; 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; };
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; }; 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; };
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; }; 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; };
60669B4220AD8FA80074E817 /* GroupBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBarItem.swift; sourceTree = "<group>"; };
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = "<group>"; }; 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = "<group>"; };
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = "<group>"; }; 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = "<group>"; };
60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicBarItem.swift; sourceTree = "<group>"; };
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = "<group>"; }; 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = "<group>"; };
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; };
B002E641216C0E38002774BA /* CoreDisplay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreDisplay.framework; path = ../../../../../System/Library/Frameworks/CoreDisplay.framework; sourceTree = "<group>"; };
B00D181C2152F4A5000806F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryBarItem.swift; sourceTree = "<group>"; }; B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryBarItem.swift; sourceTree = "<group>"; };
B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; }; B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; };
B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; };
@ -133,13 +81,6 @@
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarPrivateApi.h; sourceTree = "<group>"; }; B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarPrivateApi.h; sourceTree = "<group>"; };
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TouchBarPrivateApi-Bridging.h"; sourceTree = "<group>"; }; B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TouchBarPrivateApi-Bridging.h"; sourceTree = "<group>"; };
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = "<group>"; }; B059D62C205F11E8006E6B86 /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = "<group>"; };
B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = "<group>"; };
B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroBarItem.swift; sourceTree = "<group>"; };
B08126F0217BE19000A98970 /* WidgetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetProtocol.swift; sourceTree = "<group>"; };
B08173262135F02B005D4908 /* NightShiftBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightShiftBarItem.swift; sourceTree = "<group>"; };
B08173282135F128005D4908 /* CBBlueLightClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBBlueLightClient.h; sourceTree = "<group>"; };
B08173292135F354005D4908 /* CoreBrightness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBrightness.framework; path = ../../../../../System/Library/PrivateFrameworks/CoreBrightness.framework; sourceTree = "<group>"; };
B081732B213739FE005D4908 /* DnDBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DnDBarItem.swift; sourceTree = "<group>"; };
B082B24F205C7D8000BC04DC /* MTMR.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MTMR.app; sourceTree = BUILT_PRODUCTS_DIR; }; B082B24F205C7D8000BC04DC /* MTMR.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MTMR.app; sourceTree = BUILT_PRODUCTS_DIR; };
B082B252205C7D8000BC04DC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; B082B252205C7D8000BC04DC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B082B256205C7D8000BC04DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; B082B256205C7D8000BC04DC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -148,7 +89,6 @@
B082B25C205C7D8000BC04DC /* MTMR.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MTMR.entitlements; sourceTree = "<group>"; }; B082B25C205C7D8000BC04DC /* MTMR.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MTMR.entitlements; sourceTree = "<group>"; };
B082B261205C7D8000BC04DC /* MTMRTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B082B261205C7D8000BC04DC /* MTMRTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
B082B267205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B082B267205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B0846A742220C968000288A7 /* NetworkBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkBarItem.swift; sourceTree = "<group>"; };
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; }; B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultitouchSupport.framework; path = ../../../../../System/Library/PrivateFrameworks/MultitouchSupport.framework; sourceTree = "<group>"; }; B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultitouchSupport.framework; path = ../../../../../System/Library/PrivateFrameworks/MultitouchSupport.framework; sourceTree = "<group>"; };
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPress.swift; sourceTree = "<group>"; }; B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPress.swift; sourceTree = "<group>"; };
@ -162,13 +102,8 @@
B0B1742E207D6B590004B740 /* Vox.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.next.scpt; sourceTree = "<group>"; }; B0B1742E207D6B590004B740 /* Vox.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.next.scpt; sourceTree = "<group>"; };
B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.nowPlaying.scpt; sourceTree = "<group>"; }; B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.nowPlaying.scpt; sourceTree = "<group>"; };
B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.nowPlaying.scpt; sourceTree = "<group>"; }; B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.nowPlaying.scpt; sourceTree = "<group>"; };
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportNSTouchBar.swift; sourceTree = "<group>"; };
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>"; }; 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>"; }; 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>"; };
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextScrubberTouchBarItem.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -176,11 +111,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */,
B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */,
B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */, B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */,
B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */, B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */,
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -197,9 +129,6 @@
B059D62B205F11E8006E6B86 /* Frameworks */ = { B059D62B205F11E8006E6B86 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B002E641216C0E38002774BA /* CoreDisplay.framework */,
B00D181C2152F4A5000806F4 /* Sparkle.framework */,
B08173292135F354005D4908 /* CoreBrightness.framework */,
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */, B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */,
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */, B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
); );
@ -236,26 +165,18 @@
B082B256205C7D8000BC04DC /* Assets.xcassets */, B082B256205C7D8000BC04DC /* Assets.xcassets */,
B082B258205C7D8000BC04DC /* Main.storyboard */, B082B258205C7D8000BC04DC /* Main.storyboard */,
B082B25B205C7D8000BC04DC /* Info.plist */, B082B25B205C7D8000BC04DC /* Info.plist */,
B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */,
B082B25C205C7D8000BC04DC /* MTMR.entitlements */, B082B25C205C7D8000BC04DC /* MTMR.entitlements */,
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */, 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
B082B252205C7D8000BC04DC /* AppDelegate.swift */, B082B252205C7D8000BC04DC /* AppDelegate.swift */,
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */, B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */, B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
4CF222EC26CC43D40025BDA7 /* CPU.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */, 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */, B059D621205E03F5006E6B86 /* TouchBarController.swift */,
BAF5AB5624317B4300B04904 /* BasicView.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */, 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */, B0008E542080286C003AD4DD /* SupportHelpers.swift */,
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */, B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */, 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
B05600D22083E9BB00EB218D /* CustomSlider.swift */, B05600D22083E9BB00EB218D /* CustomSlider.swift */,
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */,
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */,
); );
path = MTMR; path = MTMR;
sourceTree = "<group>"; sourceTree = "<group>";
@ -263,11 +184,8 @@
B082B264205C7D8000BC04DC /* MTMRTests */ = { B082B264205C7D8000BC04DC /* MTMRTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
36300E85209FD16700B31C71 /* .travis.yml */,
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */, 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */,
B082B267205C7D8000BC04DC /* Info.plist */, B082B267205C7D8000BC04DC /* Info.plist */,
36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */,
36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */,
); );
path = MTMRTests; path = MTMRTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -275,8 +193,6 @@
B0B17426207D6AFE0004B740 /* AppleScripts */ = { B0B17426207D6AFE0004B740 /* AppleScripts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */,
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */,
B0B1742A207D6B580004B740 /* Battery.scpt */, B0B1742A207D6B580004B740 /* Battery.scpt */,
B0B17429207D6B580004B740 /* Finder.scpt */, B0B17429207D6B580004B740 /* Finder.scpt */,
B0B1742C207D6B590004B740 /* iTunes.next.scpt */, B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
@ -294,17 +210,12 @@
B0B1743B207D6ED40004B740 /* CBridge */ = { B0B1743B207D6ED40004B740 /* CBridge */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */,
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */,
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */, B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */, B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */, B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */, B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */, 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */,
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */, 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */,
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */,
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */,
B08173282135F128005D4908 /* CBBlueLightClient.h */,
); );
path = CBridge; path = CBridge;
sourceTree = "<group>"; sourceTree = "<group>";
@ -312,25 +223,14 @@
B0B88A07208CD12000A2C160 /* Widgets */ = { B0B88A07208CD12000A2C160 /* Widgets */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */,
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */,
B081732B213739FE005D4908 /* DnDBarItem.swift */,
60669B4220AD8FA80074E817 /* GroupBarItem.swift */,
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */,
60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */,
B0846A742220C968000288A7 /* NetworkBarItem.swift */,
B08173262135F02B005D4908 /* NightShiftBarItem.swift */,
B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */,
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */, 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */, 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */,
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */, 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */,
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */,
B08126F0217BE19000A98970 /* WidgetProtocol.swift */, 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */,
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
); );
path = Widgets; path = Widgets;
sourceTree = "<group>"; sourceTree = "<group>";
@ -345,8 +245,6 @@
B082B24B205C7D8000BC04DC /* Sources */, B082B24B205C7D8000BC04DC /* Sources */,
B082B24C205C7D8000BC04DC /* Frameworks */, B082B24C205C7D8000BC04DC /* Frameworks */,
B082B24D205C7D8000BC04DC /* Resources */, B082B24D205C7D8000BC04DC /* Resources */,
B00D181E2152F507000806F4 /* CopyFiles */,
B0679BBF215AE085000FC6B4 /* ShellScript */,
); );
buildRules = ( buildRules = (
); );
@ -368,6 +266,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
B082B263205C7D8000BC04DC /* PBXTargetDependency */,
); );
name = MTMRTests; name = MTMRTests;
productName = MTMRTests; productName = MTMRTests;
@ -381,12 +280,11 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1010; LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Anton Palgunov"; ORGANIZATIONNAME = "Anton Palgunov";
TargetAttributes = { TargetAttributes = {
B082B24E205C7D8000BC04DC = { B082B24E205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1020;
ProvisioningStyle = Manual; ProvisioningStyle = Manual;
SystemCapabilities = { SystemCapabilities = {
com.apple.Sandbox = { com.apple.Sandbox = {
@ -396,7 +294,6 @@
}; };
B082B260205C7D8000BC04DC = { B082B260205C7D8000BC04DC = {
CreatedOnToolsVersion = 9.2; CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1020;
}; };
}; };
}; };
@ -428,12 +325,9 @@
B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */, B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */,
B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */, B082B257205C7D8000BC04DC /* Assets.xcassets in Resources */,
B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt 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 */, B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */,
B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */, B082B25A205C7D8000BC04DC /* Main.storyboard in Resources */,
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */, B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */,
B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */,
B0B17433207D6B590004B740 /* Finder.scpt in Resources */, B0B17433207D6B590004B740 /* Finder.scpt in Resources */,
B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */, B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */,
B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */, B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */,
@ -452,26 +346,6 @@
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
B0679BBF215AE085000FC6B4 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
B082B24B205C7D8000BC04DC /* Sources */ = { B082B24B205C7D8000BC04DC /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
@ -480,40 +354,20 @@
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */, B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */, B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */, 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */,
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */, B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */,
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */, B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */, 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */,
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */,
B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */,
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */,
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */, B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */, 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */,
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */,
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */, 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */, 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */,
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */, 36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */, 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */,
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */, 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */, 368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */, B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
@ -524,16 +378,21 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
36300E9120A2D2B200B31C71 /* BackgroundColorTests.swift in Sources */,
36300E8F20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift in Sources */,
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */, 36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */,
36300E83209F040900B31C71 /* SupportHelpers.swift in Sources */,
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */, 36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B082B263205C7D8000BC04DC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B082B24E205C7D8000BC04DC /* MTMR */;
targetProxy = B082B262205C7D8000BC04DC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
B082B258205C7D8000BC04DC /* Main.storyboard */ = { B082B258205C7D8000BC04DC /* Main.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -664,15 +523,14 @@
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 4.0;
}; };
name = Debug; name = Debug;
}; };
@ -680,22 +538,20 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = D6D8BR2QNB; DEVELOPMENT_TEAM = D6D8BR2QNB;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
"$(PROJECT_DIR)",
); );
INFOPLIST_FILE = MTMR/Info.plist; INFOPLIST_FILE = MTMR/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h"; SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 4.0;
}; };
name = Release; name = Release;
}; };
@ -708,7 +564,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 4.0;
}; };
name = Debug; name = Debug;
}; };
@ -723,7 +579,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests; PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 4.0;
}; };
name = Release; name = Release;
}; };

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict/> <dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist> </plist>

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B260205C7D8000BC04DC"
BuildableName = "MTMRTests.xctest"
BlueprintName = "MTMRTests"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B24E205C7D8000BC04DC"
BuildableName = "MTMR.app"
BlueprintName = "MTMR"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B082B260205C7D8000BC04DC"
BuildableName = "MTMRTests.xctest"
BlueprintName = "MTMRTests"
ReferencedContainer = "container:MTMR.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -7,49 +7,28 @@
// //
import Cocoa import Cocoa
import Sparkle
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
var isBlockedApp: Bool = false
private var fileSystemSource: DispatchSourceFileSystemObject? private var fileSystemSource: DispatchSourceFileSystemObject?
func applicationDidFinishLaunching(_: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
// Configure Sparkle
SUUpdater.shared().automaticallyDownloadsUpdates = false
SUUpdater.shared().automaticallyChecksForUpdates = true
SUUpdater.shared().checkForUpdatesInBackground()
AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
TouchBarController.shared.setupControlStripPresence() TouchBarController.shared.setupControlStripPresence()
if let button = statusItem.button { if let button = statusItem.button {
button.image = #imageLiteral(resourceName: "StatusImage") button.image = #imageLiteral(resourceName: "StatusImage")
} }
createMenu() createMenu()
reloadOnDefaultConfigChanged() reloadOnDefaultConfigChanged()
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateIsBlockedApp), name: NSWorkspace.didActivateApplicationNotification, object: nil)
} }
func applicationWillTerminate(_: Notification) {} func applicationWillTerminate(_ aNotification: Notification) {
@objc func updateIsBlockedApp() {
if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
} else {
isBlockedApp = false
}
createMenu()
} }
@objc func openPreferences(_: Any?) { @objc func openPrefereces(_ sender: Any?) {
let task = Process() let task = Process()
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR") let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let presetPath = appSupportDirectory.appending("/items.json") let presetPath = appSupportDirectory.appending("/items.json")
@ -57,115 +36,65 @@ class AppDelegate: NSObject, NSApplicationDelegate {
task.arguments = [presetPath] task.arguments = [presetPath]
task.launch() task.launch()
} }
@objc func toggleControlStrip(_ item: NSMenuItem) { @objc func updatePreset(_ sender: Any?) {
item.state = item.state == .on ? .off : .on TouchBarController.shared.createAndUpdatePreset()
AppSettings.showControlStripState = item.state == .off
TouchBarController.shared.resetControlStrip()
} }
@objc func toggleBlackListedApp(_: Any?) { @objc func openPreset(_ sender: Any?) {
if let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier { let dialog = NSOpenPanel();
if let index = TouchBarController.shared.blacklistAppIdentifiers.firstIndex(of: appIdentifier) {
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index) dialog.title = "Choose a items.json file"
} else { dialog.showsResizeIndicator = true
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier) dialog.showsHiddenFiles = true
} dialog.canChooseDirectories = false
dialog.canCreateDirectories = false
AppSettings.blacklistedAppIds = TouchBarController.shared.blacklistAppIdentifiers
TouchBarController.shared.updateActiveApp()
updateIsBlockedApp()
}
}
@objc func toggleHapticFeedback(_ item: NSMenuItem) {
item.state = item.state == .on ? .off : .on
AppSettings.hapticFeedbackState = item.state == .on
}
@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?) {
let dialog = NSOpenPanel()
dialog.title = "Choose a items.json file"
dialog.showsResizeIndicator = true
dialog.showsHiddenFiles = true
dialog.canChooseDirectories = false
dialog.canCreateDirectories = false
dialog.allowsMultipleSelection = false dialog.allowsMultipleSelection = false
dialog.allowedFileTypes = ["json"] dialog.allowedFileTypes = ["json"]
dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true) dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true)
if dialog.runModal() == .OK, let path = dialog.url?.path { if (dialog.runModal() == NSApplication.ModalResponse.OK) {
TouchBarController.shared.reloadPreset(path: path) let result = dialog.url
if (result != nil) {
let path = result!.path
let jsonData = path.fileData
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
TouchBarController.shared.createAndUpdatePreset(jsonItems: jsonItems)
}
} }
} }
@objc func toggleStartAtLogin(_: Any?) {
LaunchAtLoginController().setLaunchAtLogin(!LaunchAtLoginController().launchAtLogin, for: NSURL.fileURL(withPath: Bundle.main.bundlePath))
createMenu()
}
func createMenu() { func createMenu() {
let menu = NSMenu() let menu = NSMenu()
menu.addItem(withTitle: "Preferences", action: #selector(openPrefereces(_:)), keyEquivalent: ",")
let startAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleStartAtLogin(_:)), keyEquivalent: "L") menu.addItem(withTitle: "Reload Preset", action: #selector(updatePreset(_:)), keyEquivalent: "r")
startAtLogin.state = LaunchAtLoginController().launchAtLogin ? .on : .off menu.addItem(withTitle: "Open Preset", action: #selector(openPreset(_:)), keyEquivalent: "O")
let toggleBlackList = NSMenuItem(title: "Toggle current app in blacklist", action: #selector(toggleBlackListedApp(_:)), keyEquivalent: "B")
toggleBlackList.state = isBlockedApp ? .on : .off
let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
hideControlStrip.state = AppSettings.showControlStripState ? .off : .on
let hapticFeedback = NSMenuItem(title: "Haptic Feedback", action: #selector(toggleHapticFeedback(_:)), keyEquivalent: "H")
hapticFeedback.state = AppSettings.hapticFeedbackState ? .on : .off
let multitouchGestures = NSMenuItem(title: "Volume/Brightness gestures", action: #selector(toggleMultitouch(_:)), keyEquivalent: "")
multitouchGestures.state = AppSettings.multitouchGestures ? .on : .off
let settingSeparator = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
settingSeparator.isEnabled = false
menu.addItem(withTitle: "Preferences", action: #selector(openPreferences(_:)), keyEquivalent: ",")
menu.addItem(withTitle: "Open preset", action: #selector(openPreset(_:)), keyEquivalent: "O")
menu.addItem(withTitle: "Check for Updates...", action: #selector(SUUpdater.checkForUpdates(_:)), keyEquivalent: "").target = SUUpdater.shared()
menu.addItem(NSMenuItem.separator())
menu.addItem(settingSeparator)
menu.addItem(hapticFeedback)
menu.addItem(hideControlStrip)
menu.addItem(toggleBlackList)
menu.addItem(startAtLogin)
menu.addItem(multitouchGestures)
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu statusItem.menu = menu
} }
func reloadOnDefaultConfigChanged() { func reloadOnDefaultConfigChanged() {
let file = NSURL.fileURL(withPath: standardConfigPath) let file = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR/items.json"))
let fd = open(file.path, O_EVTONLY) let fd = open(file.path, O_EVTONLY)
fileSystemSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue(label: "DefaultConfigChanged")) self.fileSystemSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue(label: "DefaultConfigChanged"))
fileSystemSource?.setEventHandler(handler: { self.fileSystemSource?.setEventHandler(handler: {
print("Config changed, reloading...") print("Config changed, reloading...")
DispatchQueue.main.async { DispatchQueue.main.async {
TouchBarController.shared.reloadPreset(path: file.path) TouchBarController.shared.createAndUpdatePreset()
} }
}) })
fileSystemSource?.setCancelHandler(handler: { self.fileSystemSource?.setCancelHandler(handler: {
close(fd) close(fd)
}) })
fileSystemSource?.resume() self.fileSystemSource?.resume()
} }
} }

View File

@ -1,33 +0,0 @@
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()
}
}
}

View File

@ -4,71 +4,50 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
private var script: NSAppleScript! private var script: NSAppleScript!
private let interval: TimeInterval private let interval: TimeInterval
private var forceHideConstraint: NSLayoutConstraint! private var forceHideConstraint: NSLayoutConstraint!
private let alternativeImages: [String: SourceProtocol]
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, onTap: @escaping ()->(), onLongTap: @escaping ()->()) {
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) {
self.interval = interval self.interval = interval
self.alternativeImages = alternativeImages super.init(identifier: identifier, title: "", onTap: onTap, onLongTap: onLongTap)
super.init(identifier: identifier, title: "") self.forceHideConstraint = self.view.widthAnchor.constraint(equalToConstant: 0)
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0) guard let script = source.appleScript else {
title = "scheduled" button.title = "no script"
DispatchQueue.appleScriptQueue.async { return
guard let script = source.appleScript else { }
DispatchQueue.main.async { self.script = script
self.title = "no script" button.bezelColor = .clear
} DispatchQueue.main.async {
return
}
self.script = script
DispatchQueue.main.async {
self.isBordered = false
}
var error: NSDictionary? var error: NSDictionary?
guard script.compileAndReturnError(&error) else { guard script.compileAndReturnError(&error) else {
#if DEBUG #if DEBUG
print(error?.description ?? "unknown error") print(error?.description ?? "unknown error")
#endif #endif
DispatchQueue.main.async { DispatchQueue.main.async {
self.title = "error" self.button.title = "error"
} }
return return
} }
self.refreshAndSchedule() self.refreshAndSchedule()
} }
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func refreshAndSchedule() { func refreshAndSchedule() {
#if DEBUG #if DEBUG
print("refresh happened (interval \(interval)), self \(identifier.rawValue))") print("refresh happened")
#endif #endif
let scriptResult = execute() let scriptResult = self.execute()
DispatchQueue.main.async { DispatchQueue.main.async {
self.title = scriptResult self.button.title = scriptResult
self.forceHideConstraint.isActive = scriptResult == "" self.forceHideConstraint.isActive = scriptResult == ""
#if DEBUG
print("did set new script result title \(scriptResult)")
#endif
} }
DispatchQueue.appleScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.refreshAndSchedule() self?.refreshAndSchedule()
} }
} }
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 { func execute() -> String {
var error: NSDictionary? var error: NSDictionary?
let output = script.executeAndReturnError(&error) let output = script.executeAndReturnError(&error)
@ -76,22 +55,14 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
print(error) print(error)
return "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 ?? "" return output.stringValue ?? ""
} }
} }
extension DispatchQueue { extension SourceProtocol {
static let appleScriptQueue = DispatchQueue(label: "mtmr.applescript") var appleScript: NSAppleScript? {
guard let source = self.string else { return nil }
return NSAppleScript(source: source)
}
} }

View File

@ -1,7 +1,5 @@
tell application "Finder" tell application "Finder"
if not (exists window 1) then make new Finder window
make new Finder window set target of front window to path to home folder as string
set target of front window to path to home folder as string
end if
activate activate
end tell end tell

View File

@ -1,7 +0,0 @@
if application "Music" is running then
tell application "Music"
if player state is playing then
next track
end if
end tell
end if

View File

@ -1,10 +0,0 @@
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 ""

View File

@ -2,10 +2,6 @@ if application "iTunes" is running then
tell application "iTunes" to playpause tell application "iTunes" to playpause
end if end if
if application "Music" is running then
tell application "Music" to playpause
end if
if application "Spotify" is running then if application "Spotify" is running then
tell application "Spotify" to playpause tell application "Spotify" to playpause
end if end if

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "cpu.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "sun-icon-256.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "39857.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "dnd-off.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "dnd-on.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "KeyboardBrightDown.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "KeyboardBrightUp.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "NightShift.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "NightShiftEnabled.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Application--> <!--Application-->
@ -619,7 +619,7 @@
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE"> <menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections> <connections>
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/> <action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa"> <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
@ -676,7 +676,6 @@
</application> </application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="MTMR" customModuleProvider="target"/> <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="MTMR" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="VW7-73-dHf" customClass="SUUpdater"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="75" y="0.0"/> <point key="canvasLocation" x="75" y="0.0"/>

View File

@ -1,107 +0,0 @@
//
// 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 = 8
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 {
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP)
} else if position < prevPos {
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN)
}
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)
}
}

View File

@ -1,326 +0,0 @@
//
// ANSIEscapeHelper.h
// AnsiColorsTest
//
// Created by Ali Rantakari on 18.3.09.
//
// Version 0.9.6
//
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <Cocoa/Cocoa.h>
#if !__has_feature(objc_arc)
#warning "This code requires ARC to be enabled."
#endif
// dictionary keys for the SGR code dictionaries that the array
// escapeCodesForString:cleanString: returns contains
#define kAMRCodeDictKey_code @"code"
#define kAMRCodeDictKey_location @"location"
// dictionary keys for the string formatting attribute
// dictionaries that the array attributesForString:cleanString:
// returns contains
#define kAMRAttrDictKey_range @"range"
#define kAMRAttrDictKey_attrName @"attributeName"
#define kAMRAttrDictKey_attrValue @"attributeValue"
/*!
@enum AMR_SGRCode
@abstract SGR (Select Graphic Rendition) ANSI control codes.
*/
typedef enum
{
AMR_SGRCodeNoneOrInvalid = -1,
AMR_SGRCodeAllReset = 0,
AMR_SGRCodeIntensityBold = 1,
AMR_SGRCodeIntensityFaint = 2,
AMR_SGRCodeIntensityNormal = 22,
AMR_SGRCodeItalicOn = 3,
AMR_SGRCodeUnderlineSingle = 4,
AMR_SGRCodeUnderlineDouble = 21,
AMR_SGRCodeUnderlineNone = 24,
AMR_SGRCodeFgBlack = 30,
AMR_SGRCodeFgRed = 31,
AMR_SGRCodeFgGreen = 32,
AMR_SGRCodeFgYellow = 33,
AMR_SGRCodeFgBlue = 34,
AMR_SGRCodeFgMagenta = 35,
AMR_SGRCodeFgCyan = 36,
AMR_SGRCodeFgWhite = 37,
AMR_SGRCodeFgReset = 39,
AMR_SGRCodeBgBlack = 40,
AMR_SGRCodeBgRed = 41,
AMR_SGRCodeBgGreen = 42,
AMR_SGRCodeBgYellow = 43,
AMR_SGRCodeBgBlue = 44,
AMR_SGRCodeBgMagenta = 45,
AMR_SGRCodeBgCyan = 46,
AMR_SGRCodeBgWhite = 47,
AMR_SGRCodeBgReset = 49,
AMR_SGRCodeFgBrightBlack = 90,
AMR_SGRCodeFgBrightRed = 91,
AMR_SGRCodeFgBrightGreen = 92,
AMR_SGRCodeFgBrightYellow = 93,
AMR_SGRCodeFgBrightBlue = 94,
AMR_SGRCodeFgBrightMagenta = 95,
AMR_SGRCodeFgBrightCyan = 96,
AMR_SGRCodeFgBrightWhite = 97,
AMR_SGRCodeBgBrightBlack = 100,
AMR_SGRCodeBgBrightRed = 101,
AMR_SGRCodeBgBrightGreen = 102,
AMR_SGRCodeBgBrightYellow = 103,
AMR_SGRCodeBgBrightBlue = 104,
AMR_SGRCodeBgBrightMagenta = 105,
AMR_SGRCodeBgBrightCyan = 106,
AMR_SGRCodeBgBrightWhite = 107
} AMR_SGRCode;
/*!
@class AMR_ANSIEscapeHelper
@abstract Contains helper methods for dealing with strings
that contain ANSI escape sequences for formatting (colors,
underlining, bold etc.)
*/
@interface AMR_ANSIEscapeHelper : NSObject
/*!
@property defaultStringColor
@abstract The default color used when creating an attributed string (default is black).
*/
@property(copy) NSColor *defaultStringColor;
/*!
@property font
@abstract The font to use when creating string formatting attribute values.
*/
@property(copy) NSFont *font;
/*!
@property ansiColors
@abstract The colors to use for displaying ANSI colors.
@discussion Keys in this dictionary should be NSNumber objects containing SGR code
values from the AMR_SGRCode enum. The corresponding values for these keys
should be NSColor objects. If this property is nil or if it doesn't
contain a key for a specific SGR code, the default color will be used
instead.
*/
@property(retain) NSMutableDictionary *ansiColors;
/*!
@method attributedStringWithANSIEscapedString:
@abstract Returns an attributed string that corresponds both in contents
and formatting to a given string that contains ANSI escape
sequences.
@param aString A String containing ANSI escape sequences
@result An attributed string that mimics as closely as possible
the formatting of the given ANSI-escaped string.
*/
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
/*!
@method ansiEscapedStringWithAttributedString:
@abstract Returns a string containing ANSI escape sequences that corresponds
both in contents and formatting to a given attributed string.
@param aAttributedString An attributed string
@result A string that mimics as closely as possible
the formatting of the given attributed string with
ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
/*!
@method escapeCodesForString:cleanString:
@abstract Returns an array of SGR codes and their locations from a
string containing ANSI escape sequences as well as a "clean"
version of the string (i.e. one without the ANSI escape
sequences.)
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences)
@result An array of NSDictionary objects, each of which has
an NSNumber value for the key "code" (specifying an SGR code) and
another NSNumber value for the key "location" (specifying the
location of the code within aCleanString.)
*/
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method ansiEscapedStringWithCodesAndLocations:cleanString:
@abstract Returns a string containing ANSI escape codes for formatting based
on a string and an array of SGR codes and their locations within
the given string.
@param aCodesArray An array of NSDictionary objects, each of which should have
an NSNumber value for the key "code" (specifying an SGR
code) and another NSNumber value for the key "location"
(specifying the location of this SGR code in aCleanString.)
@param aCleanString The string to which to insert the ANSI escape codes
described in aCodesArray.
@result A string containing ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
/*!
@method attributesForString:cleanString:
@abstract Convert ANSI escape sequences in a string to string formatting attributes.
@discussion Given a string with some ANSI escape sequences in it, this method returns
attributes for formatting the specified string according to those ANSI
escape sequences as well as a "clean" (i.e. free of the escape sequences)
version of this string.
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences.) Pass in NULL if you're not
interested in this.
@result An array containing NSDictionary objects, each of which has keys "range"
(an NSValue containing an NSRange, specifying the range for the
attribute within the "clean" version of aString), "attributeName" (an
NSString) and "attributeValue" (an NSObject). You may use these as
arguments for NSMutableAttributedString's methods for setting the
visual formatting.
*/
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method AMR_SGRCode:endsFormattingIntroducedByCode:
@abstract Whether the occurrence of a given SGR code would end the formatting run
introduced by another SGR code.
@discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
specifying a foreground color would end the formatting run
introduced by a foreground color -specifying SGR code.
@param endCode The SGR code to test as a candidate for ending the formatting run
introduced by startCode
@param startCode The SGR code that has introduced a formatting run
@result YES if the occurrence of endCode would end the formatting run
introduced by startCode, NO otherwise.
*/
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
/*!
@method colorForSGRCode:
@abstract Returns the color to use for displaying a specific ANSI color.
@discussion This method first considers the values set in the ansiColors
property and only then the standard basic colors (NSColor's
redColor, blueColor etc.)
@param code An SGR code that specifies an ANSI color.
@result The color to use for displaying the ANSI color specified by code.
*/
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
/*!
@method AMR_SGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that corresponds to a given color.
@discussion This method matches colors to their equivalent SGR codes
by going through the colors specified in the ansiColors
dictionary, and if ansiColors is null or if a match is
not found there, by comparing the given color to the
standard basic colors (NSColor's redColor, blueColor
etc.) The comparison is done simply by checking for
equality.
@param aColor The color to get a corresponding SGR code for
@param aForeground Whether you want a foreground or background color code
@result SGR code that corresponds with aColor.
*/
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
/*!
@method closestSGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that represents the closest ANSI
color to a given color.
@discussion This method attempts to find the closest ANSI color to
aColor and return its SGR code.
@param color The color to get a closest color SGR code match for
@param foreground Whether you want a foreground or background color code
@result SGR code for the ANSI color that is closest to aColor.
*/
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
@end

View File

@ -1,996 +0,0 @@
//
// ANSIEscapeHelper.m
//
// Created by Ali Rantakari on 18.3.09.
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
todo:
- don't add useless "reset" escape codes to the string in
-ansiEscapedStringWithAttributedString:
*/
#import "AMR_ANSIEscapeHelper.h"
// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
// (add your own CSI:Miami joke here)
#define kANSIEscapeCSI @"\033["
// the end byte of an SGR (Select Graphic Rendition)
// ANSI Escape Sequence
#define kANSIEscapeSGREnd @"m"
// color definition helper macros
#define kBrightColorBrightness 1.0
#define kBrightColorSaturation 0.4
#define kBrightColorAlpha 1.0
#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
// default colors
#define kDefaultANSIColorFgBlack NSColor.blackColor
#define kDefaultANSIColorFgRed NSColor.redColor
#define kDefaultANSIColorFgGreen NSColor.greenColor
#define kDefaultANSIColorFgYellow NSColor.yellowColor
#define kDefaultANSIColorFgBlue NSColor.blueColor
#define kDefaultANSIColorFgMagenta NSColor.magentaColor
#define kDefaultANSIColorFgCyan NSColor.cyanColor
#define kDefaultANSIColorFgWhite NSColor.whiteColor
#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0)
#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0)
#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0)
#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0)
#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0)
#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5)
#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor
#define kDefaultANSIColorBgBlack NSColor.blackColor
#define kDefaultANSIColorBgRed NSColor.redColor
#define kDefaultANSIColorBgGreen NSColor.greenColor
#define kDefaultANSIColorBgYellow NSColor.yellowColor
#define kDefaultANSIColorBgBlue NSColor.blueColor
#define kDefaultANSIColorBgMagenta NSColor.magentaColor
#define kDefaultANSIColorBgCyan NSColor.cyanColor
#define kDefaultANSIColorBgWhite NSColor.whiteColor
#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack
#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed
#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen
#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow
#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue
#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta
#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan
#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite
#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
#define kDefaultForegroundColor NSColor.blackColor
// minimum weight for an NSFont for it to be considered bold
#define kBoldFontMinWeight 9
@implementation AMR_ANSIEscapeHelper
- (id) init
{
if (!(self = [super init]))
return nil;
self.ansiColors = [NSMutableDictionary dictionary];
return self;
}
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
{
if (aString == nil)
return nil;
NSString *cleanString;
NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString:cleanString
attributes:@{
NSFontAttributeName: self.font ?: kDefaultFontSize,
NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
}];
for (NSDictionary *thisAttributeDict in attributesAndRanges)
{
[attributedString
addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
value:thisAttributeDict[kAMRAttrDictKey_attrValue]
range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
];
}
return attributedString;
}
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
{
NSMutableArray *codesAndLocations = [NSMutableArray array];
NSArray *attrNames = @[
NSFontAttributeName, NSForegroundColorAttributeName,
NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
];
for (NSString *thisAttrName in attrNames)
{
NSRange limitRange = NSMakeRange(0, aAttributedString.length);
id attributeValue;
NSRange effectiveRange;
while (limitRange.length > 0)
{
attributeValue = [aAttributedString
attribute:thisAttrName
atIndex:limitRange.location
longestEffectiveRange:&effectiveRange
inRange:limitRange
];
AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
else
thisSGRCode = AMR_SGRCodeFgReset;
}
else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
else
thisSGRCode = AMR_SGRCodeBgReset;
}
else if ([thisAttrName isEqualToString:NSFontAttributeName])
{
// we currently only use NSFontAttributeName for bolding so
// here we assume that the formatting "type" in ANSI SGR
// terms is indeed intensity
if (attributeValue != nil)
thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
else
thisSGRCode = AMR_SGRCodeIntensityNormal;
}
else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
{
if (attributeValue != nil)
{
if ([attributeValue intValue] == NSUnderlineStyleSingle)
thisSGRCode = AMR_SGRCodeUnderlineSingle;
else if ([attributeValue intValue] == NSUnderlineStyleDouble)
thisSGRCode = AMR_SGRCodeUnderlineDouble;
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
{
[codesAndLocations addObject: @{
kAMRCodeDictKey_code: @(thisSGRCode),
kAMRCodeDictKey_location: @(effectiveRange.location),
}];
}
limitRange = NSMakeRange(NSMaxRange(effectiveRange),
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
}
}
return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
}
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSString *cleanString = @"";
// find all escape sequence codes from aString and put them in this array
// along with their start locations within the "clean" version of aString
NSMutableArray *formatCodes = [NSMutableArray array];
NSUInteger aStringLength = aString.length;
NSUInteger coveredLength = 0;
NSRange searchRange = NSMakeRange(0,aStringLength);
NSRange thisEscapeSequenceRange;
do
{
thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
if (thisEscapeSequenceRange.location != NSNotFound)
{
// adjust range's length so that it encompasses the whole ANSI escape sequence
// and not just the Control Sequence Initiator (the "prefix") by finding the
// final byte of the control sequence (one that has an ASCII decimal value
// between 64 and 126.) at the same time, read all formatting codes from inside
// this escape sequence (there may be several, separated by semicolons.)
NSMutableArray *codes = [NSMutableArray array];
unsigned int code = 0;
unsigned int lengthAddition = 1;
NSUInteger thisIndex;
for (;;)
{
thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
if (thisIndex >= aStringLength)
break;
unichar c = [aString characterAtIndex:thisIndex];
if (('0' <= c) && (c <= '9'))
{
int digit = c - '0';
code = (code == 0) ? digit : code*10+digit;
}
// ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
// ("m"). this means that the code value we've just read specifies formatting
// for the output; exactly what we're interested in.
if (c == 'm')
{
[codes addObject:@(code)];
break;
}
else if ((64 <= c) && (c <= 126)) // any other valid final byte
{
[codes removeAllObjects];
break;
}
else if (c == ';') // separates codes within the same sequence
{
[codes addObject:@(code)];
code = 0;
}
lengthAddition++;
}
thisEscapeSequenceRange.length += lengthAddition;
NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
for (NSNumber *codeToAdd in codes)
{
[formatCodes addObject: @{
kAMRCodeDictKey_code: codeToAdd,
kAMRCodeDictKey_location: @(locationInCleanString)
}];
}
NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
if (thisCoveredLength > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
coveredLength += thisCoveredLength;
searchRange.location = NSMaxRange(thisEscapeSequenceRange);
searchRange.length = aStringLength-searchRange.location;
}
}
while(thisEscapeSequenceRange.location != NSNotFound);
if (searchRange.length > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
if (aCleanString)
*aCleanString = cleanString;
return formatCodes;
}
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
{
NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
NSUInteger aCleanStringIndex = 0;
NSUInteger aCleanStringLength = aCleanString.length;
for (NSDictionary *thisCodeDict in codesArray)
{
if (!( thisCodeDict[kAMRCodeDictKey_code] &&
thisCodeDict[kAMRCodeDictKey_location]
))
continue;
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
if (formattingRunStartLocation > aCleanStringLength)
continue;
if (aCleanStringIndex < formattingRunStartLocation)
[retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
aCleanStringIndex = formattingRunStartLocation;
}
if (aCleanStringIndex < aCleanStringLength)
[retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
return retStr;
}
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSMutableArray *attrsAndRanges = [NSMutableArray array];
NSString *cleanString;
NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
// go through all the found escape sequence codes and for each one, create
// the string formatting attribute name and value, find the next escape
// sequence that specifies the end of the formatting run started by
// the currently handled code, and generate a range from the difference
// in those codes' locations within the clean aString.
for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
{
NSDictionary *thisCodeDict = formatCodes[iCode];
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
// the attributed string attribute name for the formatting run introduced
// by this code
NSString *thisAttributeName = nil;
// the attributed string attribute value for this formatting run introduced
// by this code
NSObject *thisAttributeValue = nil;
// set attribute name
switch(thisCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
thisAttributeName = NSForegroundColorAttributeName;
break;
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
thisAttributeName = NSBackgroundColorAttributeName;
break;
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
thisAttributeName = NSFontAttributeName;
break;
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
case AMR_SGRCodeUnderlineNone:
thisAttributeName = NSUnderlineStyleAttributeName;
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
continue;
}
// set attribute value
switch(thisCode)
{
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
case AMR_SGRCodeFgBrightWhite:
thisAttributeValue = [self colorForSGRCode:thisCode];
break;
case AMR_SGRCodeIntensityBold:
{
NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
thisAttributeValue = boldFont;
}
break;
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
{
NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
thisAttributeValue = unboldFont;
}
break;
case AMR_SGRCodeUnderlineSingle:
thisAttributeValue = @(NSUnderlineStyleSingle);
break;
case AMR_SGRCodeUnderlineDouble:
thisAttributeValue = @(NSUnderlineStyleDouble);
break;
case AMR_SGRCodeUnderlineNone:
thisAttributeValue = @(NSUnderlineStyleNone);
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
break;
}
// find the next sequence that specifies the end of this formatting run
NSInteger formattingRunEndLocation = -1;
if (iCode < (formatCodes.count - 1))
{
NSDictionary *thisEndCodeCandidateDict;
unichar thisEndCodeCandidate;
for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
{
thisEndCodeCandidateDict = formatCodes[iEndCode];
thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
{
formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
break;
}
}
}
if (formattingRunEndLocation == -1)
formattingRunEndLocation = cleanString.length;
if (thisAttributeName && thisAttributeValue)
{
[attrsAndRanges addObject:@{
kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
kAMRAttrDictKey_attrName: thisAttributeName,
kAMRAttrDictKey_attrValue: thisAttributeValue,
}];
}
}
if (aCleanString)
*aCleanString = cleanString;
return attrsAndRanges;
}
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
{
switch(startCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
return NO;
}
return NO;
}
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
{
if (self.ansiColors)
{
NSColor *preferredColor = self.ansiColors[@(code)];
if (preferredColor)
return preferredColor;
}
switch(code)
{
case AMR_SGRCodeFgBlack:
return kDefaultANSIColorFgBlack;
case AMR_SGRCodeFgRed:
return kDefaultANSIColorFgRed;
case AMR_SGRCodeFgGreen:
return kDefaultANSIColorFgGreen;
case AMR_SGRCodeFgYellow:
return kDefaultANSIColorFgYellow;
case AMR_SGRCodeFgBlue:
return kDefaultANSIColorFgBlue;
case AMR_SGRCodeFgMagenta:
return kDefaultANSIColorFgMagenta;
case AMR_SGRCodeFgCyan:
return kDefaultANSIColorFgCyan;
case AMR_SGRCodeFgWhite:
return kDefaultANSIColorFgWhite;
case AMR_SGRCodeFgBrightBlack:
return kDefaultANSIColorFgBrightBlack;
case AMR_SGRCodeFgBrightRed:
return kDefaultANSIColorFgBrightRed;
case AMR_SGRCodeFgBrightGreen:
return kDefaultANSIColorFgBrightGreen;
case AMR_SGRCodeFgBrightYellow:
return kDefaultANSIColorFgBrightYellow;
case AMR_SGRCodeFgBrightBlue:
return kDefaultANSIColorFgBrightBlue;
case AMR_SGRCodeFgBrightMagenta:
return kDefaultANSIColorFgBrightMagenta;
case AMR_SGRCodeFgBrightCyan:
return kDefaultANSIColorFgBrightCyan;
case AMR_SGRCodeFgBrightWhite:
return kDefaultANSIColorFgBrightWhite;
case AMR_SGRCodeBgBlack:
return kDefaultANSIColorBgBlack;
case AMR_SGRCodeBgRed:
return kDefaultANSIColorBgRed;
case AMR_SGRCodeBgGreen:
return kDefaultANSIColorBgGreen;
case AMR_SGRCodeBgYellow:
return kDefaultANSIColorBgYellow;
case AMR_SGRCodeBgBlue:
return kDefaultANSIColorBgBlue;
case AMR_SGRCodeBgMagenta:
return kDefaultANSIColorBgMagenta;
case AMR_SGRCodeBgCyan:
return kDefaultANSIColorBgCyan;
case AMR_SGRCodeBgWhite:
return kDefaultANSIColorBgWhite;
case AMR_SGRCodeBgBrightBlack:
return kDefaultANSIColorBgBrightBlack;
case AMR_SGRCodeBgBrightRed:
return kDefaultANSIColorBgBrightRed;
case AMR_SGRCodeBgBrightGreen:
return kDefaultANSIColorBgBrightGreen;
case AMR_SGRCodeBgBrightYellow:
return kDefaultANSIColorBgBrightYellow;
case AMR_SGRCodeBgBrightBlue:
return kDefaultANSIColorBgBrightBlue;
case AMR_SGRCodeBgBrightMagenta:
return kDefaultANSIColorBgBrightMagenta;
case AMR_SGRCodeBgBrightCyan:
return kDefaultANSIColorBgBrightCyan;
case AMR_SGRCodeBgBrightWhite:
return kDefaultANSIColorBgBrightWhite;
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
break;
}
return kDefaultANSIColorFgBlack;
}
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
{
if (self.ansiColors)
{
NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
{
for (NSNumber *thisCode in codesForGivenColor)
{
BOOL thisIsForegroundColor = (thisCode.intValue < 40);
if (aForeground == thisIsForegroundColor)
return thisCode.intValue;
}
}
}
if (aForeground)
{
if ([aColor isEqual:kDefaultANSIColorFgBlack])
return AMR_SGRCodeFgBlack;
else if ([aColor isEqual:kDefaultANSIColorFgRed])
return AMR_SGRCodeFgRed;
else if ([aColor isEqual:kDefaultANSIColorFgGreen])
return AMR_SGRCodeFgGreen;
else if ([aColor isEqual:kDefaultANSIColorFgYellow])
return AMR_SGRCodeFgYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBlue])
return AMR_SGRCodeFgBlue;
else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
return AMR_SGRCodeFgMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgCyan])
return AMR_SGRCodeFgCyan;
else if ([aColor isEqual:kDefaultANSIColorFgWhite])
return AMR_SGRCodeFgWhite;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
return AMR_SGRCodeFgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
return AMR_SGRCodeFgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
return AMR_SGRCodeFgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
return AMR_SGRCodeFgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
return AMR_SGRCodeFgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
return AMR_SGRCodeFgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
return AMR_SGRCodeFgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
return AMR_SGRCodeFgBrightWhite;
}
else
{
if ([aColor isEqual:kDefaultANSIColorBgBlack])
return AMR_SGRCodeBgBlack;
else if ([aColor isEqual:kDefaultANSIColorBgRed])
return AMR_SGRCodeBgRed;
else if ([aColor isEqual:kDefaultANSIColorBgGreen])
return AMR_SGRCodeBgGreen;
else if ([aColor isEqual:kDefaultANSIColorBgYellow])
return AMR_SGRCodeBgYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBlue])
return AMR_SGRCodeBgBlue;
else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
return AMR_SGRCodeBgMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgCyan])
return AMR_SGRCodeBgCyan;
else if ([aColor isEqual:kDefaultANSIColorBgWhite])
return AMR_SGRCodeBgWhite;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
return AMR_SGRCodeBgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
return AMR_SGRCodeBgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
return AMR_SGRCodeBgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
return AMR_SGRCodeBgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
return AMR_SGRCodeBgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
return AMR_SGRCodeBgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
return AMR_SGRCodeBgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
return AMR_SGRCodeBgBrightWhite;
}
return AMR_SGRCodeNoneOrInvalid;
}
// helper struct typedef and a few functions for
// -closestSGRCodeForColor:isForegroundColor:
typedef struct {
CGFloat hue;
CGFloat saturation;
CGFloat brightness;
} AMR_HSB;
AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
{
AMR_HSB outHSB;
outHSB.hue = hue;
outHSB.saturation = saturation;
outHSB.brightness = brightness;
return outHSB;
}
AMR_HSB getHSBFromColor(NSColor *color)
{
CGFloat hue = 0.0;
CGFloat saturation = 0.0;
CGFloat brightness = 0.0;
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
getHue:&hue
saturation:&saturation
brightness:&brightness
alpha:NULL
];
return makeHSB(hue, saturation, brightness);
}
BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
{
return (fabs(first-second)) < maxAbsError;
}
#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
{
if (color == nil)
return AMR_SGRCodeNoneOrInvalid;
AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
return closestColorSGRCode;
AMR_HSB givenColorHSB = getHSBFromColor(color);
CGFloat closestColorHueDiff = FLT_MAX;
CGFloat closestColorSaturationDiff = FLT_MAX;
CGFloat closestColorBrightnessDiff = FLT_MAX;
// (background SGR codes are +10 from foreground ones:)
NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
NSArray *ansiFgColorCodes = @[
@(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
];
for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
{
AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
// comparison depends on hue, saturation and brightness
// (strictly in that order):
if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (hueDiff > closestColorHueDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (saturationDiff > closestColorSaturationDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (brightnessDiff > closestColorBrightnessDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
// If hue (especially hue!), saturation and brightness diffs all
// are equal to some other color, we need to prefer one or the
// other so we'll select the more 'distinctive' color of the
// two (this is *very* subjective, obviously). I basically just
// looked at the hue chart, went through all the points between
// our main ANSI colors and decided which side the middle point
// would lean on. (e.g. the purple color that is exactly between
// the blue and magenta ANSI colors looks more magenta than
// blue to me so I put magenta higher than blue in the list
// below.)
//
// subjective ordering of colors from most to least 'distinctive':
long colorDistinctivenessOrder[6] = {
AMR_SGRCodeFgRed+AMR_SGRCodeShift,
AMR_SGRCodeFgMagenta+AMR_SGRCodeShift,
AMR_SGRCodeFgBlue+AMR_SGRCodeShift,
AMR_SGRCodeFgGreen+AMR_SGRCodeShift,
AMR_SGRCodeFgCyan+AMR_SGRCodeShift,
AMR_SGRCodeFgYellow+AMR_SGRCodeShift
};
for (int i = 0; i < 6; i++)
{
if (colorDistinctivenessOrder[i] == closestColorSGRCode)
break;
else if (colorDistinctivenessOrder[i] == thisSGRCode)
{
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
}
}
}
return closestColorSGRCode;
}
@end

View File

@ -1,34 +0,0 @@
//
// CBBlueLightClient.h
// MTMR
//
// Created by Anton Palgunov on 28/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef struct {
int hour;
int minute;
} Time;
typedef struct {
Time fromTime;
Time toTime;
} Schedule;
typedef struct {
BOOL active;
BOOL enabled;
BOOL sunSchedulePermitted;
int mode;
Schedule schedule;
unsigned long long disableFlags;
} Status;
@interface CBBlueLightClient: NSObject
- (BOOL) setEnabled: (BOOL)enabled;
- (BOOL) setMode: (int)mode;
- (void) getBlueLightStatus: (Status *)status;
@end

View File

@ -1,35 +0,0 @@
//
// LaunchAtLoginController.h
//
// Copyright 2011 Tomáš Znamenáček
// Copyright 2010 Ben Clark-Robinson
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the Software),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@import Foundation;
@import CoreServices;
@interface LaunchAtLoginController : NSObject {}
@property(assign) BOOL launchAtLogin;
- (BOOL) willLaunchAtLogin: (NSURL*) itemURL;
- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL;
@end

View File

@ -1,128 +0,0 @@
//
// LaunchAtLoginController.m
//
// Copyright 2011 Tomáš Znamenáček
// Copyright 2010 Ben Clark-Robinson
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the Software),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "LaunchAtLoginController.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
static NSString *const StartAtLoginKey = @"launchAtLogin";
@interface LaunchAtLoginController ()
@property(assign) LSSharedFileListRef loginItems;
@end
@implementation LaunchAtLoginController
@synthesize loginItems;
#pragma mark Change Observing
void sharedFileListDidChange(LSSharedFileListRef inList, void *context)
{
LaunchAtLoginController *self = (__bridge id) context;
[self willChangeValueForKey:StartAtLoginKey];
[self didChangeValueForKey:StartAtLoginKey];
}
#pragma mark Initialization
- (id) init
{
self = [super init];
loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (voidPtr)CFBridgingRetain(self));
return self;
}
- (void) dealloc
{
LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (__bridge void *)(self));
CFRelease(loginItems);
}
#pragma mark Launch List Control
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
LSSharedFileListItemRef copyItemWithURLinFileList(NSURL* wantedURL, LSSharedFileListRef fileList) {
if (wantedURL == NULL || fileList == NULL)
return NULL;
NSArray *listSnapshot = (__bridge_transfer NSArray *)LSSharedFileListCopySnapshot(fileList, NULL);
for(NSUInteger i = 0; i< [listSnapshot count]; i++) {
LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)[listSnapshot objectAtIndex:i];
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef currentItemURL = NULL;
LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
if (currentItemURL && [(__bridge_transfer NSURL*)currentItemURL isEqual:wantedURL]) {
CFRetain(item);
return item;
}
}
return NULL;
}
#pragma clang diagnostic pop
- (BOOL) willLaunchAtLogin: (NSURL*) itemURL
{
return !!copyItemWithURLinFileList(itemURL, loginItems);
}
- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL
{
LSSharedFileListItemRef appItem = copyItemWithURLinFileList(itemURL, loginItems);
if (enabled && !appItem) {
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst,
NULL, NULL, (__bridge CFURLRef)itemURL, NULL, NULL);
} else if (!enabled && appItem) {
LSSharedFileListItemRemove(loginItems, appItem);
}
if (appItem) {
CFRelease(appItem);
}
}
#pragma mark Basic Interface
- (NSURL*) appURL
{
return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
- (void) setLaunchAtLogin: (BOOL) enabled
{
[self willChangeValueForKey:StartAtLoginKey];
[self setLaunchAtLogin:enabled forURL:[self appURL]];
[self didChangeValueForKey:StartAtLoginKey];
}
- (BOOL) launchAtLogin
{
return [self willLaunchAtLogin:[self appURL]];
}
@end
#pragma clang diagnostic pop

View File

@ -6,12 +6,9 @@
// Copyright © 2018 Anton Palgunov. All rights reserved. // Copyright © 2018 Anton Palgunov. All rights reserved.
// //
#import "AMR_ANSIEscapeHelper.h"
#import "TouchBarPrivateApi.h" #import "TouchBarPrivateApi.h"
#import "TouchBarSupport.h" #import "TouchBarSupport.h"
#import "DeprecatedCarbonAPI.h" #import "DeprecatedCarbonAPI.h"
#import "CBBlueLightClient.h"
#import "LaunchAtLoginController.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -21,7 +18,4 @@ CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef);
CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3); CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3);
CF_EXPORT bool MTActuatorIsOpen(CFTypeRef actuatorRef); CF_EXPORT bool MTActuatorIsOpen(CFTypeRef actuatorRef);
CF_EXPORT void CoreDisplay_Display_SetUserBrightness(int CGDirectDisplayID, double level);
CF_EXPORT double CoreDisplay_Display_GetUserBrightness(int CGDirectDisplayID);
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -17,16 +17,13 @@ extern void DFRSystemModalShowsCloseBoxWhenFrontMost(BOOL);
@interface NSTouchBar (PrivateMethods) @interface NSTouchBar (PrivateMethods)
// macOS 10.14 // presentSystemModalFunctionBar:placement:systemTrayItemIdentifier:
+ (void)presentSystemModalTouchBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)presentSystemModalTouchBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)dismissSystemModalTouchBar:(NSTouchBar *)touchBar;
+ (void)minimizeSystemModalTouchBar:(NSTouchBar *)touchBar;
// macOS 10.13
+ (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier; + (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar placement:(long long)placement systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier; + (void)presentSystemModalFunctionBar:(NSTouchBar *)touchBar systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)dismissSystemModalFunctionBar:(NSTouchBar *)touchBar; + (void)dismissSystemModalFunctionBar:(NSTouchBar *)touchBar;
+ (void)minimizeSystemModalFunctionBar:(NSTouchBar *)touchBar; + (void)minimizeSystemModalFunctionBar:(NSTouchBar *)touchBar;
@end @end

View File

@ -10,6 +10,8 @@
@interface MediaKeys : NSObject @interface MediaKeys : NSObject
+ (void)HIDPostAuxKey:(UInt8)keyCode; + (void)decreaseVolume;
+ (void)increaseVolume;
+ (void)muteVolume;
@end @end

View File

@ -7,7 +7,6 @@
// //
#import "TouchBarSupport.h" #import "TouchBarSupport.h"
#import <IOKit/hidsystem/ev_keymap.h>
@implementation MediaKeys @implementation MediaKeys
@ -31,7 +30,7 @@ static io_connect_t get_event_driver(void)
} }
static void HIDReleaseAuxKey( const UInt8 auxKeyCode ) static void HIDPostAuxKey( const UInt8 auxKeyCode )
{ {
NXEventData event; NXEventData event;
kern_return_t kr; kern_return_t kr;
@ -52,8 +51,16 @@ static void HIDReleaseAuxKey( const UInt8 auxKeyCode )
kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE ); kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE );
} }
+ (void)HIDPostAuxKey: (UInt8)keyCode { + (void)decreaseVolume {
HIDReleaseAuxKey(keyCode); HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN);
}
+ (void)increaseVolume {
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP);
}
+ (void)muteVolume {
HIDPostAuxKey(NX_KEYTYPE_MUTE);
} }
@end @end

View File

@ -1,191 +0,0 @@
//
// CPU.swift
// Pods
//
// Created by zixun on 2016/12/5.
// https://github.com/zixun/SystemEye
// MIT License
//
//
import Foundation
private let HOST_CPU_LOAD_INFO_COUNT : mach_msg_type_number_t =
UInt32(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
/// CPU Class
public class CPU: NSObject {
//--------------------------------------------------------------------------
// MARK: OPEN PROPERTY
//--------------------------------------------------------------------------
// /// Number of physical cores on this machine.
// public static var physicalCores: Int {
// get {
// return Int(System.hostBasicInfo.physical_cpu)
// }
// }
//
// /// Number of logical cores on this machine. Will be equal to physicalCores
// /// unless it has hyper-threading, in which case it will be double.
// public static var logicalCores: Int {
// get {
// return Int(System.hostBasicInfo.logical_cpu)
// }
// }
//--------------------------------------------------------------------------
// MARK: OPEN FUNCTIONS
//--------------------------------------------------------------------------
/// Get CPU usage of hole system (system, user, idle, nice). Determined by the delta between
/// the current and last call.
public static func systemUsage() -> (system: Double,
user: Double,
idle: Double,
nice: Double) {
let load = self.hostCPULoadInfo
let userDiff = Double(load.cpu_ticks.0 - loadPrevious.cpu_ticks.0)
let sysDiff = Double(load.cpu_ticks.1 - loadPrevious.cpu_ticks.1)
let idleDiff = Double(load.cpu_ticks.2 - loadPrevious.cpu_ticks.2)
let niceDiff = Double(load.cpu_ticks.3 - loadPrevious.cpu_ticks.3)
let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
let sys = sysDiff / totalTicks * 100.0
let user = userDiff / totalTicks * 100.0
let idle = idleDiff / totalTicks * 100.0
let nice = niceDiff / totalTicks * 100.0
loadPrevious = load
return (sys, user, idle, nice)
}
/// Get CPU usage of application,get from all thread
open class func applicationUsage() -> Double {
let threads = self.threadBasicInfos()
var result : Double = 0.0
threads.forEach { (thread:thread_basic_info) in
if self.flag(thread) {
result += Double.init(thread.cpu_usage) / Double.init(TH_USAGE_SCALE);
}
}
return result * 100
}
//--------------------------------------------------------------------------
// MARK: PRIVATE PROPERTY
//--------------------------------------------------------------------------
/// previous load of cpu
private static var loadPrevious = host_cpu_load_info()
static var hostCPULoadInfo: host_cpu_load_info {
get {
var size = HOST_CPU_LOAD_INFO_COUNT
var hostInfo = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &hostInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
#if DEBUG
if result != KERN_SUCCESS {
fatalError("ERROR - \(#file):\(#function) - kern_result_t = "
+ "\(result)")
}
#endif
return hostInfo
}
}
//--------------------------------------------------------------------------
// MARK: PRIVATE FUNCTION
//--------------------------------------------------------------------------
private class func flag(_ thread:thread_basic_info) -> Bool {
let foo = thread.flags & TH_FLAGS_IDLE
let number = NSNumber.init(value: foo)
return !Bool.init(truncating: number)
}
private class func threadActPointers() -> [thread_act_t] {
var threads_act = [thread_act_t]()
var threads_array: thread_act_array_t? = nil
var count = mach_msg_type_number_t()
let result = task_threads(mach_task_self_, &(threads_array), &count)
guard result == KERN_SUCCESS else {
return threads_act
}
guard let array = threads_array else {
return threads_act
}
for i in 0..<count {
threads_act.append(array[Int(i)])
}
let krsize = count * UInt32.init(MemoryLayout<thread_t>.size)
_ = vm_deallocate(mach_task_self_, vm_address_t(array.pointee), vm_size_t(krsize));
return threads_act
}
private class func threadBasicInfos() -> [thread_basic_info] {
var result = [thread_basic_info]()
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
var basic_info_th: thread_basic_info_t? = nil
for act_t in self.threadActPointers() {
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
let kr = thread_info(act_t ,thread_flavor_t(THREAD_BASIC_INFO),thinfo, thread_info_count);
if (kr != KERN_SUCCESS) {
return [thread_basic_info]();
}
basic_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_basic_info_t in
let int8Ptr = unsafeBitCast(ptr, to: thread_basic_info_t.self)
return int8Ptr
})
if basic_info_th != nil {
result.append(basic_info_th!.pointee)
}
}
return result
}
//TODO: this function is used for get cpu usage of all thread,and this is in developing
private class func threadIdentifierInfos() -> [thread_identifier_info] {
var result = [thread_identifier_info]()
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
var identifier_info_th: thread_identifier_info_t? = nil
for act_t in self.threadActPointers() {
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
let kr = thread_info(act_t ,thread_flavor_t(THREAD_IDENTIFIER_INFO),thinfo, thread_info_count);
if (kr != KERN_SUCCESS) {
return [thread_identifier_info]();
}
identifier_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_identifier_info_t in
let int8Ptr = unsafeBitCast(ptr, to: thread_identifier_info_t.self)
return int8Ptr
})
if identifier_info_th != nil {
result.append(identifier_info_th!.pointee)
}
}
return result
}
}

View File

@ -8,333 +8,77 @@
import Cocoa import Cocoa
struct ItemAction {
typealias TriggerClosure = (() -> Void)?
let trigger: Action.Trigger
let closure: TriggerClosure
init(trigger: Action.Trigger, _ closure: TriggerClosure) {
self.trigger = trigger
self.closure = closure
}
}
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
private let tapClosure: () -> ()?
private let longTapClosure: () -> ()?
private(set) var button: NSButton!
var actions: [ItemAction] = [] { private var singleClick: NSClickGestureRecognizer!
didSet { private var longClick: NSPressGestureRecognizer!
multiClick.isDoubleClickEnabled = actions.filter({ $0.trigger == .doubleTap }).count > 0
multiClick.isTripleClickEnabled = actions.filter({ $0.trigger == .tripleTap }).count > 0
longClick.isEnabled = actions.filter({ $0.trigger == .longTap }).count > 0
}
}
var finishViewConfiguration: ()->() = {}
private var button: NSButton!
private var longClick: LongPressGestureRecognizer!
private var multiClick: MultiClickGestureRecognizer!
init(identifier: NSTouchBarItem.Identifier, title: String) {
attributedTitle = title.defaultTouchbarAttributedString
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping () -> (), onLongTap callbackLong: @escaping () -> (), bezelColor: NSColor? = .clear) {
self.tapClosure = callback
self.longTapClosure = callbackLong
super.init(identifier: identifier) super.init(identifier: identifier)
button = CustomHeightButton(title: title, target: nil, action: nil) button = NSButton(title: title, target: self, action: nil)
button.bezelColor = bezelColor
longClick = LongPressGestureRecognizer(target: self, action: #selector(handleGestureLong)) button.title = title
longClick.isEnabled = false self.view = button
longClick = NSPressGestureRecognizer(target: self, action: #selector(handleGestureLong))
longClick.allowedTouchTypes = .direct longClick.allowedTouchTypes = .direct
longClick.delegate = self longClick.delegate = self
multiClick = MultiClickGestureRecognizer( singleClick = NSClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
target: self, singleClick.allowedTouchTypes = .direct
action: #selector(handleGestureSingleTap), singleClick.delegate = self
doubleAction: #selector(handleGestureDoubleTap),
tripleAction: #selector(handleGestureTripleTap) self.view.addGestureRecognizer(longClick)
) self.view.addGestureRecognizer(singleClick)
multiClick.allowedTouchTypes = .direct
multiClick.delegate = self
multiClick.isDoubleClickEnabled = false
multiClick.isTripleClickEnabled = false
reinstallButton()
button.attributedTitle = attributedTitle
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
var isBordered: Bool = true {
didSet {
reinstallButton()
}
}
var backgroundColor: NSColor? {
didSet {
reinstallButton()
}
}
var title: String {
get {
return attributedTitle.string
}
set {
attributedTitle = newValue.defaultTouchbarAttributedString
}
}
var attributedTitle: NSAttributedString {
didSet {
button?.imagePosition = attributedTitle.length > 0 ? .imageLeading : .imageOnly
button?.attributedTitle = attributedTitle
}
}
var image: NSImage? {
didSet {
button.image = image
}
}
private func reinstallButton() {
let title = button.attributedTitle
let image = button.image
let cell = CustomButtonCell(parentItem: self)
button.cell = cell
if let color = backgroundColor {
cell.isBordered = true
button.bezelColor = color
button.bezelStyle = .rounded
cell.backgroundColor = color
} else {
button.isBordered = isBordered
button.bezelStyle = isBordered ? .rounded : .inline
}
button.imageScaling = .scaleProportionallyDown
button.imageHugsTitle = true
button.attributedTitle = title
button?.imagePosition = title.length > 0 ? .imageLeading : .imageOnly
button.image = image
view = button
view.addGestureRecognizer(longClick)
// view.addGestureRecognizer(singleClick)
view.addGestureRecognizer(multiClick)
finishViewConfiguration()
}
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool { func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick if gestureRecognizer == singleClick && otherGestureRecognizer == longClick {
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
{
return false return false
} }
return true return true
} }
func callActions(for trigger: Action.Trigger) { @objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
let itemActions = self.actions.filter { $0.trigger == trigger } let hf: HapticFeedback = HapticFeedback()
for itemAction in itemActions {
itemAction.closure?()
}
}
@objc func handleGestureSingleTap() {
callActions(for: .singleTap)
}
@objc func handleGestureDoubleTap() {
callActions(for: .doubleTap)
}
@objc func handleGestureTripleTap() {
callActions(for: .tripleTap)
}
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
switch gr.state { switch gr.state {
case .possible: // tiny hack because we're calling action manually case .ended:
callActions(for: .longTap) hf.tap(strong: 2)
self.tapClosure()
break break
default: default:
break break
} }
} }
}
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
class CustomHeightButton: NSButton { let hf: HapticFeedback = HapticFeedback()
override var intrinsicContentSize: NSSize { switch gr.state {
var size = super.intrinsicContentSize case .began:
size.height = 30 if self.longTapClosure != nil {
return size hf.tap(strong: 2)
} self.tapClosure()
} } else {
hf.tap(strong: 6)
class CustomButtonCell: NSButtonCell { self.longTapClosure()
weak var parentItem: CustomButtonTouchBarItem? print("long click")
init(parentItem: CustomButtonTouchBarItem) {
super.init(textCell: "")
self.parentItem = parentItem
}
override func highlight(_ flag: Bool, withFrame cellFrame: NSRect, in controlView: NSView) {
super.highlight(flag, withFrame: cellFrame, in: controlView)
if !isBordered {
if flag {
setAttributedTitle(attributedTitle, withColor: .lightGray)
} else if let parentItem = self.parentItem {
attributedTitle = parentItem.attributedTitle
} }
break
default:
break
} }
} }
override func drawingRect(forBounds rect: NSRect) -> NSRect {
return rect // need that so content may better fit in button with very limited width
}
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setAttributedTitle(_ title: NSAttributedString, withColor color: NSColor) {
let attrTitle = NSMutableAttributedString(attributedString: title)
attrTitle.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attrTitle.length))
attributedTitle = attrTitle
}
} }
// Thanks to https://stackoverflow.com/a/49843893
final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
private let _action: Selector
private let _doubleAction: Selector
private let _tripleAction: Selector
private var _clickCount: Int = 0
public var isDoubleClickEnabled = true
public var isTripleClickEnabled = true
override var action: Selector? {
get {
return nil /// prevent base class from performing any actions
} set {
if newValue != nil { // if they are trying to assign an actual action
fatalError("Only use init(target:action:doubleAction) for assigning actions")
}
}
}
required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) {
_action = action
_doubleAction = doubleAction
_tripleAction = tripleAction
super.init(target: target, action: nil)
}
required init?(coder: NSCoder) {
fatalError("init(target:action:doubleAction:tripleAction) is only support atm")
}
override func touchesBegan(with event: NSEvent) {
HapticFeedback.instance.tap(type: .click)
super.touchesBegan(with: event)
}
override func touchesEnded(with event: NSEvent) {
HapticFeedback.instance.tap(type: .back)
super.touchesEnded(with: event)
_clickCount += 1
var delayThreshold: TimeInterval // fine tune this as needed
guard isDoubleClickEnabled || isTripleClickEnabled else {
_ = target?.perform(_action)
return
}
if (isTripleClickEnabled) {
delayThreshold = 0.4
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
if _clickCount == 3 {
_ = target?.perform(_tripleAction)
}
} else {
delayThreshold = 0.3
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
if _clickCount == 2 {
_ = target?.perform(_doubleAction)
}
}
}
@objc private func _resetAndPerformActionIfNecessary() {
if _clickCount == 1 {
_ = target?.perform(_action)
}
if isTripleClickEnabled && _clickCount == 2 {
_ = target?.perform(_doubleAction)
}
_clickCount = 0
}
}
class LongPressGestureRecognizer: NSPressGestureRecognizer {
var recognizeTimeout = 0.4
private var timer: Timer?
override func touchesBegan(with event: NSEvent) {
timerInvalidate()
let touches = event.touches(for: self.view!)
if touches.count == 1 { // to prevent it for built-in two/three-finger gestures
timer = Timer.scheduledTimer(timeInterval: recognizeTimeout, target: self, selector: #selector(self.onTimer), userInfo: nil, repeats: false)
}
super.touchesBegan(with: event)
}
override func touchesMoved(with event: NSEvent) {
timerInvalidate() // to prevent it for built-in two/three-finger gestures
super.touchesMoved(with: event)
}
override func touchesCancelled(with event: NSEvent) {
timerInvalidate()
super.touchesCancelled(with: event)
}
override func touchesEnded(with event: NSEvent) {
timerInvalidate()
super.touchesEnded(with: event)
}
private func timerInvalidate() {
if let timer = timer {
timer.invalidate()
self.timer = nil
}
}
@objc private func onTimer() {
if let target = self.target, let action = self.action {
target.performSelector(onMainThread: action, with: self, waitUntilDone: false)
HapticFeedback.instance.tap(type: .strong)
}
}
deinit {
timerInvalidate()
}
}
extension String {
var defaultTouchbarAttributedString: NSAttributedString {
let attrTitle = NSMutableAttributedString(string: self, attributes: [.foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 15, weight: .regular), .baselineOffset: 1])
attrTitle.setAlignment(.center, range: NSRange(location: 0, length: count))
return attrTitle
}
}

View File

@ -9,34 +9,34 @@
import Foundation import Foundation
class CustomSliderCell: NSSliderCell { class CustomSliderCell: NSSliderCell {
var knobImage: NSImage! var knobImage:NSImage!
private var _currentKnobRect: NSRect! private var _currentKnobRect:NSRect!
private var _barRect: NSRect! private var _barRect:NSRect!
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) super.init(coder: aDecoder)
} }
override init() { override init() {
super.init() super.init()
} }
init(knob: NSImage?) { init(knob:NSImage?) {
knobImage = knob knobImage = knob;
super.init() super.init()
} }
override func drawKnob(_ knobRect: NSRect) { override func drawKnob(_ knobRect: NSRect) {
if knobImage == nil { if (knobImage == nil) {
super.drawKnob(knobRect) super.drawKnob(knobRect)
return return;
} }
_currentKnobRect = knobRect _currentKnobRect = knobRect;
drawBar(inside: _barRect, flipped: true) drawBar(inside: _barRect, flipped: true)
let x = (knobRect.origin.x * (_barRect.size.width - (knobImage.size.width - knobRect.size.width)) / _barRect.size.width) + 1 let x = (knobRect.origin.x * (_barRect.size.width - (knobImage.size.width - knobRect.size.width)) / _barRect.size.width)+1;
let y = knobRect.origin.y + 3 let y = knobRect.origin.y+3
knobImage.draw( knobImage.draw(
at: NSPoint(x: x, y: y), at: NSPoint(x: x, y: y),
@ -45,62 +45,63 @@ class CustomSliderCell: NSSliderCell {
fraction: 1 fraction: 1
) )
} }
override func drawBar(inside aRect: NSRect, flipped _: Bool) { override func drawBar(inside aRect: NSRect, flipped: Bool) {
_barRect = aRect _barRect = aRect
var rect = aRect
rect.size.height = CGFloat(4)
let barRadius = CGFloat(2) let barRadius = CGFloat(2)
let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
var bgRect = aRect let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 12))
bgRect.size.height = CGFloat(4)
var leftRect = rect
let bg = NSBezierPath(roundedRect: bgRect, xRadius: barRadius, yRadius: barRadius) leftRect.size.width = finalWidth
let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
NSColor.lightGray.setFill() NSColor.lightGray.setFill()
bg.fill() bg.fill()
var activeRect = bgRect let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius)
activeRect.size.width = CGFloat((Double(bgRect.size.width) / (maxValue - minValue)) * doubleValue)
let active = NSBezierPath(roundedRect: activeRect, xRadius: barRadius, yRadius: barRadius)
NSColor.darkGray.setFill() NSColor.darkGray.setFill()
active.fill() active.fill()
} }
} }
class CustomSlider: NSSlider { class CustomSlider:NSSlider {
var currentValue: CGFloat = 0
var currentValue:CGFloat = 0
override func setNeedsDisplay(_ invalidRect: NSRect) { override func setNeedsDisplay(_ invalidRect: NSRect) {
super.setNeedsDisplay(invalidRect) super.setNeedsDisplay(invalidRect)
} }
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
if (cell?.isKind(of: CustomSliderCell.self)) == false { if ((self.cell?.isKind(of: CustomSliderCell.self)) == false) {
let cell: CustomSliderCell = CustomSliderCell() let cell:CustomSliderCell = CustomSliderCell()
self.cell = cell self.cell = cell
} }
} }
convenience init(knob: NSImage) { convenience init(knob:NSImage) {
self.init() self.init()
cell = CustomSliderCell(knob: knob) self.cell = CustomSliderCell(knob: knob)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
} }
override init(frame frameRect: NSRect) { override init(frame frameRect: NSRect) {
super.init(frame: frameRect) super.init(frame: frameRect)
} }
func knobImage() -> NSImage { func knobImage() -> NSImage {
let cell = self.cell as! CustomSliderCell let cell = self.cell as! CustomSliderCell
return cell.knobImage return cell.knobImage
} }
func setKnobImage(image: NSImage) { func setKnobImage(image:NSImage) {
let cell = self.cell as! CustomSliderCell let cell = self.cell as! CustomSliderCell
cell.knobImage = image cell.knobImage = image
} }

View File

@ -1,17 +0,0 @@
import Foundation
#if swift(>=4.1)
// compactMap supported
#else
extension Sequence {
func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
}
#endif
extension String {
var ifNotEmpty: String? {
return count > 0 ? self : nil
}
}

View File

@ -9,92 +9,49 @@
import IOKit import IOKit
class HapticFeedback { class HapticFeedback {
// Here we have list of possible IDs for Haptic Generator Device. They are not constant
// To find deviceID, you will need IORegistryExplorer app from Additional Tools for Xcode dmg
// which you can download from https://developer.apple.com/download/more/?=Additional%20Tools
// Open IORegistryExplorer app, search for "AppleMultitouchDevice" and get "Multitouch ID"
// or "AppleMultitouchTrackpadHIDEventDriver" and get "mt-device-id"
// There should be programmatic way to get it but I can't find, no docs for macOS :(
private let possibleDeviceIDs: [UInt64] = [
0x200_0000_0100_0000, // MacBook Pro 2016/2017
0x300_0000_8050_0000, // MacBook Pro 2019/2018
0x200_0000_0000_0024, // MacBook Pro (13-inch, M1, 2020)
0x200_0000_0000_0023 // MacBook Pro M1 13-Inch 2020 with 1tb
// 0x300000080500000,
]
// you can get a plist `otool -s __TEXT __tpad_act_plist /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/Current/MultitouchSupport|tail -n +3|awk -F'\t' '{print $2}'|xxd -r -p`
enum HapticType: Int32, CaseIterable {
case back = 1
case click = 2
case weak = 3
case medium = 4
case weakMedium = 5
case strong = 6
case reserved1 = 15
case reserved2 = 16
}
private var actuatorRef: CFTypeRef? private var actuatorRef: CFTypeRef?
private var deviceID: UInt64 = 0x200000001000000
private var error: IOReturn = 0
static var instance = HapticFeedback() // Don't know how to do strong is enum one of
// 1 like back Click
// 2 like Click
// 3 week
// 4 medium
// 5 week medium
// 6 strong
// 15 nothing
// 16 nothing
// you can get a plist `otool -s __TEXT __tpad_act_plist /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/Current/MultitouchSupport|tail -n +3|awk -F'\t' '{print $2}'|xxd -r -p`
// MARK: - Init func tap(strong:Int32) -> Void {
actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
private init() { guard actuatorRef != nil else {
self.recreateDevice() print("guard actuatorRef == nil")
}
private func recreateDevice() {
if let actuatorRef = self.actuatorRef {
MTActuatorClose(actuatorRef)
self.actuatorRef = nil // just in case %)
}
guard self.actuatorRef == nil else {
return return
} }
// Let's find our Haptic device error = MTActuatorOpen(actuatorRef!)
self.possibleDeviceIDs.forEach {(deviceID) in guard error == kIOReturnSuccess else {
let actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
if actuatorRef != nil {
self.actuatorRef = actuatorRef
}
}
}
// MARK: - Tap action
private func getActuatorIfPosible() -> CFTypeRef? {
guard AppSettings.hapticFeedbackState else { return nil }
guard let actuatorRef = self.actuatorRef else {
print("guard actuatorRef == nil (no haptic device found?)")
return nil
}
guard MTActuatorOpen(actuatorRef) == kIOReturnSuccess else {
print("guard MTActuatorOpen") print("guard MTActuatorOpen")
self.recreateDevice() return
return nil
} }
return actuatorRef error = MTActuatorActuate(actuatorRef!, strong, 0, 0.0, 0.0)
} guard error == kIOReturnSuccess else {
func tap(type: HapticType) {
guard let actuator = getActuatorIfPosible() else { return }
guard MTActuatorActuate(actuator, type.rawValue, 0, 0, 0) == kIOReturnSuccess else {
print("guard MTActuatorActuate") print("guard MTActuatorActuate")
return return
} }
guard MTActuatorClose(actuator) == kIOReturnSuccess else { error = MTActuatorClose(actuatorRef!)
guard error == kIOReturnSuccess else {
print("guard MTActuatorClose") print("guard MTActuatorClose")
return return
} }
return
} }
} }

View File

@ -17,52 +17,20 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.27</string> <string>0.13.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>448</string> <string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key> <key>LSUIElement</key>
<true/> <true/>
<key>NSAppleEventsUsageDescription</key>
<string>AppleEvents needed for correct work AppleScript</string>
<key>NSAppleMusicUsageDescription</key>
<string>MTMR needs access to Media for work</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>MTMR needs access to Bluetooth for work</string>
<key>NSCalendarsUsageDescription</key>
<string>MTMR needs access to Calendar for work</string>
<key>NSCameraUsageDescription</key>
<string>MTMR needs access to Camera for work</string>
<key>NSHomeKitUsageDescription</key>
<string>MTMR needs access to HomeKit for work</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 - 2020 Anton Palgunov. All rights reserved.</string> <string>Copyright © 2018 Anton Palgunov. All rights reserved.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationUsageDescription</key> <key>NSLocationUsageDescription</key>
<string>Weather widget need your location for correct work</string> <string>...</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSMainStoryboardFile</key> <key>NSMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>MTMR needs access to Photo for work</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSRemindersUsageDescription</key>
<string>MTMR needs access to Reminders for work</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>MTMR needs access to Administation for work</string>
<key>SUFeedURL</key>
<string>https://mtmr.app/appcast.xml</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
<key>kTCCServiceMediaLibrary</key>
<string>MTMR needs access to Music for work</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,283 +1,162 @@
import AppKit
import Foundation import Foundation
import AppKit
extension Data { extension Data {
func barItemDefinitions() -> [BarItemDefinition]? { func barItemDefinitions() -> [BarItemDefinition]? {
return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!)
} }
} }
struct BarItemDefinition: Decodable { struct BarItemDefinition: Decodable {
let type: ItemType let type: ItemType
let actions: [Action] let action: ActionType
let legacyAction: LegacyActionType let longAction: LongActionType
let legacyLongAction: LegacyLongActionType
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter] let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type case type
case actions
} }
init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) { init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) {
self.type = type self.type = type
self.actions = actions self.action = action
self.legacyAction = action self.longAction = longAction
self.legacyLongAction = legacyLongAction
self.additionalParameters = additionalParameters self.additionalParameters = additionalParameters
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type) let type = try container.decode(String.self, forKey: .type)
let actions = try container.decodeIfPresent([Action].self, forKey: .actions) let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type, actions: actions ?? [])
var additionalParameters = try GeneralParameters(from: decoder).parameters var additionalParameters = try GeneralParameters(from: decoder).parameters
if let result = try? parametersDecoder(decoder), if let result = try? parametersDecoder(decoder),
case let (itemType, actions, action, longAction, parameters) = result { case let (itemType, action, longAction, parameters) = result {
parameters.forEach { additionalParameters[$0] = $1 } parameters.forEach { additionalParameters[$0] = $1 }
self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters) self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters)
} else { } else {
self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters) self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
} }
} }
} }
typealias ParametersDecoder = (Decoder) throws -> (
item: ItemType,
actions: [Action],
legacyAction: LegacyActionType,
legacyLongAction: LegacyLongActionType,
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
)
class SupportedTypesHolder { class SupportedTypesHolder {
typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, longAction: LongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter])
private var supportedTypes: [String: ParametersDecoder] = [ private var supportedTypes: [String: ParametersDecoder] = [
"escape": { _ in ( "escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), longAction: .none, parameters: [.align: .align(.left)]) },
item: .staticButton(title: "esc"), "delete": { _ in return (item: .staticButton(title: "del"), action: .keyPress(keycode: 117), longAction: .none, parameters: [:])},
actions: [
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.align: .align(.left)]
) },
"delete": { _ in (
item: .staticButton(title: "del"),
actions: [
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
) },
"brightnessUp": { _ in "brightnessUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp")) let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
return ( return (item: .staticButton(title: ""), action: .keyPress(keycode: 113), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"brightnessDown": { _ in "brightnessDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown")) let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
return ( return (item: .staticButton(title: ""), action: .keyPress(keycode: 107), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"illuminationUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"illuminationDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
return (
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
},
"volumeDown": { _ in "volumeDown": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"volumeUp": { _ in "volumeUp": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"mute": { _ in "mute": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarAudioOutputMuteTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_MUTE), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"previous": { _ in "previous": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"play": { _ in "play": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"next": { _ in "next": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!)
return ( return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), longAction: .none, parameters: [.image: imageParameter])
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.image: imageParameter]
)
}, },
"weather": { decoder in
"sleep": { _ in ( enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key ; case icon_type }
item: .staticButton(title: "☕️"), let container = try decoder.container(keyedBy: CodingKeys.self)
actions: [ let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"])) let units = try container.decodeIfPresent(String.self, forKey: .units)
], let api_key = try container.decodeIfPresent(String.self, forKey: .api_key)
legacyAction: .none, let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type)
legacyLongAction: .none, let action = try ActionType(from: decoder)
parameters: [:] 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: action, longAction: longAction, parameters: [:])
},
"displaySleep": { _ in ( "currency": { decoder in
item: .staticButton(title: "☕️"), enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to }
actions: [ let container = try decoder.container(keyedBy: CodingKeys.self)
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"])) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
], let from = try container.decodeIfPresent(String.self, forKey: .from)
legacyAction: .none, let to = try container.decodeIfPresent(String.self, forKey: .to)
legacyLongAction: .none, let action = try ActionType(from: decoder)
parameters: [:] let longAction = try LongActionType(from: decoder)
) }, return (item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD"), action: action, longAction: longAction, parameters: [:])
},
"dock": { decoder in
return (item: .dock(), action: .none, longAction: .none, parameters: [:])
},
"inputsource": { decoder in
return (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 return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), longAction: .none, parameters: [:]) },
"displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), longAction: .none, parameters: [:])},
] ]
static let sharedInstance = SupportedTypesHolder() static let sharedInstance = SupportedTypesHolder()
func lookup(by type: String, actions: [Action]) -> ParametersDecoder { func lookup(by type: String) -> ParametersDecoder {
return supportedTypes[type] ?? { decoder in ( return supportedTypes[type] ?? { decoder in
item: try ItemType(from: decoder), return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), longAction: try LongActionType(from: decoder), parameters: [:])
actions: actions, }
legacyAction: try LegacyActionType(from: decoder),
legacyLongAction: try LegacyLongActionType(from: decoder),
parameters: [:]
) }
} }
func register(typename: String, decoder: @escaping ParametersDecoder) { func register(typename: String, decoder: @escaping ParametersDecoder) {
supportedTypes[typename] = decoder supportedTypes[typename] = decoder
} }
func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) { func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
register(typename: typename) { _ in register(typename: typename) { _ in
( return (item: item, action: action, longAction: longAction, parameters: [:])
item: item,
actions,
legacyAction,
legacyLongAction,
parameters: [:]
)
} }
} }
} }
enum ItemType: Decodable { enum ItemType: Decodable {
case staticButton(title: String) case staticButton(title: String)
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double, alternativeImages: [String: SourceProtocol]) case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case timeButton(formatTemplate: String)
case timeButton(formatTemplate: String, timeZone: String?, locale: String?) case battery()
case battery case dock()
case cpu(refreshInterval: Double) case volume()
case dock(autoResize: Bool, filter: String?)
case volume
case brightness(refreshInterval: Double) case brightness(refreshInterval: Double)
case weather(interval: Double, units: String, api_key: String, icon_type: String) case weather(interval: Double, units: String, api_key: String, icon_type: String)
case yandexWeather(interval: Double) case currency(interval: Double, from: String, to: String)
case currency(interval: Double, from: String, to: String, full: Bool) case inputsource()
case inputsource
case music(interval: Double, disableMarquee: Bool)
case group(items: [BarItemDefinition])
case nightShift
case dnd
case pomodoro(workTime: Double, restTime: Double)
case network(flip: Bool, units: String)
case darkMode
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool)
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type case type
@ -286,55 +165,26 @@ enum ItemType: Decodable {
case refreshInterval case refreshInterval
case from case from
case to case to
case full
case timeZone
case units case units
case api_key case api_key
case icon_type case icon_type
case formatTemplate case formatTemplate
case locale
case image case image
case url case url
case longUrl case longUrl
case items
case workTime
case restTime
case flip
case autoResize
case filter
case disableMarquee
case alternativeImages
case sourceApple
case sourceBash
case direction
case fingers
case minOffset
case maxToShow
} }
enum ItemTypeRaw: String, Decodable { enum ItemTypeRaw: String, Decodable {
case staticButton case staticButton
case appleScriptTitledButton case appleScriptTitledButton
case shellScriptTitledButton
case timeButton case timeButton
case battery case battery
case cpu
case dock case dock
case volume case volume
case brightness case brightness
case weather case weather
case yandexWeather
case currency case currency
case inputsource case inputsource
case music
case group
case nightShift
case dnd
case pomodoro
case network
case darkMode
case swipe
case upnext
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
@ -344,205 +194,46 @@ enum ItemType: Decodable {
case .appleScriptTitledButton: case .appleScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source) let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
let alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:] self = .appleScriptTitledButton(source: source, refreshInterval: interval)
self = .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages)
case .shellScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .shellScriptTitledButton(source: source, refreshInterval: interval)
case .staticButton: case .staticButton:
let title = try container.decode(String.self, forKey: .title) let title = try container.decode(String.self, forKey: .title)
self = .staticButton(title: title) self = .staticButton(title: title)
case .timeButton: case .timeButton:
let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm" let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm"
let timeZone = try container.decodeIfPresent(String.self, forKey: .timeZone) ?? nil self = .timeButton(formatTemplate: template)
let locale = try container.decodeIfPresent(String.self, forKey: .locale) ?? nil
self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale)
case .battery: case .battery:
self = .battery self = .battery()
case .cpu:
let refreshInterval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
self = .cpu(refreshInterval: refreshInterval)
case .dock: case .dock:
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false self = .dock()
let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter)
self = .dock(autoResize: autoResize, filter: filterRegexString)
case .volume: case .volume:
self = .volume self = .volume()
case .brightness: case .brightness:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
self = .brightness(refreshInterval: interval) self = .brightness(refreshInterval: interval)
case .weather: case .weather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric" let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric"
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6"
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text"
self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type) self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type)
case .yandexWeather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .yandexWeather(interval: interval)
case .currency: case .currency:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0
let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB" let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB"
let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD" let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD"
let full = try container.decodeIfPresent(Bool.self, forKey: .full) ?? false self = .currency(interval: interval, from: from, to: to)
self = .currency(interval: interval, from: from, to: to, full: full)
case .inputsource: 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 .group:
let items = try container.decode([BarItemDefinition].self, forKey: .items)
self = .group(items: items)
case .nightShift:
self = .nightShift
case .dnd:
self = .dnd
case .pomodoro:
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0
let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime) ?? 600.0
self = .pomodoro(workTime: workTime, restTime: restTime)
case .network:
let flip = try container.decodeIfPresent(Bool.self, forKey: .flip) ?? false
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "dynamic"
self = .network(flip: flip, units: units)
case .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)
case .upnext:
let from = try container.decodeIfPresent(Double.self, forKey: .from) ?? 0 // Lower bounds of period of time in hours to search for events
let to = try container.decodeIfPresent(Double.self, forKey: .to) ?? 12 // Upper bounds of period of time in hours to search for events
let maxToShow = try container.decodeIfPresent(Int.self, forKey: .maxToShow) ?? 3 // 1 indexed array. Get the 1st, 2nd, 3rd event to display multiple notifications
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 60.0
self = .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
} }
} }
} }
struct FailableDecodable<Base : Decodable> : Decodable { enum ActionType: Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
struct Action: Decodable {
enum Trigger: String, Decodable {
case singleTap
case doubleTap
case tripleTap
case longTap
}
enum Value {
case none
case hidKey(keycode: Int32)
case keyPress(keycode: Int)
case appleScript(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: () -> Void)
case openUrl(url: String)
}
private enum ActionTypeRaw: String, Decodable {
case hidKey
case keyPress
case appleScript
case shellScript
case openUrl
}
enum CodingKeys: String, CodingKey {
case trigger
case action
case keycode
case actionAppleScript
case executablePath
case shellArguments
case url
}
let trigger: Trigger
let value: Value
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
trigger = try container.decode(Trigger.self, forKey: .trigger)
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
switch type {
case .some(.hidKey):
let keycode = try container.decode(Int32.self, forKey: .keycode)
value = .hidKey(keycode: keycode)
case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .keycode)
value = .keyPress(keycode: keycode)
case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .actionAppleScript)
value = .appleScript(source: source)
case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
value = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl):
let url = try container.decode(String.self, forKey: .url)
value = .openUrl(url: url)
case .none:
value = .none
}
}
init(trigger: Trigger, value: Value) {
self.trigger = trigger
self.value = value
}
}
enum LegacyActionType: Decodable {
case none case none
case hidKey(keycode: Int32) case hidKey(keycode: Int)
case keyPress(keycode: Int) case keyPress(keycode: Int)
case appleScript(source: SourceProtocol) case appleSctipt(source: SourceProtocol)
case shellScript(executable: String, parameters: [String]) case shellScript(executable: String, parameters: [String])
case custom(closure: () -> Void) case custom(closure: ()->())
case openUrl(url: String) case openUrl(url: String)
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
@ -565,53 +256,48 @@ enum LegacyActionType: Decodable {
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action) let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
switch type { switch type {
case .some(.hidKey): case .some(.hidKey):
let keycode = try container.decode(Int32.self, forKey: .keycode) let keycode = try container.decode(Int.self, forKey: .keycode)
self = .hidKey(keycode: keycode) self = .hidKey(keycode: keycode)
case .some(.keyPress): case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .keycode) let keycode = try container.decode(Int.self, forKey: .keycode)
self = .keyPress(keycode: keycode) self = .keyPress(keycode: keycode)
case .some(.appleScript): case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .actionAppleScript) let source = try container.decode(Source.self, forKey: .actionAppleScript)
self = .appleScript(source: source) self = .appleSctipt(source: source)
case .some(.shellScript): case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .executablePath) let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters) self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl): case .some(.openUrl):
let url = try container.decode(String.self, forKey: .url) let url = try container.decode(String.self, forKey: .url)
self = .openUrl(url: url) self = .openUrl(url: url)
case .none: case .none:
self = .none self = .none
} }
} }
} }
enum LegacyLongActionType: Decodable {
case none
case hidKey(keycode: Int32)
case keyPress(keycode: Int)
case appleScript(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: () -> Void)
case openUrl(url: String)
enum LongActionType: Decodable {
case none
case hidKey(keycode: Int)
case keyPress(keycode: Int)
case appleSctipt(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: ()->())
case openUrl(url: String)
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case longAction case longAction
case longKeycode case keycode
case longActionAppleScript case actionAppleScript
case longExecutablePath case executablePath
case longShellArguments case shellArguments
case longUrl case longUrl
} }
private enum LongActionTypeRaw: String, Decodable { private enum LongActionTypeRaw: String, Decodable {
case hidKey case hidKey
case keyPress case keyPress
@ -619,47 +305,89 @@ enum LegacyLongActionType: Decodable {
case shellScript case shellScript
case openUrl case openUrl
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction) let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction)
switch longType { switch longType {
case .some(.hidKey): case .some(.hidKey):
let keycode = try container.decode(Int32.self, forKey: .longKeycode) let keycode = try container.decode(Int.self, forKey: .keycode)
self = .hidKey(keycode: keycode) self = .hidKey(keycode: keycode)
case .some(.keyPress): case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .longKeycode) let keycode = try container.decode(Int.self, forKey: .keycode)
self = .keyPress(keycode: keycode) self = .keyPress(keycode: keycode)
case .some(.appleScript): case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .longActionAppleScript) let source = try container.decode(Source.self, forKey: .actionAppleScript)
self = .appleScript(source: source) self = .appleSctipt(source: source)
case .some(.shellScript): case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .longExecutablePath) let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .longShellArguments) ?? [] let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters) self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl): case .some(.openUrl):
let longUrl = try container.decode(String.self, forKey: .longUrl) let longUrl = try container.decode(String.self, forKey: .longUrl)
self = .openUrl(url: longUrl) self = .openUrl(url: longUrl)
case .none: case .none:
self = .none self = .none
} }
} }
} }
extension ItemType: Equatable {}
func ==(lhs: ItemType, rhs: ItemType) -> Bool {
switch (lhs, rhs) {
case let (.staticButton(a), .staticButton(b)):
return a == b
case let (.appleScriptTitledButton(a, b), .appleScriptTitledButton(c, d)):
return a == c && b == d
default:
return false
}
}
extension ActionType: Equatable {}
func ==(lhs: ActionType, rhs: ActionType) -> Bool {
switch (lhs, rhs) {
case (.none, .none):
return true
case let (.hidKey(a), .hidKey(b)),
let (.keyPress(a), .keyPress(b)):
return a == b
case let (.appleSctipt(a), .appleSctipt(b)):
return a == b
case let (.shellScript(a, b), .shellScript(c, d)):
return a == c && b == d
case let (.openUrl(a), .openUrl(b)):
return a == b
default:
return false
}
}
extension LongActionType: Equatable {}
func ==(lhs: LongActionType, rhs: LongActionType) -> Bool {
switch (lhs, rhs) {
case (.none, .none):
return true
case let (.hidKey(a), .hidKey(b)),
let (.keyPress(a), .keyPress(b)):
return a == b
case let (.appleSctipt(a), .appleSctipt(b)):
return a == b
case let (.shellScript(a, b), .shellScript(c, d)):
return a == c && b == d
case let (.openUrl(a), .openUrl(b)):
return a == b
default:
return false
}
}
enum GeneralParameter { enum GeneralParameter {
case width(_: CGFloat) case width(_: CGFloat)
case image(source: SourceProtocol) case image(source: SourceProtocol)
case align(_: Align) case align(_: Align)
case bordered(_: Bool)
case background(_: NSColor)
case title(_: String)
case matchAppId(_: String)
} }
struct GeneralParameters: Decodable { struct GeneralParameters: Decodable {
@ -669,138 +397,84 @@ struct GeneralParameters: Decodable {
case width case width
case image case image
case align case align
case bordered
case background
case title
case matchAppId
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:] var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:]
if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) { if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) {
result[.width] = .width(value) result[.width] = .width(value)
} }
if let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) { if let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) {
result[.image] = .image(source: imageSource) result[.image] = .image(source: imageSource)
} }
let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center
result[.align] = .align(align) result[.align] = .align(align)
if let borderedFlag = try container.decodeIfPresent(Bool.self, forKey: .bordered) {
result[.bordered] = .bordered(borderedFlag)
}
if let backgroundColor = try container.decodeIfPresent(String.self, forKey: .background)?.hexColor {
result[.background] = .background(backgroundColor)
}
if let title = try container.decodeIfPresent(String.self, forKey: .title) {
result[.title] = .title(title)
}
if let matchAppId = try container.decodeIfPresent(String.self, forKey: .matchAppId) {
result[.matchAppId] = .matchAppId(matchAppId)
}
parameters = result parameters = result
} }
} }
protocol SourceProtocol { protocol SourceProtocol {
var data: Data? { get } var data: Data? { get }
var string: String? { get } var string: String? { get }
var image: NSImage? { get } var image: NSImage? { get }
var appleScript: NSAppleScript? { get }
} }
struct Source: Decodable, SourceProtocol { struct Source: Decodable, SourceProtocol {
let filePath: String? let filePath: String?
let base64: String? let base64: String?
let inline: String? let inline: String?
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case filePath case filePath
case base64 case base64
case inline case inline
} }
var data: Data? { var data: Data? {
return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData
} }
var string: String? { var string: String? {
return inline ?? filePath?.fileString return inline ?? self.data?.utf8string
} }
var image: NSImage? { var image: NSImage? {
return data?.image return data?.image
} }
var appleScript: NSAppleScript? {
return filePath?.fileURL.appleScript ?? string?.appleScript
}
private init(filePath: String?, base64: String?, inline: String?) { private init(filePath: String?, base64: String?, inline: String?) {
self.filePath = filePath self.filePath = filePath
self.base64 = base64 self.base64 = base64
self.inline = inline self.inline = inline
} }
init(filePath: String) { init(filePath: String) {
self.init(filePath: filePath, base64: nil, inline: nil) self.init(filePath: filePath, base64: nil, inline: nil)
} }
} }
extension NSImage: SourceProtocol { extension NSImage: SourceProtocol {
var data: Data? { var data: Data? {
return nil return nil
} }
var string: String? { var string: String? {
return nil return nil
} }
var image: NSImage? { var image: NSImage? {
return self return self
} }
}
var appleScript: NSAppleScript? { extension SourceProtocol where Self: Equatable {}
return nil func ==(left: SourceProtocol, right: SourceProtocol) -> Bool {
} return left.data == right.data
} }
extension String { extension String {
var base64Data: Data? { var base64Data: Data? {
return Data(base64Encoded: self) return Data(base64Encoded: self)
} }
var fileData: Data? { var fileData: Data? {
return try? Data(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath)) return try? Data(contentsOf: URL(fileURLWithPath: self))
}
var fileString: String? {
var encoding: String.Encoding = .utf8
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)
} }
} }
extension Data { extension Data {
var utf8string: String? { var utf8string: String? {
return String(data: self, encoding: .utf8) return String(data: self, encoding: .utf8)
} }
var image: NSImage? { var image: NSImage? {
return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24)) return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24))
} }
@ -811,10 +485,3 @@ enum Align: String, Decodable {
case center case center
case right case right
} }
extension URL {
var appleScript: NSAppleScript? {
guard FileManager.default.fileExists(atPath: path) else { return nil }
return NSAppleScript(contentsOf: self, error: nil)
}
}

View File

@ -18,7 +18,7 @@ struct GenericKeyPress: KeyPress {
} }
extension KeyPress { extension KeyPress {
func send() { func send () {
let src = CGEventSource(stateID: .hidSystemState) let src = CGEventSource(stateID: .hidSystemState)
let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true) let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true)
let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false) let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false)
@ -29,7 +29,45 @@ extension KeyPress {
} }
} }
func HIDPostAuxKey(_ key: Int32) { func doKey(_ key: Int, down: Bool) {
let key = UInt8(key) let flags = NSEvent.ModifierFlags(rawValue: down ? 0xa00 : 0xb00)
MediaKeys.hidPostAuxKey(key) let data1 = (key << 16) | ((down ? 0xa : 0xb) << 8)
let ev = NSEvent.otherEvent(
with: NSEvent.EventType.systemDefined,
location: NSPoint(x:0.0, y:0.0),
modifierFlags: flags,
timestamp: TimeInterval(0),
windowNumber: 0,
context: nil,
// context: 0,
subtype: 8,
data1: data1,
data2: -1
)
let cev = ev!.cgEvent!
cev.post(tap: CGEventTapLocation(rawValue: 0)!)
} }
func HIDPostAuxKey(_ key: Int) {
doKey(key, down: true)
doKey(key, down: false)
}
// hidsystem/ev_keymap.h
let NX_KEYTYPE_SOUND_UP = 0
let NX_KEYTYPE_SOUND_DOWN = 1
let NX_KEYTYPE_MUTE = 7
let NX_KEYTYPE_BRIGHTNESS_UP = 2
let NX_KEYTYPE_BRIGHTNESS_DOWN = 3
let NX_KEYTYPE_PLAY = 16
let NX_KEYTYPE_NEXT = 17
let NX_KEYTYPE_PREVIOUS = 18

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ { class ScrollViewItem: NSCustomTouchBarItem {
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) { init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
super.init(identifier: identifier) super.init(identifier: identifier)
let views = items.compactMap { $0.view } let views = items.compactMap { $0.view }
@ -10,11 +10,11 @@ class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
stackView.orientation = .horizontal stackView.orientation = .horizontal
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize)) let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
scrollView.documentView = stackView scrollView.documentView = stackView
view = scrollView self.view = scrollView
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
} }

View File

@ -1,113 +0,0 @@
//
// ShellScriptTouchBarItem.swift
// MTMR
//
// Created by bobr on 08/08/2019.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
private let interval: TimeInterval
private let source: String
private var forceHideConstraint: NSLayoutConstraint!
struct ScriptResult: Decodable {
var title: String?
var image: Source?
}
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
self.interval = interval
self.source = source.string ?? "echo No \"source\""
super.init(identifier: identifier, title: "")
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
DispatchQueue.shellScriptQueue.async {
self.refreshAndSchedule()
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
// Execute script and get result
let scriptResult = execute(source)
var rawTitle: String, image: NSImage?
var json: Bool
do {
let decoder = JSONDecoder()
let result = try decoder.decode(ScriptResult.self, from: scriptResult.data(using: .utf8)!)
json = true
rawTitle = result.title ?? ""
image = result.image?.image
} catch {
json = false
rawTitle = scriptResult
}
// Apply returned text attributes (if they were returned) to our result string
let helper = AMR_ANSIEscapeHelper.init()
helper.defaultStringColor = NSColor.white
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: rawTitle) ?? NSAttributedString(string: ""))
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
let newBackgoundColor: NSColor? = title.length != 0 ? title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor : nil
// Update UI
DispatchQueue.main.async { [weak self, newBackgoundColor] in
if (newBackgoundColor != self?.backgroundColor) { // performance optimization because of reinstallButton
self?.backgroundColor = newBackgoundColor
}
self?.attributedTitle = title
if json {
self?.image = image
}
self?.forceHideConstraint.isActive = scriptResult == ""
}
// Schedule next update
DispatchQueue.shellScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
self?.refreshAndSchedule()
}
}
func execute(_ command: String) -> String {
let task = Process()
if let shell = getenv("SHELL") {
task.launchPath = String.init(cString: shell)
} else {
task.launchPath = "/bin/bash"
}
task.arguments = ["-c", command]
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"
}
return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression)
}
}
extension DispatchQueue {
static let shellScriptQueue = DispatchQueue(label: "mtmr.shellscript")
}

View File

@ -6,99 +6,83 @@
// Copyright © 2018 Anton Palgunov. All rights reserved. // Copyright © 2018 Anton Palgunov. All rights reserved.
// //
import AppKit
import Foundation import Foundation
extension String { extension String {
func trim() -> String { func trim() -> String {
return trimmingCharacters(in: NSCharacterSet.whitespaces) return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
} }
func stripComments() -> String { func stripComments() -> String {
// ((\s|,)\/\*[\s\S]*?\*\/)|(( |, ")\/\/.*) // ((\s|,)\/\*[\s\S]*?\*\/)|(( |, ")\/\/.*)
return replacingOccurrences(of: "((\\s|,)\\/\\*[\\s\\S]*?\\*\\/)|(( |, \\\")\\/\\/.*)", with: "", options: .regularExpression) return self.replacingOccurrences(of: "((\\s|,)\\/\\*[\\s\\S]*?\\*\\/)|(( |, \\\")\\/\\/.*)", with: "", options: .regularExpression)
}
var hexColor: NSColor? {
let hex = trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.count {
case 3: // RGB (12-bit)
(r, g, b, a) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17, 255)
case 6: // RGB (24-bit)
(r, g, b, a) = (int >> 16, int >> 8 & 0xFF, int & 0xFF, 255)
case 8: // ARGB (32-bit)
(r, g, b, a) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
return nil
}
return NSColor(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
} }
} }
extension NSImage { extension NSImage {
func resize(maxSize: NSSize) -> NSImage { func resize(maxSize:NSSize) -> NSImage {
var ratio: Float = 0.0 var ratio:Float = 0.0
let imageWidth = Float(size.width) let imageWidth = Float(self.size.width)
let imageHeight = Float(size.height) let imageHeight = Float(self.size.height)
let maxWidth = Float(maxSize.width) let maxWidth = Float(maxSize.width)
let maxHeight = Float(maxSize.height) let maxHeight = Float(maxSize.height)
// Get ratio (landscape or portrait) // Get ratio (landscape or portrait)
if imageWidth > imageHeight { if (imageWidth > imageHeight) {
// Landscape // Landscape
ratio = maxWidth / imageWidth ratio = maxWidth / imageWidth;
} else {
// Portrait
ratio = maxHeight / imageHeight
} }
else {
// Portrait
ratio = maxHeight / imageHeight;
}
// Calculate new size based on the ratio // Calculate new size based on the ratio
let newWidth = imageWidth * ratio let newWidth = imageWidth * ratio
let newHeight = imageHeight * ratio let newHeight = imageHeight * ratio
// Create a new NSSize object with the newly calculated size // Create a new NSSize object with the newly calculated size
let newSize: NSSize = NSSize(width: Int(newWidth), height: Int(newHeight)) let newSize:NSSize = NSSize(width: Int(newWidth), height: Int(newHeight))
// Cast the NSImage to a CGImage // Cast the NSImage to a CGImage
var imageRect: NSRect = NSMakeRect(0, 0, size.width, size.height) var imageRect:NSRect = NSMakeRect(0, 0, self.size.width, self.size.height)
let imageRef = cgImage(forProposedRect: &imageRect, context: nil, hints: nil) let imageRef = self.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
// Create NSImage from the CGImage using the new size // Create NSImage from the CGImage using the new size
let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize) let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize)
// Return the new image // Return the new image
return imageWithNewSize return imageWithNewSize
} }
func rotateByDegreess(degrees: CGFloat) -> NSImage { func rotateByDegreess(degrees:CGFloat) -> NSImage {
var imageBounds = NSZeroRect; imageBounds.size = size
var imageBounds = NSZeroRect ; imageBounds.size = self.size
let pathBounds = NSBezierPath(rect: imageBounds) let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform() var transform = NSAffineTransform()
transform.rotate(byDegrees: degrees) transform.rotate(byDegrees: degrees)
pathBounds.transform(using: transform as AffineTransform) pathBounds.transform(using: transform as AffineTransform)
let rotatedBounds: NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, size.width, size.height) let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y , self.size.width, self.size.height )
let rotatedImage = NSImage(size: rotatedBounds.size) let rotatedImage = NSImage(size: rotatedBounds.size)
// Center the image within the rotated bounds //Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2) imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2) imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform // Start a new transform
transform = NSAffineTransform() transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center) // Move coordinate system to the center (since we want to rotate around the center)
transform.translateX(by: +(NSWidth(rotatedBounds) / 2), yBy: +(NSHeight(rotatedBounds) / 2)) transform.translateX(by: +(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotate(byDegrees: degrees) transform.rotate(byDegrees: degrees)
// Move the coordinate system bak to normal // Move the coordinate system bak to normal
transform.translateX(by: -(NSWidth(rotatedBounds) / 2), yBy: -(NSHeight(rotatedBounds) / 2)) transform.translateX(by: -(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image // Draw the original image, rotated, into the new image
rotatedImage.lockFocus() rotatedImage.lockFocus()
transform.concat() transform.concat()
draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0) self.draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus() rotatedImage.unlockFocus()
return rotatedImage return rotatedImage
} }
} }

View File

@ -1,31 +0,0 @@
//
// ExtendNSTouchBar.swift
// MTMR
//
// Created by Anton Palgunov on 07/06/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
func presentSystemModal(_ touchBar: NSTouchBar!, systemTrayItemIdentifier identifier: NSTouchBarItem.Identifier!) {
if #available(OSX 10.14, *) {
NSTouchBar.presentSystemModalTouchBar(touchBar, systemTrayItemIdentifier: identifier)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, systemTrayItemIdentifier: identifier)
}
}
func presentSystemModal(_ touchBar: NSTouchBar!, placement: Int64, systemTrayItemIdentifier identifier: NSTouchBarItem.Identifier!) {
if #available(OSX 10.14, *) {
NSTouchBar.presentSystemModalTouchBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
}
}
func minimizeSystemModal(_ touchBar: NSTouchBar!) {
if #available(OSX 10.14, *) {
NSTouchBar.minimizeSystemModalTouchBar(touchBar)
} else {
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
}
}

View File

@ -1,78 +0,0 @@
//
// 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()
if let shell = getenv("SHELL") {
task.launchPath = String.init(cString: shell)
} else {
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)")
}
}
}
}
func isEqual(_ object: AnyObject?) -> Bool {
if let object = object as? SwipeItem {
return self.scriptApple?.source as String? == object.scriptApple?.source as String? && self.scriptBash == object.scriptBash && self.direction == object.direction && self.fingers == object.fingers && self.minOffset == object.minOffset
} else {
return false
}
}
}

View File

@ -13,58 +13,33 @@ struct ExactItem {
let presetItem: BarItemDefinition let presetItem: BarItemDefinition
} }
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let standardConfigPath = appSupportDirectory.appending("/items.json")
extension ItemType { extension ItemType {
var identifierBase: String { var identifierBase: String {
switch self { switch self {
case .staticButton(title: _): case .staticButton(title: _):
return "com.toxblh.mtmr.staticButton." return "com.toxblh.mtmr.staticButton."
case .appleScriptTitledButton(source: _): case .appleScriptTitledButton(source: _):
return "com.toxblh.mtmr.appleScriptButton." return "com.toxblh.mtmr.appleScriptButton."
case .shellScriptTitledButton(source: _): case .timeButton(formatTemplate: _):
return "com.toxblh.mtmr.shellScriptButton."
case .timeButton(formatTemplate: _, timeZone: _, locale: _):
return "com.toxblh.mtmr.timeButton." return "com.toxblh.mtmr.timeButton."
case .battery: case .battery():
return "com.toxblh.mtmr.battery." return "com.toxblh.mtmr.battery."
case .cpu(refreshInterval: _): case .dock():
return "com.toxblh.mtmr.cpu."
case .dock(autoResize: _, filter: _):
return "com.toxblh.mtmr.dock" return "com.toxblh.mtmr.dock"
case .volume: case .volume():
return "com.toxblh.mtmr.volume" return "com.toxblh.mtmr.volume"
case .brightness(refreshInterval: _): case .brightness(refreshInterval: _):
return "com.toxblh.mtmr.brightness" return "com.toxblh.mtmr.brightness"
case .weather(interval: _, units: _, api_key: _, icon_type: _): case .weather(interval: _, units: _, api_key: _, icon_type: _):
return "com.toxblh.mtmr.weather" return "com.toxblh.mtmr.weather"
case .yandexWeather(interval: _): case .currency(interval: _, from: _, to: _):
return "com.toxblh.mtmr.yandexWeather"
case .currency(interval: _, from: _, to: _, full: _):
return "com.toxblh.mtmr.currency" return "com.toxblh.mtmr.currency"
case .inputsource: case .inputsource():
return "com.toxblh.mtmr.inputsource." return "com.toxblh.mtmr.inputsource."
case .music(interval: _):
return "com.toxblh.mtmr.music."
case .group(items: _):
return "com.toxblh.mtmr.groupBar."
case .nightShift:
return "com.toxblh.mtmr.nightShift."
case .dnd:
return "com.toxblh.mtmr.dnd."
case .pomodoro(interval: _):
return PomodoroBarItem.identifier
case .network(flip: _):
return NetworkBarItem.identifier
case .darkMode:
return DarkModeBarItem.identifier
case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _):
return "com.toxblh.mtmr.swipe."
case .upnext(from: _, to: _, maxToShow: _, autoResize: _):
return "com.connorgmeehan.mtmrup.next."
} }
} }
} }
extension NSTouchBarItem.Identifier { extension NSTouchBarItem.Identifier {
@ -72,177 +47,71 @@ extension NSTouchBarItem.Identifier {
} }
class TouchBarController: NSObject, NSTouchBarDelegate { class TouchBarController: NSObject, NSTouchBarDelegate {
static let shared = TouchBarController() static let shared = TouchBarController()
var touchBar: NSTouchBar! let touchBar = NSTouchBar()
fileprivate var lastPresetPath = ""
var jsonItems: [BarItemDefinition] = []
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:] var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:] var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
var leftIdentifiers: [NSTouchBarItem.Identifier] = [] var leftIdentifiers: [NSTouchBarItem.Identifier] = []
var centerIdentifiers: [NSTouchBarItem.Identifier] = [] var centerIdentifiers: [NSTouchBarItem.Identifier] = []
var centerItems: [NSTouchBarItem] = []
var rightIdentifiers: [NSTouchBarItem.Identifier] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = []
var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString)) var scrollArea: NSCustomTouchBarItem?
var basicView: BasicView? var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
var swipeItems: [SwipeItem] = []
var blacklistAppIdentifiers: [String] = []
var frontmostApplicationIdentifier: String? {
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
}
private override init() { private override init() {
super.init() super.init()
SupportedTypesHolder.sharedInstance.register( SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar()}), longAction: .none)
typename: "exitTouchbar",
item: .staticButton(title: "exit"),
actions: [
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in self?.dismissTouchBar() }))
],
legacyAction: .none,
legacyLongAction: .none
)
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in createAndUpdatePreset()
(
item: .staticButton(title: ""),
actions: [
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
guard let `self` = self else { return }
self.reloadPreset(path: self.lastPresetPath)
}))
],
legacyAction: .none,
legacyLongAction: .none,
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
}
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
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)
reloadStandardConfig()
}
func createAndUpdatePreset(newJsonItems: [BarItemDefinition]) {
if let oldBar = self.touchBar {
minimizeSystemModal(oldBar)
}
touchBar = NSTouchBar()
jsonItems = newJsonItems
itemDefinitions = [:]
loadItemDefinitions(jsonItems: jsonItems)
updateActiveApp()
} }
func didItemsChange(prevItems: [NSTouchBarItem.Identifier: NSTouchBarItem], prevSwipeItems: [SwipeItem]) -> Bool { func createAndUpdatePreset(jsonItems: [BarItemDefinition]? = nil) {
var changed = items.count != prevItems.count || swipeItems.count != prevSwipeItems.count var jsonItems = jsonItems
self.itemDefinitions = [:]
self.items = [:]
self.leftIdentifiers = []
self.centerItems = []
self.rightIdentifiers = []
if !changed { if (jsonItems == nil) {
for (item, prevItem) in zip(items, prevItems) { jsonItems = readConfig()
if item.key != prevItem.key {
changed = true
break
}
}
} }
loadItemDefinitions(jsonItems: jsonItems!)
if !changed {
for (swipeItem, prevSwipeItem) in zip(swipeItems, prevSwipeItems) {
if !swipeItem.isEqual(prevSwipeItem) {
changed = true
break
}
}
}
return changed
}
func prepareTouchBar() {
let prevItems = items
let prevSwipeItems = swipeItems
createItems() createItems()
let changed = didItemsChange(prevItems: prevItems, prevSwipeItems: prevSwipeItems)
if !changed {
return
}
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier] return items[identifier]
}) })
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString)) self.centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
self.scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
touchBar.delegate = self touchBar.delegate = self
touchBar.defaultItemIdentifiers = [basicViewIdentifier] touchBar.defaultItemIdentifiers = []
touchBar.defaultItemIdentifiers = self.leftIdentifiers + [centerScrollArea] + self.rightIdentifiers
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in self.presentTouchBar()
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
}
@objc func activeApplicationChanged(_: Notification) {
updateActiveApp()
}
func updateActiveApp() {
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
dismissTouchBar()
} else {
prepareTouchBar()
if touchBarContainsAnyItems() {
presentTouchBar()
} else {
dismissTouchBar()
}
}
} }
func touchBarContainsAnyItems() -> Bool { func readConfig() -> [BarItemDefinition]? {
return items.count != 0 || swipeItems.count != 0 let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
} let presetPath = appSupportDirectory.appending("/items.json")
func reloadStandardConfig() {
let presetPath = standardConfigPath
if !FileManager.default.fileExists(atPath: presetPath), if !FileManager.default.fileExists(atPath: presetPath),
let defaultPreset = Bundle.main.path(forResource: "defaultPreset", ofType: "json") { let defaultPreset = Bundle.main.path(forResource: "defaultPreset", ofType: "json") {
try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil) try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath) try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath)
} }
reloadPreset(path: presetPath) let jsonData = presetPath.fileData
return jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
} }
func reloadPreset(path: String) {
lastPresetPath = path
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: [:])]
createAndUpdatePreset(newJsonItems: items)
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) { func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH-mm-ss"
let time = dateFormatter.string(from: Date())
for item in jsonItems { for item in jsonItems {
let identifierString = item.type.identifierBase.appending(time + "--" + UUID().uuidString) let identifierString = item.type.identifierBase.appending(UUID().uuidString)
let identifier = NSTouchBarItem.Identifier(identifierString) let identifier = NSTouchBarItem.Identifier(identifierString)
itemDefinitions[identifier] = item itemDefinitions[identifier] = item
if item.align == .left { if item.align == .left {
@ -256,271 +125,102 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
} }
} }
func createItems() { func createItems() {
items = [:] for (identifier, definition) in self.itemDefinitions {
swipeItems = [] self.items[identifier] = self.createItem(forIdentifier: identifier, definition: definition)
for (identifier, definition) in itemDefinitions {
var show = true
if let frontApp = frontmostApplicationIdentifier {
if case let .matchAppId(regexString)? = definition.additionalParameters[.matchAppId] {
let regex = try! NSRegularExpression(pattern: regexString)
let range = NSRange(location: 0, length: frontApp.count)
if regex.firstMatch(in: frontApp, range: range) == nil {
show = false
}
}
}
if show {
let item = createItem(forIdentifier: identifier, definition: definition)
if item is SwipeItem {
swipeItems.append(item as! SwipeItem)
} else {
items[identifier] = item
}
}
} }
} }
@objc func setupControlStripPresence() { @objc func setupControlStripPresence() {
DFRSystemModalShowsCloseBoxWhenFrontMost(false) DFRSystemModalShowsCloseBoxWhenFrontMost(false)
let item = NSCustomTouchBarItem(identifier: .controlStripItem) let item = NSCustomTouchBarItem(identifier: .controlStripItem)
item.view = NSButton(image: #imageLiteral(resourceName: "StatusImage"), target: self, action: #selector(presentTouchBar)) item.view = NSButton(image: #imageLiteral(resourceName: "Strip"), target: self, action: #selector(presentTouchBar))
NSTouchBarItem.addSystemTrayItem(item) NSTouchBarItem.addSystemTrayItem(item)
updateControlStripPresence() DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
} }
func updateControlStripPresence() { func updateControlStripPresence() {
let showMtmrButtonOnControlStrip = touchBarContainsAnyItems() DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, showMtmrButtonOnControlStrip)
} }
@objc private func presentTouchBar() { @objc private func presentTouchBar() {
if AppSettings.showControlStripState { NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
updateControlStripPresence()
} }
@objc private func dismissTouchBar() { @objc private func dismissTouchBar() {
if touchBarContainsAnyItems() { NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
minimizeSystemModal(touchBar)
}
updateControlStripPresence()
} }
@objc func resetControlStrip() { func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
dismissTouchBar() if identifier == centerScrollArea {
updateActiveApp() return self.scrollArea
}
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == basicViewIdentifier {
return basicView
} }
return nil guard let item = self.items[identifier],
let definition = self.itemDefinitions[identifier],
definition.align != .center else {
return nil
}
return item
} }
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? { func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
let action = self.action(forItem: item)
let longAction = self.longAction(forItem: item)
var barItem: NSTouchBarItem! var barItem: NSTouchBarItem!
switch item.type { switch item.type {
case let .staticButton(title: title): case .staticButton(title: let title):
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title) barItem = CustomButtonTouchBarItem(identifier: identifier, title: title, onTap: action, onLongTap: longAction, bezelColor: NSColor.controlColor)
case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages): case .appleScriptTitledButton(source: let source, refreshInterval: let interval):
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages) barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action, onLongTap: longAction)
case let .shellScriptTitledButton(source: source, refreshInterval: interval): case .timeButton(formatTemplate: let template):
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval) barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, onTap: action, onLongTap: longAction)
case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale): case .battery():
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale) barItem = BatteryBarItem(identifier: identifier, onTap: action, onLongTap: longAction)
case .battery: case .dock:
barItem = BatteryBarItem(identifier: identifier) barItem = AppScrubberTouchBarItem(identifier: identifier)
case let .cpu(refreshInterval: refreshInterval):
barItem = CPUBarItem(identifier: identifier, refreshInterval: refreshInterval)
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: case .volume:
if case let .image(source)? = item.additionalParameters[.image] { if case .image(let source)? = item.additionalParameters[.image] {
barItem = VolumeViewController(identifier: identifier, image: source.image) barItem = VolumeViewController(identifier: identifier, image: source.image)
} else { } else {
barItem = VolumeViewController(identifier: identifier) barItem = VolumeViewController(identifier: identifier)
} }
case let .brightness(refreshInterval: interval): case .brightness(refreshInterval: let interval):
if case let .image(source)? = item.additionalParameters[.image] { if case .image(let source)? = item.additionalParameters[.image] {
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval, image: source.image) barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval, image: source.image)
} else { } else {
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval) barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval)
} }
case let .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type): case .weather(interval: let interval, units: let units, api_key: let api_key, icon_type: let icon_type):
barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key, icon_type: icon_type) barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key, icon_type: icon_type, onTap: action, onLongTap: longAction)
case let .yandexWeather(interval: interval): case .currency(interval: let interval, from: let from, to: let to):
barItem = YandexWeatherBarItem(identifier: identifier, interval: interval) barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, onTap: action, onLongTap: longAction)
case let .currency(interval: interval, from: from, to: to, full: full): case .inputsource():
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full) barItem = InputSourceBarItem(identifier: identifier, onTap: action, onLongTap: longAction)
case .inputsource:
barItem = InputSourceBarItem(identifier: identifier)
case let .music(interval: interval, disableMarquee: disableMarquee):
barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee)
case let .group(items: items):
barItem = GroupBarItem(identifier: identifier, items: items)
case .nightShift:
barItem = NightShiftBarItem(identifier: identifier)
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, units: units):
barItem = NetworkBarItem(identifier: identifier, flip: flip, units: units)
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)
case let .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize):
barItem = UpNextScrubberTouchBarItem(identifier: identifier, interval: 60, from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
}
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
item.actions.append(ItemAction(trigger: .singleTap, action))
}
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
item.actions.append(ItemAction(trigger: .longTap, longAction))
} }
if let touchBarItem = barItem as? CustomButtonTouchBarItem { if case .width(let value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth {
for action in item.actions {
touchBarItem.actions.append(ItemAction(trigger: action.trigger, self.closure(for: action)))
}
}
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
item.isBordered = bordered
}
if case let .background(color)? = item.additionalParameters[.background], let item = barItem as? CustomButtonTouchBarItem {
item.backgroundColor = color
}
if case let .width(value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth {
widthBarItem.setWidth(value: value) widthBarItem.setWidth(value: value)
} }
if case let .image(source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem { if case .image(let source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
item.image = source.image let button = item.button!
} button.image = source.image
if case let .title(value)? = item.additionalParameters[.title] { button.imagePosition = .imageLeading
if let item = barItem as? GroupBarItem { button.imageHugsTitle = true
item.collapsedRepresentationLabel = value button.bezelColor = .clear
} else if let item = barItem as? CustomButtonTouchBarItem {
item.title = value
}
} }
return barItem return barItem
} }
func closure(for action: Action) -> (() -> Void)? {
switch action.value {
case let .hidKey(keycode: keycode):
return { HIDPostAuxKey(keycode) }
case let .keyPress(keycode: keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case let .appleScript(source: source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(action)")
return {}
}
return {
DispatchQueue.appleScriptQueue.async {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(action) ")
}
}
}
case let .shellScript(executable: executable, parameters: parameters):
return {
let task = Process()
task.launchPath = executable
task.arguments = parameters
task.launch()
}
case let .openUrl(url: url):
return {
if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG
print("URL was successfully opened")
#endif
} else {
print("error", url)
}
}
case let .custom(closure: closure):
return closure
case .none:
return nil
}
}
func action(forItem item: BarItemDefinition) -> (() -> Void)? { func action(forItem item: BarItemDefinition) -> ()->() {
switch item.legacyAction { switch item.action {
case let .hidKey(keycode: keycode): case .hidKey(keycode: let keycode):
return { HIDPostAuxKey(keycode) } return { HIDPostAuxKey(keycode) }
case let .keyPress(keycode: keycode): case .keyPress(keycode: let keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() } return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case let .appleScript(source: source): case .appleSctipt(source: let source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)")
return {}
}
return {
DispatchQueue.appleScriptQueue.async {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(item) ")
}
}
}
case let .shellScript(executable: executable, parameters: parameters):
return {
let task = Process()
task.launchPath = executable
task.arguments = parameters
task.launch()
}
case let .openUrl(url: url):
return {
if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG
print("URL was successfully opened")
#endif
} else {
print("error", url)
}
}
case let .custom(closure: closure):
return closure
case .none:
return nil
}
}
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
switch item.legacyLongAction {
case let .hidKey(keycode: keycode):
return { HIDPostAuxKey(keycode) }
case let .keyPress(keycode: keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case let .appleScript(source: source):
guard let appleScript = source.appleScript else { guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)") print("cannot create apple script for item \(item)")
return {} return {}
@ -532,27 +232,70 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
print("error \(error) when handling \(item) ") print("error \(error) when handling \(item) ")
} }
} }
case let .shellScript(executable: executable, parameters: parameters): case .shellScript(executable: let executable, parameters: let parameters):
return { return {
let task = Process() let task = Process()
task.launchPath = executable task.launchPath = executable
task.arguments = parameters task.arguments = parameters
task.launch() task.launch()
} }
case let .openUrl(url: url): case .openUrl(url: let url):
return { return {
if let url = URL(string: url), NSWorkspace.shared.open(url) { if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG #if DEBUG
print("URL was successfully opened") print("URL was successfully opened")
#endif #endif
} else { } else {
print("error", url) print("error", url)
} }
} }
case let .custom(closure: closure): case .custom(closure: let closure):
return closure return closure
case .none: case .none:
return nil return {}
}
}
func longAction(forItem item: BarItemDefinition) -> ()->() {
switch item.longAction {
case .hidKey(keycode: let keycode):
return { HIDPostAuxKey(keycode) }
case .keyPress(keycode: let keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case .appleSctipt(source: let source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)")
return {}
}
return {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(item) ")
}
}
case .shellScript(executable: let executable, parameters: let parameters):
return {
let task = Process()
task.launchPath = executable
task.arguments = parameters
task.launch()
}
case .openUrl(url: let url):
return {
if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG
print("URL was successfully opened")
#endif
} else {
print("error", url)
}
}
case .custom(closure: let closure):
return closure
case .none:
return {}
} }
} }
} }
@ -563,19 +306,13 @@ protocol CanSetWidth {
extension NSCustomTouchBarItem: CanSetWidth { extension NSCustomTouchBarItem: CanSetWidth {
func setWidth(value: CGFloat) { func setWidth(value: CGFloat) {
view.widthAnchor.constraint(equalToConstant: value).isActive = true self.view.widthAnchor.constraint(equalToConstant: value).isActive = true
}
}
extension NSPopoverTouchBarItem: CanSetWidth {
func setWidth(value: CGFloat) {
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
} }
} }
extension BarItemDefinition { extension BarItemDefinition {
var align: Align { var align: Align {
if case let .align(result)? = additionalParameters[.align] { if case .align(let result)? = self.additionalParameters[.align] {
return result return result
} }
return .center return .center

View File

@ -7,169 +7,232 @@
import Cocoa import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem { class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
private var scrollView = NSScrollView()
private var autoResize: Bool = false private var scrubber: NSScrubber!
private var widthConstraint: NSLayoutConstraint?
private let filter: NSRegularExpression? private let hf: HapticFeedback = HapticFeedback()
private var timer: Timer!
private var ticks: Int = 0
private let minTicks: Int = 5
private let maxTicks: Int = 20
private var lastSelected: Int = 0
private var persistentAppIdentifiers: [String] = [] private var persistentAppIdentifiers: [String] = []
private var runningAppsIdentifiers: [String] = [] private var runningAppsIdentifiers: [String] = []
private var frontmostApplicationIdentifier: String? { private var frontmostApplicationIdentifier: String? {
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier get {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
return frontmostId
}
} }
private var applications: [DockItem] = [] private var applications: [DockItem] = []
private var items: [DockBarItem] = []
override init(identifier: NSTouchBarItem.Identifier) {
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false, filter: NSRegularExpression? = nil) {
self.filter = filter
super.init(identifier: identifier) super.init(identifier: identifier)
self.autoResize = autoResize
view = scrollView scrubber = NSScrubber();
scrubber.delegate = self
scrubber.dataSource = self
scrubber.mode = .free // .fixed
let layout = NSScrubberFlowLayout();
layout.itemSize = NSSize(width: 36, height: 32)
layout.itemSpacing = 2
scrubber.scrubberLayout = layout
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.showsAdditionalContentIndicators = true
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didLaunchApplicationNotification, object: nil) view = scrubber
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(softReloadItems), name: NSWorkspace.didActivateApplicationNotification, object: nil) scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"))
persistentAppIdentifiers = AppSettings.dockPersistentAppIds NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
hardReloadItems() 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)
required init?(coder _: NSCoder) { if let persistent = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.dock.persistent") {
fatalError("init(coder:) has not been implemented") self.persistentAppIdentifiers = persistent
}
@objc func hardReloadItems() {
applications = launchedApplications()
applications += getDockPersistentAppsList()
reloadData()
softReloadItems()
updateSize()
}
@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 {
self.widthConstraint?.isActive = false
let width = self.scrollView.documentView?.fittingSize.width ?? 0
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
self.widthConstraint!.isActive = true
}
}
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 createAppButton(for app: DockItem) -> DockBarItem {
let item = DockBarItem(app)
item.isBordered = false
item.actions.append(contentsOf: [
ItemAction(trigger: .singleTap) { [weak self] in
self?.switchToApp(app: app)
},
ItemAction(trigger: .longTap) { [weak self] in
self?.handleHalfLongPress(item: app)
}
])
item.killAppClosure = {[weak self] in
self?.handleLongPress(item: app)
} }
updateRunningApplication()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func activeApplicationChanged(n: Notification) {
updateRunningApplication()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
updateRunningApplication()
}
func updateRunningApplication() {
let newApplications = launchedApplications()
let index = newApplications.index {
$0.bundleIdentifier == frontmostApplicationIdentifier
}
applications = newApplications
applications += getDockPersistentAppsList()
scrubber.reloadData()
scrubber.selectedIndex = index ?? 0
}
public func numberOfItems(for scrubber: NSScrubber) -> Int {
return applications.count
}
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)
}
item.removeFromSuperview()
let dotView = NSView(frame: .zero)
dotView.wantsLayer = true
if self.runningAppsIdentifiers.contains(app.bundleIdentifier!) {
dotView.layer?.backgroundColor = NSColor.white.cgColor
} else {
dotView.layer?.backgroundColor = NSColor.black.cgColor
}
dotView.layer?.cornerRadius = 1.5
dotView.setFrameOrigin(NSPoint(x: 17, y: 1))
dotView.frame.size = NSSize(width: 3, height: 3)
item.addSubview(dotView)
return item return item
} }
public func switchToApp(app: DockItem) { public func didBeginInteracting(with scrubber: NSScrubber) {
let bundleIdentifier = app.bundleIdentifier stopTimer()
self.ticks = 0
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true)
}
@objc private func checkTimer() {
self.ticks += 1
if (self.ticks == minTicks) {
hf.tap(strong: 2)
}
if (self.ticks > maxTicks) {
stopTimer()
hf.tap(strong: 6)
}
}
private func stopTimer() {
self.timer?.invalidate()
self.timer = nil
self.lastSelected = 0
}
public func didCancelInteracting(with scrubber: NSScrubber) {
stopTimer()
}
public func didFinishInteracting(with scrubber: NSScrubber) {
stopTimer()
if (ticks == 0) {
return
}
if (self.ticks >= minTicks && scrubber.selectedIndex > 0) {
self.longPress(selected: scrubber.selectedIndex)
return
}
let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier
if bundleIdentifier!.contains("file://") { if bundleIdentifier!.contains("file://") {
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: "")) NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
} else { } else {
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
hf.tap(strong: 6)
} }
softReloadItems() updateRunningApplication()
// NB: if you can't open app which on another space, try to check mark // NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application" // "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings // in Mission control settings
} }
//todo private func longPress(selected: Int) {
private func handleLongPress(item: DockItem) { let bundleIdentifier = applications[selected].bundleIdentifier
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
if !app.terminate() { if (self.ticks > maxTicks) {
app.forceTerminate() if let processIdentifier = applications[selected].pid {
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
}
} }
hardReloadItems()
}
}
private func handleHalfLongPress(item: DockItem) {
if let index = self.persistentAppIdentifiers.firstIndex(of: item.bundleIdentifier) {
persistentAppIdentifiers.remove(at: index)
hardReloadItems()
} else { } else {
persistentAppIdentifiers.append(item.bundleIdentifier) hf.tap(strong: 6)
if let index = self.persistentAppIdentifiers.index(of: bundleIdentifier!) {
self.persistentAppIdentifiers.remove(at: index)
} else {
self.persistentAppIdentifiers.append(bundleIdentifier!)
}
UserDefaults.standard.set(self.persistentAppIdentifiers, forKey: "com.toxblh.mtmr.dock.persistent")
UserDefaults.standard.synchronize()
} }
self.ticks = 0
AppSettings.dockPersistentAppIds = persistentAppIdentifiers updateRunningApplication()
} }
private func launchedApplications() -> [DockItem] { private func launchedApplications() -> [DockItem] {
runningAppsIdentifiers = [] self.runningAppsIdentifiers = []
var returnable: [DockItem] = [] var returnable: [DockItem] = []
for app in NSWorkspace.shared.runningApplications { for app in NSWorkspace.shared.runningApplications {
guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue } guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue }
guard let bundleIdentifier = app.bundleIdentifier else { continue } guard let bundleIdentifier = app.bundleIdentifier else { continue }
if let filter = self.filter,
let name = app.localizedName,
filter.numberOfMatches(in: name, options: [], range: NSRange(location: 0, length: name.count)) == 0 {
continue
}
runningAppsIdentifiers.append(bundleIdentifier) self.runningAppsIdentifiers.append(bundleIdentifier)
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier) let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
returnable.append(dockItem) returnable.append(dockItem)
} }
return returnable return returnable
} }
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage { public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType type: String? = nil) -> NSImage {
if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) { if bundleIdentifier != nil {
return NSWorkspace.shared.icon(forFile: appPath) if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) {
return NSWorkspace.shared.icon(forFile: appPath)
}
} }
if let path = path { if path != nil {
return NSWorkspace.shared.icon(forFile: path) return NSWorkspace.shared.icon(forFile: path!)
} }
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns") let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
return genericIcon ?? NSImage(size: .zero) return genericIcon ?? NSImage(size: .zero)
} }
public func getDockPersistentAppsList() -> [DockItem] { public func getDockPersistentAppsList() -> [DockItem] {
var returnable: [DockItem] = [] var returnable: [DockItem] = []
for bundleIdentifier in persistentAppIdentifiers { for (index, bundleIdentifier) in persistentAppIdentifiers.enumerated() {
if !runningAppsIdentifiers.contains(bundleIdentifier) { if !self.runningAppsIdentifiers.contains(bundleIdentifier) {
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier)) let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier))
returnable.append(dockItem) returnable.append(dockItem)
} }
@ -189,60 +252,3 @@ public class DockItem: NSObject {
self.pid = pid self.pid = pid
} }
} }
private let iconWidth = 32.0
class DockBarItem: CustomButtonTouchBarItem {
let dotView = NSView(frame: .zero)
let dockItem: DockItem
fileprivate var killGestureRecognizer: LongPressGestureRecognizer!
var killAppClosure: () -> Void = { }
var isRunning = false {
didSet {
redrawDotView()
}
}
var isFrontmost = false {
didSet {
redrawDotView()
}
}
init(_ app: DockItem) {
self.dockItem = app
super.init(identifier: .init(app.bundleIdentifier), title: "")
dotView.wantsLayer = true
image = app.icon
image?.size = NSSize(width: iconWidth, height: iconWidth)
killGestureRecognizer = LongPressGestureRecognizer(target: self, action: #selector(firePanGestureRecognizer))
killGestureRecognizer.allowedTouchTypes = .direct
killGestureRecognizer.recognizeTimeout = 1.5
killGestureRecognizer.minimumPressDuration = 1.5
killGestureRecognizer.isEnabled = isRunning
self.finishViewConfiguration = { [weak self] in
guard let selfie = self else { return }
selfie.dotView.layer?.cornerRadius = 1.5
selfie.view.addSubview(selfie.dotView)
selfie.redrawDotView()
selfie.view.addGestureRecognizer(selfie.killGestureRecognizer)
}
}
func redrawDotView() {
dotView.layer?.backgroundColor = isRunning ? NSColor.white.cgColor : NSColor.clear.cgColor
dotView.frame.size = NSSize(width: isFrontmost ? iconWidth - 14 : 3, height: 3)
dotView.setFrameOrigin(NSPoint(x: 18.0 - Double(dotView.frame.size.width) / 2.0, y: iconWidth - 5))
}
@objc func firePanGestureRecognizer() {
self.killAppClosure()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -6,32 +6,24 @@
// Copyright © 2018 Anton Palgunov. All rights reserved. // Copyright © 2018 Anton Palgunov. All rights reserved.
// //
import Foundation
import IOKit.ps import IOKit.ps
import Foundation
class BatteryBarItem: CustomButtonTouchBarItem { class BatteryBarItem: CustomButtonTouchBarItem {
private let batteryInfo = BatteryInfo() private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier) { init(identifier: NSTouchBarItem.Identifier, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
super.init(identifier: identifier, title: " ") super.init(identifier: identifier, title: " ", onTap: onTap, onLongTap: onLongTap)
self.view = button
batteryInfo.start { [weak self] in
self?.refresh() let batteryInfo = BatteryInfo(button: button)
} batteryInfo.start()
refresh() batteryInfo.updateInfo()
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func refresh() {
attributedTitle = batteryInfo.formattedInfo()
}
deinit {
batteryInfo.stop()
}
} }
class BatteryInfo: NSObject { class BatteryInfo: NSObject {
@ -42,99 +34,115 @@ class BatteryInfo: NSObject {
var isCharging: Bool = false var isCharging: Bool = false
var ACPower: String = "" var ACPower: String = ""
var timeRemaining: String = "" var timeRemaining: String = ""
var notifyBlock: () -> Void = {}
var loop: CFRunLoopSource? var button: NSButton?
var loop:CFRunLoopSource?
func start(notifyBlock: @escaping () -> Void) {
self.notifyBlock = notifyBlock init(button: NSButton) {
super.init()
self.button = button
self.start()
}
func start() {
let opaque = Unmanaged.passRetained(self).toOpaque() let opaque = Unmanaged.passRetained(self).toOpaque()
let context = UnsafeMutableRawPointer(opaque) let context = UnsafeMutableRawPointer(opaque)
loop = IOPSNotificationCreateRunLoopSource({ context in loop = IOPSNotificationCreateRunLoopSource({ (context) in
guard let ctx = context else { guard let ctx = context else {
return return
} }
let watcher = Unmanaged<BatteryInfo>.fromOpaque(ctx).takeUnretainedValue() let watcher = Unmanaged<BatteryInfo>.fromOpaque(ctx).takeUnretainedValue()
watcher.notifyBlock() watcher.updateInfo()
}, context).takeRetainedValue() as CFRunLoopSource }, context).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode) CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
} }
func stop() { func stop() {
notifyBlock = {} if !(self.loop != nil) {
guard let loop = self.loop else {
return return
} }
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode) CFRunLoopRemoveSource(CFRunLoopGetCurrent(), self.loop, CFRunLoopMode.defaultMode)
self.loop = nil self.loop = nil
} }
func getPSInfo() { func getPSInfo() {
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef] let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
for ps in psList { for ps in psList {
if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] { if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] {
if let current = psDesc[kIOPSCurrentCapacityKey] as? Int { let current = psDesc[kIOPSCurrentCapacityKey]
self.current = current if (current != nil) {
self.current = current as! Int
} }
if let timeToEmpty = psDesc[kIOPSTimeToEmptyKey] as? Int { let timeToEmpty = psDesc[kIOPSTimeToEmptyKey]
self.timeToEmpty = timeToEmpty if (timeToEmpty != nil) {
self.timeToEmpty = timeToEmpty as! Int
} }
if let timeToFull = psDesc[kIOPSTimeToFullChargeKey] as? Int { let timeToFull = psDesc[kIOPSTimeToFullChargeKey]
self.timeToFull = timeToFull if (timeToFull != nil) {
self.timeToFull = timeToFull as! Int
} }
if let isCharged = psDesc[kIOPSIsChargedKey] as? Bool { let isCharged = psDesc[kIOPSIsChargedKey]
self.isCharged = isCharged if (isCharged != nil) {
self.isCharged = isCharged as! Bool
} }
if let isCharging = psDesc[kIOPSIsChargingKey] as? Bool { let isCharging = psDesc[kIOPSIsChargingKey]
self.isCharging = isCharging if (isCharging != nil) {
self.isCharging = isCharging as! Bool
} }
if let ACPower = psDesc[kIOPSPowerSourceStateKey] as? String { let ACPower = psDesc[kIOPSPowerSourceStateKey]
self.ACPower = ACPower if (ACPower != nil) {
self.ACPower = ACPower as! String
} }
} }
} }
} }
func getFormattedTime(time: Int) -> String { func getFormattedTime(time: Int) -> String {
if time > 0 { if (time > 0) {
let timeFormatted = NSString(format: " %d:%02d", time / 60, time % 60) as String let timeFormatted = NSString(format: " (%d:%02d)", time / 60, time % 60) as String
return timeFormatted return timeFormatted
} else if (time == 0) {
return ""
} }
return "" return " (?)"
} }
public func formattedInfo() -> NSAttributedString { public func updateInfo() {
var title = "" var title = ""
getPSInfo() self.getPSInfo()
if ACPower == "AC Power" { if ACPower == "AC Power" {
if current < 100 { title += "⚡️"
title += "⚡️"
}
timeRemaining = getFormattedTime(time: timeToFull) timeRemaining = getFormattedTime(time: timeToFull)
} else { } else {
timeRemaining = getFormattedTime(time: timeToEmpty) timeRemaining = getFormattedTime(time: timeToEmpty)
} }
title += String(current) + "%" title += String(current) + "%" + timeRemaining
button?.title = title
var color = NSColor.white
if current <= 10 && ACPower != "AC Power" { if current < 10 && ACPower != "AC Power" {
color = NSColor.red let pstyle = NSMutableParagraphStyle()
pstyle.alignment = .center
button?.attributedTitle = NSMutableAttributedString(
string: title,
attributes: [
NSAttributedStringKey.foregroundColor: NSColor.red,
NSAttributedStringKey.paragraphStyle: pstyle,
NSAttributedStringKey.font: NSFont.systemFont(ofSize: 16)
])
} }
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: NSFont.systemFont(ofSize: 15), .baselineOffset: 1])
let newTitleSecond = NSMutableAttributedString(string: timeRemaining as String, attributes: [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular), NSAttributedString.Key.baselineOffset: 7])
newTitle.append(newTitleSecond)
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
return newTitle
} }
} }

View File

@ -1,71 +1,63 @@
import Cocoa
import AppKit import AppKit
import AVFoundation import AVFoundation
import Cocoa
import CoreAudio import CoreAudio
class BrightnessViewController: NSCustomTouchBarItem { class BrightnessViewController: NSCustomTouchBarItem {
private(set) var sliderItem: CustomSlider! private(set) var sliderItem: CustomSlider!
init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) { init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) {
super.init(identifier: identifier) super.init(identifier: identifier)
if image == nil { if (image == nil) {
sliderItem = CustomSlider() sliderItem = CustomSlider()
} else { } else {
sliderItem = CustomSlider(knob: image!) sliderItem = CustomSlider(knob: image!)
} }
sliderItem.target = self sliderItem.target = self
sliderItem.action = #selector(BrightnessViewController.sliderValueChanged(_:)) sliderItem.action = #selector(BrightnessViewController.sliderValueChanged(_:))
sliderItem.minValue = 0.0 sliderItem.minValue = 0.0
sliderItem.maxValue = 100.0 sliderItem.maxValue = 100.0
sliderItem.floatValue = getBrightness() * 100 sliderItem.floatValue = getBrightness()*100
view = sliderItem self.view = sliderItem
let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true) let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoop.Mode.common) RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit { deinit {
sliderItem.unbind(NSBindingName.value) sliderItem.unbind(NSBindingName.value)
} }
@objc func updateBrightnessSlider() { @objc func updateBrightnessSlider() {
DispatchQueue.main.async { DispatchQueue.main.async {
self.sliderItem.floatValue = self.getBrightness() * 100 self.sliderItem.floatValue = self.getBrightness() * 100
} }
} }
@objc func sliderValueChanged(_ sender: Any) { @objc func sliderValueChanged(_ sender: Any) {
if let sliderItem = sender as? NSSlider { if let sliderItem = sender as? NSSlider {
setBrightness(level: Float32(sliderItem.intValue) / 100.0) setBrightness(level: Float32(sliderItem.intValue)/100.0)
} }
} }
private func getBrightness() -> Float32 { private func getBrightness() -> Float32 {
if #available(OSX 10.13, *) { var level: Float32 = 0.5
return Float32(CoreDisplay_Display_GetUserBrightness(0)) let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
} else {
var level: Float32 = 0.5 IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &level)
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect")) return level
IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &level)
return level
}
} }
private func setBrightness(level: Float) { private func setBrightness(level: Float) {
if #available(OSX 10.13, *) { let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
CoreDisplay_Display_SetUserBrightness(0, Double(level))
} else { IODisplaySetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, level)
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect")) IOObjectRelease(service)
IODisplaySetFloatParameter(service, 1, kIODisplayBrightnessKey as CFString, level)
IOObjectRelease(service)
}
} }
} }

View File

@ -1,82 +0,0 @@
//
// CPUBarItem.swift
// MTMR
//
// Created by bobrosoft on 17/08/2021.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class CPUBarItem: CustomButtonTouchBarItem {
private let refreshInterval: TimeInterval
private var refreshQueue: DispatchQueue? = DispatchQueue(label: "mtmr.cpu")
private let defaultSingleTapScript: NSAppleScript! = "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell".appleScript
init(identifier: NSTouchBarItem.Identifier, refreshInterval: TimeInterval) {
self.refreshInterval = refreshInterval
super.init(identifier: identifier, title: "")
// Set default image
if self.image == nil {
self.image = #imageLiteral(resourceName: "cpu").resize(maxSize: NSSize(width: 24, height: 24));
}
// Set default action
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
actions.append(ItemAction(
trigger: .singleTap,
defaultTapAction
))
}
refreshAndSchedule()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
DispatchQueue.main.async {
// Get CPU load
let usage = 100 - CPU.systemUsage().idle
guard usage.isFinite else {
return
}
// Choose color based on CPU load
var color: NSColor? = nil
var bgColor: NSColor? = nil
if usage > 70 {
color = .black
bgColor = .yellow
} else if usage > 30 {
color = .yellow
}
// Update layout
let attrTitle = NSMutableAttributedString.init(attributedString: String(format: "%.1f%%", usage).defaultTouchbarAttributedString)
if let color = color {
attrTitle.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attrTitle.length))
}
self.attributedTitle = attrTitle
self.backgroundColor = bgColor
}
refreshQueue?.asyncAfter(deadline: .now() + refreshInterval) { [weak self] in
self?.refreshAndSchedule()
}
}
func defaultTapAction() {
refreshQueue?.async { [weak self] in
self?.defaultSingleTapScript.executeAndReturnError(nil)
}
}
deinit {
refreshQueue?.suspend()
refreshQueue = nil
}
}

View File

@ -12,14 +12,9 @@ import CoreLocation
class CurrencyBarItem: CustomButtonTouchBarItem { class CurrencyBarItem: CustomButtonTouchBarItem {
private let activity: NSBackgroundActivityScheduler private let activity: NSBackgroundActivityScheduler
private var prefix: String private var prefix: String
private var postfix: String
private var from: String private var from: String
private var to: String private var to: String
private var decimal: Int
private var decimalValue: Float32!
private var decimalString: String!
private var oldValue: Float32! private var oldValue: Float32!
private var full: Bool = false
private let currencies = [ private let currencies = [
"USD": "$", "USD": "$",
@ -35,69 +30,27 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
"IDR": "Rp", "IDR": "Rp",
"MXN": "$", "MXN": "$",
"SGD": "$", "SGD": "$",
"CHF": "Fr.",
"BTC": "฿", "BTC": "฿",
"LTC": "Ł", "LTC": "Ł",
"ETH": "Ξ", "ETH": "Ξ",
"SOL": "",
"DOT": "",
"DOGE": "Ð",
"XMR": "ɱ",
"ADA": "",
"PLN": "",
"UAH": "",
]
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": 3,
"LTC": 2,
"ETH": 2,
"DOT": 3,
"DOGE": 4,
"ADA": 3,
"USDT": 3
] ]
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) { init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval activity.interval = interval
self.from = from self.from = from
self.to = to self.to = to
self.full = full
if let prefix = currencies[from] { if let prefix = currencies[from] {
self.prefix = prefix self.prefix = prefix
} else { } else {
prefix = from self.prefix = from
} }
if let postfix = currencies[to] { super.init(identifier: identifier, title: "", onTap: onTap, onLongTap: onLongTap)
self.postfix = postfix
} else {
postfix = to
}
self.view = button
if let decimal = decimals[to] {
self.decimal = decimal
} else {
decimal = 2
}
super.init(identifier: identifier, title: "")
activity.repeats = true activity.repeats = true
activity.qualityOfService = .utility activity.qualityOfService = .utility
@ -108,21 +61,21 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
updateCurrency() updateCurrency()
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func updateCurrency() { @objc func updateCurrency() {
let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!) let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!)
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error == nil { if error == nil {
do { do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject] let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
var value: Float32! var value: Float32!
if let data_array = json["data"] as? [String: AnyObject] { if let data_array = json["data"] as? [String : AnyObject] {
if let rates = data_array["rates"] as? [String: AnyObject] { if let rates = data_array["rates"] as? [String : AnyObject] {
if let item = rates["\(self.to)"] as? String { if let item = rates["\(self.to)"] as? String {
value = Float32(item) value = Float32(item)
} }
@ -152,25 +105,16 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
color = NSColor.red color = NSColor.red
} }
} }
self.oldValue = value
oldValue = value button.title = String(format: "%@%.2f", self.prefix, value)
decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal))
decimalString = String(decimalValue)
var title = "" let textRange = NSRange(location: 0, length: button.title.count)
if full { let newTitle = NSMutableAttributedString(string: button.title)
title = String(format: "%@%@‣%@", prefix, postfix, decimalString) newTitle.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: textRange)
} else { newTitle.addAttribute(NSAttributedStringKey.font, value: button.font!, range: textRange)
title = String(format: "%@%.2f", prefix, value) newTitle.setAlignment(.center, range: textRange)
}
let regularFont = attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15) button.attributedTitle = newTitle
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: regularFont, .baselineOffset: 1])
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
attributedTitle = newTitle
}
deinit {
activity.invalidate()
} }
} }

View File

@ -1,57 +0,0 @@
import Foundation
class DarkModeBarItem: CustomButtonTouchBarItem, Widget {
static var name: String = "darkmode"
static var identifier: String = "com.toxblh.mtmr.darkmode"
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 24)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DarkModeToggle() })
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func DarkModeToggle() {
DarkMode.isEnabled = !DarkMode.isEnabled
refresh()
}
@objc func refresh() {
image = DarkMode.isEnabled ? #imageLiteral(resourceName: "dark-mode-on") : #imageLiteral(resourceName: "dark-mode-off")
}
}
struct DarkMode {
private static let prefix = "tell application \"System Events\" to tell appearance preferences to"
static var isEnabled: Bool {
get {
return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark"
}
set {
toggle(force: newValue)
}
}
static func toggle(force: Bool? = nil) {
let value = force.map(String.init) ?? "not dark mode"
_ = runAppleScript("\(prefix) set dark mode to \(value)")
}
}
func runAppleScript(_ source: String) -> String? {
return NSAppleScript(source: source)?.executeAndReturnError(nil).stringValue
}

View File

@ -1,78 +0,0 @@
//
// DnDBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 29/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class DnDBarItem: CustomButtonTouchBarItem {
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 32)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DnDToggle() })
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func DnDToggle() {
DoNotDisturb.isEnabled = !DoNotDisturb.isEnabled
refresh()
}
@objc func refresh() {
image = DoNotDisturb.isEnabled ? #imageLiteral(resourceName: "dnd-on") : #imageLiteral(resourceName: "dnd-off")
}
}
public struct DoNotDisturb {
private static let appId = "com.apple.notificationcenterui" as CFString
private static let dndPref = "com.apple.notificationcenterui.dndprefs_changed"
private static func set(_ key: String, value: CFPropertyList?) {
CFPreferencesSetValue(key as CFString, value, appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
}
private static func commitChanges() {
CFPreferencesSynchronize(appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
DistributedNotificationCenter.default().postNotificationName(NSNotification.Name(dndPref), object: nil, userInfo: nil, deliverImmediately: true)
NSRunningApplication.runningApplications(withBundleIdentifier: appId as String).first?.terminate()
}
private static func enable() {
set("dndStart", value: nil)
set("dndEnd", value: nil)
set("doNotDisturb", value: true as CFPropertyList)
set("doNotDisturbDate", value: Date() as CFPropertyList)
commitChanges()
}
private static func disable() {
set("dndStart", value: nil)
set("dndEnd", value: nil)
set("doNotDisturb", value: false as CFPropertyList)
set("doNotDisturbDate", value: nil)
commitChanges()
}
static var isEnabled: Bool {
get {
return CFPreferencesGetAppBooleanValue("doNotDisturb" as CFString, appId, nil)
}
set {
newValue ? enable() : disable()
}
}
}

View File

@ -1,100 +0,0 @@
//
// GroupBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 11.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate {
var jsonItems: [BarItemDefinition]
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
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))
init(identifier: NSTouchBarItem.Identifier, items: [BarItemDefinition]) {
jsonItems = items
super.init(identifier: identifier)
popoverTouchBar.delegate = self
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {}
@objc override func showPopover(_: Any?) {
itemDefinitions = [:]
items = [:]
leftIdentifiers = []
centerItems = []
rightIdentifiers = []
loadItemDefinitions(jsonItems: jsonItems)
createItems()
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
items[identifier]
})
centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
TouchBarController.shared.touchBar.delegate = self
TouchBarController.shared.touchBar.defaultItemIdentifiers = []
TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers
if AppSettings.showControlStripState {
presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
}
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == centerScrollArea {
return scrollArea
}
guard let item = self.items[identifier],
let definition = self.itemDefinitions[identifier],
definition.align != .center else {
return nil
}
return item
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH-mm-ss"
let time = dateFormatter.string(from: Date())
for item in jsonItems {
let identifierString = item.type.identifierBase.appending(time + "--" + UUID().uuidString)
let identifier = NSTouchBarItem.Identifier(identifierString)
itemDefinitions[identifier] = item
if item.align == .left {
leftIdentifiers.append(identifier)
}
if item.align == .right {
rightIdentifiers.append(identifier)
}
if item.align == .center {
centerIdentifiers.append(identifier)
}
}
}
func createItems() {
for (identifier, definition) in itemDefinitions {
items[identifier] = TouchBarController.shared.createItem(forIdentifier: identifier, definition: definition)
}
}
}

View File

@ -9,47 +9,42 @@
import Cocoa import Cocoa
class InputSourceBarItem: CustomButtonTouchBarItem { class InputSourceBarItem: CustomButtonTouchBarItem {
fileprivate var notificationCenter: CFNotificationCenter fileprivate var notificationCenter: CFNotificationCenter
let buttonSize = NSSize(width: 21, height: 21)
init(identifier: NSTouchBarItem.Identifier) { init(identifier: NSTouchBarItem.Identifier, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
notificationCenter = CFNotificationCenterGetDistributedCenter() notificationCenter = CFNotificationCenterGetDistributedCenter();
super.init(identifier: identifier, title: "") super.init(identifier: identifier, title: "", onTap: onTap, onLongTap: onLongTap)
observeIputSourceChangedNotification() observeIputSourceChangedNotification();
textInputSourceDidChange() textInputSourceDidChange()
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self.button.action = #selector(switchInputSource)
self?.switchInputSource()
})
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
CFNotificationCenterRemoveEveryObserver(notificationCenter, UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()))
}
@objc public func textInputSourceDidChange() { @objc public func textInputSourceDidChange() {
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue() let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
var iconImage: NSImage? var iconImage: NSImage? = nil
if let imageURL = currentSource.iconImageURL, if let imageURL = currentSource.iconImageURL {
let image = NSImage(contentsOf: imageURL) { if let image = NSImage(contentsOf: imageURL) {
iconImage = image iconImage = image
} else if let iconRef = currentSource.iconRef { }
}
if iconImage == nil, let iconRef = currentSource.iconRef {
iconImage = NSImage(iconRef: iconRef) iconImage = NSImage(iconRef: iconRef)
} }
if let iconImage = iconImage { if (iconImage != nil) {
iconImage.size = buttonSize self.button.image = iconImage
image = iconImage
title = ""
} else { } else {
title = currentSource.name self.button.title = currentSource.name
} }
} }
@ -65,15 +60,15 @@ class InputSourceBarItem: CustomButtonTouchBarItem {
}) })
for item in inputSources { for item in inputSources {
if item.id != currentSource.id { if (item.id != currentSource.id) {
TISSelectInputSource(item) TISSelectInputSource(item)
break break
} }
} }
} }
@objc public func observeIputSourceChangedNotification() { @objc public func observeIputSourceChangedNotification(){
let callback: CFNotificationCallback = { _, observer, _, _, _ in let callback: CFNotificationCallback = { center, observer, name, object, info in
let mySelf = Unmanaged<InputSourceBarItem>.fromOpaque(observer!).takeUnretainedValue() let mySelf = Unmanaged<InputSourceBarItem>.fromOpaque(observer!).takeUnretainedValue()
mySelf.textInputSourceDidChange() mySelf.textInputSourceDidChange()
} }
@ -96,7 +91,7 @@ extension TISInputSource {
private func getProperty(_ key: CFString) -> AnyObject? { private func getProperty(_ key: CFString) -> AnyObject? {
let cfType = TISGetInputSourceProperty(self, key) let cfType = TISGetInputSourceProperty(self, key)
if cfType != nil { if (cfType != nil) {
return Unmanaged<AnyObject>.fromOpaque(cfType!).takeUnretainedValue() return Unmanaged<AnyObject>.fromOpaque(cfType!).takeUnretainedValue()
} else { } else {
return nil return nil
@ -131,3 +126,4 @@ extension TISInputSource {
return OpaquePointer(TISGetInputSourceProperty(self, kTISPropertyIconRef)) as IconRef? return OpaquePointer(TISGetInputSourceProperty(self, kTISPropertyIconRef)) as IconRef?
} }
} }

View File

@ -1,461 +0,0 @@
//
// MusicBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 05.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
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"
case Chrome = "com.google.Chrome"
case Safari = "com.apple.Safari"
}
private let playerBundleIdentifiers = [
Player.Music,
Player.iTunes,
Player.Spotify,
Player.VOX,
Player.Chrome,
Player.Safari,
]
private let interval: TimeInterval
private let disableMarquee: Bool
private var songTitle: String?
private var timer: Timer?
private let iconSize = NSSize(width: 21, height: 21)
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, disableMarquee: Bool) {
self.interval = interval
self.disableMarquee = disableMarquee
super.init(identifier: identifier, title: "")
isBordered = false
actions = [
ItemAction(trigger: .singleTap) { [weak self] in self?.playPause() },
ItemAction(trigger: .doubleTap) { [weak self] in self?.previousTrack() },
ItemAction(trigger: .longTap) { [weak self] in self?.nextTrack() }
]
refreshAndSchedule()
}
@objc func marquee() {
let str = title
if str.count > 10 {
let indexFirst = str.index(str.startIndex, offsetBy: 0)
let indexSecond = str.index(str.startIndex, offsetBy: 1)
title = String(str.suffix(from: indexSecond)) + String(str[indexFirst])
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func playPause() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.playpause!()
return
} else if ident == .iTunes {
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!()
return
} else if ident == .Safari {
// You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use 'do JavaScript'.
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('player-controls__btn_play')[0].click()", in: tab)
return
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('audio_page_player_play')[0].click()", in: tab)
return
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
_ = safariApplication.doJavaScript!("document.getElementById('movie_player').click()", in: tab)
return
}
}
}
}
// else if (ident == .Chrome) {
// let chromeApplication = musicPlayer as GoogleChromeApplication
// let chromeWindows = chromeApplication.windows?().compactMap({ $0 as? GoogleChromeWindow })
// for window in chromeWindows! {
// for tab in window.tabs!() {
// let tab = tab as! GoogleChromeTab
// if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
// chromeApplication.executeJavaScript!(javascript: "document.getElementsByClassName('player-controls__btn_play')[0].click()")
// break
// } else if ((tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))!) {
// chromeApplication.executeJavaScript!(javascript: "document.getElementsByClassName('audio_page_player_ctrl')[0].click()")
// break
// } else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
// chromeApplication.executeJavaScript!(javascript: "alert(document.title)") // , id: tab
// break // document.getElementById('movie_player').click()
// }
// }
// }
// }
break
}
}
}
}
@objc func nextTrack() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.nextTrack!()
updatePlayer()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
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!()
updatePlayer()
return
} else if ident == .Safari {
// You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use 'do JavaScript'.
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('player-controls__btn_next')[0].click()", in: tab)
updatePlayer()
return
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('audio_page_player_next')[0].click()", in: tab)
updatePlayer()
return
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
_ = safariApplication.doJavaScript!("document.getElementsByClassName('ytp-next-button')[0].click()", in: tab)
updatePlayer()
return
}
}
}
}
}
}
}
}
@objc func previousTrack() {
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
if ident == .Spotify {
let mp = (musicPlayer as SpotifyApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .iTunes {
let mp = (musicPlayer as iTunesApplication)
mp.previousTrack!()
updatePlayer()
return
} else if ident == .Music {
let mp = (musicPlayer as MusicApplication)
mp.previousTrack!()
updatePlayer()
return
}
}
}
}
}
func refreshAndSchedule() {
DispatchQueue.main.async {
self.updatePlayer()
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.refreshAndSchedule()
}
}
}
func updatePlayer() {
var iconUpdated = false
var titleUpdated = false
for ident in playerBundleIdentifiers {
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
if musicPlayer.isRunning {
var tempTitle = ""
if ident == .Spotify {
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 {
let safariApplication = musicPlayer as SafariApplication
let safariWindows = safariApplication.windows?().compactMap({ $0 as? SafariWindow })
for window in safariWindows! {
for tab in window.tabs!() {
let tab = tab as! SafariTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
// if (!(tab.name?.hasSuffix("на Яндекс.Музыке"))!) {
tempTitle = (tab.name)!
break
// }
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
tempTitle = (tab.name)!
break
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
tempTitle = (tab.name)!
break
}
}
}
} else if ident == .Chrome {
let chromeApplication = musicPlayer as GoogleChromeApplication
let chromeWindows = chromeApplication.windows?().compactMap({ $0 as? GoogleChromeWindow })
for window in chromeWindows! {
for tab in window.tabs!() {
let tab = tab as! GoogleChromeTab
if (tab.URL?.starts(with: "https://music.yandex.ru"))! {
if !(tab.title?.hasSuffix("на Яндекс.Музыке"))! {
tempTitle = tab.title!
break
}
} else if (tab.URL?.starts(with: "https://vk.com/audios"))! || (tab.URL?.starts(with: "https://vk.com/music"))! {
tempTitle = tab.title!
break
} else if (tab.URL?.starts(with: "https://www.youtube.com/watch"))! {
tempTitle = tab.title!
break
}
}
}
}
if tempTitle == self.songTitle {
return
} else {
self.songTitle = tempTitle
}
if let songTitle = self.songTitle?.ifNotEmpty {
self.timer?.invalidate()
self.timer = nil
if (disableMarquee) {
self.title = " " + songTitle
} else {
self.title = " " + songTitle + " "
self.timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(self.marquee), userInfo: nil, repeats: true)
}
titleUpdated = true
}
if let _ = tempTitle.ifNotEmpty,
let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: ident.rawValue) {
let image = NSWorkspace.shared.icon(forFile: appPath)
image.size = self.iconSize
self.image = image
iconUpdated = true
}
break
}
}
}
DispatchQueue.main.async {
if !iconUpdated {
self.image = nil
}
if !titleUpdated {
self.title = ""
}
}
}
}
@objc protocol SpotifyApplication {
@objc optional var currentTrack: SpotifyTrack { get }
@objc optional func nextTrack()
@objc optional func previousTrack()
@objc optional func playpause()
}
extension SBApplication: SpotifyApplication {}
@objc protocol SpotifyTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: SpotifyTrack {}
extension SpotifyApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + "" + (t.name ?? "")
}
}
@objc protocol iTunesApplication {
@objc optional var currentTrack: iTunesTrack { get }
@objc optional func playpause()
@objc optional func nextTrack()
@objc optional func previousTrack()
}
extension SBApplication: iTunesApplication {}
@objc protocol iTunesTrack {
@objc optional var artist: String { get }
@objc optional var name: String { get }
}
extension SBObject: iTunesTrack {}
extension iTunesApplication {
var title: String {
guard let t = currentTrack else { return "" }
return (t.artist ?? "") + "" + (t.name ?? "")
}
}
@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()
@objc optional func previous()
@objc optional var track: String { get }
@objc optional var artist: String { get }
}
extension SBApplication: VoxApplication {}
extension VoxApplication {
var title: String {
return (artist ?? "") + "" + (track ?? "")
}
}
@objc public protocol SBObjectProtocol: NSObjectProtocol {
func get() -> Any!
}
@objc public protocol SBApplicationProtocol: SBObjectProtocol {
func activate()
var delegate: SBApplicationDelegate! { get set }
}
@objc public protocol SafariApplication: SBApplicationProtocol {
@objc optional func windows() -> SBElementArray
@objc optional func doJavaScript(_ x: String!, in in_: Any!) -> Any // Applies a string of JavaScript code to a document.
}
extension SBApplication: SafariApplication {}
@objc public protocol SafariWindow: SBObjectProtocol {
@objc optional var name: String { get } // The title of the window.
@objc optional func tabs() -> SBElementArray
// @objc optional var document: SafariDocument { get } // The document whose contents are displayed in the window.
// @objc optional func setCurrentTab(_ currentTab: SafariTab!) // The current tab.
}
extension SBObject: SafariWindow {}
// @objc public protocol SafariDocument: SBObjectProtocol {
// @objc optional var name: String { get } // Its name.
// @objc optional var URL: String { get } // The current URL of the document.
// }
// extension SBObject: SafariDocument {}
@objc public protocol SafariTab: SBObjectProtocol {
@objc optional var URL: String { get } // The current URL of the tab.
@objc optional var name: String { get } // The name of the tab.
}
extension SBObject: SafariTab {}
@objc public protocol GoogleChromeApplication: SBApplicationProtocol {
@objc optional func windows() -> SBElementArray
@objc optional func executeJavaScript(javascript: String!) -> Any // Applies a string of JavaScript code to a document. //, id: Any!
}
extension SBApplication: GoogleChromeApplication {}
@objc public protocol GoogleChromeWindow: SBObjectProtocol {
@objc optional var name: String { get } // The title of the window.
@objc optional func tabs() -> SBElementArray
}
extension SBObject: GoogleChromeWindow {}
@objc public protocol GoogleChromeTab: SBObjectProtocol {
@objc optional var URL: String { get } // The current URL of the tab.
@objc optional var title: String { get } // The name of the tab.
}
extension SBObject: GoogleChromeTab {}

View File

@ -1,178 +0,0 @@
//
// NetworkBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 23/02/2019.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
class NetworkBarItem: CustomButtonTouchBarItem, Widget {
static var name: String = "network"
static var identifier: String = "com.toxblh.mtmr.network"
private let flip: Bool
private let units: String
init(identifier: NSTouchBarItem.Identifier, flip: Bool = false, units: String) {
self.flip = flip
self.units = units
super.init(identifier: identifier, title: " ")
startMonitoringProcess()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startMonitoringProcess() {
var pipe: Pipe
var outputHandle: FileHandle
var bandwidthProcess: Process?
var dSpeed: UInt64?
var uSpeed: UInt64?
var curr: Array<Substring>?
var dataAvailable: NSObjectProtocol?
pipe = Pipe()
bandwidthProcess = Process()
bandwidthProcess?.launchPath = "/usr/bin/env"
bandwidthProcess?.arguments = ["netstat", "-w1", "-l", "en0"]
bandwidthProcess?.standardOutput = pipe
outputHandle = pipe.fileHandleForReading
outputHandle.waitForDataInBackgroundAndNotify(forModes: [RunLoop.Mode.common])
dataAvailable = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NSFileHandleDataAvailable,
object: outputHandle,
queue: nil
) { _ -> Void in
let data = pipe.fileHandleForReading.availableData
if data.count > 0 {
if let str = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
curr = [""]
curr = str
.replacingOccurrences(of: " ", with: " ")
.split(separator: " ")
if curr == nil || (curr?.count)! < 6 {} else {
if Int64(curr![2]) == nil {} else {
dSpeed = UInt64(curr![2])
uSpeed = UInt64(curr![5])
self.setTitle(up: self.getHumanizeSize(speed: uSpeed!), down: self.getHumanizeSize(speed: dSpeed!))
}
}
}
outputHandle.waitForDataInBackgroundAndNotify()
} else if let dataAvailable = dataAvailable {
NotificationCenter.default.removeObserver(dataAvailable)
}
}
var dataReady: NSObjectProtocol?
dataReady = NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: outputHandle,
queue: nil
) { _ -> Void in
print("Task terminated!")
if let observer = dataReady {
NotificationCenter.default.removeObserver(observer)
}
}
bandwidthProcess?.launch()
}
func getHumanizeSize(speed: UInt64) -> String {
let humanText: String
func speedB(speed: UInt64)-> String {
return String(format: "%.0f", Double(speed)) + " B/s"
}
func speedKB(speed: UInt64)-> String {
return String(format: "%.1f", Double(speed) / 1024) + " KB/s"
}
func speedMB(speed: UInt64)-> String {
return String(format: "%.1f", Double(speed) / (1024 * 1024)) + " MB/s"
}
func speedGB(speed: UInt64)-> String {
return String(format: "%.2f", Double(speed) / (1024 * 1024 * 1024)) + " GB/s"
}
switch self.units {
case "B/s":
humanText = speedB(speed: speed)
case "KB/s":
humanText = speedKB(speed: speed)
case "MB/s":
humanText = speedMB(speed: speed)
case "GB/s":
humanText = speedGB(speed: speed)
default:
if speed < 1024 {
humanText = speedB(speed: speed)
} else if speed < (1024 * 1024) {
humanText = speedKB(speed: speed)
} else if speed < (1024 * 1024 * 1024) {
humanText = speedMB(speed: speed)
} else {
humanText = speedGB(speed: speed)
}
}
return humanText
}
func appendUpSpeed(appendString: NSMutableAttributedString, up: String, titleFont: NSFont, newStr: Bool = false) {
appendString.append(NSMutableAttributedString(
string: newStr ? "\n" : "",
attributes: [
NSAttributedString.Key.foregroundColor: NSColor.blue,
NSAttributedString.Key.font: titleFont,
]))
appendString.append(NSMutableAttributedString(
string: up,
attributes: [
NSAttributedString.Key.font: titleFont,
]))
}
func appendDownSpeed(appendString: NSMutableAttributedString, down: String, titleFont: NSFont, newStr: Bool = false) {
appendString.append(NSMutableAttributedString(
string: newStr ? "\n" : "",
attributes: [
NSAttributedString.Key.foregroundColor: NSColor.red,
NSAttributedString.Key.font: titleFont,
]))
appendString.append(NSMutableAttributedString(
string: down,
attributes: [
NSAttributedString.Key.font: titleFont
]))
}
func setTitle(up: String, down: String) {
let titleFont = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: NSFont.Weight.light)
let newTitle: NSMutableAttributedString = NSMutableAttributedString(string: "")
if (self.flip) {
appendUpSpeed(appendString: newTitle, up: up, titleFont: titleFont)
appendDownSpeed(appendString: newTitle, down: down, titleFont: titleFont, newStr: true)
} else {
appendDownSpeed(appendString: newTitle, down: down, titleFont: titleFont)
appendUpSpeed(appendString: newTitle, up: up, titleFont: titleFont, newStr: true)
}
self.attributedTitle = newTitle
}
}

View File

@ -1,53 +0,0 @@
//
// NightShiftBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 28/08/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Foundation
class NightShiftBarItem: CustomButtonTouchBarItem {
private let nsclient = CBBlueLightClient()
private var timer: Timer!
private var blueLightStatus: Status {
var status: Status = Status()
nsclient.getBlueLightStatus(&status)
return status
}
private var isNightShiftEnabled: Bool {
return blueLightStatus.enabled.boolValue
}
private func setNightShift(state: Bool) {
nsclient.setEnabled(state)
}
init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier, title: "")
isBordered = false
setWidth(value: 28)
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.nightShiftAction() })
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
refresh()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func nightShiftAction() {
setNightShift(state: !isNightShiftEnabled)
refresh()
}
@objc func refresh() {
image = isNightShiftEnabled ? #imageLiteral(resourceName: "nightShiftOn") : #imageLiteral(resourceName: "nightShiftOff")
}
}

View File

@ -1,127 +0,0 @@
//
// PomodoroBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 10.05.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
static let identifier = "com.toxblh.mtmr.pomodoro."
static let name = "pomodoro"
static let decoder: ParametersDecoder = { decoder in
enum CodingKeys: String, CodingKey {
case workTime
case restTime
}
let container = try decoder.container(keyedBy: CodingKeys.self)
let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime)
let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime)
return (
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
actions: [],
legacyAction: .none,
legacyLongAction: .none,
parameters: [:]
)
}
private enum TimeTypes {
case work
case rest
case none
}
private let defaultTitle = "🍅 "
private let workTime: TimeInterval
private let restTime: TimeInterval
private var typeTime: TimeTypes = .none
private var timer: DispatchSourceTimer?
private var timeLeft: Int = 0
private var timeLeftString: String {
return String(format: "%.2i:%.2i", timeLeft / 60, timeLeft % 60)
}
init(identifier: NSTouchBarItem.Identifier, workTime: TimeInterval, restTime: TimeInterval) {
self.workTime = workTime
self.restTime = restTime
super.init(identifier: identifier, title: defaultTitle)
actions.append(contentsOf: [
ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() },
ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() }
])
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
timer?.cancel()
timer = nil
}
@objc func startStopWork() {
typeTime = .work
startStopTimer()
}
@objc func startStopRest() {
typeTime = .rest
startStopTimer()
}
func startStopTimer() {
timer == nil ? start() : reset()
}
private func start() {
timeLeft = Int(typeTime == .work ? workTime : restTime)
let queue: DispatchQueue = DispatchQueue(label: "Timer")
timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
timer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .never)
timer?.setEventHandler(handler: tick)
timer?.resume()
NSSound.beep()
}
private func finish() {
if typeTime != .none {
sendNotification()
}
reset()
}
private func reset() {
typeTime = .none
timer?.cancel()
timer = nil
title = defaultTitle
}
private func tick() {
timeLeft -= 1
DispatchQueue.main.async {
if self.timeLeft >= 0 {
self.title = self.defaultTitle + " " + self.timeLeftString
} else {
self.finish()
}
}
}
private func sendNotification() {
let notification: NSUserNotification = NSUserNotification()
notification.title = "Pomodoro"
notification.informativeText = typeTime == .work ? "it's time to rest your mind!" : "It's time to work!"
notification.soundName = "Submarine"
NSUserNotificationCenter.default.deliver(notification)
}
}

View File

@ -3,26 +3,22 @@ import Cocoa
class TimeTouchBarItem: CustomButtonTouchBarItem { class TimeTouchBarItem: CustomButtonTouchBarItem {
private let dateFormatter = DateFormatter() private let dateFormatter = DateFormatter()
private var timer: Timer! private var timer: Timer!
// private let button = NSButton(title: "", target: nil, action: nil)
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil, locale: String? = nil) {
dateFormatter.dateFormat = formatTemplate init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
if let locale = locale { dateFormatter.setLocalizedDateFormatFromTemplate(formatTemplate)
dateFormatter.locale = Locale(identifier: locale) super.init(identifier: identifier, title: " ", onTap: onTap, onLongTap: onLongTap)
}
if let abbr = timeZone {
dateFormatter.timeZone = TimeZone(abbreviation: abbr)
}
super.init(identifier: identifier, title: " ")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
isBordered = false self.view = button
updateTime() updateTime()
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func updateTime() { @objc func updateTime() {
title = dateFormatter.string(from: Date()) button.title = self.dateFormatter.string(from: Date())
} }
} }

View File

@ -1,256 +0,0 @@
//
// UpNextScrubberTouchBarItems.swift
// MTMR
//
// Created by Connor Meehan on 13/7/20.
// Copyright © 2020 Anton Palgunov. All rights reserved.
//
//
import Foundation
import EventKit
class UpNextScrubberTouchBarItem: NSCustomTouchBarItem {
// Dependencies
private let scrollView = NSScrollView()
private let activity: NSBackgroundActivityScheduler // Update scheduler
private var eventSources : [IUpNextSource] = []
private var items: [UpNextItem] = []
// Settings
private var futureSearchCutoff: Double
private var pastSearchCutoff: Double
private var maxToShow: Int
private var widthConstraint: NSLayoutConstraint?
private var autoResize: Bool = false
/// <#Description#>
/// - Parameters:
/// - identifier: Unique identifier of widget
/// - interval: Update view interval in seconds
/// - from: Relative to current time, how far back we search for events in hours
/// - to: Relative to current time, how far forward we search for events in hours
/// - maxToShow: Which event to show (1 is first, 2 is second, and so on)
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, maxToShow: Int, autoResize: Bool) {
// Initialise member properties
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck")
pastSearchCutoff = from * 3600
futureSearchCutoff = to * 3600
self.maxToShow = maxToShow
self.autoResize = autoResize
UpNextItem.df.dateFormat = "HH:mm"
// Error handling
if (maxToShow <= 0) {
fatalError("Error on UpNext bar item. maxToShow property must be greater than 0.")
}
// Init super
super.init(identifier: identifier)
view = scrollView
// Add event sources
// Can optionally pass an update view callback to an event source to redraw element
self.eventSources.append(UpNextCalenderSource(updateCallback: self.updateView))
// Fallback interactivity via interval
activity.interval = interval
activity.repeats = true
activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
self.updateView()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateView()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateView() -> Void {
items = []
var upcomingEvents = self.getUpcomingEvents()
upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending})
var index = 1
DispatchQueue.main.async {
for event in upcomingEvents {
// Create UpNextItem
let item = UpNextItem(event: event)
item.backgroundColor = self.getBackgroundColor(startDate: event.startDate)
// Bind tap event
item.actions.append(ItemAction(trigger: .singleTap) { [weak self] in
self?.switchToApp(event: event)
})
// Add to view
self.items.append(item)
// Check if should display any more
if (index == self.maxToShow) {
break;
}
index += 1
}
self.reloadData()
self.updateSize()
}
}
private func reloadData() {
let stackView = NSStackView(views: items.compactMap { $0.view })
stackView.spacing = 5
stackView.orientation = .horizontal
let visibleRect = self.scrollView.documentVisibleRect
self.scrollView.documentView = stackView
stackView.scroll(visibleRect.origin)
}
func updateSize() {
if self.autoResize {
self.widthConstraint?.isActive = false
let width = self.scrollView.documentView?.fittingSize.width ?? 0
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
self.widthConstraint!.isActive = true
}
}
private func getUpcomingEvents() -> [UpNextEventModel] {
var upcomingEvents: [UpNextEventModel] = []
// Calculate the range we're going to search for events in
let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff)
let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff)
// Get all events from all sources
for eventSource in self.eventSources {
if (eventSource.hasPermission) {
let events = eventSource.getUpcomingEvents(dateLowerBounds: dateLowerBounds, dateUpperBounds: dateUpperBounds)
upcomingEvents.append(contentsOf: events)
}
}
return upcomingEvents
}
public func switchToApp(event: UpNextEventModel) {
var bundleIdentifier: String
switch(event.sourceType) {
case .iCalendar:
bundleIdentifier = UpNextCalenderSource.bundleIdentifier
}
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
// NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings
}
func getBackgroundColor(startDate: Date) -> NSColor {
let distance = startDate.timeIntervalSinceReferenceDate/60 - Date().timeIntervalSinceReferenceDate/60 // Get time difference in minutes
if (distance < 0 as TimeInterval) { // If it's in the past, draw as blue
return NSColor.systemBlue
} else if (distance < 30 as TimeInterval) { // Less than 30 minutes, backround is red
return NSColor.systemRed
}
return NSColor.clear
}
}
private class UpNextItem : CustomButtonTouchBarItem {
static public let df = DateFormatter()
init(event: UpNextEventModel) {
let identifier = UpNextItem.getIdentifier(event: event)
let title = UpNextItem.getTitle(event: event)
super.init(identifier: NSTouchBarItem.Identifier(rawValue: identifier), title: title)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private static func getTitle(event: UpNextEventModel) -> String {
var title = ""
let startDateString = UpNextItem.df.string(for: event.startDate)
switch event.sourceType {
case .iCalendar:
title = String.init(format: "🗓 %@ - %@ ", event.title, startDateString!)
}
return title
}
private static func getIdentifier(event: UpNextEventModel) -> String {
var identifier : String
switch event.sourceType {
case .iCalendar:
identifier = "com.mtmr.iCalendarEvent"
}
return identifier + "." + event.title
}
}
enum UpNextSourceType {
case iCalendar
}
// Model for events to be displayed in dock
struct UpNextEventModel {
let title: String
let startDate: Date
let sourceType: UpNextSourceType
}
// Interface for any event source
protocol IUpNextSource {
static var bundleIdentifier: String { get }
var hasPermission : Bool { get }
var updateCallback : () -> Void { get set }
init(updateCallback: @escaping () -> Void)
func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel]
}
class UpNextCalenderSource : IUpNextSource {
static public let bundleIdentifier: String = "com.apple.iCal"
public var hasPermission: Bool = false
private var eventStore : EKEventStore
internal var updateCallback: () -> Void
required init(updateCallback: @escaping () -> Void = {}) {
self.updateCallback = updateCallback
eventStore = EKEventStore()
NotificationCenter.default.addObserver(forName: .EKEventStoreChanged, object: eventStore, queue: nil, using: handleUpdate)
let authStatus = EKEventStore.authorizationStatus(for: .event)
if (authStatus != .authorized) {
eventStore.requestAccess(to: .event){ granted, error in
self.hasPermission = granted;
self.handleUpdate()
if(!granted) {
NSLog("Error: MTMR UpNextBarWidget not given calendar access.")
return
}
}
} else {
self.handleUpdate()
}
}
public func handleUpdate() {
self.handleUpdate(note: Notification(name: Notification.Name("refresh view")))
}
public func handleUpdate(note: Notification) {
self.updateCallback()
}
public func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] {
var upcomingEvents: [UpNextEventModel] = []
let calendars = self.eventStore.calendars(for: .event)
let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: calendars)
let events = self.eventStore.events(matching: predicate)
for event in events {
upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate, sourceType: UpNextSourceType.iCalendar))
}
return upcomingEvents
}
}

View File

@ -1,92 +1,55 @@
import Cocoa
import AppKit import AppKit
import AVFoundation import AVFoundation
import Cocoa
import CoreAudio import CoreAudio
class VolumeViewController: NSCustomTouchBarItem { class VolumeViewController: NSCustomTouchBarItem {
private(set) var sliderItem: CustomSlider! private(set) var sliderItem: CustomSlider!
private var currentDeviceId: AudioObjectID = AudioObjectID(0)
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) { init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
super.init(identifier: identifier) super.init(identifier: identifier)
if image == nil { var forPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
mScope: kAudioDevicePropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster)
AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
if (image == nil) {
sliderItem = CustomSlider() sliderItem = CustomSlider()
} else { } else {
sliderItem = CustomSlider(knob: image!) sliderItem = CustomSlider(knob: image!)
} }
sliderItem.target = self sliderItem.target = self
sliderItem.action = #selector(VolumeViewController.sliderValueChanged(_:)) sliderItem.action = #selector(VolumeViewController.sliderValueChanged(_:))
sliderItem.minValue = 0.0 sliderItem.minValue = 0.0
sliderItem.maxValue = 100.0 sliderItem.maxValue = 100.0
sliderItem.floatValue = getInputGain() * 100 sliderItem.floatValue = getInputGain()*100
view = sliderItem self.view = sliderItem
currentDeviceId = defaultDeviceID
self.addAudioRouteChangedListener()
self.addCurrentAudioVolumeChangedListener()
} }
private func addAudioRouteChangedListener() { func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
let audioId = AudioObjectID(bitPattern: kAudioObjectSystemObject)
var forPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster)
AudioObjectAddPropertyListenerBlock(audioId, &forPropertyAddress, nil, audioRouteChanged)
}
func audioRouteChanged(numberAddresses _: UInt32, addresses _: UnsafePointer<AudioObjectPropertyAddress>) {
self.removeLastAudioVolumeChangeListener()
currentDeviceId = defaultDeviceID
self.addCurrentAudioVolumeChangedListener()
DispatchQueue.main.async { DispatchQueue.main.async {
self.sliderItem.floatValue = self.getInputGain() * 100 self.sliderItem.floatValue = self.getInputGain() * 100
} }
} }
private func addCurrentAudioVolumeChangedListener() { required init?(coder: NSCoder) {
var forPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
mScope: kAudioDevicePropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster
)
AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
}
private func removeLastAudioVolumeChangeListener() {
var forPropertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
mScope: kAudioDevicePropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster
)
AudioObjectRemovePropertyListenerBlock(currentDeviceId, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
}
func audioObjectPropertyListenerBlock(numberAddresses _: UInt32, addresses _: UnsafePointer<AudioObjectPropertyAddress>) {
DispatchQueue.main.async {
self.sliderItem.floatValue = self.getInputGain() * 100
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit { deinit {
sliderItem.unbind(NSBindingName.value) sliderItem.unbind(NSBindingName.value)
} }
@objc func sliderValueChanged(_ sender: Any) { @objc func sliderValueChanged(_ sender: Any) {
if let sliderItem = sender as? NSSlider { if let sliderItem = sender as? NSSlider {
_ = setInputGain(Float32(sliderItem.intValue) / 100.0) _ = setInputGain(Float32(sliderItem.intValue)/100.0)
} }
} }
private var defaultDeviceID: AudioObjectID { private var defaultDeviceID: AudioObjectID {
var deviceID: AudioObjectID = AudioObjectID(0) var deviceID: AudioObjectID = AudioObjectID(0)
var size: UInt32 = UInt32(MemoryLayout<AudioObjectID>.size) var size: UInt32 = UInt32(MemoryLayout<AudioObjectID>.size)
@ -97,36 +60,36 @@ class VolumeViewController: NSCustomTouchBarItem {
AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID) AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID)
return deviceID return deviceID
} }
private func getInputGain() -> Float32 { private func getInputGain() -> Float32 {
var volume: Float32 = 0.5 var volume: Float32 = 0.5
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume)) var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume))
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress() var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume) address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput) address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster) address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume) AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume)
return volume return volume
} }
private func setInputGain(_ volume: Float32) -> OSStatus { private func setInputGain(_ volume: Float32) -> OSStatus {
var inputVolume: Float32 = volume var inputVolume: Float32 = volume
if inputVolume == 0.0 { if inputVolume == 0.0 {
_ = setMute(mute: 1) _ = setMute( mute: 1)
} else { } else {
_ = setMute(mute: 0) _ = setMute( mute: 0)
} }
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: inputVolume)) let size: UInt32 = UInt32(MemoryLayout.size(ofValue: inputVolume))
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress() var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput) address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster) address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume) address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume) return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume)
} }
private func setMute(mute: Int) -> OSStatus { private func setMute( mute: Int) -> OSStatus {
var muteVal: Int = mute var muteVal: Int = mute
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress() var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
address.mSelector = AudioObjectPropertySelector(kAudioDevicePropertyMute) address.mSelector = AudioObjectPropertySelector(kAudioDevicePropertyMute)
@ -136,3 +99,4 @@ class VolumeViewController: NSCustomTouchBarItem {
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &muteVal) return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &muteVal)
} }
} }

View File

@ -16,42 +16,44 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
private var units_str = "°F" private var units_str = "°F"
private var prev_location: CLLocation! private var prev_location: CLLocation!
private var location: CLLocation! private var location: CLLocation!
private let iconsImages = ["01d": "☀️", "01n": "☀️", "02d": "⛅️", "02n": "⛅️", "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️", "09d": "⛅️", "09n": "⛅️", "10d": "🌦", "10n": "🌦", "11d": "🌩", "11n": "🌩", "13d": "❄️", "13n": "❄️", "50d": "🌫", "50n": "🌫"] private let iconsImages = ["01d": "☀️", "01n": "☀️", "02d": "⛅️", "02n": "⛅️", "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️", "09d": "⛅️", "09n": "⛅️", "10d": "🌦", "10n": "🌦", "11d": "🌩", "11n": "🌩", "13d": "❄️", "13n": "❄️", "50d": "🌫", "50n": "🌫"]
private let iconsText = ["01d": "", "01n": "", "02d": "", "02n": "", "03d": "", "03n": "", "04d": "", "04n": "", "09d": "", "09n": "", "10d": "", "10n": "", "11d": "", "11n": "", "13d": "", "13n": "", "50d": "", "50n": ""] private let iconsText = ["01d": "", "01n": "", "02d": "", "02n": "", "03d": "", "03n": "", "04d": "", "04n": "", "09d": "", "09n": "", "10d": "", "10n": "", "11d": "", "11n": "", "13d": "", "13n": "", "50d": "", "50n": ""]
private var iconsSource: Dictionary<String, String> private var iconsSource: Dictionary<String, String>
private var manager: CLLocationManager! private var manager:CLLocationManager!
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String, icon_type: String? = "text") { init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String, icon_type: String? = "text", onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval activity.interval = interval
self.units = units self.units = units
self.api_key = api_key self.api_key = api_key
if self.units == "metric" { if self.units == "metric" {
units_str = "°C" units_str = "°C"
} }
if self.units == "imperial" { if self.units == "imperial" {
units_str = "°F" units_str = "°F"
} }
if icon_type == "images" { if icon_type == "images" {
iconsSource = iconsImages iconsSource = iconsImages
} else { } else {
iconsSource = iconsText iconsSource = iconsText
} }
super.init(identifier: identifier, title: "", onTap: onTap, onLongTap: onLongTap)
super.init(identifier: identifier, title: "") self.view = button
let status = CLLocationManager.authorizationStatus() let status = CLLocationManager.authorizationStatus()
if status == .restricted || status == .denied { if status == .restricted || status == .denied {
print("User permission not given") print("User permission not given")
return return
} }
if !CLLocationManager.locationServicesEnabled() { if !CLLocationManager.locationServicesEnabled() {
print("Location services not enabled") print("Location services not enabled");
return return
} }
@ -62,43 +64,43 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
completion(NSBackgroundActivityScheduler.Result.finished) completion(NSBackgroundActivityScheduler.Result.finished)
} }
updateWeather() updateWeather()
manager = CLLocationManager() manager = CLLocationManager()
manager.delegate = self manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.startUpdatingLocation() manager.startUpdatingLocation()
} }
required init?(coder _: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func updateWeather() { @objc func updateWeather() {
if location != nil { if self.location != nil {
let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(units)&appid=\(api_key)")!) let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(self.units)&appid=\(self.api_key)")!)
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error == nil { if error == nil {
do { do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject] let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
// print(json) // print(json)
var temperature: Int! var temperature: Int!
var condition_icon = "" var condition_icon = ""
if let main = json["main"] as? [String: AnyObject] { if let main = json["main"] as? [String : AnyObject] {
if let temp = main["temp"] as? Double { if let temp = main["temp"] as? Double {
temperature = Int(temp) temperature = Int(temp)
} }
} }
if let weather = json["weather"] as? NSArray, let item = weather[0] as? NSDictionary { if let weather = json["weather"] as? NSArray, let item = weather[0] as? NSDictionary {
let icon = item["icon"] as! String let icon = item["icon"] as! String
if let test = self.iconsSource[icon] { if let test = self.iconsSource[icon] {
condition_icon = test condition_icon = test
} }
} }
if temperature != nil { if temperature != nil {
DispatchQueue.main.async { DispatchQueue.main.async {
self.setWeather(text: "\(condition_icon) \(temperature!)\(self.units_str)") self.setWeather(text: "\(condition_icon) \(temperature!)\(self.units_str)")
@ -109,34 +111,31 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
} }
} }
} }
task.resume() task.resume()
} }
} }
func setWeather(text: String) { func setWeather(text: String) {
title = text button.title = text
} }
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last! let lastLocation = locations.last!
location = lastLocation self.location = lastLocation
if prev_location == nil { if prev_location == nil {
updateWeather() updateWeather()
} }
prev_location = lastLocation prev_location = lastLocation
} }
func locationManager(_: CLLocationManager, didFailWithError error: Error) { func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error) print(error);
} }
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) { func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// print("inside didChangeAuthorization "); // print("inside didChangeAuthorization ");
updateWeather() updateWeather()
} }
deinit {
activity.invalidate()
}
} }

View File

@ -1,12 +0,0 @@
//
// WidgetProtocol.swift
// MTMR
//
// Created by Anton Palgunov on 20/10/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
protocol Widget {
static var name: String { get }
static var identifier: String { get }
}

View File

@ -1,172 +0,0 @@
//
// YandexWeatherBarItem.swift
// MTMR
//
// Created by bobrosoft on 22/07/2019.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
import CoreLocation
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
private let activity: NSBackgroundActivityScheduler
private let unitsStr = "°C"
private let iconsSource = [
"clear": "☀️",
"mostly-clear": "🌤",
"partly-cloudy": "⛅️",
"overcast": "☁️",
"cloudy": "☁️",
"light-rain": "🌦",
"drizzle": "💦",
"rain": "🌧",
"heavy-rain": "",
"storm": "🌩",
"thunderstorm-with-rain": "",
"sleet": "☔️",
"light-snow": "❄️",
"snow": "🌨",
"fog": "🌫"
]
private var location: CLLocation!
private var prevLocation: CLLocation!
private var manager: CLLocationManager!
private var updateWeatherTask: URLSessionDataTask?
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
self.updateWeather()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateWeather()
manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.startUpdatingLocation()
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
actions.append(ItemAction(
trigger: .singleTap,
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
updateWeatherTask?.cancel()
updateWeatherTask = 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: "\"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)")
}
}
}
updateWeatherTask?.resume()
}
func getWeatherUrl() -> String {
if location != nil {
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&lang=ru"
} else {
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
if prevLocation == nil {
updateWeather()
}
prevLocation = lastLocation
}
func locationManager(_: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func locationManager(_: CLLocationManager, didChangeAuthorization _: CLAuthorizationStatus) {
updateWeather()
}
deinit {
activity.invalidate()
}
}
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))
return results.map { result in
(0 ..< result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0))
: ""
}
}
}
}
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

View File

@ -1,109 +1,78 @@
[ [
{ "type": "escape", "align": "left" },
{ "type": "exitTouchbar", "width": 44, "align": "left" },
{ "type": "brightnessDown", "width": 44, "align": "left" },
{ {
"type": "escape", "type": "brightness",
"width": 64, "width": 60,
"align": "left"
},
{
"type": "dnd",
"align": "left", "align": "left",
"width": 38 "image": {
"base64":
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURffLOPfLNyaSVzUAAAACdFJOU/kBxOqnWgAAAbJJREFUSMfVljtyhDAQBVulQCFH4CgcDR1NR9ERFBKoeA5GfGddtkNvwFINFKP5tED22+Zxwviv6QVKfIEc/iNoF5gkpLIeYI8SUp4PsAUJiekADQntF6isQjvxCTrhAJlFqMMBeIH9BMsD7DAb2BhvYbIyNAOCZIWqYKGTpDZJFQu9EKVd44RxQRq3IrULWD62C8wSssWUZEsR0k6wcDOrJZmoBpMKI+s5qkBQCQOUJADVOECdOsDS0gDbsgHMfT4rVwHSrZQFIN5ABka8BgDgAeZ+BztBgvUEnSgVlhNsTFJjvoF5HAZorBpdYKAiSRbqNyBIUr6AjZMdPwO72R40MElS+wZUWA+wQ6LAYkFvdIhkmA+wQSDDdIAGAZ6A34H0x0fca11gBZZsIHSIfnE/5+NjCn/OuiuUB+/aunZwDeNayjXdTpDN0wlY+r1PfWu75nfj8RogN2JuCN2Y5qgMwTI0wGPUnQw6Qarx0sVNKA5Mn6VUL22lIbZoYitDbPmlvocc9Umfl2D7adz1reC3pF8av4m+DCenp/ndZuG3E7fhuC3pH2+vnz8V3MfE+bnxBTXuuIMTrLWHAAAAAElFTkSuQmCC"
}
}, },
{ "type": "brightnessDown", "width": 32, "bordered": false, "align": "left" }, { "type": "brightnessUp", "width": 44, "align": "left" },
{ "type": "brightnessUp", "width": 32, "bordered": false, "align": "left" },
// Spotify
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"inline": "if application \"Spotify\" is running then\rtell application \"Spotify\"\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" "inline":
"if application \"iTunes\" is running then\rtell application \"iTunes\"\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", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"inline": "if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r" "inline":
"if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
}, },
"refreshInterval": 1, "refreshInterval": 1,
"image": { "image": {
"base64": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUdwTB3WXx3UXh3VXx7XYBkXFRpVLRyURmIaeAQAAAAEdFJOUwDDO3fSqUUkAAABbklEQVRIx61VbW6DMAztoAeYNA7ApB6gkzhAWS24wIAL0HABaK6/pHFNEhy8TXu/kPzkPD/8cTj8K7KPAqB+K5NhQPCUrABCXe7HOUYYZxgVRLiG8RfY4DUgFFtC7cffAfZTFBwBdhWEKfgEq4ocEjgj4ZQifO6/QG9kkETp1dDeVWfRKx3XYSW0LoqY5kCElXDrQkyeCCuh6WL0M4nIWQIyzqixdfKU1koFDKvyCA8NJMzU4xiD+b4kfHRpsIyKc6hBwjVptFHVY51EMAINNDFGJITKDNQcdpX74Hz0CQ3rY5qwMp4EIxrlafzrsYZ2Veb0DkRgfNCUok4Y1fqEijfyi2b8RE9beWqa48Y/uvCNMcH9btfUi+/CGLR1vhL6Zz9N/vBlaCU+7lwY/cmJ67Ryen/2tj23PLqJBodZH8vgj544vOL4pxfI5acrSFxi8hrkU9TSKr78ZpnL50A8KPJJEo+afBblwyqf5j/iGys5j6ScrST2AAAAAElFTkSuQmCC" "base64":
"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"
} }
}, },
// Music
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "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" "inline":
"if application \"Spotify\" is running then\rtell application \"Spotify\"\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", "action": "appleScript",
"actionAppleScript": { "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" "inline":
"if application \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
}, },
"longAction": "appleScript", "refreshInterval": 1,
"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": { "image": {
"base64": "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" "base64":
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUdwTB3WXx3UXh3VXx7XYBkXFRpVLRyURmIaeAQAAAAEdFJOUwDDO3fSqUUkAAABbklEQVRIx61VbW6DMAztoAeYNA7ApB6gkzhAWS24wIAL0HABaK6/pHFNEhy8TXu/kPzkPD/8cTj8K7KPAqB+K5NhQPCUrABCXe7HOUYYZxgVRLiG8RfY4DUgFFtC7cffAfZTFBwBdhWEKfgEq4ocEjgj4ZQifO6/QG9kkETp1dDeVWfRKx3XYSW0LoqY5kCElXDrQkyeCCuh6WL0M4nIWQIyzqixdfKU1koFDKvyCA8NJMzU4xiD+b4kfHRpsIyKc6hBwjVptFHVY51EMAINNDFGJITKDNQcdpX74Hz0CQ3rY5qwMp4EIxrlafzrsYZ2Veb0DkRgfNCUok4Y1fqEijfyi2b8RE9beWqa48Y/uvCNMcH9btfUi+/CGLR1vhL6Zz9N/vBlaCU+7lwY/cmJ67Ryen/2tj23PLqJBodZH8vgj544vOL4pxfI5acrSFxi8hrkU9TSKr78ZpnL50A8KPJJEo+afBblwyqf5j/iGys5j6ScrST2AAAAAElFTkSuQmCC"
} }
}, },
// iTunes
{ {
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"source": { "source": {
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\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" "inline":
"if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rreturn (get artist) & \" \" & (get track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
}, },
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r" "inline":
"if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rnext\rend if\rend tell\rend if\r"
}, },
"longAction": "appleScript", "refreshInterval": 1,
"longActionAppleScript": {
"inline": "if application \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rprev track\rend if\rend tell\rend if\r"
},
"refreshInterval": 2,
"image": { "image": {
"base64": "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" "base64":
"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"
} }
}, },
{ "type": "displaySleep", "width": 40, "align": "right", "bordered": false }, { "type": "previous", "width": 44 },
{ { "type": "play", "width": 44 },
"type": "weather", { "type": "next", "width": 44 },
"align": "right", { "type": "weather", "icon_type": "images", "units": "metric" },
"icon_type": "images", { "type": "currency", "from": "BTC", "to": "USD" },
"api_key": "ca93a0bb8cdb428552660d83249e4bc9", { "type": "sleep", "width": 44 },
"bordered": false { "type": "mute", "width": 40, "align": "right" },
}, { "type": "volumeDown", "width": 34, "align": "right" },
{ { "type": "volume", "width": 60, "align": "right" },
"type": "volumeDown", { "type": "volumeUp", "width": 34, "align": "right" },
"bordered": false, { "type": "battery", "align": "right" },
"align": "right", { "type": "timeButton", "align": "right" }
"width": 28
},
{
"type": "volumeUp",
"bordered": false,
"align": "right",
"width": 28
},
{
"type": "play",
"align": "right",
"width": 38
},
{
"type": "battery",
"align": "right",
"bordered": false
},
{
"type": "timeButton",
"formatTemplate": "HH:mm",
"align": "right",
"bordered": false,
"longAction": "shellScript",
"longExecutablePath": "/usr/bin/pmset",
"longShellArguments": ["sleepnow"]
}
] ]

View File

@ -1,36 +0,0 @@
-----BEGIN PUBLIC KEY-----
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
35sZE2RHp4aEb9WceYHpjz8orPiLHJ/7OPbZ
-----END PUBLIC KEY-----

View File

@ -1,66 +0,0 @@
import XCTest
class AppleScriptDefinitionTests: XCTestCase {
func testInline() {
let buttonNoActionFixture = """
[ { "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 {
XCTFail()
return
}
XCTAssertEqual(source.string, "tell everything fine")
}
func testPath() {
let buttonNoActionFixture = """
[ { "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 {
XCTFail()
return
}
let sourceStruct = source as? Source
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 {
XCTFail()
return
}
}
}

View File

@ -1,27 +0,0 @@
import XCTest
class BackgroundColorTests: XCTestCase {
func testOpaque() {
let buttonNoActionFixture = """
[ { "type": "staticButton", "title": "Pew", "background": "#FF0000" } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
guard case let .background(color)? = result?.first?.additionalParameters[.background] else {
XCTFail()
return
}
XCTAssertEqual(color, .red)
}
func testAlpha() {
let buttonNoActionFixture = """
[ { "type": "staticButton", "title": "Pew", "background": "#FF000080" } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
guard case let .background(color)? = result?.first?.additionalParameters[.background] else {
XCTFail()
return
}
XCTAssertEqual(color.alphaComponent, 0.5, accuracy: 0.01)
}
}

View File

@ -1,82 +1,33 @@
import XCTest import XCTest
@testable import MTMR
class ParseConfig: XCTestCase { class ParseConfig: XCTestCase {
func testButtonNoAction() { func testButtonNoAction() {
let buttonNoActionFixture = """ let buttonNoActionFixture = """
[ { "type": "staticButton", "title": "Pew" } ] [ { "type": "staticButton", "title": "Pew" } ]
""".data(using: .utf8)! """.data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture) let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonNoActionFixture)
guard case .staticButton("Pew")? = result?.first?.type else { XCTAssertEqual(result?.first?.type, .staticButton(title: "Pew"))
XCTFail() XCTAssertEqual(result?.first?.action, .some(.none))
return
}
guard result?.first?.actions.count == 0 else {
XCTFail()
return
}
} }
func testButtonKeyCodeAction() { func testButtonKeyCodeAction() {
let buttonKeycodeFixture = """ let buttonKeycodeFixture = """
[ { "type": "staticButton", "title": "Pew", "actions": [ { "trigger": "singleTap", "action": "hidKey", "keycode": 123 } ] } ] [ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ]
""".data(using: .utf8)! """.data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture) let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("Pew")? = result?.first?.type else { XCTAssertEqual(result?.first?.type, .staticButton(title: "Pew"))
XCTFail() XCTAssertEqual(result?.first?.action, .hidKey(keycode: 123))
return
}
guard case .hidKey(keycode: 123)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
XCTFail()
return
}
} }
func testButtonKeyCodeLegacyAction() {
let buttonKeycodeFixture = """
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("Pew")? = result?.first?.type else {
XCTFail()
return
}
guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else {
XCTFail()
return
}
}
func testPredefinedItem() { func testPredefinedItem() {
let buttonKeycodeFixture = """ let buttonKeycodeFixture = """
[ { "type": "escape" } ] [ { "type": "brightnessUp" } ]
""".data(using: .utf8)! """.data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture) let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("esc")? = result?.first?.type else { XCTAssertEqual(result?.first?.type, .staticButton(title: "🔆"))
XCTFail() XCTAssertEqual(result?.first?.action, .keyPress(keycode: 113))
return
}
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
XCTFail()
return
}
}
func testExtendedWidthForPredefinedItem() {
let buttonKeycodeFixture = """
[ { "type": "escape", "width": 110}, ]
""".data(using: .utf8)!
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
guard case .staticButton("esc")? = result?.first?.type else {
XCTFail()
return
}
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
XCTFail()
return
}
guard case .width(110)? = result?.first?.additionalParameters[.width] else {
XCTFail()
return
}
} }
} }

510
README.md
View File

@ -1,437 +1,159 @@
# My touchbar. My rules. [![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases) [![license](https://img.shields.io/github/license/Toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/blob/master/LICENSE) ![minimal system requirements](https://img.shields.io/badge/required-macOS%2010.12.2-blue.svg) ![travis](https://travis-ci.org/Toxblh/MTMR.svg?branch=master)
<img src="Resources/logo.png" align="right"
title="MTMR by Toxblh" width="110" height="110">
_The TouchBar Customization App for your MacBook Pro_
My idea is to create a platform for creating plugins to customize the TouchBar. I very much like BTT and having a full custom TouchBar (my BTT preset), and I wanted to create it. It's my first Swift project for MacOS :)
**Share your presets [here](https://github.com/Toxblh/MTMR-presets)**
<p align="center"> <p align="center">
<img src="./Resources/aaaaa-acc6-17fee7572ed0.png" alt="Mackbook with touchbar" width="800"> <img src="Resources/logo.png" width="120">
</p> </p>
# My TouchBar. My rules
[![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases)
<p align="center"> <p align="center">
<a href="https://discord.gg/CmNcDuQ"><img height="20px" src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62fddf0fde45a8baedcc7ee5_847541504914fd33810e70a0ea73177e%20(2)-1.png"> Discord</a> <img src="Resources/TouchBar-v0.8.1.png">
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
</p> </p>
<p align="center"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a> My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :)
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a> ### Roadmap
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr"> - [x] Create the first prototype with TouchBar in Storyboard
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=my-touchbar-my-rules-mtmr&theme=light" alt="My TouchBar My Rules (MTMR)" height="36px" style="max-width:100%"> - [x] Put in stripe menu on startup the application
</a></p> - [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
- [x] Time in touchbar!
- [x] First the weather plugin
- [x] Find how to open full-screen TouchBar without the cross and stripe menu
- [x] Find how to add haptic feedback
- [x] Add icon and menu in StatusBar
- [x] Hide from Dock
- [x] Status menu: "preferences", "quit"
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
- [x] Custom buttons size, actions by click
- [x] Layout: [always left, NSSliderView for center, always right]
- [ ] Overwrite default values from item types (e.g. title for brightness)
- [ ] Custom settings for paddings and margins for buttons
- [ ] XPC Service for scripts
- [ ] UI for settings
- [ ] Import config from BTT
- [ ] System for autoupdate (maybe https://sparkle-project.org/)
Settings:
- [ ] Interface for plugins and export like presets
- [ ] Startup at login
- [ ] Show on/off in Dock
- [ ] Show on/off in StatusBar
- [ ] On/off Haptic Feedback
Maybe:
- [ ] Refactoring the application on packages (AppleScript, JavaScript? and Swift?)
## Installation ## Installation
1. Download last release [Releases](https://github.com/Toxblh/MTMR/releases)
3. Open MTMR
4. Open preset `open ~/Library/Application\ Support/MTMR/items.json` and customize it. Restart MTMR to apply changes.
- Download latest [release](https://github.com/Toxblh/MTMR/releases) (.dmg) from github ## Preset
- Or via Homebrew `brew install --cask mtmr`
- [Dario Prski](https://medium.com/@urdigitalpulse) has written a [fantastic article on medium](https://medium.com/@urdigitalpulse/customise-your-macbook-pro-touch-bar-966998e606b5) that goes into more detail on installing MTMR
**On first install** you need to allow access for MTMR in Accessibility otherwise buttons like <kbd>Esc</kbd>, <kbd>Volume</kbd>, <kbd>Brightness</kbd> and other system keys won't work. File for customize your preset for MTMR: `open ~/Library/Application\ Support/MTMR/items.json`
<p align="center">
<img width="450" alt="screenshot 2019-02-24 at 23 19 20" src="https://user-images.githubusercontent.com/2198153/53307057-2b078200-388c-11e9-8212-8c2b1aff0aa6.png">
</p>
<p align="center">
🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
</p>
## Examples
[MTMR presets](https://github.com/Toxblh/MTMR-presets)
<p align="center">
<img src="./Resources/Artboard.png" alt="Presets for touchbar" width="800">
</p>
## Customization
MTMR preferences are stored in `~/Library/Application\ Support/MTMR/items.json`.
The pre-installed configuration contains less or more than you'll probably want, try to configure:
## Built-in button types: ## Built-in button types:
> Buttons
- escape - escape
- exitTouchbar - exitTouchbar
- brightnessUp - brightnessUp
- brightnessDown - brightnessDown
- illuminationUp (keyboard illumination)
- illuminationDown (keyboard illumination)
- volumeDown - volumeDown
- volumeUp - volumeUp
- mute - mute
- dock
> Native Plugins > Native Plugins
- timeButton
- battery - battery
- cpu
- currency - currency
- weather - weather
- yandexWeather
- inputsource
- music (tap for pause, longTap for next)
- dock (half-long click to open app, full-long click to kill app)
- nightShift
- dnd (Don't disturb)
- darkMode
- pomodoro
- network
- upnext (Calendar events)
> Media Keys > Media Keys
- previous - previous
- play - play
- next - next
> AppleScript plugins > AppleScript plugins
- sleep - sleep
- displaySleep - displaySleep
> Custom buttons
- staticButton
- appleScriptTitledButton
- shellScriptTitledButton
## 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: ## Built-in slider types:
- brightness - brightness
- volume - volume
### You can also make custom buttons using these types ### You can also make a custom buttons using these types
- `staticButton`
#### `staticButton`
```json ```json
"type": "staticButton", "type": "staticButton",
"title": "esc", "title": "esc",
``` ```
#### `appleScriptTitledButton` - `appleScriptTitledButton`
```js ```js
{
"type": "appleScriptTitledButton", "type": "appleScriptTitledButton",
"refreshInterval": 60, //optional "refreshInterval": 60, //optional
"source": { "source": {
"filePath": "~/Library/Application Support/MTMR/iTunes.nowPlaying.scpt", "filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt",
// or // 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", "inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell",
// or // or
"base64": "StringInbase64" "base64": "StringInbase64"
}, },
}
``` ```
> Note: You can change appleScriptTitledButton's icon by following these steps: - `timeButton`
1. Declare 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 ```js
{ "type": "timeButton",
"type": "appleScriptTitledButton", "formatTemplate": "HH:mm" //optional
"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 also use escape sequences to return colors (read https://misc.flogisoft.com/bash/tip_colors_and_formatting for more information)
> "16 Colors" is the only mode supported presently. Buttons will set their own background color to the color returned.
Example of "CPU load" button which also changes color based on load value (Note: The native `cpu` plugin runs runs better):
```js
{
"type": "shellScriptTitledButton",
"width": 80,
"refreshInterval": 2,
"source": {
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
},
"actions": [
{
"trigger": "singleTap",
"action": "appleScript",
"actionAppleScript": {
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
}
}
],
"align": "right",
"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"
},
"bordered": false
}
```
## Groups
```js
{
"type": "group",
"align": "center",
"bordered": true,
"title": "stats",
"items": [
{ "type": "play" },
{ "type": "mute" },
...
]
}
```
To close a group, use the button:
```
{
"type": "close",
"width": 64
},
``` ```
## Native plugins ## Native plugins
- `weather`
#### `cpu` > Provider: https://openweathermap.org Need allowance location service
> Shows current CPU load in percent, changes color based on load value.
> Has lower power consumption and higher stability than the shell-based solution.
```js
{
"type": "cpu",
"refreshInterval": 3,
"width": 80
}
```
#### `timeButton`
> NOTE: Some values don't work properly: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
> formatTemplate examples: https://www.datetimeformatter.com/how-to-format-date-time-in-swift/
> locale examples: https://gist.github.com/jacobbubu/1836273
```js
{
"type": "timeButton",
"formatTemplate": "dd HH:mm",
"locale": "en_GB",
"timeZone": "UTC"
}
```
#### `weather`
> Provider: https://openweathermap.org \
> Note: Register at https://openweathermap.org to get your API key \
> Note: Wait for 20 minutes or so for Openweathermap to activate your API key.\
> Note: Enable MTMR in "Location Services" in the "Security & Privacy" System Preferences pane
```js ```js
"type": "weather", "type": "weather",
"refreshInterval": 600, // in seconds "refreshInterval": 600,
"units": "metric", // or imperial "units": "metric", // or imperial
"icon_type": "text", // or images "icon_type": "text" // or images
"api_key": "" // you can get the key on openweather "api_key": "" // you can get the key on openweather
``` ```
#### `yandexWeather` (experimental) - `currency`
> Provider: https://yandex.ru/pogoda. One click to open up weather forecast in your browser. \
> Note: Enable MTMR in "Location Services" in the "Security & Privacy" System Preferences pane
```js
"type": "yandexWeather",
"refreshInterval": 600 // in seconds
```
#### `currency`
> Provider: https://coinbase.com > Provider: https://coinbase.com
```js ```js
"type": "currency", "type": "currency",
"refreshInterval": 600, // in seconds "refreshInterval": 600,
"align": "right", "align": "right",
"from": "BTC", "from": "BTC",
"to": "USD", "to": "USD",
"full": true // £‣1.29$
``` ```
#### `music`
```js
{
"type": "music",
"align": "center",
"width": 80, // Optional
"bordered": false, // Optional
"refreshInterval": 2, // in seconds. Optional. Default 5 seconds
"disableMarquee": true // to disable marquee effect. Optional. Default false
},
```
#### `pomodoro`
> Pomodoro plugin. One tap starts the work timer, long-press to start the rest timer. Tap an in-progress timer to reset.
```js
{
"type": "pomodoro",
"workTime": 1200, // set time work in seconds. Default 1500 (25 min)
"restTime": 600 // set time rest in seconds. Default 300 (5 min)
},
```
#### `network`
> Network plugin. The plugin to show network usage
```js
{
"type": "network",
"flip": true,
"units": "dynamic" // or B/s, KB/s, MB/s, GB/s
},
```
#### `dock`
> Dock plugin
```js
{
"type": "dock",
"filter": "(^Xcode$)|(Safari)|(.*player)",
"autoResize": true
},
```
#### `upnext`
> Calendar next event plugin
Displays upcoming events from macOS Calendar. Does not display current event.
```js
{
"type": "upnext",
"from": 0, // Lower bound of search range for next event in hours. Default 0 (current time)(can be negative to view events in the past)
"to": 12, // Upper bounds of search range for next event in hours. Default 12 (12 hours in the future)
"maxToShow": 3, // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events)
"autoResize": false // If true, widget will expand to display all events. Default false (scrollable view within "width")
},
```
## Actions: ## Actions:
### Example:
```js
"actions": [
{
"trigger": "singleTap",
"action": "hidKey",
"keycode": 53
}
]
```
### Triggers:
- `singleTap`
- `doubleTap`
- `tripleTap`
- `longTap`
### Types
- `hidKey` - `hidKey`
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers > https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
```json ```json
"action": "hidKey", "action": "hidKey",
"keycode": 53, "keycode": 53,
``` ```
- `keyPress` - `keyPress`
> https://eastmanreference.com/complete-list-of-applescript-key-codes
```json ```json
"action": "keyPress", "action": "keyPress",
"keycode": 1, "keycode": 1,
``` ```
- `appleScript` - `appleScript`
```js ```js
"action": "appleScript", "action": "appleScript",
"actionAppleScript": { "actionAppleScript": {
"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", "inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell"
// "filePath" or "base64" will work as well // "filePath" or "base64" will work as well
}, },
``` ```
- `shellScript` - `shellScript`
```js ```js
"action": "shellScript", "action": "shellScript",
"executablePath": "/usr/bin/pmset", "executablePath": "/usr/bin/pmset",
@ -440,7 +162,6 @@ Displays upcoming events from macOS Calendar. Does not display current event.
``` ```
- `openUrl` - `openUrl`
```js ```js
"action": "openUrl", "action": "openUrl",
"url": "https://google.com", "url": "https://google.com",
@ -448,73 +169,80 @@ Displays upcoming events from macOS Calendar. Does not display current event.
## Additional parameters: ## Additional parameters:
- `width` restrict how much room a particular button will take - `width` allow to restrict how much room a particular button will take
```json ```json
"width": 34 "width": 34
``` ```
- `align` can stick the item to the side. default is center - `align` can stick the item to the side. default is center
```js ```js
"align": "left" // "left", "right" or "center" "align": "left" //or "right" or "center"
``` ```
- `bordered` you can do button without border ## Example configuration:
```js
"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"
}
```
- `matchAppId` displays the button only when active app's id matches given regexp
```json ```json
"matchAppId": "Safari" [
{ "type": "escape", "width": 110 },
{ "type": "exitTouchbar", "align": "left" },
{
"type": "brightnessUp",
"align": "left",
"width": 36
},
{
"type": "staticButton",
"align": "left",
"title": "🔆",
"action": "keyPress",
"keycode": 113,
"width": 36
},
{
"type": "appleScriptTitledButton",
"source": {
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt"
},
"refreshInterval": 1
},
{
"type": "staticButton",
"align": "left",
"image": { "base64" : "%base64Finder%"},
"action": "appleScript",
"actionAppleScript": {
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell"
},
"width": 36
},
{
"type": "appleScriptTitledButton",
"source": {
"inline": "if application \"Safari\" is running then\r\ttell application \"Safari\"\r\t\trepeat with t in tabs of windows\r\t\t\ttell t\r\t\t\t\tif URL starts with \"https:\/\/music.yandex.ru\" and name does not end with \"на Яндекс.Музыке\" then\r\t\t\t\t\treturn name of t as text\r\t\t\t\tend if\r\t\t\tend tell\r\t\tend repeat\r\tend tell\rend if\rreturn \"\""
},
"refreshInterval": 1
},
{ "type": "previous", "width": 36, "align": "right" },
{ "type": "play", "width": 36, "align": "right" },
{ "type": "next", "width": 36, "align": "right" },
{ "type": "sleep", "width": 36 , "align": "right"},
{ "type": "displaySleep", "align": "right" },
{ "type": "weather", "refreshInterval": 1800, "width": 70, "align": "right" },
{ "type": "volumeDown", "width": 36 , "align": "right"},
{ "type": "volumeUp", "width": 36 , "align": "right"},
{ "type": "battery", "refreshInterval": 60 , "align": "right"},
{ "type": "appleScriptTitledButton", "refreshInterval": 1800, "source": { "filePath": "/Users/redetection/Library/Application Support/MTMR/Weather.scpt"} , "align": "right"},
{ "type": "timeButton", "formatTemplate": "HH:mm", "width": 64, "align": "right" }
]
``` ```
## Troubleshooting ### Author's presets
#### If you can't open preferences: [@Toxblh preset](Resources/toxblh.json)
- Opening another program which can't edit text
1. Open Terminal.app
2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press <kbd>Enter</kbd>
[@ReDetection preset](Resources/ReDetection.json)
#### Buttons or gestures doesn't work:
- "After the last update my mtmr is not working anymore!"
- "Buttons sometimes do not trigger action"
- "ESC don't work"
- "Gestures don't work"
Re-tick or check a tick for access 🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
## Credits ## Credits
Built by [@Toxblh](https://patreon.com/toxblh) and [@ReDetection](http://patreon.com/ReDetection). Built by [@Toxblh](https://patreon.com/toxblh) and [@ReDetection](http://patreon.com/ReDetection).
[![Analytics](https://ga-beacon.appspot.com/UA-96373624-2/mtmr?pixel)](https://github.com/igrigorik/ga-beacon)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 KiB

View File

@ -1,129 +0,0 @@
[
{ "type": "escape", "align": "left" },
// --- iTunes ---
{
"type": "appleScriptTitledButton",
"source": {
"inline":
"if application \"iTunes\" is running then\rtell application \"iTunes\"\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 \"iTunes\" is running then\rtell application \"iTunes\"\rif player state is playing then\rnext track\rend if\rend tell\rend if\r"
},
"refreshInterval": 1,
"image": {
"base64":
"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"
}
},
// --- VOX ---
// {
// "type": "appleScriptTitledButton",
// "source": {
// "inline":
// "if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rreturn (get artist) & \" \" & (get track)\relse\rreturn \"\"\rend if\rend tell\rend if\rreturn \"\"\r"
// },
// "action": "appleScript",
// "actionAppleScript": {
// "inline":
// "if application \"VOX\" is running then\rtell application \"VOX\"\rif player state is 1 then\rnext\rend if\rend tell\rend if\r"
// },
// "refreshInterval": 1,
// "image": {
// "base64":
// "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"
// }
// },
{ "type": "dock", "width": 200, "align": "left"},
//{ "type": "button", "title": "alfred"},
//{ "type": "volume", "width": 160, "align": "right"},
{
"type": "group",
"align": "right",
"bordered": false,
"title": "Media",
"items": [
{"type": "close", "bordered": false, "align": "left"},
{
"type": "brightnessDown",
"bordered": false,
"align": "left",
"width": 36
},
{
"type": "brightness",
"width": 200,
"align": "left",
"image": {
"base64":
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURffLOPfLNyaSVzUAAAACdFJOU/kBxOqnWgAAAbJJREFUSMfVljtyhDAQBVulQCFH4CgcDR1NR9ERFBKoeA5GfGddtkNvwFINFKP5tED22+Zxwviv6QVKfIEc/iNoF5gkpLIeYI8SUp4PsAUJiekADQntF6isQjvxCTrhAJlFqMMBeIH9BMsD7DAb2BhvYbIyNAOCZIWqYKGTpDZJFQu9EKVd44RxQRq3IrULWD62C8wSssWUZEsR0k6wcDOrJZmoBpMKI+s5qkBQCQOUJADVOECdOsDS0gDbsgHMfT4rVwHSrZQFIN5ABka8BgDgAeZ+BztBgvUEnSgVlhNsTFJjvoF5HAZorBpdYKAiSRbqNyBIUr6AjZMdPwO72R40MElS+wZUWA+wQ6LAYkFvdIhkmA+wQSDDdIAGAZ6A34H0x0fca11gBZZsIHSIfnE/5+NjCn/OuiuUB+/aunZwDeNayjXdTpDN0wlY+r1PfWu75nfj8RogN2JuCN2Y5qgMwTI0wGPUnQw6Qarx0sVNKA5Mn6VUL22lIbZoYitDbPmlvocc9Umfl2D7adz1reC3pF8av4m+DCenp/ndZuG3E7fhuC3pH2+vnz8V3MfE+bnxBTXuuIMTrLWHAAAAAElFTkSuQmCC"
}
},
{
"type": "brightnessUp",
"bordered": false,
"align": "left",
"width": 36
},
{
"type": "volumeDown",
"bordered": false,
"align": "left",
"width": 36,
},
{ "type": "volume", "width": 200, "align": "left"},
{
"type": "volumeUp",
"bordered": false,
"align": "left",
"width": 36
},
{"type": "previous", "bordered": false, "align": "center"},
//{"type": "play", "bordered": false, "align": "center"},
{
"type": "appleScriptTitledButton",
"align": "center",
"source": {
"inline":
"if application \"Spotify\" is running then\rtell application \"Spotify\"\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 \"Spotify\" is running then\rtell application \"Spotify\"\rif player state is playing then\rpause\rend if\rend tell\rend if\r"
},
"refreshInterval": 0.0001,
"image": {
"base64":
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUdwTB3WXx3UXh3VXx7XYBkXFRpVLRyURmIaeAQAAAAEdFJOUwDDO3fSqUUkAAABbklEQVRIx61VbW6DMAztoAeYNA7ApB6gkzhAWS24wIAL0HABaK6/pHFNEhy8TXu/kPzkPD/8cTj8K7KPAqB+K5NhQPCUrABCXe7HOUYYZxgVRLiG8RfY4DUgFFtC7cffAfZTFBwBdhWEKfgEq4ocEjgj4ZQifO6/QG9kkETp1dDeVWfRKx3XYSW0LoqY5kCElXDrQkyeCCuh6WL0M4nIWQIyzqixdfKU1koFDKvyCA8NJMzU4xiD+b4kfHRpsIyKc6hBwjVptFHVY51EMAINNDFGJITKDNQcdpX74Hz0CQ3rY5qwMp4EIxrlafzrsYZ2Veb0DkRgfNCUok4Y1fqEijfyi2b8RE9beWqa48Y/uvCNMcH9btfUi+/CGLR1vhL6Zz9N/vBlaCU+7lwY/cmJ67Ryen/2tj23PLqJBodZH8vgj544vOL4pxfI5acrSFxi8hrkU9TSKr78ZpnL50A8KPJJEo+afBblwyqf5j/iGys5j6ScrST2AAAAAElFTkSuQmCC"
}
},
{"type": "next", "bordered": false, "align": "center"},
]
},
{ "type": "inputsource", "align": "right", },
{ "type": "mute", "bordered": false, "align": "right"},
{ "type": "dnd", "align": "right"},
{ "type": "nightShift", "align": "right"},
{ "type": "sleep", "align": "right", "bordered": false},
{ "type": "currency",
"refreshInterval": 600, // in seconds
"bordered": false,
"align": "right",
"from": "USD",
"to": "HKD",},
{
"type": "pomodoro",
"bordered": false,
"align": "right",
"workTime": 1800, // set time work in seconds. Default 1500 (25 min)
"restTime": 600, // set time rest in seconds. Default 300 (5 min)
},
{ "type": "weather", "refreshInterval": 60, "units": "metric", "align": "right", "bordered": false, "icon_type": "images", "api_key": "84645702688e83a35e2549ca77f73369"},
{ "type": "battery", "align": "right", "bordered": false },
{ "type": "timeButton", "align": "right", "formatTemplate": "E MMM d h:mm a", "bordered": false }
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 112 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Some files were not shown because too many files have changed in this diff Show More