Compare commits
No commits in common. "master" and "v0.2" have entirely different histories.
6
.github/FUNDING.yml
vendored
@ -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
|
|
||||||
22
.github/workflows/build-test.yml
vendored
@ -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]}
|
|
||||||
41
.github/workflows/publish.yml
vendored
@ -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
|
|
||||||
5
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
.DS_Store
|
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||||
@ -7,7 +5,6 @@
|
|||||||
## Build generated
|
## Build generated
|
||||||
build/
|
build/
|
||||||
DerivedData/
|
DerivedData/
|
||||||
Release/
|
|
||||||
|
|
||||||
## Various settings
|
## Various settings
|
||||||
*.pbxuser
|
*.pbxuser
|
||||||
@ -69,5 +66,3 @@ fastlane/report.xml
|
|||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
fastlane/screenshots
|
fastlane/screenshots
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
|
|
||||||
.vscode
|
|
||||||
|
|||||||
@ -7,168 +7,63 @@
|
|||||||
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 */; };
|
|
||||||
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A778BD20A6C27100B38714 /* GeneralExtensions.swift */; };
|
|
||||||
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; };
|
|
||||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */; };
|
|
||||||
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
|
|
||||||
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
|
|
||||||
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
|
|
||||||
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
|
|
||||||
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */; };
|
|
||||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
|
|
||||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
|
|
||||||
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 */; };
|
|
||||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
|
|
||||||
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; };
|
|
||||||
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 */; };
|
|
||||||
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 */; };
|
|
||||||
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 */; };
|
|
||||||
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 /* TouchBarItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* TouchBarItems.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 */; };
|
||||||
|
B082B255205C7D8000BC04DC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B254205C7D8000BC04DC /* ViewController.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 */; };
|
B082B266205C7D8000BC04DC /* MTMRTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B265205C7D8000BC04DC /* MTMRTests.swift */; };
|
||||||
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */; };
|
B082B271205C7D8000BC04DC /* MTMRUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B082B270205C7D8000BC04DC /* MTMRUITests.swift */; };
|
||||||
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 */; };
|
||||||
B0B17431207D6B590004B740 /* PlaySmart.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17427207D6B580004B740 /* PlaySmart.scpt */; };
|
B0C1CFCA205C97D30021C862 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C1CFC9205C97D30021C862 /* WindowController.swift */; };
|
||||||
B0B17432207D6B590004B740 /* Weather.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17428207D6B580004B740 /* Weather.scpt */; };
|
|
||||||
B0B17433207D6B590004B740 /* Finder.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17429207D6B580004B740 /* Finder.scpt */; };
|
|
||||||
B0B17434207D6B590004B740 /* Battery.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742A207D6B580004B740 /* Battery.scpt */; };
|
|
||||||
B0B17435207D6B590004B740 /* Spotify.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742B207D6B590004B740 /* Spotify.next.scpt */; };
|
|
||||||
B0B17436207D6B590004B740 /* iTunes.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742C207D6B590004B740 /* iTunes.next.scpt */; };
|
|
||||||
B0B17437207D6B590004B740 /* Spotify.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742D207D6B590004B740 /* Spotify.nowPlaying.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 */; };
|
|
||||||
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 */; };
|
B0F8771D207AD35400D6E430 /* battery.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0F8771C207AD35400D6E430 /* battery.scpt */; };
|
||||||
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 */
|
B082B26D205C7D8000BC04DC /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = B082B247205C7D8000BC04DC /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = B082B24E205C7D8000BC04DC;
|
||||||
|
remoteInfo = MTMR;
|
||||||
|
};
|
||||||
|
/* 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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptTouchBarItem.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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
|
||||||
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>"; };
|
||||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = "<group>"; };
|
B059D623205E04F3006E6B86 /* TouchBarItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarItems.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
|
B082B254205C7D8000BC04DC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.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>"; };
|
||||||
B082B259205C7D8000BC04DC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
B082B259205C7D8000BC04DC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
B082B25B205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
B082B25B205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
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; };
|
||||||
|
B082B265205C7D8000BC04DC /* MTMRTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTMRTests.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MTMRUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
|
B082B270205C7D8000BC04DC /* MTMRUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTMRUITests.swift; sourceTree = "<group>"; };
|
||||||
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultitouchSupport.framework; path = ../../../../../System/Library/PrivateFrameworks/MultitouchSupport.framework; sourceTree = "<group>"; };
|
B082B272205C7D8000BC04DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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>"; };
|
||||||
B0B17427207D6B580004B740 /* PlaySmart.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PlaySmart.scpt; sourceTree = "<group>"; };
|
B0C1CFC9205C97D30021C862 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
||||||
B0B17428207D6B580004B740 /* Weather.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Weather.scpt; sourceTree = "<group>"; };
|
|
||||||
B0B17429207D6B580004B740 /* Finder.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Finder.scpt; sourceTree = "<group>"; };
|
|
||||||
B0B1742A207D6B580004B740 /* Battery.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Battery.scpt; sourceTree = "<group>"; };
|
|
||||||
B0B1742B207D6B590004B740 /* Spotify.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Spotify.next.scpt; sourceTree = "<group>"; };
|
|
||||||
B0B1742C207D6B590004B740 /* iTunes.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.next.scpt; sourceTree = "<group>"; };
|
|
||||||
B0B1742D207D6B590004B740 /* Spotify.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Spotify.nowPlaying.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>"; };
|
|
||||||
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>"; };
|
B0F8771C207AD35400D6E430 /* battery.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = battery.scpt; 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 +71,7 @@
|
|||||||
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 */,
|
|
||||||
B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -191,16 +82,19 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
B082B269205C7D8000BC04DC /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
B059D62B205F11E8006E6B86 /* Frameworks */ = {
|
B059D62B205F11E8006E6B86 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B002E641216C0E38002774BA /* CoreDisplay.framework */,
|
|
||||||
B00D181C2152F4A5000806F4 /* Sparkle.framework */,
|
|
||||||
B08173292135F354005D4908 /* CoreBrightness.framework */,
|
|
||||||
B09EB1E5207C0F8E00D5C1E0 /* MultitouchSupport.framework */,
|
|
||||||
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
|
B059D62C205F11E8006E6B86 /* DFRFoundation.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
@ -210,9 +104,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
36C2ECD2207B3B1D003CDA33 /* README.md */,
|
36C2ECD2207B3B1D003CDA33 /* README.md */,
|
||||||
36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */,
|
|
||||||
B082B251205C7D8000BC04DC /* MTMR */,
|
B082B251205C7D8000BC04DC /* MTMR */,
|
||||||
B082B264205C7D8000BC04DC /* MTMRTests */,
|
B082B264205C7D8000BC04DC /* MTMRTests */,
|
||||||
|
B082B26F205C7D8000BC04DC /* MTMRUITests */,
|
||||||
B082B250205C7D8000BC04DC /* Products */,
|
B082B250205C7D8000BC04DC /* Products */,
|
||||||
B059D62B205F11E8006E6B86 /* Frameworks */,
|
B059D62B205F11E8006E6B86 /* Frameworks */,
|
||||||
);
|
);
|
||||||
@ -223,6 +117,7 @@
|
|||||||
children = (
|
children = (
|
||||||
B082B24F205C7D8000BC04DC /* MTMR.app */,
|
B082B24F205C7D8000BC04DC /* MTMR.app */,
|
||||||
B082B261205C7D8000BC04DC /* MTMRTests.xctest */,
|
B082B261205C7D8000BC04DC /* MTMRTests.xctest */,
|
||||||
|
B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -230,32 +125,21 @@
|
|||||||
B082B251205C7D8000BC04DC /* MTMR */ = {
|
B082B251205C7D8000BC04DC /* MTMR */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B0B88A07208CD12000A2C160 /* Widgets */,
|
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
|
||||||
B0B1743B207D6ED40004B740 /* CBridge */,
|
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
|
||||||
B0B17426207D6AFE0004B740 /* AppleScripts */,
|
B082B252205C7D8000BC04DC /* AppDelegate.swift */,
|
||||||
|
B082B254205C7D8000BC04DC /* ViewController.swift */,
|
||||||
B082B256205C7D8000BC04DC /* Assets.xcassets */,
|
B082B256205C7D8000BC04DC /* Assets.xcassets */,
|
||||||
|
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
|
||||||
|
B059D623205E04F3006E6B86 /* TouchBarItems.swift */,
|
||||||
|
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||||
|
B0C1CFC9205C97D30021C862 /* WindowController.swift */,
|
||||||
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 */,
|
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
|
||||||
B082B252205C7D8000BC04DC /* AppDelegate.swift */,
|
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
|
||||||
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
|
B0F8771C207AD35400D6E430 /* battery.scpt */,
|
||||||
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
|
|
||||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
|
||||||
4CF222EC26CC43D40025BDA7 /* CPU.swift */,
|
|
||||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
|
||||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
|
|
||||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
|
||||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
|
||||||
BAF5AB5624317B4300B04904 /* BasicView.swift */,
|
|
||||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
|
||||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
|
||||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
|
||||||
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
|
|
||||||
B05600D22083E9BB00EB218D /* CustomSlider.swift */,
|
|
||||||
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */,
|
|
||||||
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */,
|
|
||||||
);
|
);
|
||||||
path = MTMR;
|
path = MTMR;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -263,76 +147,19 @@
|
|||||||
B082B264205C7D8000BC04DC /* MTMRTests */ = {
|
B082B264205C7D8000BC04DC /* MTMRTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
36300E85209FD16700B31C71 /* .travis.yml */,
|
B082B265205C7D8000BC04DC /* MTMRTests.swift */,
|
||||||
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */,
|
|
||||||
B082B267205C7D8000BC04DC /* Info.plist */,
|
B082B267205C7D8000BC04DC /* Info.plist */,
|
||||||
36300E8E20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift */,
|
|
||||||
36300E9020A2D2B200B31C71 /* BackgroundColorTests.swift */,
|
|
||||||
);
|
);
|
||||||
path = MTMRTests;
|
path = MTMRTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B0B17426207D6AFE0004B740 /* AppleScripts */ = {
|
B082B26F205C7D8000BC04DC /* MTMRUITests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */,
|
B082B270205C7D8000BC04DC /* MTMRUITests.swift */,
|
||||||
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */,
|
B082B272205C7D8000BC04DC /* Info.plist */,
|
||||||
B0B1742A207D6B580004B740 /* Battery.scpt */,
|
|
||||||
B0B17429207D6B580004B740 /* Finder.scpt */,
|
|
||||||
B0B1742C207D6B590004B740 /* iTunes.next.scpt */,
|
|
||||||
B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */,
|
|
||||||
B0B17427207D6B580004B740 /* PlaySmart.scpt */,
|
|
||||||
B0B1742B207D6B590004B740 /* Spotify.next.scpt */,
|
|
||||||
B0B1742D207D6B590004B740 /* Spotify.nowPlaying.scpt */,
|
|
||||||
B0B1742E207D6B590004B740 /* Vox.next.scpt */,
|
|
||||||
B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */,
|
|
||||||
B0B17428207D6B580004B740 /* Weather.scpt */,
|
|
||||||
);
|
);
|
||||||
path = AppleScripts;
|
path = MTMRUITests;
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B0B1743B207D6ED40004B740 /* CBridge */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */,
|
|
||||||
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */,
|
|
||||||
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
|
|
||||||
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
|
|
||||||
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
|
|
||||||
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
|
|
||||||
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */,
|
|
||||||
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */,
|
|
||||||
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */,
|
|
||||||
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */,
|
|
||||||
B08173282135F128005D4908 /* CBBlueLightClient.h */,
|
|
||||||
);
|
|
||||||
path = CBridge;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B0B88A07208CD12000A2C160 /* Widgets */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
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 */,
|
|
||||||
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
|
|
||||||
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */,
|
|
||||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
|
|
||||||
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
|
||||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
|
||||||
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
|
|
||||||
);
|
|
||||||
path = Widgets;
|
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
@ -345,8 +172,6 @@
|
|||||||
B082B24B205C7D8000BC04DC /* Sources */,
|
B082B24B205C7D8000BC04DC /* Sources */,
|
||||||
B082B24C205C7D8000BC04DC /* Frameworks */,
|
B082B24C205C7D8000BC04DC /* Frameworks */,
|
||||||
B082B24D205C7D8000BC04DC /* Resources */,
|
B082B24D205C7D8000BC04DC /* Resources */,
|
||||||
B00D181E2152F507000806F4 /* CopyFiles */,
|
|
||||||
B0679BBF215AE085000FC6B4 /* ShellScript */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -368,12 +193,31 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
B082B263205C7D8000BC04DC /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = MTMRTests;
|
name = MTMRTests;
|
||||||
productName = MTMRTests;
|
productName = MTMRTests;
|
||||||
productReference = B082B261205C7D8000BC04DC /* MTMRTests.xctest */;
|
productReference = B082B261205C7D8000BC04DC /* MTMRTests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
|
B082B26B205C7D8000BC04DC /* MTMRUITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = B082B27B205C7D8000BC04DC /* Build configuration list for PBXNativeTarget "MTMRUITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
B082B268205C7D8000BC04DC /* Sources */,
|
||||||
|
B082B269205C7D8000BC04DC /* Frameworks */,
|
||||||
|
B082B26A205C7D8000BC04DC /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
B082B26E205C7D8000BC04DC /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = MTMRUITests;
|
||||||
|
productName = MTMRUITests;
|
||||||
|
productReference = B082B26C205C7D8000BC04DC /* MTMRUITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
@ -381,13 +225,12 @@
|
|||||||
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 = Automatic;
|
||||||
ProvisioningStyle = Manual;
|
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.Sandbox = {
|
com.apple.Sandbox = {
|
||||||
enabled = 0;
|
enabled = 0;
|
||||||
@ -396,7 +239,13 @@
|
|||||||
};
|
};
|
||||||
B082B260205C7D8000BC04DC = {
|
B082B260205C7D8000BC04DC = {
|
||||||
CreatedOnToolsVersion = 9.2;
|
CreatedOnToolsVersion = 9.2;
|
||||||
LastSwiftMigration = 1020;
|
ProvisioningStyle = Automatic;
|
||||||
|
TestTargetID = B082B24E205C7D8000BC04DC;
|
||||||
|
};
|
||||||
|
B082B26B205C7D8000BC04DC = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
TestTargetID = B082B24E205C7D8000BC04DC;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -415,6 +264,7 @@
|
|||||||
targets = (
|
targets = (
|
||||||
B082B24E205C7D8000BC04DC /* MTMR */,
|
B082B24E205C7D8000BC04DC /* MTMR */,
|
||||||
B082B260205C7D8000BC04DC /* MTMRTests */,
|
B082B260205C7D8000BC04DC /* MTMRTests */,
|
||||||
|
B082B26B205C7D8000BC04DC /* MTMRUITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -424,22 +274,9 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B0B17434207D6B590004B740 /* Battery.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 */,
|
|
||||||
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */,
|
|
||||||
5DC6CA03241F92CB005CD5E8 /* Music.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 */,
|
B0F8771D207AD35400D6E430 /* battery.scpt in Resources */,
|
||||||
B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */,
|
|
||||||
B0B17433207D6B590004B740 /* Finder.scpt in Resources */,
|
|
||||||
B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */,
|
|
||||||
B0B17438207D6B590004B740 /* Vox.next.scpt in Resources */,
|
|
||||||
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */,
|
|
||||||
B0B17432207D6B590004B740 /* Weather.scpt in Resources */,
|
|
||||||
B0B17431207D6B590004B740 /* PlaySmart.scpt in Resources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -450,27 +287,14 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
B082B26A205C7D8000BC04DC /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
B0679BBF215AE085000FC6B4 /* ShellScript */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
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 */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
B082B24B205C7D8000BC04DC /* Sources */ = {
|
B082B24B205C7D8000BC04DC /* Sources */ = {
|
||||||
@ -478,45 +302,12 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
||||||
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */,
|
B082B255205C7D8000BC04DC /* ViewController.swift in Sources */,
|
||||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
B0C1CFCA205C97D30021C862 /* WindowController.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 */,
|
|
||||||
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 */,
|
B059D624205E04F3006E6B86 /* TouchBarItems.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 */,
|
|
||||||
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
|
|
||||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
|
||||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.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 */,
|
|
||||||
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
|
|
||||||
B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */,
|
|
||||||
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */,
|
|
||||||
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
|
|
||||||
36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */,
|
|
||||||
60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */,
|
|
||||||
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */,
|
|
||||||
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
|
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
|
||||||
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
|
|
||||||
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
|
|
||||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */,
|
|
||||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
|
|
||||||
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
|
|
||||||
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -524,16 +315,33 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
36300E9120A2D2B200B31C71 /* BackgroundColorTests.swift in Sources */,
|
B082B266205C7D8000BC04DC /* MTMRTests.swift in Sources */,
|
||||||
36300E8F20A2CF3400B31C71 /* AppleScriptDefinitionTests.swift in Sources */,
|
);
|
||||||
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */,
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
36300E83209F040900B31C71 /* SupportHelpers.swift in Sources */,
|
};
|
||||||
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */,
|
B082B268205C7D8000BC04DC /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
B082B271205C7D8000BC04DC /* MTMRUITests.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 */;
|
||||||
|
};
|
||||||
|
B082B26E205C7D8000BC04DC /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = B082B24E205C7D8000BC04DC /* MTMR */;
|
||||||
|
targetProxy = B082B26D205C7D8000BC04DC /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
B082B258205C7D8000BC04DC /* Main.storyboard */ = {
|
B082B258205C7D8000BC04DC /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
@ -577,6 +385,7 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
@ -595,7 +404,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@ -648,7 +457,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
@ -659,20 +468,19 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
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 = "";
|
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/TouchBarPrivateApi-Bridging.h";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
|
SWIFT_VERSION = 4.0;
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@ -680,22 +488,19 @@
|
|||||||
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 = Automatic;
|
||||||
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 = "";
|
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/TouchBarPrivateApi-Bridging.h";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MTMR/CBridge/TouchBarPrivateApi-Bridging.h";
|
SWIFT_VERSION = 4.0;
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@ -703,12 +508,16 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = D6D8BR2QNB;
|
||||||
INFOPLIST_FILE = MTMRTests/Info.plist;
|
INFOPLIST_FILE = MTMRTests/Info.plist;
|
||||||
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;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MTMR.app/Contents/MacOS/MTMR";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@ -716,14 +525,48 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Manual;
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = D6D8BR2QNB;
|
||||||
INFOPLIST_FILE = MTMRTests/Info.plist;
|
INFOPLIST_FILE = MTMRTests/Info.plist;
|
||||||
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;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MTMR.app/Contents/MacOS/MTMR";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
B082B27C205C7D8000BC04DC /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = D6D8BR2QNB;
|
||||||
|
INFOPLIST_FILE = MTMRUITests/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRUITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 4.0;
|
||||||
|
TEST_TARGET_NAME = MTMR;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
B082B27D205C7D8000BC04DC /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = D6D8BR2QNB;
|
||||||
|
INFOPLIST_FILE = MTMRUITests/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMRUITests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 4.0;
|
||||||
|
TEST_TARGET_NAME = MTMR;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@ -757,6 +600,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
B082B27B205C7D8000BC04DC /* Build configuration list for PBXNativeTarget "MTMRUITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
B082B27C205C7D8000BC04DC /* Debug */,
|
||||||
|
B082B27D205C7D8000BC04DC /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = B082B247205C7D8000BC04DC /* Project object */;
|
rootObject = B082B247205C7D8000BC04DC /* Project object */;
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict/>
|
|
||||||
</plist>
|
|
||||||
@ -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>
|
|
||||||
@ -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>
|
|
||||||
@ -7,165 +7,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Sparkle
|
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
|
|
||||||
var isBlockedApp: Bool = false
|
|
||||||
|
|
||||||
private var fileSystemSource: DispatchSourceFileSystemObject?
|
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_: Notification) {
|
|
||||||
// Configure Sparkle
|
|
||||||
SUUpdater.shared().automaticallyDownloadsUpdates = false
|
|
||||||
SUUpdater.shared().automaticallyChecksForUpdates = true
|
|
||||||
SUUpdater.shared().checkForUpdatesInBackground()
|
|
||||||
|
|
||||||
AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
|
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
TouchBarController.shared.setupControlStripPresence()
|
TouchBarController.shared.setupControlStripPresence()
|
||||||
|
// Insert code here to initialize your application
|
||||||
if let button = statusItem.button {
|
|
||||||
button.image = #imageLiteral(resourceName: "StatusImage")
|
|
||||||
}
|
|
||||||
createMenu()
|
|
||||||
|
|
||||||
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) {
|
||||||
|
// Insert code here to tear down your application
|
||||||
@objc func updateIsBlockedApp() {
|
|
||||||
if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
|
|
||||||
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
|
|
||||||
} else {
|
|
||||||
isBlockedApp = false
|
|
||||||
}
|
|
||||||
createMenu()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPreferences(_: Any?) {
|
|
||||||
let task = Process()
|
|
||||||
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
|
|
||||||
let presetPath = appSupportDirectory.appending("/items.json")
|
|
||||||
task.launchPath = "/usr/bin/open"
|
|
||||||
task.arguments = [presetPath]
|
|
||||||
task.launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func toggleControlStrip(_ item: NSMenuItem) {
|
|
||||||
item.state = item.state == .on ? .off : .on
|
|
||||||
AppSettings.showControlStripState = item.state == .off
|
|
||||||
TouchBarController.shared.resetControlStrip()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func toggleBlackListedApp(_: Any?) {
|
|
||||||
if let appIdentifier = TouchBarController.shared.frontmostApplicationIdentifier {
|
|
||||||
if let index = TouchBarController.shared.blacklistAppIdentifiers.firstIndex(of: appIdentifier) {
|
|
||||||
TouchBarController.shared.blacklistAppIdentifiers.remove(at: index)
|
|
||||||
} else {
|
|
||||||
TouchBarController.shared.blacklistAppIdentifiers.append(appIdentifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.allowedFileTypes = ["json"]
|
|
||||||
dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true)
|
|
||||||
|
|
||||||
if dialog.runModal() == .OK, let path = dialog.url?.path {
|
|
||||||
TouchBarController.shared.reloadPreset(path: path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func toggleStartAtLogin(_: Any?) {
|
|
||||||
LaunchAtLoginController().setLaunchAtLogin(!LaunchAtLoginController().launchAtLogin, for: NSURL.fileURL(withPath: Bundle.main.bundlePath))
|
|
||||||
createMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
func createMenu() {
|
|
||||||
let menu = NSMenu()
|
|
||||||
|
|
||||||
let startAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleStartAtLogin(_:)), keyEquivalent: "L")
|
|
||||||
startAtLogin.state = LaunchAtLoginController().launchAtLogin ? .on : .off
|
|
||||||
|
|
||||||
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(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
|
||||||
statusItem.menu = menu
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadOnDefaultConfigChanged() {
|
|
||||||
let file = NSURL.fileURL(withPath: standardConfigPath)
|
|
||||||
|
|
||||||
let fd = open(file.path, O_EVTONLY)
|
|
||||||
|
|
||||||
fileSystemSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue(label: "DefaultConfigChanged"))
|
|
||||||
|
|
||||||
fileSystemSource?.setEventHandler(handler: {
|
|
||||||
print("Config changed, reloading...")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
TouchBarController.shared.reloadPreset(path: file.path)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fileSystemSource?.setCancelHandler(handler: {
|
|
||||||
close(fd)
|
|
||||||
})
|
|
||||||
|
|
||||||
fileSystemSource?.resume()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
|
||||||
private var script: NSAppleScript!
|
|
||||||
private let interval: TimeInterval
|
|
||||||
private var forceHideConstraint: NSLayoutConstraint!
|
|
||||||
private let alternativeImages: [String: SourceProtocol]
|
|
||||||
|
|
||||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) {
|
|
||||||
self.interval = interval
|
|
||||||
self.alternativeImages = alternativeImages
|
|
||||||
super.init(identifier: identifier, title: "⏳")
|
|
||||||
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
|
||||||
title = "scheduled"
|
|
||||||
DispatchQueue.appleScriptQueue.async {
|
|
||||||
guard let script = source.appleScript else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.title = "no script"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.script = script
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.isBordered = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var error: NSDictionary?
|
|
||||||
guard script.compileAndReturnError(&error) else {
|
|
||||||
#if DEBUG
|
|
||||||
print(error?.description ?? "unknown error")
|
|
||||||
#endif
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.title = "error"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.refreshAndSchedule()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshAndSchedule() {
|
|
||||||
#if DEBUG
|
|
||||||
print("refresh happened (interval \(interval)), self \(identifier.rawValue))")
|
|
||||||
#endif
|
|
||||||
let scriptResult = execute()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.title = 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
|
|
||||||
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 {
|
|
||||||
var error: NSDictionary?
|
|
||||||
let output = script.executeAndReturnError(&error)
|
|
||||||
if let error = error {
|
|
||||||
print(error)
|
|
||||||
return "error"
|
|
||||||
}
|
|
||||||
if output.descriptorType == typeAEList {
|
|
||||||
let arr = Array(1...output.numberOfItems).compactMap({ output.atIndex($0)!.stringValue ?? "" })
|
|
||||||
|
|
||||||
if arr.count <= 0 {
|
|
||||||
return ""
|
|
||||||
} else if arr.count == 1 {
|
|
||||||
return arr[0]
|
|
||||||
} else {
|
|
||||||
updateIcon(iconLabel: arr[1])
|
|
||||||
return arr[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output.stringValue ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DispatchQueue {
|
|
||||||
static let appleScriptQueue = DispatchQueue(label: "mtmr.applescript")
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
tell application "Finder"
|
|
||||||
if not (exists window 1) then
|
|
||||||
make new Finder window
|
|
||||||
set target of front window to path to home folder as string
|
|
||||||
end if
|
|
||||||
activate
|
|
||||||
end tell
|
|
||||||
@ -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
|
|
||||||
@ -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 ""
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
if application "iTunes" is running then
|
|
||||||
tell application "iTunes" to playpause
|
|
||||||
end if
|
|
||||||
|
|
||||||
if application "Music" is running then
|
|
||||||
tell application "Music" to playpause
|
|
||||||
end if
|
|
||||||
|
|
||||||
if application "Spotify" is running then
|
|
||||||
tell application "Spotify" to playpause
|
|
||||||
end if
|
|
||||||
|
|
||||||
if application "VOX" is running then
|
|
||||||
tell application "VOX" to playpause
|
|
||||||
end if
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
if application "Spotify" is running then
|
|
||||||
tell application "Spotify"
|
|
||||||
if player state is playing then
|
|
||||||
next track
|
|
||||||
end if
|
|
||||||
end tell
|
|
||||||
end if
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
if application "Spotify" is running then
|
|
||||||
tell application "Spotify"
|
|
||||||
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 ""
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
if application "VOX" is running then
|
|
||||||
tell application "VOX"
|
|
||||||
if player state is 1 then
|
|
||||||
next
|
|
||||||
end if
|
|
||||||
end tell
|
|
||||||
end if
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
if application "VOX" is running then
|
|
||||||
tell application "VOX"
|
|
||||||
if player state is 1 then
|
|
||||||
return (get artist) & " – " & (get track)
|
|
||||||
else
|
|
||||||
return ""
|
|
||||||
end if
|
|
||||||
end tell
|
|
||||||
end if
|
|
||||||
return ""
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
# This script requires two libs. Download them:
|
|
||||||
# https://itunes.apple.com/ru/app/json-helper-for-applescript/id453114608?l=en&mt=12
|
|
||||||
# https://itunes.apple.com/ru/app/location-helper-for-applescript/id488536386?mt=12
|
|
||||||
tell application "Location Helper"
|
|
||||||
set clocation_coords to get location coordinates
|
|
||||||
tell application "JSON Helper"
|
|
||||||
set weather to fetch JSON from "http://api.openweathermap.org/data/2.5/weather?lat=" & item 1 of clocation_coords & "&lon=" & item 2 of clocation_coords & "&units=metric&appid=32c4256d09a4c52b38aecddba7a078f6"
|
|
||||||
set temp to temp of main of weather as string
|
|
||||||
set cond_icon to icon of item 1 of weather of weather as string
|
|
||||||
if cond_icon is in ["01d", "01n"] then
|
|
||||||
set cond to "☀️"
|
|
||||||
else if cond_icon is in ["02d", "02n"] then
|
|
||||||
set cond to "⛅️"
|
|
||||||
else if cond_icon is in ["03d", "03n", "04d", "04n"] then
|
|
||||||
set cond to "☁️"
|
|
||||||
else if cond_icon is in ["09d", "09n"] then
|
|
||||||
set cond to "🌧"
|
|
||||||
else if cond_icon is in ["10d", "10n"] then
|
|
||||||
set cond to "🌦"
|
|
||||||
else if cond_icon is in ["11d", "11n"] then
|
|
||||||
set cond to "🌩"
|
|
||||||
else if cond_icon is in ["13d", "13n"] then
|
|
||||||
set cond to "❄️"
|
|
||||||
else if cond_icon is in ["50d", "50n"] then
|
|
||||||
set cond to "🌫"
|
|
||||||
else
|
|
||||||
set cond to ""
|
|
||||||
end if
|
|
||||||
set temp_round to round (temp * 1.0)
|
|
||||||
return cond & " " & temp_round & "°C"
|
|
||||||
end tell
|
|
||||||
end tell
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
if application "iTunes" is running then
|
|
||||||
tell application "iTunes"
|
|
||||||
if player state is playing then
|
|
||||||
next track
|
|
||||||
end if
|
|
||||||
end tell
|
|
||||||
end if
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
if application "iTunes" is running then
|
|
||||||
tell application "iTunes"
|
|
||||||
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 ""
|
|
||||||
@ -1,63 +1,53 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-16.png",
|
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-32.png",
|
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-32.png",
|
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-64.png",
|
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-128.png",
|
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "logo-256.png",
|
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "logo-256.png",
|
"size" : "256x256",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "logo-512.png",
|
"size" : "256x256",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "logo-512.png",
|
"size" : "512x512",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "logo-1024.png",
|
"size" : "512x512",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "StatusImage.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"template-rendering-intent" : "template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "brightnessDown.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"template-rendering-intent" : "template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "brightnessUp.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"template-rendering-intent" : "template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.2 KiB |
21
MTMR/Assets.xcassets/cpu.imageset/Contents.json
vendored
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
MTMR/Assets.xcassets/cpu.imageset/cpu.png
vendored
|
Before Width: | Height: | Size: 1.0 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 16 KiB |
BIN
MTMR/Assets.xcassets/dark-mode-on.imageset/39857.png
vendored
|
Before Width: | Height: | Size: 8.1 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
MTMR/Assets.xcassets/dnd-off.imageset/dnd-off.png
vendored
|
Before Width: | Height: | Size: 4.8 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
MTMR/Assets.xcassets/dnd-on.imageset/dnd-on.png
vendored
|
Before Width: | Height: | Size: 7.8 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
@ -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" initialViewController="B8D-0N-5wS">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</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,10 +676,54 @@
|
|||||||
</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"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Window Controller-->
|
||||||
|
<scene sceneID="R2V-B0-nI4">
|
||||||
|
<objects>
|
||||||
|
<windowController id="B8D-0N-5wS" customClass="WindowController" customModule="MTMR" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
|
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
|
||||||
|
</connections>
|
||||||
|
</window>
|
||||||
|
<connections>
|
||||||
|
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
|
||||||
|
</connections>
|
||||||
|
</windowController>
|
||||||
|
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="75" y="250"/>
|
||||||
|
</scene>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="hIz-AP-VOD">
|
||||||
|
<objects>
|
||||||
|
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="MTMR" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="178" height="57"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bso-ZY-Qqn">
|
||||||
|
<rect key="frame" x="18" y="20" width="142" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="My TouchBar. My rules" id="cmP-Ef-Jrj">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="74" y="613"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
//
|
|
||||||
// DeprecatedCarbonAPI.c
|
|
||||||
//
|
|
||||||
// This file is part of TouchDock
|
|
||||||
// Copyright (C) 2017 Xander Deng
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "DeprecatedCarbonAPI.h"
|
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
|
|
||||||
CFStringRef kPidKey = CFSTR("pid");
|
|
||||||
|
|
||||||
pid_t pidFromASN(void const *asn) {
|
|
||||||
pid_t pid = -1;
|
|
||||||
ProcessSerialNumber psn = {kNoProcess, kNoProcess};
|
|
||||||
if (CFGetTypeID(asn) == _LSASNGetTypeID()) {
|
|
||||||
_LSASNExtractHighAndLowParts(asn, &psn.highLongOfPSN, &psn.lowLongOfPSN);
|
|
||||||
CFDictionaryRef processInfo = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
|
|
||||||
if (processInfo) {
|
|
||||||
CFNumberRef pidNumber = CFDictionaryGetValue(processInfo, kPidKey);
|
|
||||||
if (pidNumber) {
|
|
||||||
CFNumberGetValue(pidNumber, kCFNumberSInt32Type, &pid);
|
|
||||||
}
|
|
||||||
CFRelease(processInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// DeprecatedCarbonAPI.h
|
|
||||||
//
|
|
||||||
// This file is part of TouchDock
|
|
||||||
// Copyright (C) 2017 Xander Deng
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Carbon/Carbon.h>
|
|
||||||
|
|
||||||
extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(uint32_t sessionID);
|
|
||||||
extern void _LSASNExtractHighAndLowParts(void const* asn, UInt32* psnHigh, UInt32* psnLow);
|
|
||||||
extern CFTypeID _LSASNGetTypeID(void);
|
|
||||||
|
|
||||||
pid_t pidFromASN(void const *asn);
|
|
||||||
@ -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
|
|
||||||
@ -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, ¤tItemURL, 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
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// TouchBarPrivateApi-Bridging.h
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 18/03/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "AMR_ANSIEscapeHelper.h"
|
|
||||||
#import "TouchBarPrivateApi.h"
|
|
||||||
#import "TouchBarSupport.h"
|
|
||||||
#import "DeprecatedCarbonAPI.h"
|
|
||||||
#import "CBBlueLightClient.h"
|
|
||||||
#import "LaunchAtLoginController.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
CF_EXPORT CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID);
|
|
||||||
CF_EXPORT IOReturn MTActuatorOpen(CFTypeRef actuatorRef);
|
|
||||||
CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef);
|
|
||||||
CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 arg1, Float32 arg2, Float32 arg3);
|
|
||||||
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
|
|
||||||
191
MTMR/CPU.swift
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,340 +0,0 @@
|
|||||||
//
|
|
||||||
// TouchBarItems.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 18/03/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
var actions: [ItemAction] = [] {
|
|
||||||
didSet {
|
|
||||||
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
|
|
||||||
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
button = CustomHeightButton(title: title, target: nil, action: nil)
|
|
||||||
|
|
||||||
longClick = LongPressGestureRecognizer(target: self, action: #selector(handleGestureLong))
|
|
||||||
longClick.isEnabled = false
|
|
||||||
longClick.allowedTouchTypes = .direct
|
|
||||||
longClick.delegate = self
|
|
||||||
|
|
||||||
multiClick = MultiClickGestureRecognizer(
|
|
||||||
target: self,
|
|
||||||
action: #selector(handleGestureSingleTap),
|
|
||||||
doubleAction: #selector(handleGestureDoubleTap),
|
|
||||||
tripleAction: #selector(handleGestureTripleTap)
|
|
||||||
)
|
|
||||||
multiClick.allowedTouchTypes = .direct
|
|
||||||
multiClick.delegate = self
|
|
||||||
multiClick.isDoubleClickEnabled = false
|
|
||||||
multiClick.isTripleClickEnabled = false
|
|
||||||
|
|
||||||
reinstallButton()
|
|
||||||
button.attributedTitle = attributedTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
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 {
|
|
||||||
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
|
|
||||||
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
|
|
||||||
{
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func callActions(for trigger: Action.Trigger) {
|
|
||||||
let itemActions = self.actions.filter { $0.trigger == trigger }
|
|
||||||
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 {
|
|
||||||
case .possible: // tiny hack because we're calling action manually
|
|
||||||
callActions(for: .longTap)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomHeightButton: NSButton {
|
|
||||||
override var intrinsicContentSize: NSSize {
|
|
||||||
var size = super.intrinsicContentSize
|
|
||||||
size.height = 30
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomButtonCell: NSButtonCell {
|
|
||||||
weak var parentItem: CustomButtonTouchBarItem?
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
//
|
|
||||||
// CustomSlider.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 15/04/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class CustomSliderCell: NSSliderCell {
|
|
||||||
var knobImage: NSImage!
|
|
||||||
private var _currentKnobRect: NSRect!
|
|
||||||
private var _barRect: NSRect!
|
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
|
||||||
super.init(coder: aDecoder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init(knob: NSImage?) {
|
|
||||||
knobImage = knob
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func drawKnob(_ knobRect: NSRect) {
|
|
||||||
if knobImage == nil {
|
|
||||||
super.drawKnob(knobRect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentKnobRect = knobRect
|
|
||||||
drawBar(inside: _barRect, flipped: true)
|
|
||||||
|
|
||||||
let x = (knobRect.origin.x * (_barRect.size.width - (knobImage.size.width - knobRect.size.width)) / _barRect.size.width) + 1
|
|
||||||
let y = knobRect.origin.y + 3
|
|
||||||
|
|
||||||
knobImage.draw(
|
|
||||||
at: NSPoint(x: x, y: y),
|
|
||||||
from: NSZeroRect,
|
|
||||||
operation: NSCompositingOperation.sourceOver,
|
|
||||||
fraction: 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func drawBar(inside aRect: NSRect, flipped _: Bool) {
|
|
||||||
_barRect = aRect
|
|
||||||
|
|
||||||
let barRadius = CGFloat(2)
|
|
||||||
|
|
||||||
var bgRect = aRect
|
|
||||||
bgRect.size.height = CGFloat(4)
|
|
||||||
|
|
||||||
let bg = NSBezierPath(roundedRect: bgRect, xRadius: barRadius, yRadius: barRadius)
|
|
||||||
NSColor.lightGray.setFill()
|
|
||||||
bg.fill()
|
|
||||||
|
|
||||||
var activeRect = bgRect
|
|
||||||
|
|
||||||
activeRect.size.width = CGFloat((Double(bgRect.size.width) / (maxValue - minValue)) * doubleValue)
|
|
||||||
let active = NSBezierPath(roundedRect: activeRect, xRadius: barRadius, yRadius: barRadius)
|
|
||||||
NSColor.darkGray.setFill()
|
|
||||||
active.fill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomSlider: NSSlider {
|
|
||||||
var currentValue: CGFloat = 0
|
|
||||||
|
|
||||||
override func setNeedsDisplay(_ invalidRect: NSRect) {
|
|
||||||
super.setNeedsDisplay(invalidRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
|
||||||
super.awakeFromNib()
|
|
||||||
if (cell?.isKind(of: CustomSliderCell.self)) == false {
|
|
||||||
let cell: CustomSliderCell = CustomSliderCell()
|
|
||||||
self.cell = cell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
convenience init(knob: NSImage) {
|
|
||||||
self.init()
|
|
||||||
cell = CustomSliderCell(knob: knob)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override init(frame frameRect: NSRect) {
|
|
||||||
super.init(frame: frameRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func knobImage() -> NSImage {
|
|
||||||
let cell = self.cell as! CustomSliderCell
|
|
||||||
return cell.knobImage
|
|
||||||
}
|
|
||||||
|
|
||||||
func setKnobImage(image: NSImage) {
|
|
||||||
let cell = self.cell as! CustomSliderCell
|
|
||||||
cell.knobImage = image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
//
|
|
||||||
// HapticFeedback.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 09/04/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import IOKit
|
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
static var instance = HapticFeedback()
|
|
||||||
|
|
||||||
// MARK: - Init
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
self.recreateDevice()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func recreateDevice() {
|
|
||||||
if let actuatorRef = self.actuatorRef {
|
|
||||||
MTActuatorClose(actuatorRef)
|
|
||||||
self.actuatorRef = nil // just in case %)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard self.actuatorRef == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's find our Haptic device
|
|
||||||
self.possibleDeviceIDs.forEach {(deviceID) in
|
|
||||||
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")
|
|
||||||
self.recreateDevice()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return actuatorRef
|
|
||||||
}
|
|
||||||
|
|
||||||
func tap(type: HapticType) {
|
|
||||||
guard let actuator = getActuatorIfPosible() else { return }
|
|
||||||
|
|
||||||
guard MTActuatorActuate(actuator, type.rawValue, 0, 0, 0) == kIOReturnSuccess else {
|
|
||||||
print("guard MTActuatorActuate")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard MTActuatorClose(actuator) == kIOReturnSuccess else {
|
|
||||||
print("guard MTActuatorClose")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,52 +17,16 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.27</string>
|
<string>1.0</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>
|
|
||||||
<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>
|
|
||||||
<string>Weather widget need your location for correct work</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>
|
||||||
|
|||||||
@ -1,820 +0,0 @@
|
|||||||
import AppKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension Data {
|
|
||||||
func barItemDefinitions() -> [BarItemDefinition]? {
|
|
||||||
return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BarItemDefinition: Decodable {
|
|
||||||
let type: ItemType
|
|
||||||
let actions: [Action]
|
|
||||||
let legacyAction: LegacyActionType
|
|
||||||
let legacyLongAction: LegacyLongActionType
|
|
||||||
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case type
|
|
||||||
case actions
|
|
||||||
}
|
|
||||||
|
|
||||||
init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
|
||||||
self.type = type
|
|
||||||
self.actions = actions
|
|
||||||
self.legacyAction = action
|
|
||||||
self.legacyLongAction = legacyLongAction
|
|
||||||
self.additionalParameters = additionalParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
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, actions: actions ?? [])
|
|
||||||
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
|
||||||
|
|
||||||
if let result = try? parametersDecoder(decoder),
|
|
||||||
case let (itemType, actions, action, longAction, parameters) = result {
|
|
||||||
parameters.forEach { additionalParameters[$0] = $1 }
|
|
||||||
self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters)
|
|
||||||
} else {
|
|
||||||
self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias ParametersDecoder = (Decoder) throws -> (
|
|
||||||
item: ItemType,
|
|
||||||
actions: [Action],
|
|
||||||
legacyAction: LegacyActionType,
|
|
||||||
legacyLongAction: LegacyLongActionType,
|
|
||||||
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
|
||||||
)
|
|
||||||
|
|
||||||
class SupportedTypesHolder {
|
|
||||||
private var supportedTypes: [String: ParametersDecoder] = [
|
|
||||||
"escape": { _ in (
|
|
||||||
item: .staticButton(title: "esc"),
|
|
||||||
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
|
|
||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"brightnessDown": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
|
||||||
return (
|
|
||||||
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
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"volumeUp": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"mute": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"previous": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"play": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"next": { _ in
|
|
||||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
|
||||||
return (
|
|
||||||
item: .staticButton(title: ""),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [.image: imageParameter]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
"sleep": { _ in (
|
|
||||||
item: .staticButton(title: "☕️"),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [:]
|
|
||||||
) },
|
|
||||||
|
|
||||||
"displaySleep": { _ in (
|
|
||||||
item: .staticButton(title: "☕️"),
|
|
||||||
actions: [
|
|
||||||
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
|
|
||||||
],
|
|
||||||
legacyAction: .none,
|
|
||||||
legacyLongAction: .none,
|
|
||||||
parameters: [:]
|
|
||||||
) },
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
static let sharedInstance = SupportedTypesHolder()
|
|
||||||
|
|
||||||
func lookup(by type: String, actions: [Action]) -> ParametersDecoder {
|
|
||||||
return supportedTypes[type] ?? { decoder in (
|
|
||||||
item: try ItemType(from: decoder),
|
|
||||||
actions: actions,
|
|
||||||
legacyAction: try LegacyActionType(from: decoder),
|
|
||||||
legacyLongAction: try LegacyLongActionType(from: decoder),
|
|
||||||
parameters: [:]
|
|
||||||
) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(typename: String, decoder: @escaping ParametersDecoder) {
|
|
||||||
supportedTypes[typename] = decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) {
|
|
||||||
register(typename: typename) { _ in
|
|
||||||
(
|
|
||||||
item: item,
|
|
||||||
actions,
|
|
||||||
legacyAction,
|
|
||||||
legacyLongAction,
|
|
||||||
parameters: [:]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ItemType: Decodable {
|
|
||||||
case staticButton(title: String)
|
|
||||||
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double, alternativeImages: [String: SourceProtocol])
|
|
||||||
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
|
||||||
case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
|
|
||||||
case battery
|
|
||||||
case cpu(refreshInterval: Double)
|
|
||||||
case dock(autoResize: Bool, filter: String?)
|
|
||||||
case volume
|
|
||||||
case brightness(refreshInterval: Double)
|
|
||||||
case weather(interval: Double, units: String, api_key: String, icon_type: String)
|
|
||||||
case yandexWeather(interval: Double)
|
|
||||||
case currency(interval: Double, from: String, to: String, full: Bool)
|
|
||||||
case inputsource
|
|
||||||
case 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 {
|
|
||||||
case type
|
|
||||||
case title
|
|
||||||
case source
|
|
||||||
case refreshInterval
|
|
||||||
case from
|
|
||||||
case to
|
|
||||||
case full
|
|
||||||
case timeZone
|
|
||||||
case units
|
|
||||||
case api_key
|
|
||||||
case icon_type
|
|
||||||
case formatTemplate
|
|
||||||
case locale
|
|
||||||
case image
|
|
||||||
case url
|
|
||||||
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 {
|
|
||||||
case staticButton
|
|
||||||
case appleScriptTitledButton
|
|
||||||
case shellScriptTitledButton
|
|
||||||
case timeButton
|
|
||||||
case battery
|
|
||||||
case cpu
|
|
||||||
case dock
|
|
||||||
case volume
|
|
||||||
case brightness
|
|
||||||
case weather
|
|
||||||
case yandexWeather
|
|
||||||
case currency
|
|
||||||
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 {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
let type = try container.decode(ItemTypeRaw.self, forKey: .type)
|
|
||||||
switch type {
|
|
||||||
case .appleScriptTitledButton:
|
|
||||||
let source = try container.decode(Source.self, forKey: .source)
|
|
||||||
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, 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:
|
|
||||||
let title = try container.decode(String.self, forKey: .title)
|
|
||||||
self = .staticButton(title: title)
|
|
||||||
|
|
||||||
case .timeButton:
|
|
||||||
let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm"
|
|
||||||
let timeZone = try container.decodeIfPresent(String.self, forKey: .timeZone) ?? nil
|
|
||||||
let locale = try container.decodeIfPresent(String.self, forKey: .locale) ?? nil
|
|
||||||
self = .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale)
|
|
||||||
|
|
||||||
case .battery:
|
|
||||||
self = .battery
|
|
||||||
|
|
||||||
case .cpu:
|
|
||||||
let refreshInterval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
|
|
||||||
self = .cpu(refreshInterval: refreshInterval)
|
|
||||||
|
|
||||||
case .dock:
|
|
||||||
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
|
||||||
let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter)
|
|
||||||
self = .dock(autoResize: autoResize, filter: filterRegexString)
|
|
||||||
|
|
||||||
case .volume:
|
|
||||||
self = .volume
|
|
||||||
|
|
||||||
case .brightness:
|
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
|
|
||||||
self = .brightness(refreshInterval: interval)
|
|
||||||
|
|
||||||
case .weather:
|
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
|
||||||
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric"
|
|
||||||
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6"
|
|
||||||
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text"
|
|
||||||
self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type)
|
|
||||||
|
|
||||||
case .yandexWeather:
|
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
|
||||||
self = .yandexWeather(interval: interval)
|
|
||||||
|
|
||||||
case .currency:
|
|
||||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0
|
|
||||||
let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB"
|
|
||||||
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, full: full)
|
|
||||||
|
|
||||||
case .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 {
|
|
||||||
|
|
||||||
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 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 CodingKeys: String, CodingKey {
|
|
||||||
case action
|
|
||||||
case keycode
|
|
||||||
case actionAppleScript
|
|
||||||
case executablePath
|
|
||||||
case shellArguments
|
|
||||||
case url
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ActionTypeRaw: String, Decodable {
|
|
||||||
case hidKey
|
|
||||||
case keyPress
|
|
||||||
case appleScript
|
|
||||||
case shellScript
|
|
||||||
case openUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .some(.hidKey):
|
|
||||||
let keycode = try container.decode(Int32.self, forKey: .keycode)
|
|
||||||
self = .hidKey(keycode: keycode)
|
|
||||||
|
|
||||||
case .some(.keyPress):
|
|
||||||
let keycode = try container.decode(Int.self, forKey: .keycode)
|
|
||||||
self = .keyPress(keycode: keycode)
|
|
||||||
|
|
||||||
case .some(.appleScript):
|
|
||||||
let source = try container.decode(Source.self, forKey: .actionAppleScript)
|
|
||||||
self = .appleScript(source: source)
|
|
||||||
|
|
||||||
case .some(.shellScript):
|
|
||||||
let executable = try container.decode(String.self, forKey: .executablePath)
|
|
||||||
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
|
|
||||||
self = .shellScript(executable: executable, parameters: parameters)
|
|
||||||
|
|
||||||
case .some(.openUrl):
|
|
||||||
let url = try container.decode(String.self, forKey: .url)
|
|
||||||
self = .openUrl(url: url)
|
|
||||||
|
|
||||||
case .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)
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case longAction
|
|
||||||
case longKeycode
|
|
||||||
case longActionAppleScript
|
|
||||||
case longExecutablePath
|
|
||||||
case longShellArguments
|
|
||||||
case longUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LongActionTypeRaw: String, Decodable {
|
|
||||||
case hidKey
|
|
||||||
case keyPress
|
|
||||||
case appleScript
|
|
||||||
case shellScript
|
|
||||||
case openUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction)
|
|
||||||
|
|
||||||
switch longType {
|
|
||||||
case .some(.hidKey):
|
|
||||||
let keycode = try container.decode(Int32.self, forKey: .longKeycode)
|
|
||||||
self = .hidKey(keycode: keycode)
|
|
||||||
|
|
||||||
case .some(.keyPress):
|
|
||||||
let keycode = try container.decode(Int.self, forKey: .longKeycode)
|
|
||||||
self = .keyPress(keycode: keycode)
|
|
||||||
|
|
||||||
case .some(.appleScript):
|
|
||||||
let source = try container.decode(Source.self, forKey: .longActionAppleScript)
|
|
||||||
self = .appleScript(source: source)
|
|
||||||
|
|
||||||
case .some(.shellScript):
|
|
||||||
let executable = try container.decode(String.self, forKey: .longExecutablePath)
|
|
||||||
let parameters = try container.decodeIfPresent([String].self, forKey: .longShellArguments) ?? []
|
|
||||||
self = .shellScript(executable: executable, parameters: parameters)
|
|
||||||
|
|
||||||
case .some(.openUrl):
|
|
||||||
let longUrl = try container.decode(String.self, forKey: .longUrl)
|
|
||||||
self = .openUrl(url: longUrl)
|
|
||||||
|
|
||||||
case .none:
|
|
||||||
self = .none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GeneralParameter {
|
|
||||||
case width(_: CGFloat)
|
|
||||||
case image(source: SourceProtocol)
|
|
||||||
case align(_: Align)
|
|
||||||
case bordered(_: Bool)
|
|
||||||
case background(_: NSColor)
|
|
||||||
case title(_: String)
|
|
||||||
case matchAppId(_: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GeneralParameters: Decodable {
|
|
||||||
let parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case width
|
|
||||||
case image
|
|
||||||
case align
|
|
||||||
case bordered
|
|
||||||
case background
|
|
||||||
case title
|
|
||||||
case matchAppId
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:]
|
|
||||||
|
|
||||||
if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) {
|
|
||||||
result[.width] = .width(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) {
|
|
||||||
result[.image] = .image(source: imageSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol SourceProtocol {
|
|
||||||
var data: Data? { get }
|
|
||||||
var string: String? { get }
|
|
||||||
var image: NSImage? { get }
|
|
||||||
var appleScript: NSAppleScript? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Source: Decodable, SourceProtocol {
|
|
||||||
let filePath: String?
|
|
||||||
let base64: String?
|
|
||||||
let inline: String?
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case filePath
|
|
||||||
case base64
|
|
||||||
case inline
|
|
||||||
}
|
|
||||||
|
|
||||||
var data: Data? {
|
|
||||||
return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData
|
|
||||||
}
|
|
||||||
|
|
||||||
var string: String? {
|
|
||||||
return inline ?? filePath?.fileString
|
|
||||||
}
|
|
||||||
|
|
||||||
var image: NSImage? {
|
|
||||||
return data?.image
|
|
||||||
}
|
|
||||||
|
|
||||||
var appleScript: NSAppleScript? {
|
|
||||||
return filePath?.fileURL.appleScript ?? string?.appleScript
|
|
||||||
}
|
|
||||||
|
|
||||||
private init(filePath: String?, base64: String?, inline: String?) {
|
|
||||||
self.filePath = filePath
|
|
||||||
self.base64 = base64
|
|
||||||
self.inline = inline
|
|
||||||
}
|
|
||||||
|
|
||||||
init(filePath: String) {
|
|
||||||
self.init(filePath: filePath, base64: nil, inline: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NSImage: SourceProtocol {
|
|
||||||
var data: Data? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var string: String? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var image: NSImage? {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
var appleScript: NSAppleScript? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
var base64Data: Data? {
|
|
||||||
return Data(base64Encoded: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileData: Data? {
|
|
||||||
return try? Data(contentsOf: URL(fileURLWithPath: (self as NSString).expandingTildeInPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
var utf8string: String? {
|
|
||||||
return String(data: self, encoding: .utf8)
|
|
||||||
}
|
|
||||||
|
|
||||||
var image: NSImage? {
|
|
||||||
return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Align: String, Decodable {
|
|
||||||
case left
|
|
||||||
case center
|
|
||||||
case right
|
|
||||||
}
|
|
||||||
|
|
||||||
extension URL {
|
|
||||||
var appleScript: NSAppleScript? {
|
|
||||||
guard FileManager.default.fileExists(atPath: path) else { return nil }
|
|
||||||
return NSAppleScript(contentsOf: self, error: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -13,12 +13,8 @@ protocol KeyPress {
|
|||||||
func send()
|
func send()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GenericKeyPress: KeyPress {
|
|
||||||
var keyCode: CGKeyCode
|
|
||||||
}
|
|
||||||
|
|
||||||
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 +25,56 @@ extension KeyPress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HIDPostAuxKey(_ key: Int32) {
|
struct ESCKeyPress: KeyPress {
|
||||||
let key = UInt8(key)
|
let keyCode: CGKeyCode = 53
|
||||||
MediaKeys.hidPostAuxKey(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BrightnessUpPress: KeyPress {
|
||||||
|
let keyCode: CGKeyCode = 107
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BrightnessDownPress: KeyPress {
|
||||||
|
let keyCode: CGKeyCode = 113
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func doKey(_ key: Int, down: Bool) {
|
||||||
|
let flags = NSEvent.ModifierFlags(rawValue: down ? 0xa00 : 0xb00)
|
||||||
|
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_BRIGHTNESS_UP = 2
|
||||||
|
let NX_KEYTYPE_BRIGHTNESS_DOWN = 3
|
||||||
|
|
||||||
|
let NX_KEYTYPE_PLAY = 16
|
||||||
|
let NX_KEYTYPE_NEXT = 17
|
||||||
|
let NX_KEYTYPE_PREVIOUS = 18
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
class ScrollViewItem: NSCustomTouchBarItem/*, NSGestureRecognizerDelegate*/ {
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
let views = items.compactMap { $0.view }
|
|
||||||
let stackView = NSStackView(views: views)
|
|
||||||
stackView.spacing = 1
|
|
||||||
stackView.orientation = .horizontal
|
|
||||||
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
|
|
||||||
scrollView.documentView = stackView
|
|
||||||
view = scrollView
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -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")
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
//
|
|
||||||
// SupportHelpers.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 13/04/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
func trim() -> String {
|
|
||||||
return trimmingCharacters(in: NSCharacterSet.whitespaces)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripComments() -> String {
|
|
||||||
// ((\s|,)\/\*[\s\S]*?\*\/)|(( |, ")\/\/.*)
|
|
||||||
return 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 {
|
|
||||||
func resize(maxSize: NSSize) -> NSImage {
|
|
||||||
var ratio: Float = 0.0
|
|
||||||
let imageWidth = Float(size.width)
|
|
||||||
let imageHeight = Float(size.height)
|
|
||||||
let maxWidth = Float(maxSize.width)
|
|
||||||
let maxHeight = Float(maxSize.height)
|
|
||||||
|
|
||||||
// Get ratio (landscape or portrait)
|
|
||||||
if imageWidth > imageHeight {
|
|
||||||
// Landscape
|
|
||||||
ratio = maxWidth / imageWidth
|
|
||||||
} else {
|
|
||||||
// Portrait
|
|
||||||
ratio = maxHeight / imageHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate new size based on the ratio
|
|
||||||
let newWidth = imageWidth * ratio
|
|
||||||
let newHeight = imageHeight * ratio
|
|
||||||
|
|
||||||
// Create a new NSSize object with the newly calculated size
|
|
||||||
let newSize: NSSize = NSSize(width: Int(newWidth), height: Int(newHeight))
|
|
||||||
|
|
||||||
// Cast the NSImage to a CGImage
|
|
||||||
var imageRect: NSRect = NSMakeRect(0, 0, size.width, size.height)
|
|
||||||
let imageRef = cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
|
|
||||||
|
|
||||||
// Create NSImage from the CGImage using the new size
|
|
||||||
let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize)
|
|
||||||
|
|
||||||
// Return the new image
|
|
||||||
return imageWithNewSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func rotateByDegreess(degrees: CGFloat) -> NSImage {
|
|
||||||
var imageBounds = NSZeroRect; imageBounds.size = size
|
|
||||||
let pathBounds = NSBezierPath(rect: imageBounds)
|
|
||||||
var transform = NSAffineTransform()
|
|
||||||
transform.rotate(byDegrees: degrees)
|
|
||||||
pathBounds.transform(using: transform as AffineTransform)
|
|
||||||
let rotatedBounds: NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, size.width, size.height)
|
|
||||||
let rotatedImage = NSImage(size: rotatedBounds.size)
|
|
||||||
|
|
||||||
// Center the image within the rotated bounds
|
|
||||||
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
|
|
||||||
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
|
|
||||||
|
|
||||||
// Start a new transform
|
|
||||||
transform = NSAffineTransform()
|
|
||||||
// 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.rotate(byDegrees: degrees)
|
|
||||||
// Move the coordinate system bak to normal
|
|
||||||
transform.translateX(by: -(NSWidth(rotatedBounds) / 2), yBy: -(NSHeight(rotatedBounds) / 2))
|
|
||||||
// Draw the original image, rotated, into the new image
|
|
||||||
rotatedImage.lockFocus()
|
|
||||||
transform.concat()
|
|
||||||
draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
|
|
||||||
rotatedImage.unlockFocus()
|
|
||||||
|
|
||||||
return rotatedImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,576 +8,175 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
struct ExactItem {
|
|
||||||
let identifier: NSTouchBarItem.Identifier
|
|
||||||
let presetItem: BarItemDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
|
|
||||||
let standardConfigPath = appSupportDirectory.appending("/items.json")
|
|
||||||
|
|
||||||
extension ItemType {
|
|
||||||
var identifierBase: String {
|
|
||||||
switch self {
|
|
||||||
case .staticButton(title: _):
|
|
||||||
return "com.toxblh.mtmr.staticButton."
|
|
||||||
case .appleScriptTitledButton(source: _):
|
|
||||||
return "com.toxblh.mtmr.appleScriptButton."
|
|
||||||
case .shellScriptTitledButton(source: _):
|
|
||||||
return "com.toxblh.mtmr.shellScriptButton."
|
|
||||||
case .timeButton(formatTemplate: _, timeZone: _, locale: _):
|
|
||||||
return "com.toxblh.mtmr.timeButton."
|
|
||||||
case .battery:
|
|
||||||
return "com.toxblh.mtmr.battery."
|
|
||||||
case .cpu(refreshInterval: _):
|
|
||||||
return "com.toxblh.mtmr.cpu."
|
|
||||||
case .dock(autoResize: _, filter: _):
|
|
||||||
return "com.toxblh.mtmr.dock"
|
|
||||||
case .volume:
|
|
||||||
return "com.toxblh.mtmr.volume"
|
|
||||||
case .brightness(refreshInterval: _):
|
|
||||||
return "com.toxblh.mtmr.brightness"
|
|
||||||
case .weather(interval: _, units: _, api_key: _, icon_type: _):
|
|
||||||
return "com.toxblh.mtmr.weather"
|
|
||||||
case .yandexWeather(interval: _):
|
|
||||||
return "com.toxblh.mtmr.yandexWeather"
|
|
||||||
case .currency(interval: _, from: _, to: _, full: _):
|
|
||||||
return "com.toxblh.mtmr.currency"
|
|
||||||
case .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 {
|
|
||||||
static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 timer = Timer()
|
||||||
var jsonItems: [BarItemDefinition] = []
|
var timeButton: NSButton = NSButton()
|
||||||
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
|
|
||||||
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
|
|
||||||
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
|
|
||||||
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
|
|
||||||
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
|
|
||||||
var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
|
|
||||||
var basicView: BasicView?
|
|
||||||
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(
|
|
||||||
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
|
|
||||||
(
|
|
||||||
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 {
|
|
||||||
var changed = items.count != prevItems.count || swipeItems.count != prevSwipeItems.count
|
|
||||||
|
|
||||||
if !changed {
|
|
||||||
for (item, prevItem) in zip(items, prevItems) {
|
|
||||||
if item.key != prevItem.key {
|
|
||||||
changed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
let changed = didItemsChange(prevItems: prevItems, prevSwipeItems: prevSwipeItems)
|
|
||||||
|
|
||||||
if !changed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
|
||||||
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))
|
|
||||||
|
|
||||||
touchBar.delegate = self
|
touchBar.delegate = self
|
||||||
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
touchBar.defaultItemIdentifiers = [
|
||||||
|
.escButton,
|
||||||
|
.dismissButton,
|
||||||
|
|
||||||
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
.brightDown,
|
||||||
items[identifier]
|
.brightUp,
|
||||||
})
|
|
||||||
let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
|
||||||
items[identifier]
|
|
||||||
})
|
|
||||||
|
|
||||||
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
.prev,
|
||||||
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
.play,
|
||||||
|
.next,
|
||||||
|
|
||||||
|
.sleep,
|
||||||
|
.weather,
|
||||||
|
|
||||||
|
.volumeDown,
|
||||||
|
.volumeUp,
|
||||||
|
.battery,
|
||||||
|
.time,
|
||||||
|
]
|
||||||
|
self.presentTouchBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func activeApplicationChanged(_: Notification) {
|
func setupControlStripPresence() {
|
||||||
updateActiveApp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveApp() {
|
|
||||||
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
|
|
||||||
dismissTouchBar()
|
|
||||||
} else {
|
|
||||||
prepareTouchBar()
|
|
||||||
if touchBarContainsAnyItems() {
|
|
||||||
presentTouchBar()
|
|
||||||
} else {
|
|
||||||
dismissTouchBar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func touchBarContainsAnyItems() -> Bool {
|
|
||||||
return items.count != 0 || swipeItems.count != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadStandardConfig() {
|
|
||||||
let presetPath = standardConfigPath
|
|
||||||
if !FileManager.default.fileExists(atPath: presetPath),
|
|
||||||
let defaultPreset = Bundle.main.path(forResource: "defaultPreset", ofType: "json") {
|
|
||||||
try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadPreset(path: presetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
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]) {
|
|
||||||
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() {
|
|
||||||
items = [:]
|
|
||||||
swipeItems = []
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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)
|
||||||
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: 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()
|
switch identifier {
|
||||||
updateActiveApp()
|
case .escButton:
|
||||||
}
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
item.view = NSButton(title: "esc", target: self, action: #selector(handleEsc))
|
||||||
|
return item
|
||||||
|
case .dismissButton:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
item.view = NSButton(title: "exit", target: self, action: #selector(dismissTouchBar))
|
||||||
|
return item
|
||||||
|
|
||||||
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
case .brightUp:
|
||||||
if identifier == basicViewIdentifier {
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
return basicView
|
item.view = NSButton(title: "🔆", target: self, action: #selector(handleBrightUp))
|
||||||
}
|
return item
|
||||||
|
case .brightDown:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
item.view = NSButton(title: "🔅", target: self, action: #selector(handleBrightDown))
|
||||||
|
return item
|
||||||
|
|
||||||
return nil
|
case .volumeDown:
|
||||||
}
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
item.view = NSButton(title: "🔉", target: self, action: #selector(handleVolumeDown))
|
||||||
|
return item
|
||||||
|
case .volumeUp:
|
||||||
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
|
item.view = NSButton(title: "🔊", target: self, action: #selector(handleVolumeUp))
|
||||||
|
return item
|
||||||
|
|
||||||
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
case .prev:
|
||||||
var barItem: NSTouchBarItem!
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
switch item.type {
|
item.view = NSButton(title: "⏪", target: self, action: #selector(handlePrev))
|
||||||
case let .staticButton(title: title):
|
return item
|
||||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
case .play:
|
||||||
case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages):
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages)
|
item.view = NSButton(title: "⏯", target: self, action: #selector(handlePlay))
|
||||||
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
|
return item
|
||||||
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
case .next:
|
||||||
case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale):
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale)
|
item.view = NSButton(title: "⏩", target: self, action: #selector(handleNext))
|
||||||
case .battery:
|
return item
|
||||||
barItem = BatteryBarItem(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:
|
|
||||||
if case let .image(source)? = item.additionalParameters[.image] {
|
|
||||||
barItem = VolumeViewController(identifier: identifier, image: source.image)
|
|
||||||
} else {
|
|
||||||
barItem = VolumeViewController(identifier: identifier)
|
|
||||||
}
|
|
||||||
case let .brightness(refreshInterval: interval):
|
|
||||||
if case let .image(source)? = item.additionalParameters[.image] {
|
|
||||||
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval, image: source.image)
|
|
||||||
} else {
|
|
||||||
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval)
|
|
||||||
}
|
|
||||||
case let .weather(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)
|
|
||||||
case let .yandexWeather(interval: interval):
|
|
||||||
barItem = YandexWeatherBarItem(identifier: identifier, interval: interval)
|
|
||||||
case let .currency(interval: interval, from: from, to: to, full: full):
|
|
||||||
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, full: full)
|
|
||||||
case .inputsource:
|
|
||||||
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 {
|
case .time:
|
||||||
item.actions.append(ItemAction(trigger: .singleTap, action))
|
let item = NSCustomTouchBarItem(identifier: identifier)
|
||||||
}
|
timeButton = NSButton(title: self.getCurrentTime(), target: self, action: nil)
|
||||||
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
item.view = timeButton
|
||||||
item.actions.append(ItemAction(trigger: .longTap, longAction))
|
return item
|
||||||
}
|
|
||||||
|
|
||||||
if let touchBarItem = barItem as? CustomButtonTouchBarItem {
|
default:
|
||||||
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)
|
|
||||||
}
|
|
||||||
if case let .image(source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
|
|
||||||
item.image = source.image
|
|
||||||
}
|
|
||||||
if case let .title(value)? = item.additionalParameters[.title] {
|
|
||||||
if let item = barItem as? GroupBarItem {
|
|
||||||
item.collapsedRepresentationLabel = value
|
|
||||||
} else if let item = barItem as? CustomButtonTouchBarItem {
|
|
||||||
item.title = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
func getCurrentTime() -> String {
|
||||||
switch item.legacyAction {
|
let date = Date()
|
||||||
case let .hidKey(keycode: keycode):
|
let dateFormatter = DateFormatter()
|
||||||
return { HIDPostAuxKey(keycode) }
|
dateFormatter.setLocalizedDateFormatFromTemplate("HH:mm")
|
||||||
case let .keyPress(keycode: keycode):
|
let timestamp = dateFormatter.string(from: date)
|
||||||
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
|
return timestamp
|
||||||
case let .appleScript(source: 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)? {
|
@objc func updateTime() {
|
||||||
switch item.legacyLongAction {
|
timeButton.title = getCurrentTime()
|
||||||
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 \(item)")
|
|
||||||
return {}
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol CanSetWidth {
|
@objc func handleEsc() {
|
||||||
func setWidth(value: CGFloat)
|
let sender = ESCKeyPress()
|
||||||
}
|
sender.send()
|
||||||
|
}
|
||||||
|
|
||||||
extension NSCustomTouchBarItem: CanSetWidth {
|
@objc func handleVolumeUp() {
|
||||||
func setWidth(value: CGFloat) {
|
HIDPostAuxKey(Int(NX_KEYTYPE_SOUND_UP))
|
||||||
view.widthAnchor.constraint(equalToConstant: value).isActive = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension NSPopoverTouchBarItem: CanSetWidth {
|
@objc func handleVolumeDown() {
|
||||||
func setWidth(value: CGFloat) {
|
HIDPostAuxKey(Int(NX_KEYTYPE_SOUND_DOWN))
|
||||||
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension BarItemDefinition {
|
@objc func handleBrightDown() {
|
||||||
var align: Align {
|
// HIDPostAuxKey(Int(NX_KEYTYPE_BRIGHTNESS_DOWN))
|
||||||
if case let .align(result)? = additionalParameters[.align] {
|
|
||||||
return result
|
let sender = BrightnessUpPress()
|
||||||
|
sender.send()
|
||||||
}
|
}
|
||||||
return .center
|
|
||||||
|
@objc func handleBrightUp() {
|
||||||
|
// HIDPostAuxKey(Int(NX_KEYTYPE_BRIGHTNESS_UP))
|
||||||
|
|
||||||
|
let sender = BrightnessDownPress()
|
||||||
|
sender.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func handlePrev() {
|
||||||
|
HIDPostAuxKey(Int(NX_KEYTYPE_PREVIOUS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handlePlay() {
|
||||||
|
HIDPostAuxKey(Int(NX_KEYTYPE_PLAY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleNext() {
|
||||||
|
HIDPostAuxKey(Int(NX_KEYTYPE_NEXT))
|
||||||
|
}
|
||||||
|
|
||||||
|
// func getBattery() {
|
||||||
|
// var error: NSDictionary?
|
||||||
|
// if let scriptObject = NSAppleScript(source: <#T##String#>) {
|
||||||
|
// if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(
|
||||||
|
// &error) {
|
||||||
|
// print(output.stringValue)
|
||||||
|
// } else if (error != nil) {
|
||||||
|
// print("error: \(error)")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
MTMR/TouchBarItems.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// TouchBarItems.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Anton Palgunov on 18/03/2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
extension NSTouchBarItem.Identifier {
|
||||||
|
static let escButton = NSTouchBarItem.Identifier("com.toxblh.mtmr.escButton")
|
||||||
|
static let dismissButton = NSTouchBarItem.Identifier("com.toxblh.mtmr.dismissButton")
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
static let volumeUp = NSTouchBarItem.Identifier("com.toxblh.mtmr.volumeUp")
|
||||||
|
static let volumeDown = NSTouchBarItem.Identifier("com.toxblh.mtmr.volumeDown")
|
||||||
|
|
||||||
|
// Brightness
|
||||||
|
static let brightUp = NSTouchBarItem.Identifier("com.toxblh.mtmr.brightUp")
|
||||||
|
static let brightDown = NSTouchBarItem.Identifier("com.toxblh.mtmr.brightDown")
|
||||||
|
|
||||||
|
// Music
|
||||||
|
static let prev = NSTouchBarItem.Identifier("com.toxblh.mtmr.prev")
|
||||||
|
static let next = NSTouchBarItem.Identifier("com.toxblh.mtmr.next")
|
||||||
|
static let play = NSTouchBarItem.Identifier("com.toxblh.mtmr.play")
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
static let sleep = NSTouchBarItem.Identifier("com.toxblh.mtmr.sleep")
|
||||||
|
static let weather = NSTouchBarItem.Identifier("com.toxblh.mtmr.weather")
|
||||||
|
static let time = NSTouchBarItem.Identifier("com.toxblh.mtmr.time")
|
||||||
|
static let battery = NSTouchBarItem.Identifier("com.toxblh.mtmr.battery")
|
||||||
|
static let nowPlaying = NSTouchBarItem.Identifier("com.toxblh.mtmr.nowPlaying")
|
||||||
|
|
||||||
|
static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip")
|
||||||
|
}
|
||||||
10
MTMR/TouchBarPrivateApi-Bridging.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//
|
||||||
|
// TouchBarPrivateApi-Bridging.h
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Anton Palgunov on 18/03/2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "TouchBarPrivateApi.h"
|
||||||
|
#import "TouchBarSupport.h"
|
||||||
@ -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
|
||||||
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
@interface MediaKeys : NSObject
|
@interface MediaKeys : NSObject
|
||||||
|
|
||||||
+ (void)HIDPostAuxKey:(UInt8)keyCode;
|
+ (void)decreaseVolume;
|
||||||
|
+ (void)increaseVolume;
|
||||||
|
+ (void)muteVolume;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -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
|
||||||
27
MTMR/ViewController.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// ViewController.swift
|
||||||
|
// MTMR
|
||||||
|
//
|
||||||
|
// Created by Anton Palgunov on 16/03/2018.
|
||||||
|
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class ViewController: NSViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Do any additional setup after loading the view.
|
||||||
|
}
|
||||||
|
|
||||||
|
override var representedObject: Any? {
|
||||||
|
didSet {
|
||||||
|
// Update the view, if already loaded.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,248 +0,0 @@
|
|||||||
//
|
|
||||||
// AppScrubberTouchBarItem.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Daniel Apatin on 18.04.2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class AppScrubberTouchBarItem: NSCustomTouchBarItem {
|
|
||||||
private var scrollView = NSScrollView()
|
|
||||||
private var autoResize: Bool = false
|
|
||||||
private var widthConstraint: NSLayoutConstraint?
|
|
||||||
private let filter: NSRegularExpression?
|
|
||||||
|
|
||||||
private var persistentAppIdentifiers: [String] = []
|
|
||||||
private var runningAppsIdentifiers: [String] = []
|
|
||||||
|
|
||||||
private var frontmostApplicationIdentifier: String? {
|
|
||||||
return NSWorkspace.shared.frontmostApplication?.bundleIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
private var applications: [DockItem] = []
|
|
||||||
private var items: [DockBarItem] = []
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, autoResize: Bool = false, filter: NSRegularExpression? = nil) {
|
|
||||||
self.filter = filter
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
self.autoResize = autoResize
|
|
||||||
view = scrollView
|
|
||||||
|
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(hardReloadItems), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
|
|
||||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(softReloadItems), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
|
||||||
|
|
||||||
persistentAppIdentifiers = AppSettings.dockPersistentAppIds
|
|
||||||
hardReloadItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
public func switchToApp(app: DockItem) {
|
|
||||||
let bundleIdentifier = app.bundleIdentifier
|
|
||||||
if bundleIdentifier!.contains("file://") {
|
|
||||||
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
|
|
||||||
} else {
|
|
||||||
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
|
||||||
}
|
|
||||||
softReloadItems()
|
|
||||||
|
|
||||||
// NB: if you can't open app which on another space, try to check mark
|
|
||||||
// "When switching to an application, switch to a Space with open windows for the application"
|
|
||||||
// in Mission control settings
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo
|
|
||||||
private func handleLongPress(item: DockItem) {
|
|
||||||
if let pid = item.pid, let app = NSRunningApplication(processIdentifier: pid) {
|
|
||||||
if !app.terminate() {
|
|
||||||
app.forceTerminate()
|
|
||||||
}
|
|
||||||
hardReloadItems()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleHalfLongPress(item: DockItem) {
|
|
||||||
if let index = self.persistentAppIdentifiers.firstIndex(of: item.bundleIdentifier) {
|
|
||||||
persistentAppIdentifiers.remove(at: index)
|
|
||||||
hardReloadItems()
|
|
||||||
} else {
|
|
||||||
persistentAppIdentifiers.append(item.bundleIdentifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.dockPersistentAppIds = persistentAppIdentifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
private func launchedApplications() -> [DockItem] {
|
|
||||||
runningAppsIdentifiers = []
|
|
||||||
var returnable: [DockItem] = []
|
|
||||||
for app in NSWorkspace.shared.runningApplications {
|
|
||||||
guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue }
|
|
||||||
guard let bundleIdentifier = app.bundleIdentifier else { continue }
|
|
||||||
if let filter = self.filter,
|
|
||||||
let name = app.localizedName,
|
|
||||||
filter.numberOfMatches(in: name, options: [], range: NSRange(location: 0, length: name.count)) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
runningAppsIdentifiers.append(bundleIdentifier)
|
|
||||||
|
|
||||||
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: app.icon ?? getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
|
|
||||||
returnable.append(dockItem)
|
|
||||||
}
|
|
||||||
return returnable
|
|
||||||
}
|
|
||||||
|
|
||||||
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil) -> NSImage {
|
|
||||||
if let bundleIdentifier = bundleIdentifier, let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier) {
|
|
||||||
return NSWorkspace.shared.icon(forFile: appPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let path = path {
|
|
||||||
return NSWorkspace.shared.icon(forFile: path)
|
|
||||||
}
|
|
||||||
|
|
||||||
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
|
|
||||||
return genericIcon ?? NSImage(size: .zero)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func getDockPersistentAppsList() -> [DockItem] {
|
|
||||||
var returnable: [DockItem] = []
|
|
||||||
|
|
||||||
for bundleIdentifier in persistentAppIdentifiers {
|
|
||||||
if !runningAppsIdentifiers.contains(bundleIdentifier) {
|
|
||||||
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier))
|
|
||||||
returnable.append(dockItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DockItem: NSObject {
|
|
||||||
var bundleIdentifier: String!, icon: NSImage!, pid: Int32!
|
|
||||||
|
|
||||||
convenience init(bundleIdentifier: String, icon: NSImage, pid: Int32? = nil) {
|
|
||||||
self.init()
|
|
||||||
self.bundleIdentifier = bundleIdentifier
|
|
||||||
self.icon = icon
|
|
||||||
self.pid = pid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let iconWidth = 32.0
|
|
||||||
class DockBarItem: CustomButtonTouchBarItem {
|
|
||||||
let dotView = NSView(frame: .zero)
|
|
||||||
let dockItem: DockItem
|
|
||||||
fileprivate var killGestureRecognizer: LongPressGestureRecognizer!
|
|
||||||
var killAppClosure: () -> Void = { }
|
|
||||||
|
|
||||||
var isRunning = false {
|
|
||||||
didSet {
|
|
||||||
redrawDotView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isFrontmost = false {
|
|
||||||
didSet {
|
|
||||||
redrawDotView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(_ app: DockItem) {
|
|
||||||
self.dockItem = app
|
|
||||||
super.init(identifier: .init(app.bundleIdentifier), title: "")
|
|
||||||
dotView.wantsLayer = true
|
|
||||||
|
|
||||||
image = app.icon
|
|
||||||
image?.size = NSSize(width: iconWidth, height: iconWidth)
|
|
||||||
|
|
||||||
killGestureRecognizer = LongPressGestureRecognizer(target: self, action: #selector(firePanGestureRecognizer))
|
|
||||||
killGestureRecognizer.allowedTouchTypes = .direct
|
|
||||||
killGestureRecognizer.recognizeTimeout = 1.5
|
|
||||||
killGestureRecognizer.minimumPressDuration = 1.5
|
|
||||||
killGestureRecognizer.isEnabled = isRunning
|
|
||||||
|
|
||||||
self.finishViewConfiguration = { [weak self] in
|
|
||||||
guard let selfie = self else { return }
|
|
||||||
selfie.dotView.layer?.cornerRadius = 1.5
|
|
||||||
selfie.view.addSubview(selfie.dotView)
|
|
||||||
selfie.redrawDotView()
|
|
||||||
selfie.view.addGestureRecognizer(selfie.killGestureRecognizer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func redrawDotView() {
|
|
||||||
dotView.layer?.backgroundColor = isRunning ? NSColor.white.cgColor : NSColor.clear.cgColor
|
|
||||||
dotView.frame.size = NSSize(width: isFrontmost ? iconWidth - 14 : 3, height: 3)
|
|
||||||
dotView.setFrameOrigin(NSPoint(x: 18.0 - Double(dotView.frame.size.width) / 2.0, y: iconWidth - 5))
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func firePanGestureRecognizer() {
|
|
||||||
self.killAppClosure()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
//
|
|
||||||
// BatteryBarItem.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Anton Palgunov on 18/04/2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import IOKit.ps
|
|
||||||
|
|
||||||
class BatteryBarItem: CustomButtonTouchBarItem {
|
|
||||||
private let batteryInfo = BatteryInfo()
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier) {
|
|
||||||
super.init(identifier: identifier, title: " ")
|
|
||||||
|
|
||||||
batteryInfo.start { [weak self] in
|
|
||||||
self?.refresh()
|
|
||||||
}
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func refresh() {
|
|
||||||
attributedTitle = batteryInfo.formattedInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
batteryInfo.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BatteryInfo: NSObject {
|
|
||||||
var current: Int = 0
|
|
||||||
var timeToEmpty: Int = 0
|
|
||||||
var timeToFull: Int = 0
|
|
||||||
var isCharged: Bool = false
|
|
||||||
var isCharging: Bool = false
|
|
||||||
var ACPower: String = ""
|
|
||||||
var timeRemaining: String = ""
|
|
||||||
var notifyBlock: () -> Void = {}
|
|
||||||
var loop: CFRunLoopSource?
|
|
||||||
|
|
||||||
func start(notifyBlock: @escaping () -> Void) {
|
|
||||||
self.notifyBlock = notifyBlock
|
|
||||||
let opaque = Unmanaged.passRetained(self).toOpaque()
|
|
||||||
let context = UnsafeMutableRawPointer(opaque)
|
|
||||||
loop = IOPSNotificationCreateRunLoopSource({ context in
|
|
||||||
guard let ctx = context else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let watcher = Unmanaged<BatteryInfo>.fromOpaque(ctx).takeUnretainedValue()
|
|
||||||
watcher.notifyBlock()
|
|
||||||
}, context).takeRetainedValue() as CFRunLoopSource
|
|
||||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() {
|
|
||||||
notifyBlock = {}
|
|
||||||
guard let loop = self.loop else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
|
|
||||||
self.loop = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPSInfo() {
|
|
||||||
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
|
||||||
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
|
|
||||||
|
|
||||||
for ps in psList {
|
|
||||||
if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] {
|
|
||||||
if let current = psDesc[kIOPSCurrentCapacityKey] as? Int {
|
|
||||||
self.current = current
|
|
||||||
}
|
|
||||||
|
|
||||||
if let timeToEmpty = psDesc[kIOPSTimeToEmptyKey] as? Int {
|
|
||||||
self.timeToEmpty = timeToEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
if let timeToFull = psDesc[kIOPSTimeToFullChargeKey] as? Int {
|
|
||||||
self.timeToFull = timeToFull
|
|
||||||
}
|
|
||||||
|
|
||||||
if let isCharged = psDesc[kIOPSIsChargedKey] as? Bool {
|
|
||||||
self.isCharged = isCharged
|
|
||||||
}
|
|
||||||
|
|
||||||
if let isCharging = psDesc[kIOPSIsChargingKey] as? Bool {
|
|
||||||
self.isCharging = isCharging
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ACPower = psDesc[kIOPSPowerSourceStateKey] as? String {
|
|
||||||
self.ACPower = ACPower
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFormattedTime(time: Int) -> String {
|
|
||||||
if time > 0 {
|
|
||||||
let timeFormatted = NSString(format: " %d:%02d", time / 60, time % 60) as String
|
|
||||||
return timeFormatted
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
public func formattedInfo() -> NSAttributedString {
|
|
||||||
var title = ""
|
|
||||||
getPSInfo()
|
|
||||||
|
|
||||||
if ACPower == "AC Power" {
|
|
||||||
if current < 100 {
|
|
||||||
title += "⚡️"
|
|
||||||
}
|
|
||||||
timeRemaining = getFormattedTime(time: timeToFull)
|
|
||||||
} else {
|
|
||||||
timeRemaining = getFormattedTime(time: timeToEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
title += String(current) + "%"
|
|
||||||
|
|
||||||
var color = NSColor.white
|
|
||||||
if current <= 10 && ACPower != "AC Power" {
|
|
||||||
color = NSColor.red
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import AppKit
|
|
||||||
import AVFoundation
|
|
||||||
import Cocoa
|
|
||||||
import CoreAudio
|
|
||||||
|
|
||||||
class BrightnessViewController: NSCustomTouchBarItem {
|
|
||||||
private(set) var sliderItem: CustomSlider!
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) {
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
|
|
||||||
if image == nil {
|
|
||||||
sliderItem = CustomSlider()
|
|
||||||
} else {
|
|
||||||
sliderItem = CustomSlider(knob: image!)
|
|
||||||
}
|
|
||||||
sliderItem.target = self
|
|
||||||
sliderItem.action = #selector(BrightnessViewController.sliderValueChanged(_:))
|
|
||||||
sliderItem.minValue = 0.0
|
|
||||||
sliderItem.maxValue = 100.0
|
|
||||||
sliderItem.floatValue = getBrightness() * 100
|
|
||||||
|
|
||||||
view = sliderItem
|
|
||||||
|
|
||||||
let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true)
|
|
||||||
RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
sliderItem.unbind(NSBindingName.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func updateBrightnessSlider() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.sliderItem.floatValue = self.getBrightness() * 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func sliderValueChanged(_ sender: Any) {
|
|
||||||
if let sliderItem = sender as? NSSlider {
|
|
||||||
setBrightness(level: Float32(sliderItem.intValue) / 100.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getBrightness() -> Float32 {
|
|
||||||
if #available(OSX 10.13, *) {
|
|
||||||
return Float32(CoreDisplay_Display_GetUserBrightness(0))
|
|
||||||
} else {
|
|
||||||
var level: Float32 = 0.5
|
|
||||||
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
|
|
||||||
|
|
||||||
IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &level)
|
|
||||||
return level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setBrightness(level: Float) {
|
|
||||||
if #available(OSX 10.13, *) {
|
|
||||||
CoreDisplay_Display_SetUserBrightness(0, Double(level))
|
|
||||||
} else {
|
|
||||||
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
|
|
||||||
|
|
||||||
IODisplaySetFloatParameter(service, 1, kIODisplayBrightnessKey as CFString, level)
|
|
||||||
IOObjectRelease(service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
//
|
|
||||||
// CurrencyBarItem.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Daniel Apatin on 18.04.2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
import CoreLocation
|
|
||||||
|
|
||||||
class CurrencyBarItem: CustomButtonTouchBarItem {
|
|
||||||
private let activity: NSBackgroundActivityScheduler
|
|
||||||
private var prefix: String
|
|
||||||
private var postfix: String
|
|
||||||
private var from: String
|
|
||||||
private var to: String
|
|
||||||
private var decimal: Int
|
|
||||||
private var decimalValue: Float32!
|
|
||||||
private var decimalString: String!
|
|
||||||
private var oldValue: Float32!
|
|
||||||
private var full: Bool = false
|
|
||||||
|
|
||||||
private let currencies = [
|
|
||||||
"USD": "$",
|
|
||||||
"EUR": "€",
|
|
||||||
"RUB": "₽",
|
|
||||||
"JPY": "¥",
|
|
||||||
"GBP": "₤",
|
|
||||||
"CAD": "$",
|
|
||||||
"KRW": "₩",
|
|
||||||
"CNY": "¥",
|
|
||||||
"AUD": "$",
|
|
||||||
"BRL": "R$",
|
|
||||||
"IDR": "Rp",
|
|
||||||
"MXN": "$",
|
|
||||||
"SGD": "$",
|
|
||||||
"BTC": "฿",
|
|
||||||
"LTC": "Ł",
|
|
||||||
"ETH": "Ξ",
|
|
||||||
"SOL": "◎",
|
|
||||||
"DOT": "●",
|
|
||||||
"DOGE": "Ð",
|
|
||||||
"XMR": "ɱ",
|
|
||||||
"ADA": "₳",
|
|
||||||
"PLN": "zł",
|
|
||||||
"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) {
|
|
||||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
|
||||||
activity.interval = interval
|
|
||||||
self.from = from
|
|
||||||
self.to = to
|
|
||||||
self.full = full
|
|
||||||
|
|
||||||
if let prefix = currencies[from] {
|
|
||||||
self.prefix = prefix
|
|
||||||
} else {
|
|
||||||
prefix = from
|
|
||||||
}
|
|
||||||
|
|
||||||
if let postfix = currencies[to] {
|
|
||||||
self.postfix = postfix
|
|
||||||
} else {
|
|
||||||
postfix = to
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if let decimal = decimals[to] {
|
|
||||||
self.decimal = decimal
|
|
||||||
} else {
|
|
||||||
decimal = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
super.init(identifier: identifier, title: "⏳")
|
|
||||||
|
|
||||||
activity.repeats = true
|
|
||||||
activity.qualityOfService = .utility
|
|
||||||
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
|
||||||
self.updateCurrency()
|
|
||||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
|
||||||
}
|
|
||||||
updateCurrency()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func updateCurrency() {
|
|
||||||
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
|
|
||||||
if error == nil {
|
|
||||||
do {
|
|
||||||
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
|
|
||||||
var value: Float32!
|
|
||||||
|
|
||||||
if let data_array = json["data"] as? [String: AnyObject] {
|
|
||||||
if let rates = data_array["rates"] as? [String: AnyObject] {
|
|
||||||
if let item = rates["\(self.to)"] as? String {
|
|
||||||
value = Float32(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if value != nil {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.setCurrency(value: value!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch let jsonError {
|
|
||||||
print(jsonError.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCurrency(value: Float32) {
|
|
||||||
var color = NSColor.white
|
|
||||||
|
|
||||||
if let oldValue = self.oldValue {
|
|
||||||
if oldValue < value {
|
|
||||||
color = NSColor.green
|
|
||||||
} else if oldValue > value {
|
|
||||||
color = NSColor.red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oldValue = value
|
|
||||||
decimalValue = (value * pow(10,Float(decimal))).rounded() / pow(10,Float(decimal))
|
|
||||||
decimalString = String(decimalValue)
|
|
||||||
|
|
||||||
var title = ""
|
|
||||||
if full {
|
|
||||||
title = String(format: "%@%@‣%@", prefix, postfix, decimalString)
|
|
||||||
} else {
|
|
||||||
title = String(format: "%@%.2f", prefix, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
let regularFont = attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
//
|
|
||||||
// InputSourceBarItem.swift
|
|
||||||
// MTMR
|
|
||||||
//
|
|
||||||
// Created by Daniel Apatin on 22.04.2018.
|
|
||||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class InputSourceBarItem: CustomButtonTouchBarItem {
|
|
||||||
fileprivate var notificationCenter: CFNotificationCenter
|
|
||||||
let buttonSize = NSSize(width: 21, height: 21)
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier) {
|
|
||||||
notificationCenter = CFNotificationCenterGetDistributedCenter()
|
|
||||||
super.init(identifier: identifier, title: "⏳")
|
|
||||||
|
|
||||||
observeIputSourceChangedNotification()
|
|
||||||
textInputSourceDidChange()
|
|
||||||
|
|
||||||
actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
|
||||||
self?.switchInputSource()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
CFNotificationCenterRemoveEveryObserver(notificationCenter, UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()))
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public func textInputSourceDidChange() {
|
|
||||||
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
|
|
||||||
|
|
||||||
var iconImage: NSImage?
|
|
||||||
|
|
||||||
if let imageURL = currentSource.iconImageURL,
|
|
||||||
let image = NSImage(contentsOf: imageURL) {
|
|
||||||
iconImage = image
|
|
||||||
} else if let iconRef = currentSource.iconRef {
|
|
||||||
iconImage = NSImage(iconRef: iconRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let iconImage = iconImage {
|
|
||||||
iconImage.size = buttonSize
|
|
||||||
image = iconImage
|
|
||||||
title = ""
|
|
||||||
} else {
|
|
||||||
title = currentSource.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func switchInputSource() {
|
|
||||||
var inputSources: [TISInputSource] = []
|
|
||||||
|
|
||||||
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
|
|
||||||
let inputSourceNSArray = TISCreateInputSourceList(nil, false).takeRetainedValue() as NSArray
|
|
||||||
let elements = inputSourceNSArray as! [TISInputSource]
|
|
||||||
|
|
||||||
inputSources = elements.filter({
|
|
||||||
$0.category == TISInputSource.Category.keyboardInputSource && $0.isSelectable
|
|
||||||
})
|
|
||||||
|
|
||||||
for item in inputSources {
|
|
||||||
if item.id != currentSource.id {
|
|
||||||
TISSelectInputSource(item)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public func observeIputSourceChangedNotification() {
|
|
||||||
let callback: CFNotificationCallback = { _, observer, _, _, _ in
|
|
||||||
let mySelf = Unmanaged<InputSourceBarItem>.fromOpaque(observer!).takeUnretainedValue()
|
|
||||||
mySelf.textInputSourceDidChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
CFNotificationCenterAddObserver(notificationCenter,
|
|
||||||
UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()),
|
|
||||||
callback,
|
|
||||||
kTISNotifySelectedKeyboardInputSourceChanged,
|
|
||||||
nil,
|
|
||||||
.deliverImmediately)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TISInputSource {
|
|
||||||
enum Category {
|
|
||||||
static var keyboardInputSource: String {
|
|
||||||
return kTISCategoryKeyboardInputSource as String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getProperty(_ key: CFString) -> AnyObject? {
|
|
||||||
let cfType = TISGetInputSourceProperty(self, key)
|
|
||||||
if cfType != nil {
|
|
||||||
return Unmanaged<AnyObject>.fromOpaque(cfType!).takeUnretainedValue()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String {
|
|
||||||
return getProperty(kTISPropertyInputSourceID) as! String
|
|
||||||
}
|
|
||||||
|
|
||||||
var name: String {
|
|
||||||
return getProperty(kTISPropertyLocalizedName) as! String
|
|
||||||
}
|
|
||||||
|
|
||||||
var category: String {
|
|
||||||
return getProperty(kTISPropertyInputSourceCategory) as! String
|
|
||||||
}
|
|
||||||
|
|
||||||
var isSelectable: Bool {
|
|
||||||
return getProperty(kTISPropertyInputSourceIsSelectCapable) as! Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceLanguages: [String] {
|
|
||||||
return getProperty(kTISPropertyInputSourceLanguages) as! [String]
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconImageURL: URL? {
|
|
||||||
return getProperty(kTISPropertyIconImageURL) as! URL?
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconRef: IconRef? {
|
|
||||||
return OpaquePointer(TISGetInputSourceProperty(self, kTISPropertyIconRef)) as IconRef?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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 {}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import Cocoa
|
|
||||||
|
|
||||||
class TimeTouchBarItem: CustomButtonTouchBarItem {
|
|
||||||
private let dateFormatter = DateFormatter()
|
|
||||||
private var timer: Timer!
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil, locale: String? = nil) {
|
|
||||||
dateFormatter.dateFormat = formatTemplate
|
|
||||||
if let locale = locale {
|
|
||||||
dateFormatter.locale = Locale(identifier: locale)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
isBordered = false
|
|
||||||
updateTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func updateTime() {
|
|
||||||
title = dateFormatter.string(from: Date())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
import AppKit
|
|
||||||
import AVFoundation
|
|
||||||
import Cocoa
|
|
||||||
import CoreAudio
|
|
||||||
|
|
||||||
class VolumeViewController: NSCustomTouchBarItem {
|
|
||||||
private(set) var sliderItem: CustomSlider!
|
|
||||||
private var currentDeviceId: AudioObjectID = AudioObjectID(0)
|
|
||||||
|
|
||||||
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
|
|
||||||
super.init(identifier: identifier)
|
|
||||||
|
|
||||||
if image == nil {
|
|
||||||
sliderItem = CustomSlider()
|
|
||||||
} else {
|
|
||||||
sliderItem = CustomSlider(knob: image!)
|
|
||||||
}
|
|
||||||
sliderItem.target = self
|
|
||||||
sliderItem.action = #selector(VolumeViewController.sliderValueChanged(_:))
|
|
||||||
sliderItem.minValue = 0.0
|
|
||||||
sliderItem.maxValue = 100.0
|
|
||||||
sliderItem.floatValue = getInputGain() * 100
|
|
||||||
|
|
||||||
view = sliderItem
|
|
||||||
|
|
||||||
currentDeviceId = defaultDeviceID
|
|
||||||
self.addAudioRouteChangedListener()
|
|
||||||
self.addCurrentAudioVolumeChangedListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addAudioRouteChangedListener() {
|
|
||||||
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 {
|
|
||||||
self.sliderItem.floatValue = self.getInputGain() * 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addCurrentAudioVolumeChangedListener() {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
sliderItem.unbind(NSBindingName.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func sliderValueChanged(_ sender: Any) {
|
|
||||||
if let sliderItem = sender as? NSSlider {
|
|
||||||
_ = setInputGain(Float32(sliderItem.intValue) / 100.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var defaultDeviceID: AudioObjectID {
|
|
||||||
var deviceID: AudioObjectID = AudioObjectID(0)
|
|
||||||
var size: UInt32 = UInt32(MemoryLayout<AudioObjectID>.size)
|
|
||||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
|
||||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice)
|
|
||||||
address.mScope = AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal)
|
|
||||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
|
||||||
AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID)
|
|
||||||
return deviceID
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getInputGain() -> Float32 {
|
|
||||||
var volume: Float32 = 0.5
|
|
||||||
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume))
|
|
||||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
|
||||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume)
|
|
||||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
|
||||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
|
||||||
AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume)
|
|
||||||
return volume
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setInputGain(_ volume: Float32) -> OSStatus {
|
|
||||||
var inputVolume: Float32 = volume
|
|
||||||
|
|
||||||
if inputVolume == 0.0 {
|
|
||||||
_ = setMute(mute: 1)
|
|
||||||
} else {
|
|
||||||
_ = setMute(mute: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: inputVolume))
|
|
||||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
|
||||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
|
||||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
|
||||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume)
|
|
||||||
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setMute(mute: Int) -> OSStatus {
|
|
||||||
var muteVal: Int = mute
|
|
||||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
|
||||||
address.mSelector = AudioObjectPropertySelector(kAudioDevicePropertyMute)
|
|
||||||
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: muteVal))
|
|
||||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
|
||||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
|
||||||
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &muteVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||