Compare commits

..
45 Commits
Author SHA1 Message Date
toxblh 10d94d65e2 version 0.17 2018-08-26 21:12:18 +01:00
toxblh 7de87ad029 Merge remote-tracking branch 'origin/master' 2018-08-26 21:10:27 +01:00
toxblh ad27d26c86 Added keyboard illumination buttons #106 2018-08-26 21:10:16 +01:00
Anton Palgunov ecbd2beb48 Merge pull request #108 from luongvo209/master
Add Luong Vo's customisation
2018-08-22 21:14:52 +01:00
Luong Vo 0aed9a8680 Add Luong Vo's customisation 2018-08-23 00:49:31 +07:00
toxblh 1f764d539a longActions 2018-06-29 15:08:09 +01:00
toxblh e95c707009 fix for 10.14 2018-06-29 15:07:59 +01:00
Anton Palgunov 1914dbb08f Update README.md 2018-06-26 17:17:26 +01:00
Anton Palgunov 34074a1b9b Update README.md 2018-06-20 15:27:13 +01:00
Anton Palgunov e07ee1e263 Update README.md 2018-06-13 09:06:29 +01:00
toxblh 9228eaffc8 upd 0.16.2 2018-06-10 11:13:10 +01:00
Anton Palgunov f89871d294 Merge pull request #96 from Toxblh/prepare10.14
Prepare10.14
2018-06-08 09:32:25 +01:00
toxblh 8e35d38b59 prepare for 10.14 2018-06-08 00:08:15 +01:00
toxblh 17e023d2d2 correct state in menu 2018-06-08 00:07:25 +01:00
Anton Palgunov dc94857ac4 Merge pull request #93 from ReDetection/cleanup-and-fix-close-group
Fix close group and cleanup
2018-06-04 11:19:51 +01:00
Serg ec228fddde fix wrong preset on close group 2018-06-03 10:30:03 +07:00
Serg 958f93f440 handy method to open particular preset file, expose bug with wrong preset reload on group close 2018-06-03 00:49:48 +07:00
Serg d03b16eb89 simplify logic 2018-06-03 00:28:48 +07:00
Anton Palgunov ea573882a0 Merge pull request #92 from ReDetection/compiled-applescript
Support compiled applescript files
2018-06-01 15:52:41 +01:00
Serg 7fb936bd1d support compiled applescript files. related to #87 2018-06-01 21:12:48 +07:00
toxblh f97a3be6d3 resolve confict 2018-06-01 10:50:36 +01:00
toxblh 3bc26eae82 Update menu loayout 2018-06-01 10:49:06 +01:00
Anton Palgunov f84463dbb2 Update README.md 2018-06-01 09:27:38 +01:00
toxblh 46c86a71de Parse AS in any encoding. Groups description 2018-06-01 09:26:07 +01:00
Anton Palgunov ad70c034da Merge pull request #90 from ReDetection/support-all-encodings
Read any text encoding
2018-06-01 09:16:34 +01:00
Serg 221565f604 fix #87: read any text encoding 2018-06-01 13:39:26 +07:00
Anton Palgunov 6ca7f11a2b Merge pull request #88 from Toxblh/startAtLogin
+ start at login
2018-05-31 11:45:42 +01:00
Daniel Apatin 63795b3d41 Update README.md 2018-05-31 13:44:21 +03:00
ad 8a7de99bce + start at login 2018-05-31 13:20:18 +03:00
Anton Palgunov 7f13422805 Merge pull request #85 from ReDetection/fix-open-and-reset
fix broken close/open and toggle control strip
2018-05-26 10:39:01 +01:00
Serg 56d64155ae fix broken close/open and toggle control strip 2018-05-26 13:09:24 +07:00
Daniel Apatin 088f141da3 Update music title right after switch to the next track 2018-05-23 19:24:58 +03:00
toxblh 091208e536 new gestures 2018-05-22 11:33:15 +01:00
Anton Palgunov c6f6fbc5cd Merge pull request #84 from Toxblh/gestures
Gestures for Volume (2 fingers) and Brightness (3 fingers) on ScrollView
2018-05-21 11:01:39 +01:00
ad 3cdf44903b Gestures for Volume (2 fingers) and Brightness (3 fingers) on ScrollView 2018-05-21 12:18:04 +03:00
Anton Palgunov e13e8598a7 Merge pull request #79 from Toxblh/removeReloadPreset
Remove "Reload Preset" and fix reloading preset
2018-05-18 08:52:43 +01:00
Anton Palgunov d82c837902 Merge pull request #80 from Toxblh/groupBarItem
+ Groups
2018-05-18 08:49:51 +01:00
ad d5483cdd03 * group items "decodeIfPresent" -> "decode" 2018-05-18 09:53:15 +03:00
ad 9f2dd277fb * touchbarNeedRefresh 2018-05-18 00:33:14 +03:00
ad e7c949209d + Groups 2018-05-17 13:47:53 +03:00
ad 42d3d29e0d Remove "Reload Preset" and fix reloading preset 2018-05-17 13:21:58 +03:00
Anton Palgunov 6674b5aef7 Merge pull request #77 from Toxblh/customTitle
+ custom title
2018-05-15 20:45:26 +01:00
ad f83f331a99 + custom title 2018-05-15 15:11:34 +03:00
Anton Palgunov cd16c09dbd Update README.md 2018-05-14 22:35:42 +01:00
Anton Palgunov 4ba9d11494 Update README.md 2018-05-14 22:27:52 +01:00
22 changed files with 833 additions and 133 deletions
+14
View File
@@ -18,10 +18,12 @@
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
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 */; };
@@ -48,6 +50,7 @@
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 */; };
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
/* End PBXBuildFile section */
@@ -64,11 +67,14 @@
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>"; };
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>"; };
@@ -102,6 +108,7 @@
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>"; };
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>"; };
/* End PBXFileReference section */
@@ -178,6 +185,7 @@
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
B05600D22083E9BB00EB218D /* CustomSlider.swift */,
36A778BD20A6C27100B38714 /* GeneralExtensions.swift */,
B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */,
);
path = MTMR;
sourceTree = "<group>";
@@ -220,6 +228,8 @@
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */,
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */,
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */,
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */,
);
path = CBridge;
sourceTree = "<group>";
@@ -236,6 +246,7 @@
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */,
60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */,
60669B4220AD8FA80074E817 /* GroupBarItem.swift */,
);
path = Widgets;
sourceTree = "<group>";
@@ -364,12 +375,15 @@
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */,
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.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 */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
+29 -22
View File
@@ -14,6 +14,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var fileSystemSource: DispatchSourceFileSystemObject?
func applicationDidFinishLaunching(_ aNotification: Notification) {
let _ = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
TouchBarController.shared.setupControlStripPresence()
if let button = statusItem.button {
@@ -28,7 +30,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
@objc func openPrefereces(_ sender: Any?) {
@objc func openPreferences(_ sender: Any?) {
let task = Process()
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let presetPath = appSupportDirectory.appending("/items.json")
@@ -37,10 +39,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
task.launch()
}
@objc func updatePreset(_ sender: Any?) {
TouchBarController.shared.createAndUpdatePreset()
}
@objc func toggleControlStrip(_ sender: Any?) {
TouchBarController.shared.controlStripState = !TouchBarController.shared.controlStripState
createMenu()
@@ -75,33 +73,42 @@ class AppDelegate: NSObject, NSApplicationDelegate {
dialog.allowedFileTypes = ["json"]
dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true)
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url
if (result != nil) {
let path = result!.path
let jsonData = path.fileData
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
TouchBarController.shared.createAndUpdatePreset(jsonItems: jsonItems)
}
if dialog.runModal() == .OK, let path = dialog.url?.path {
TouchBarController.shared.reloadPreset(path: path)
}
}
@objc func toggleStartAtLogin(_ sender: Any?) {
LaunchAtLoginController().setLaunchAtLogin(!LaunchAtLoginController().launchAtLogin, for: NSURL.fileURL(withPath: Bundle.main.bundlePath))
createMenu()
}
func createMenu() {
let menu = NSMenu()
menu.addItem(withTitle: "Preferences", action: #selector(openPrefereces(_:)), keyEquivalent: ",")
menu.addItem(withTitle: "Reload Preset", action: #selector(updatePreset(_:)), keyEquivalent: "r")
menu.addItem(withTitle: "Open Preset", action: #selector(openPreset(_:)), keyEquivalent: "O")
menu.addItem(withTitle: TouchBarController.shared.controlStripState ? "Hide Control Strip" : "Show Control Strip" , action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
menu.addItem(withTitle: "Toggle blackList current app" , action: #selector(toggleBlackListedApp(_:)), keyEquivalent: "B")
let startAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleStartAtLogin(_:)), keyEquivalent: "L")
startAtLogin.state = LaunchAtLoginController().launchAtLogin ? .on : .off
let hideControlStrip = NSMenuItem(title: "Hide Control Strip", action: #selector(toggleControlStrip(_:)), keyEquivalent: "T")
hideControlStrip.state = TouchBarController.shared.controlStripState ? .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(NSMenuItem.separator())
menu.addItem(settingSeparator)
menu.addItem(hideControlStrip)
menu.addItem(withTitle: "Toggle current app in blacklist" , action: #selector(toggleBlackListedApp(_:)), keyEquivalent: "B")
menu.addItem(startAtLogin)
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu
}
func reloadOnDefaultConfigChanged() {
let file = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR/items.json"))
let file = NSURL.fileURL(withPath: standardConfigPath)
let fd = open(file.path, O_EVTONLY)
@@ -110,7 +117,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.fileSystemSource?.setEventHandler(handler: {
print("Config changed, reloading...")
DispatchQueue.main.async {
TouchBarController.shared.createAndUpdatePreset()
TouchBarController.shared.reloadPreset(path: file.path)
}
})
-7
View File
@@ -63,13 +63,6 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
}
extension SourceProtocol {
var appleScript: NSAppleScript? {
guard let source = self.string else { return nil }
return NSAppleScript(source: source)
}
}
extension DispatchQueue {
static let appleScriptQueue = DispatchQueue(label: "mtmr.applescript")
}
+24
View File
@@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ill_down.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+24
View File
@@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ill_up.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+35
View File
@@ -0,0 +1,35 @@
//
// 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
+128
View File
@@ -0,0 +1,128 @@
//
// LaunchAtLoginController.m
//
// Copyright 2011 Tomáš Znamenáček
// Copyright 2010 Ben Clark-Robinson
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the ‘Software’),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "LaunchAtLoginController.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
static NSString *const StartAtLoginKey = @"launchAtLogin";
@interface LaunchAtLoginController ()
@property(assign) LSSharedFileListRef loginItems;
@end
@implementation LaunchAtLoginController
@synthesize loginItems;
#pragma mark Change Observing
void sharedFileListDidChange(LSSharedFileListRef inList, void *context)
{
LaunchAtLoginController *self = (__bridge id) context;
[self willChangeValueForKey:StartAtLoginKey];
[self didChangeValueForKey:StartAtLoginKey];
}
#pragma mark Initialization
- (id) init
{
self = [super init];
loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (voidPtr)CFBridgingRetain(self));
return self;
}
- (void) dealloc
{
LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(),
(CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (__bridge void *)(self));
CFRelease(loginItems);
}
#pragma mark Launch List Control
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
LSSharedFileListItemRef copyItemWithURLinFileList(NSURL* wantedURL, LSSharedFileListRef fileList) {
if (wantedURL == NULL || fileList == NULL)
return NULL;
NSArray *listSnapshot = (__bridge_transfer NSArray *)LSSharedFileListCopySnapshot(fileList, NULL);
for(NSUInteger i = 0; i< [listSnapshot count]; i++) {
LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)[listSnapshot objectAtIndex:i];
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef currentItemURL = NULL;
LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
if (currentItemURL && [(__bridge_transfer NSURL*)currentItemURL isEqual:wantedURL]) {
CFRetain(item);
return item;
}
}
return NULL;
}
#pragma clang diagnostic pop
- (BOOL) willLaunchAtLogin: (NSURL*) itemURL
{
return !!copyItemWithURLinFileList(itemURL, loginItems);
}
- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL
{
LSSharedFileListItemRef appItem = copyItemWithURLinFileList(itemURL, loginItems);
if (enabled && !appItem) {
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst,
NULL, NULL, (__bridge CFURLRef)itemURL, NULL, NULL);
} else if (!enabled && appItem) {
LSSharedFileListItemRemove(loginItems, appItem);
}
if (appItem) {
CFRelease(appItem);
}
}
#pragma mark Basic Interface
- (NSURL*) appURL
{
return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
- (void) setLaunchAtLogin: (BOOL) enabled
{
[self willChangeValueForKey:StartAtLoginKey];
[self setLaunchAtLogin:enabled forURL:[self appURL]];
[self didChangeValueForKey:StartAtLoginKey];
}
- (BOOL) launchAtLogin
{
return [self willLaunchAtLogin:[self appURL]];
}
@end
#pragma clang diagnostic pop
@@ -9,6 +9,7 @@
#import "TouchBarPrivateApi.h"
#import "TouchBarSupport.h"
#import "DeprecatedCarbonAPI.h"
#include "LaunchAtLoginController.h"
NS_ASSUME_NONNULL_BEGIN
+7 -4
View File
@@ -17,13 +17,16 @@ extern void DFRSystemModalShowsCloseBoxWhenFrontMost(BOOL);
@interface NSTouchBar (PrivateMethods)
// presentSystemModalFunctionBar:placement:systemTrayItemIdentifier:
// macOS 10.14
+ (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 systemTrayItemIdentifier:(NSTouchBarItemIdentifier)identifier;
+ (void)dismissSystemModalFunctionBar:(NSTouchBar *)touchBar;
+ (void)minimizeSystemModalFunctionBar:(NSTouchBar *)touchBar;
@end
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.15</string>
<string>0.17</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
+188 -61
View File
@@ -3,7 +3,7 @@ import AppKit
extension Data {
func barItemDefinitions() -> [BarItemDefinition]? {
return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!)
return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!)
}
}
@@ -12,23 +12,24 @@ struct BarItemDefinition: Decodable {
let action: ActionType
let longAction: LongActionType
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
private enum CodingKeys: String, CodingKey {
case type
}
init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) {
self.type = type
self.action = action
self.longAction = longAction
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 parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
var additionalParameters = try GeneralParameters(from: decoder).parameters
if let result = try? parametersDecoder(decoder),
case let (itemType, action, longAction, parameters) = result {
parameters.forEach { additionalParameters[$0] = $1 }
@@ -37,46 +38,66 @@ struct BarItemDefinition: Decodable {
self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
}
}
}
class SupportedTypesHolder {
typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, longAction: LongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter])
private var supportedTypes: [String: ParametersDecoder] = [
"escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), longAction: .none, parameters: [.align: .align(.left)]) },
"delete": { _ in return (item: .staticButton(title: "del"), action: .keyPress(keycode: 117), longAction: .none, parameters: [:])},
"brightnessUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
return (item: .staticButton(title: ""), action: .keyPress(keycode: 144), longAction: .none, parameters: [.image: imageParameter])
},
"brightnessDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
return (item: .staticButton(title: ""), action: .keyPress(keycode: 145), longAction: .none, parameters: [.image: imageParameter])
},
"illuminationUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP), longAction: .none, parameters: [.image: imageParameter])
},
"illuminationDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN), longAction: .none, parameters: [.image: imageParameter])
},
"volumeDown": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), longAction: .none, parameters: [.image: imageParameter])
},
"volumeUp": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), longAction: .none, parameters: [.image: imageParameter])
},
"mute": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarAudioOutputMuteTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_MUTE), longAction: .none, parameters: [.image: imageParameter])
},
"previous": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), longAction: .none, parameters: [.image: imageParameter])
},
"play": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), longAction: .none, parameters: [.image: imageParameter])
},
"next": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), longAction: .none, parameters: [.image: imageParameter])
},
"weather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key ; case icon_type }
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -88,6 +109,7 @@ class SupportedTypesHolder {
let longAction = try LongActionType(from: decoder)
return (item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"), action: action, longAction: longAction, parameters: [:])
},
"currency": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to }
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -98,12 +120,15 @@ class SupportedTypesHolder {
let longAction = try LongActionType(from: decoder)
return (item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD"), action: action, longAction: longAction, parameters: [:])
},
"dock": { decoder in
return (item: .dock(), action: .none, longAction: .none, parameters: [:])
},
"inputsource": { decoder in
return (item: .inputsource(), action: .none, longAction: .none, parameters: [:])
},
"volume": { decoder in
enum CodingKeys: String, CodingKey { case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -113,6 +138,7 @@ class SupportedTypesHolder {
return (item: .volume(), action: .none, longAction: .none, parameters: [:])
}
},
"brightness": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case image }
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -123,8 +149,11 @@ class SupportedTypesHolder {
return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, longAction: .none, parameters: [:])
}
},
"sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), longAction: .none, parameters: [:]) },
"displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), longAction: .none, parameters: [:])},
"music": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval }
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -136,20 +165,32 @@ class SupportedTypesHolder {
parameters: [:]
)
},
]
"group": { decoder in
enum CodingKeys: CodingKey { case items }
let container = try decoder.container(keyedBy: CodingKeys.self)
let items = try container.decode([BarItemDefinition].self, forKey: .items)
return (
item: .groupBar(items: items),
action: .none,
longAction: .none,
parameters: [:]
)
},
]
static let sharedInstance = SupportedTypesHolder()
func lookup(by type: String) -> ParametersDecoder {
return supportedTypes[type] ?? { decoder in
return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), longAction: try LongActionType(from: decoder), parameters: [:])
}
}
func register(typename: String, decoder: @escaping ParametersDecoder) {
supportedTypes[typename] = decoder
}
func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
register(typename: typename) { _ in
return (item: item, action: action, longAction: longAction, parameters: [:])
@@ -169,7 +210,8 @@ enum ItemType: Decodable {
case currency(interval: Double, from: String, to: String)
case inputsource()
case music(interval: Double)
case groupBar(items: [BarItemDefinition])
private enum CodingKeys: String, CodingKey {
case type
case title
@@ -184,8 +226,9 @@ enum ItemType: Decodable {
case image
case url
case longUrl
case items
}
enum ItemTypeRaw: String, Decodable {
case staticButton
case appleScriptTitledButton
@@ -198,47 +241,63 @@ enum ItemType: Decodable {
case currency
case inputsource
case music
case groupBar
}
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
self = .appleScriptTitledButton(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"
self = .timeButton(formatTemplate: template)
case .battery:
self = .battery()
case .dock:
self = .dock()
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 .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"
self = .currency(interval: interval, from: from, to: to)
case .inputsource:
self = .inputsource()
case .music:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .music(interval: interval)
case .groupBar:
let items = try container.decode([BarItemDefinition].self, forKey: .items)
self = .groupBar(items: items)
}
}
}
@@ -247,11 +306,11 @@ enum ActionType: Decodable {
case none
case hidKey(keycode: Int32)
case keyPress(keycode: Int)
case appleSctipt(source: SourceProtocol)
case appleScript(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: ()->())
case openUrl(url: String)
private enum CodingKeys: String, CodingKey {
case action
case keycode
@@ -260,7 +319,7 @@ enum ActionType: Decodable {
case shellArguments
case url
}
private enum ActionTypeRaw: String, Decodable {
case hidKey
case keyPress
@@ -268,29 +327,35 @@ enum ActionType: Decodable {
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 = .appleSctipt(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
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
}
}
}
@@ -300,17 +365,17 @@ enum LongActionType: Decodable {
case none
case hidKey(keycode: Int32)
case keyPress(keycode: Int)
case appleSctipt(source: SourceProtocol)
case appleScript(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: ()->())
case openUrl(url: String)
private enum CodingKeys: String, CodingKey {
case longAction
case keycode
case actionAppleScript
case executablePath
case shellArguments
case longKeycode
case longActionAppleScript
case longExecutablePath
case longShellArguments
case longUrl
}
@@ -325,25 +390,31 @@ enum LongActionType: Decodable {
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: .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 = .appleSctipt(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 longUrl = try container.decode(String.self, forKey: .longUrl)
self = .openUrl(url: longUrl)
case .none:
self = .none
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
}
}
}
@@ -355,43 +426,59 @@ enum GeneralParameter {
case align(_: Align)
case bordered(_: Bool)
case background(_:NSColor)
case title(_: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
}
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)
}
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?
@@ -406,46 +493,69 @@ struct Source: Decodable, SourceProtocol {
var data: Data? {
return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData
}
var string: String? {
return inline ?? self.data?.utf8string
return inline ?? filePath?.fileString
}
var image: NSImage? {
return data?.image
}
var appleScript: NSAppleScript? {
return filePath?.fileURL.appleScript ?? self.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))
}
var fileString: String? {
var encoding: String.Encoding = .utf8
return try? String(contentsOfFile: self, usedEncoding: &encoding)
}
}
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))
}
@@ -456,3 +566,20 @@ enum Align: String, Decodable {
case center
case right
}
extension String {
var fileURL: URL {
return URL(fileURLWithPath: self)
}
var appleScript: NSAppleScript? {
return NSAppleScript(source: self)
}
}
extension URL {
var appleScript: NSAppleScript? {
guard FileManager.default.fileExists(atPath: self.path) else { return nil }
return NSAppleScript(contentsOf: self, error: nil)
}
}
+56 -1
View File
@@ -1,6 +1,8 @@
import Foundation
class ScrollViewItem: NSCustomTouchBarItem {
class ScrollViewItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
var twofingersPrev: CGFloat = 0.0
var threefingersPrev: CGFloat = 0.0
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
super.init(identifier: identifier)
@@ -11,10 +13,63 @@ class ScrollViewItem: NSCustomTouchBarItem {
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
scrollView.documentView = stackView
self.view = scrollView
let twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
twofingers.allowedTouchTypes = .direct
twofingers.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(twofingers)
let threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
threefingers.allowedTouchTypes = .direct
threefingers.numberOfTouchesRequired = 3
self.view.addGestureRecognizer(threefingers)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) { // Volume
let position = (sender?.location(in: sender?.view).x)!
switch sender!.state {
case .began:
twofingersPrev = position
case .changed:
if (((position-twofingersPrev) > 10) || ((twofingersPrev-position) > 10)) {
if position > twofingersPrev {
HIDPostAuxKey(NX_KEYTYPE_SOUND_UP)
} else if position < twofingersPrev {
HIDPostAuxKey(NX_KEYTYPE_SOUND_DOWN)
}
twofingersPrev = position
}
case .ended:
twofingersPrev = 0.0
default:
break
}
}
@objc func threefingersHandler(_ sender: NSGestureRecognizer?) { // Brightness
let position = (sender?.location(in: sender?.view).x)!
switch sender!.state {
case .began:
threefingersPrev = position
case .changed:
if (((position-threefingersPrev) > 15) || ((threefingersPrev-position) > 15)) {
if position > threefingersPrev {
GenericKeyPress(keyCode: CGKeyCode(144)).send()
} else if position < threefingersPrev {
GenericKeyPress(keyCode: CGKeyCode(145)).send()
}
threefingersPrev = position
}
case .ended:
threefingersPrev = 0.0
default:
break
}
}
}
+31
View File
@@ -0,0 +1,31 @@
//
// 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!) -> Void {
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!) -> Void {
if #available(OSX 10.14, *) {
NSTouchBar.presentSystemModalTouchBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: placement, systemTrayItemIdentifier: identifier)
}
}
func minimizeSystemModal(_ touchBar: NSTouchBar!) -> Void {
if #available(OSX 10.14, *) {
NSTouchBar.minimizeSystemModalTouchBar(touchBar)
} else {
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
}
}
+57 -26
View File
@@ -13,6 +13,9 @@ struct ExactItem {
let presetItem: BarItemDefinition
}
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let standardConfigPath = appSupportDirectory.appending("/items.json")
extension ItemType {
var identifierBase: String {
@@ -39,6 +42,8 @@ extension ItemType {
return "com.toxblh.mtmr.inputsource."
case .music(interval: _):
return "com.toxblh.mtmr.music."
case .groupBar(items: _):
return "com.toxblh.mtmr.groupBar."
}
}
@@ -54,6 +59,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
var touchBar: NSTouchBar!
fileprivate var lastPresetPath = ""
var jsonItems: [BarItemDefinition] = []
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
@@ -72,6 +79,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
}
}
var touchbarNeedRefresh: Bool = true
var blacklistAppIdentifiers: [String] = []
var frontmostApplicationIdentifier: String? {
get {
@@ -83,6 +92,13 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
private override init() {
super.init()
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar()}), longAction: .none)
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
return (item: .staticButton(title: ""), action: .custom(closure: { [weak self] in
guard let `self` = self else { return }
self.reloadPreset(path: self.lastPresetPath)
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: .stopProgressFreestandingTemplate))!)])
}
if let blackListed = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.blackListedApps") {
self.blacklistAppIdentifiers = blackListed
@@ -92,25 +108,22 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
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)
createAndUpdatePreset()
reloadStandardConfig()
}
func createAndUpdatePreset(jsonItems: [BarItemDefinition]? = nil) {
func createAndUpdatePreset(newJsonItems: [BarItemDefinition]) {
if let oldBar = self.touchBar {
NSTouchBar.minimizeSystemModalFunctionBar(oldBar)
minimizeSystemModal(oldBar)
}
self.touchBar = NSTouchBar()
var jsonItems = jsonItems
self.jsonItems = newJsonItems
self.itemDefinitions = [:]
self.items = [:]
self.leftIdentifiers = []
self.centerItems = []
self.rightIdentifiers = []
if (jsonItems == nil) {
jsonItems = readConfig()
}
loadItemDefinitions(jsonItems: jsonItems!)
loadItemDefinitions(jsonItems: self.jsonItems)
createItems()
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
@@ -134,23 +147,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
func updateActiveApp() {
if self.blacklistAppIdentifiers.index(of: self.frontmostApplicationIdentifier!) != nil {
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, false)
self.touchbarNeedRefresh = true
} else {
presentTouchBar()
self.touchbarNeedRefresh = false
}
}
func readConfig() -> [BarItemDefinition]? {
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let presetPath = appSupportDirectory.appending("/items.json")
func reloadStandardConfig() {
let presetPath = standardConfigPath
if !FileManager.default.fileExists(atPath: presetPath),
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)
}
let jsonData = presetPath.fileData
return jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
reloadPreset(path: presetPath)
}
func reloadPreset(path: String) {
lastPresetPath = path
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
touchbarNeedRefresh = true
createAndUpdatePreset(newJsonItems: items)
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
@@ -192,20 +211,23 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
}
@objc private func presentTouchBar() {
if self.controlStripState {
NSTouchBar.presentSystemModalFunctionBar(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
if touchbarNeedRefresh {
if self.controlStripState {
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
}
}
@objc func resetControlStrip() {
NSTouchBar.minimizeSystemModalFunctionBar(self.touchBar)
presentTouchBar()
}
@objc private func dismissTouchBar() {
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
self.touchbarNeedRefresh = true
minimizeSystemModal(touchBar)
}
@objc func resetControlStrip() {
dismissTouchBar()
presentTouchBar()
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
@@ -255,6 +277,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
barItem = InputSourceBarItem(identifier: identifier)
case .music(interval: let interval):
barItem = MusicBarItem(identifier: identifier, interval: interval)
case .groupBar(items: let items):
barItem = GroupBarItem(identifier: identifier, items: items)
}
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
@@ -275,6 +299,13 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
if case .image(let source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
item.image = source.image
}
if case .title(let 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
}
@@ -284,7 +315,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
return { HIDPostAuxKey(keycode) }
case .keyPress(keycode: let keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case .appleSctipt(source: let source):
case .appleScript(source: let source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)")
return {}
@@ -329,7 +360,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
return { HIDPostAuxKey(keycode) }
case .keyPress(keycode: let keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case .appleSctipt(source: let source):
case .appleScript(source: let source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)")
return {}
+103
View File
@@ -0,0 +1,103 @@
//
// 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)
self.popoverTouchBar.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc override func showPopover(_ sender: Any?) {
self.itemDefinitions = [:]
self.items = [:]
self.leftIdentifiers = []
self.centerItems = []
self.rightIdentifiers = []
self.loadItemDefinitions(jsonItems: jsonItems)
self.createItems()
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
return items[identifier]
})
self.centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
self.scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
TouchBarController.shared.touchBar.delegate = self
TouchBarController.shared.touchBar.defaultItemIdentifiers = []
TouchBarController.shared.touchBar.defaultItemIdentifiers = self.leftIdentifiers + [centerScrollArea] + self.rightIdentifiers
if TouchBarController.shared.controlStripState {
presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem)
} else {
presentSystemModal(TouchBarController.shared.touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
}
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == centerScrollArea {
return self.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 self.itemDefinitions {
self.items[identifier] = TouchBarController.shared.createItem(forIdentifier: identifier, definition: definition)
}
}
}
+16 -9
View File
@@ -32,9 +32,7 @@ class MusicBarItem: CustomButtonTouchBarItem {
self.tapClosure = { [weak self] in self?.playPause() }
self.longTapClosure = { [weak self] in self?.nextTrack() }
DispatchQueue.main.async {
self.updatePlayer()
}
self.refreshAndSchedule()
}
@objc func marquee(){
@@ -118,14 +116,17 @@ class MusicBarItem: CustomButtonTouchBarItem {
if (musicPlayer.className == "SpotifyApplication") {
let mp = (musicPlayer as SpotifyApplication)
mp.nextTrack!()
updatePlayer()
return
} else if (musicPlayer.className == "ITunesApplication") {
let mp = (musicPlayer as iTunesApplication)
mp.nextTrack!()
updatePlayer()
return
} else if (musicPlayer.className == "VOXApplication") {
let mp = (musicPlayer as VoxApplication)
mp.next!()
updatePlayer()
return
} else if (musicPlayer.className == "SafariApplication") {
// You must enable the 'Allow JavaScript from Apple Events' option in Safari's Develop menu to use 'do JavaScript'.
@@ -136,12 +137,15 @@ class MusicBarItem: CustomButtonTouchBarItem {
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
}
}
@@ -152,6 +156,15 @@ class MusicBarItem: CustomButtonTouchBarItem {
}
}
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
@@ -215,9 +228,6 @@ class MusicBarItem: CustomButtonTouchBarItem {
}
if (tempTitle == self.songTitle) {
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.updatePlayer()
}
return
} else {
self.songTitle = tempTitle
@@ -251,9 +261,6 @@ class MusicBarItem: CustomButtonTouchBarItem {
self.title = ""
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in
self?.updatePlayer()
}
}
}
+47 -2
View File
@@ -4,6 +4,9 @@
</p>
# My TouchBar. My rules
*The TouchBar Customization App for your MacBook Pro*
[![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases)
[![license](https://img.shields.io/github/license/Toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/blob/master/LICENSE) [![Total downloads](https://img.shields.io/github/downloads/Toxblh/MTMR/total.svg)](https://github.com/Toxblh/MTMR/releases/latest) ![minimal system requirements](https://img.shields.io/badge/required-macOS%2010.12.2-blue.svg) ![travis](https://travis-ci.org/Toxblh/MTMR.svg?branch=master)
@@ -11,6 +14,10 @@
<img src="Resources/TouchBar-v0.8.1.png">
</p>
**MTMR** Community:
[<img height="24px" src="https://github.com/discourse/DiscourseMobile/raw/master/icon.png" /> Discourse](https://forum.mtmr.app)
[<img height="24px" src="https://telegram.org/img/t_logo.png" /> Telegram](https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g)
My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :)
### Roadmap
@@ -36,7 +43,7 @@ My the idea is to create the program like a platform for plugins for customizati
Settings:
- [ ] Interface for plugins and export like presets
- [ ] Startup at login
- [x] Startup at login
- [ ] Show on/off in Dock
- [ ] Show on/off in StatusBar
- [ ] On/off Haptic Feedback
@@ -59,10 +66,12 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
- exitTouchbar
- brightnessUp
- brightnessDown
- illuminationUp (keyboard illumination)
- illuminationDown (keyboard illumination)
- volumeDown
- volumeUp
- mute
- dock
- dock (half-long click to open app, full-long click to kill app)
> Native Plugins
- battery
@@ -80,6 +89,10 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
- sleep
- displaySleep
## Gestures on central part:
- two finger slide: change you Volume
- three finger slide: change you Brightness
## Built-in slider types:
- brightness
@@ -111,6 +124,17 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
"formatTemplate": "HH:mm" //optional
```
## Groups
```js
{
"type": "group",
"align": "center",
"bordered": true,
"title": "stats",
"items": [{ button }, {button}, ...]
}
```
## Native plugins
- `weather`
> Provider: https://openweathermap.org Need allowance location service
@@ -180,6 +204,20 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
"url": "https://google.com",
```
## LongActions
This then you want to use longPress for some operations is will the same values like for Actions but different additional parameters, example:
```js
"longAction": "hidKey",
"longKeycode": 53,
```
- longAction
- longKeycode
- longActionAppleScript
- longExecutablePath
- longShellArguments
- longUrl
## Additional parameters:
- `width` allow to restrict how much room a particular button will take
@@ -256,8 +294,15 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
[@ReDetection preset](Resources/ReDetection.json)
### User's presets
[@luongvo209 preset](Resources/luongvo209.json)
![](Resources/luongvo209.png)
## Credits
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
Built by [@Toxblh](https://patreon.com/toxblh) and [@ReDetection](http://patreon.com/ReDetection).
[![Analytics](https://ga-beacon.appspot.com/UA-96373624-2/mtmr?pixel)](https://github.com/igrigorik/ga-beacon)
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+1
View File
@@ -3,3 +3,4 @@
* try view controllers on `NSCustomTouchBarItem` instead of subclassing item itself
* try move away from enums when parse preset – enums are hard to extend
* find better way to hide bar items
* extract bar items creating from TouchBarController to separate class, cover with tests