From 101a81bf3b4ad7fede6d4bf4a7b46695453c869e Mon Sep 17 00:00:00 2001 From: Fedor Zaitsev Date: Wed, 29 Apr 2020 22:45:36 -0700 Subject: [PATCH] Refactored parsing Rewrote presets parsing. Instead of parsing presets in a separate file using bunch of huge enums it is now parsed inside each object. To implement a new class: 1. Derive your class from CustomTouchBarItem (for static) or CustomButtonTouchBarItem (for buttons) 2. Override class var typeIdentifier with your object identificator 3. Override init(from decoder: Decoder) and read all custom json params you need 4. Don't forget to call super.init(identifier: CustomTouchBarItem.createIdentifier(type)) in the init() function 5. Add your new class to BarItemDefinition.types in ItemParsing.swift Good example is PomodoroBarItem If you want to inherid from some other NS class (NSSlider or NSPopoverTouchBarItem or other) then look into GroupBarItem and BrightnessViewController --- MTMR/AppleScriptTouchBarItem.swift | 38 +- MTMR/BasicView.swift | 2 + MTMR/CustomButtonTouchBarItem.swift | 200 ++++++- MTMR/EventActions.swift | 184 ++++++ MTMR/Info.plist | 2 +- MTMR/ItemsParsing.swift | 606 +++----------------- MTMR/ShellScriptTouchBarItem.swift | 35 +- MTMR/SwipeItem.swift | 28 +- MTMR/TouchBarController.swift | 334 ++--------- MTMR/Widgets/AppScrubberTouchBarItem.swift | 75 ++- MTMR/Widgets/BatteryBarItem.swift | 24 +- MTMR/Widgets/BrightnessDownBarItem.swift | 27 + MTMR/Widgets/BrightnessUpBarItem.swift | 27 + MTMR/Widgets/BrightnessViewController.swift | 35 +- MTMR/Widgets/CloseBarItem.swift | 48 ++ MTMR/Widgets/CurrencyBarItem.swift | 64 ++- MTMR/Widgets/DarkModeBarItem.swift | 43 +- MTMR/Widgets/DeleteBarItem.swift | 27 + MTMR/Widgets/DisplaySleepBarItem.swift | 27 + MTMR/Widgets/DnDBarItem.swift | 36 +- MTMR/Widgets/EscapeBarItem.swift | 27 + MTMR/Widgets/ExitTouchbarBarItem.swift | 50 ++ MTMR/Widgets/GroupBarItem.swift | 138 +++-- MTMR/Widgets/IlluminationDownBarItem.swift | 27 + MTMR/Widgets/IlluminationUpBarItem.swift | 27 + MTMR/Widgets/InputSourceBarItem.swift | 31 +- MTMR/Widgets/MusicBarItem.swift | 44 +- MTMR/Widgets/MuteBarItem.swift | 27 + MTMR/Widgets/NetworkBarItem.swift | 23 +- MTMR/Widgets/NextBarItem.swift | 27 + MTMR/Widgets/NightShiftBarItem.swift | 36 +- MTMR/Widgets/PlayBarItem.swift | 27 + MTMR/Widgets/PomodoroBarItem.swift | 57 +- MTMR/Widgets/PreviousBarItem.swift | 27 + MTMR/Widgets/SleepBarItem.swift | 27 + MTMR/Widgets/TimeTouchBarItem.swift | 43 +- MTMR/Widgets/VolumeDownBarItem.swift | 27 + MTMR/Widgets/VolumeUpBarItem.swift | 27 + MTMR/Widgets/VolumeViewController.swift | 24 +- MTMR/Widgets/WeatherBarItem.swift | 56 +- MTMR/Widgets/YandexWeatherBarItem.swift | 36 +- 41 files changed, 1628 insertions(+), 1042 deletions(-) create mode 100644 MTMR/EventActions.swift create mode 100644 MTMR/Widgets/BrightnessDownBarItem.swift create mode 100644 MTMR/Widgets/BrightnessUpBarItem.swift create mode 100644 MTMR/Widgets/CloseBarItem.swift create mode 100644 MTMR/Widgets/DeleteBarItem.swift create mode 100644 MTMR/Widgets/DisplaySleepBarItem.swift create mode 100644 MTMR/Widgets/EscapeBarItem.swift create mode 100644 MTMR/Widgets/ExitTouchbarBarItem.swift create mode 100644 MTMR/Widgets/IlluminationDownBarItem.swift create mode 100644 MTMR/Widgets/IlluminationUpBarItem.swift create mode 100644 MTMR/Widgets/MuteBarItem.swift create mode 100644 MTMR/Widgets/NextBarItem.swift create mode 100644 MTMR/Widgets/PlayBarItem.swift create mode 100644 MTMR/Widgets/PreviousBarItem.swift create mode 100644 MTMR/Widgets/SleepBarItem.swift create mode 100644 MTMR/Widgets/VolumeDownBarItem.swift create mode 100644 MTMR/Widgets/VolumeUpBarItem.swift diff --git a/MTMR/AppleScriptTouchBarItem.swift b/MTMR/AppleScriptTouchBarItem.swift index 1ee67a7..f61cbad 100644 --- a/MTMR/AppleScriptTouchBarItem.swift +++ b/MTMR/AppleScriptTouchBarItem.swift @@ -5,11 +5,43 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { private let interval: TimeInterval private var forceHideConstraint: NSLayoutConstraint! private let alternativeImages: [String: SourceProtocol] + + private enum CodingKeys: String, CodingKey { + case source + case alternativeImages + case refreshInterval + } + + override class var typeIdentifier: String { + return "appleScriptTitledButton" + } init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, alternativeImages: [String: SourceProtocol]) { self.interval = interval self.alternativeImages = alternativeImages super.init(identifier: identifier, title: "⏳") + + initScripts(source: source) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let source = try container.decode(Source.self, forKey: .source) + self.interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + self.alternativeImages = try container.decodeIfPresent([String: Source].self, forKey: .alternativeImages) ?? [:] + + print("AppleScriptTouchBarItem.init(from decoder)") + try super.init(from: decoder) + self.title = "⏳" + + initScripts(source: source) + } + + func initScripts(source: SourceProtocol) { forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0) title = "scheduled" DispatchQueue.appleScriptQueue.async { @@ -38,10 +70,6 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { } } - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - func refreshAndSchedule() { #if DEBUG print("refresh happened (interval \(interval)), self \(identifier.rawValue))") @@ -62,7 +90,7 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { func updateIcon(iconLabel: String) { if alternativeImages[iconLabel] != nil { DispatchQueue.main.async { - self.image = self.alternativeImages[iconLabel]!.image + self.setImage(self.alternativeImages[iconLabel]!.image) } } else { print("Cannot find icon with label \"\(iconLabel)\"") diff --git a/MTMR/BasicView.swift b/MTMR/BasicView.swift index 5d388cb..3b04e33 100644 --- a/MTMR/BasicView.swift +++ b/MTMR/BasicView.swift @@ -15,6 +15,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { var threefingers: NSPanGestureRecognizer! var fourfingers: NSPanGestureRecognizer! var swipeItems: [SwipeItem] = [] + var items: [NSTouchBarItem] = [] var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0] // legacy gesture positions @@ -25,6 +26,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate { init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) { super.init(identifier: identifier) self.swipeItems = swipeItems + self.items = items let views = items.compactMap { $0.view } let stackView = NSStackView(views: views) stackView.spacing = 1 diff --git a/MTMR/CustomButtonTouchBarItem.swift b/MTMR/CustomButtonTouchBarItem.swift index 6b734d2..6dd9b82 100644 --- a/MTMR/CustomButtonTouchBarItem.swift +++ b/MTMR/CustomButtonTouchBarItem.swift @@ -8,24 +8,130 @@ import Cocoa -class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate { - var tapClosure: (() -> Void)? - var longTapClosure: (() -> Void)? { - didSet { - longClick.isEnabled = longTapClosure != nil +enum Align: String, Decodable { + case left + case center + case right +} + +// CustomTouchBarItem is a base class for all widgets +// This class provides some basic parameter parsing (width, align) +// To implement a new class: +// 1. Derive your class from CustomTouchBarItem (for static) or CustomButtonTouchBarItem (for buttons) +// 2. Override class var typeIdentifier with your object identificator +// 3. Override init(from decoder: Decoder) and read all custom json params you need +// 4. Don't forget to call super.init(identifier: CustomTouchBarItem.createIdentifier(type)) in the init() function +// 5. Add your new class to BarItemDefinition.types in ItemParsing.swift +// +// Good example is PomodoroBarItem +// +// If you want to inherid from some other NS class (NSSlider or NSPopoverTouchBarItem or other) then +// look into GroupBarItem and BrightnessViewController + +class CustomTouchBarItem: NSCustomTouchBarItem, Decodable { + var align: Align + private var width: NSLayoutConstraint? + + class var typeIdentifier: String { + return "NOTDEFINED" + } + + func setWidth(value: CGFloat) { + guard value > 0 else { + return + } + + if let width = self.width { + width.isActive = false + } + self.width = view.widthAnchor.constraint(equalToConstant: value) + self.width!.isActive = true + } + + func getWidth() -> CGFloat { + return width?.constant ?? 0.0 + } + + private enum CodingKeys: String, CodingKey { + case type + case width + case align + // TODO move bordered and background from custom button class + //case bordered + //case background + //case title + } + + override init(identifier: NSTouchBarItem.Identifier) { + self.align = .center + + // setting width here wouldn't make any affect + super.init(identifier: identifier) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let type = try container.decode(String.self, forKey: .type) + self.align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center + + super.init(identifier: CustomTouchBarItem.createIdentifier(type)) + + if let width = try container.decodeIfPresent(CGFloat.self, forKey: .width) { + self.setWidth(value: width) } } + + static func identifierBase(_ type: String) -> String { + return "com.toxblh.mtmr." + type + } + + static func createIdentifier(_ type: String) -> NSTouchBarItem.Identifier { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH-mm-ss" + let time = dateFormatter.string(from: Date()) + let identifierString = CustomTouchBarItem.identifierBase(type).appending(time + "--" + UUID().uuidString) + return NSTouchBarItem.Identifier(identifierString) + } +} + +class CustomButtonTouchBarItem: CustomTouchBarItem, NSGestureRecognizerDelegate { + private var tapAction: EventAction? + private var longTapAction: EventAction? var finishViewConfiguration: ()->() = {} + override class var typeIdentifier: String { + return "staticButton" + } + + private enum CodingKeys: String, CodingKey { + case title + case bordered + case background + case image + case action + case longAction + } private var button: NSButton! private var singleClick: HapticClickGestureRecognizer! private var longClick: LongPressGestureRecognizer! + private var attributedTitle: NSAttributedString init(identifier: NSTouchBarItem.Identifier, title: String) { attributedTitle = title.defaultTouchbarAttributedString super.init(identifier: identifier) + + initButton(title: title, imageSource: nil) + } + + func initButton(title: String, imageSource: Source?) { button = CustomHeightButton(title: title, target: nil, action: nil) + self.setImage(imageSource?.image) longClick = LongPressGestureRecognizer(target: self, action: #selector(handleGestureLong)) longClick.isEnabled = false @@ -33,6 +139,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat longClick.delegate = self singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle)) + singleClick.isEnabled = false singleClick.allowedTouchTypes = .direct singleClick.delegate = self @@ -40,6 +147,45 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat button.attributedTitle = attributedTitle } + required init(from decoder: Decoder) throws { + attributedTitle = "".defaultTouchbarAttributedString + + let container = try decoder.container(keyedBy: CodingKeys.self) + let title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" + + try super.init(from: decoder) + + if let borderedFlag = try container.decodeIfPresent(Bool.self, forKey: .bordered) { + self.isBordered = borderedFlag + } + + if let bgColor = try container.decodeIfPresent(String.self, forKey: .background)?.hexColor { + self.backgroundColor = bgColor + } + + + let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) + initButton(title: title, imageSource: imageSource) + + self.setTapAction(try? SingleTapEventAction(from: decoder)) + self.setLongTapAction(try? LongTapEventAction(from: decoder)) + } + + // From for static buttons + convenience init(title: String) { + self.init(identifier: CustomTouchBarItem.createIdentifier(CustomButtonTouchBarItem.typeIdentifier), title: title) + } + + func setTapAction(_ action: EventAction?) { + self.tapAction = action + self.singleClick?.isEnabled = action != nil + } + + func setLongTapAction(_ action: EventAction?) { + self.longTapAction = action + self.longClick?.isEnabled = action != nil + } + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -58,27 +204,35 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat var title: String { get { - return attributedTitle.string + return getAttributedTitle().string } set { - attributedTitle = newValue.defaultTouchbarAttributedString + setAttributedTitle(newValue.defaultTouchbarAttributedString) } } - - var attributedTitle: NSAttributedString { - didSet { - button?.imagePosition = attributedTitle.length > 0 ? .imageLeading : .imageOnly - button?.attributedTitle = attributedTitle - } + + func getAttributedTitle() -> NSAttributedString { + return attributedTitle + } + + func setAttributedTitle(_ attributedTitle: NSAttributedString) { + self.attributedTitle = attributedTitle + button?.imagePosition = attributedTitle.length > 0 ? .imageLeading : .imageOnly + button?.attributedTitle = attributedTitle + } + + private var image: NSImage? + + func getImage() -> NSImage? { + return image + } + + func setImage(_ image: NSImage?) { + self.image = image + button.image = image } - var image: NSImage? { - didSet { - button.image = image - } - } - - private func reinstallButton() { + func reinstallButton() { let title = button.attributedTitle let image = button.image let cell = CustomButtonCell(parentItem: self) @@ -116,7 +270,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat @objc func handleGestureSingle(gr: NSClickGestureRecognizer) { switch gr.state { case .ended: - tapClosure?() + self.tapAction?.closure(self) break default: break @@ -126,7 +280,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat @objc func handleGestureLong(gr: NSPressGestureRecognizer) { switch gr.state { case .possible: // tiny hack because we're calling action manually - (self.longTapClosure ?? self.tapClosure)?() + (self.longTapAction?.closure ?? self.tapAction?.closure)?(self) break default: break @@ -156,7 +310,7 @@ class CustomButtonCell: NSButtonCell { if flag { setAttributedTitle(attributedTitle, withColor: .lightGray) } else if let parentItem = self.parentItem { - attributedTitle = parentItem.attributedTitle + attributedTitle = parentItem.getAttributedTitle() } } } diff --git a/MTMR/EventActions.swift b/MTMR/EventActions.swift new file mode 100644 index 0000000..77cb5e5 --- /dev/null +++ b/MTMR/EventActions.swift @@ -0,0 +1,184 @@ +// +// EventActions.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class EventAction { + var closure: ((_ caller: CustomButtonTouchBarItem) -> Void) + + func setHidKeyClosure(keycode: Int32) -> EventAction { + closure = { (_ caller: CustomButtonTouchBarItem) in + HIDPostAuxKey(keycode) + } + return self + } + + func setKeyPressClosure(keycode: Int) -> EventAction { + closure = { (_ caller: CustomButtonTouchBarItem) in + GenericKeyPress(keyCode: CGKeyCode(keycode)).send() + } + return self + } + + func setAppleScriptClosure(appleScript: NSAppleScript) -> EventAction { + closure = { (_ caller: CustomButtonTouchBarItem) in + DispatchQueue.appleScriptQueue.async { + var error: NSDictionary? + appleScript.executeAndReturnError(&error) + if let error = error { + print("error \(error) when handling apple script ") + } + } + } + return self + } + + func setShellScriptClosure(executable: String, parameters: [String]) -> EventAction { + closure = { (_ caller: CustomButtonTouchBarItem) in + let task = Process() + task.launchPath = executable + task.arguments = parameters + task.launch() + } + return self + } + + func setOpenUrlClosure(url: String) -> EventAction { + closure = { (_ caller: CustomButtonTouchBarItem) in + if let url = URL(string: url), NSWorkspace.shared.open(url) { + #if DEBUG + print("URL was successfully opened") + #endif + } else { + print("error", url) + } + } + return self + } + + init() { + self.closure = { (_ caller: CustomButtonTouchBarItem) in } + } + + init(_ closure: @escaping (_ caller: CustomButtonTouchBarItem) -> Void) { + self.closure = closure + } + +} + +class LongTapEventAction: EventAction, Decodable { + 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 + } + + required init(from decoder: Decoder) throws { + super.init() + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction) + + switch type { + case .some(.hidKey): + let keycode = try container.decode(Int32.self, forKey: .longKeycode) + + _ = setHidKeyClosure(keycode: keycode) + case .some(.keyPress): + let keycode = try container.decode(Int.self, forKey: .longKeycode) + + _ = setKeyPressClosure(keycode: keycode) + case .some(.appleScript): + let source = try container.decode(Source.self, forKey: .longActionAppleScript) + + guard let appleScript = source.appleScript else { + print("cannot create apple script") + return + } + + _ = setAppleScriptClosure(appleScript: appleScript) + case .some(.shellScript): + let executable = try container.decode(String.self, forKey: .longExecutablePath) + let parameters = try container.decodeIfPresent([String].self, forKey: .longShellArguments) ?? [] + + _ = setShellScriptClosure(executable: executable, parameters: parameters) + case .some(.openUrl): + let url = try container.decode(String.self, forKey: .longUrl) + + _ = setOpenUrlClosure(url: url) + case .none: + break + } + } +} + +class SingleTapEventAction: EventAction, Decodable { + 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 + } + + required init(from decoder: Decoder) throws { + super.init() + 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) + + _ = setHidKeyClosure(keycode: keycode) + case .some(.keyPress): + let keycode = try container.decode(Int.self, forKey: .keycode) + + _ = setKeyPressClosure(keycode: keycode) + case .some(.appleScript): + let source = try container.decode(Source.self, forKey: .actionAppleScript) + + guard let appleScript = source.appleScript else { + print("cannot create apple script") + return + } + + _ = setAppleScriptClosure(appleScript: appleScript) + case .some(.shellScript): + let executable = try container.decode(String.self, forKey: .executablePath) + let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] + + _ = setShellScriptClosure(executable: executable, parameters: parameters) + case .some(.openUrl): + let url = try container.decode(String.self, forKey: .url) + + _ = setOpenUrlClosure(url: url) + case .none: + break + } + } +} diff --git a/MTMR/Info.plist b/MTMR/Info.plist index ae0a326..2afd22d 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.25 CFBundleVersion - 402 + 622 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 851cf15..9f347c8 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -3,550 +3,90 @@ import Foundation extension Data { func barItemDefinitions() -> [BarItemDefinition]? { - return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) + return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!) } } struct BarItemDefinition: Decodable { - let type: ItemType - let action: ActionType - let longAction: LongActionType - let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter] + let obj: CustomTouchBarItem + + enum ParsingErrors: Error { + case noMatchingType(description: String) + } private enum CodingKeys: String, CodingKey { - case type + case objtype = "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 } - self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters) - } else { - self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters) - } - } -} - -typealias ParametersDecoder = (Decoder) throws -> ( - item: ItemType, - action: ActionType, - longAction: LongActionType, - parameters: [GeneralParameters.CodingKeys: GeneralParameter] -) - -class SupportedTypesHolder { - private var supportedTypes: [String: ParametersDecoder] = [ - "escape": { _ in ( - item: .staticButton(title: "esc"), - action: .keyPress(keycode: 53), - longAction: .none, - parameters: [.align: .align(.left)] - ) }, - - "delete": { _ in ( - 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: NSImage.touchBarVolumeDownTemplateName)!) - 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: NSImage.touchBarVolumeUpTemplateName)!) - 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: NSImage.touchBarAudioOutputMuteTemplateName)!) - return ( - item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_MUTE), - longAction: .none, - parameters: [.image: imageParameter] - ) - }, - - "previous": { _ in - let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!) - return ( - item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), - longAction: .none, - parameters: [.image: imageParameter] - ) - }, - - "play": { _ in - let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!) - return ( - item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_PLAY), - longAction: .none, - parameters: [.image: imageParameter] - ) - }, - - "next": { _ in - let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!) - return ( - item: .staticButton(title: ""), - action: .hidKey(keycode: NX_KEYTYPE_NEXT), - longAction: .none, - parameters: [.image: imageParameter] - ) - }, - - "sleep": { _ in ( - item: .staticButton(title: "☕️"), - action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), - longAction: .none, - parameters: [:] - ) }, - - "displaySleep": { _ in ( - item: .staticButton(title: "☕️"), - action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), - longAction: .none, - parameters: [:] - ) }, - + + static let types: [CustomTouchBarItem.Type] = [ + + // custom buttons + CustomButtonTouchBarItem.self, + AppleScriptTouchBarItem.self, + ShellScriptTouchBarItem.self, + + + // basic widget buttons + EscapeBarItem.self, + DeleteBarItem.self, + BrightnessUpBarItem.self, + BrightnessDownBarItem.self, + IlluminationUpBarItem.self, + IlluminationDownBarItem.self, + VolumeUpBarItem.self, + VolumeDownBarItem.self, + MuteBarItem.self, + PreviousBarItem.self, + PlayBarItem.self, + NextBarItem.self, + SleepBarItem.self, + DisplaySleepBarItem.self, + + + // custom widgets + TimeTouchBarItem.self, + BatteryBarItem.self, + AppScrubberTouchBarItem.self, + VolumeViewController.self, + BrightnessViewController.self, + WeatherBarItem.self, + YandexWeatherBarItem.self, + CurrencyBarItem.self, + InputSourceBarItem.self, + MusicBarItem.self, + NightShiftBarItem.self, + DnDBarItem.self, + PomodoroBarItem.self, + NetworkBarItem.self, + DarkModeBarItem.self, + + + // custom-custom objects! + SwipeItem.self, + GroupBarItem.self, + ExitTouchbarBarItem.self, + CloseBarItem.self, ] - static let sharedInstance = SupportedTypesHolder() - - func lookup(by type: String) -> ParametersDecoder { - return supportedTypes[type] ?? { decoder in ( - 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 - ( - item: item, - action, - longAction, - 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 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) - case darkMode - case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) - - 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 - } - - enum ItemTypeRaw: String, Decodable { - case staticButton - case appleScriptTitledButton - case shellScriptTitledButton - case timeButton - case battery - 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 + init(obj: CustomTouchBarItem) { + self.obj = obj } 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 .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 - self = .network(flip: flip) - - 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) + let objType = try container.decode(String.self, forKey: .objtype) + + + for obj in BarItemDefinition.types { + if obj.typeIdentifier == objType { + self.obj = try obj.init(from: decoder) + return + } } - } -} - -enum ActionType: 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 LongActionType: 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) -} - -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 + + + print("Cannot find preset mapping for \(objType)") + throw ParsingErrors.noMatchingType(description: "Cannot find preset mapping for \(objType)") } } @@ -646,12 +186,6 @@ extension Data { } } -enum Align: String, Decodable { - case left - case center - case right -} - extension URL { var appleScript: NSAppleScript? { guard FileManager.default.fileExists(atPath: path) else { return nil } diff --git a/MTMR/ShellScriptTouchBarItem.swift b/MTMR/ShellScriptTouchBarItem.swift index 94d51ec..098a3fb 100644 --- a/MTMR/ShellScriptTouchBarItem.swift +++ b/MTMR/ShellScriptTouchBarItem.swift @@ -12,11 +12,40 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem { private let source: String private var forceHideConstraint: NSLayoutConstraint! + private enum CodingKeys: String, CodingKey { + case source + case refreshInterval + } + + override class var typeIdentifier: String { + return "shellScriptTitledButton" + } + init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) { self.interval = interval self.source = source.string ?? "echo No \"source\"" super.init(identifier: identifier, title: "⏳") + initScripts() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let source = try container.decode(Source.self, forKey: .source) + self.source = source.string ?? "echo No \"source\"" + self.interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + + try super.init(from: decoder) + self.title = "⏳" + + initScripts() + } + + func initScripts() { forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0) DispatchQueue.shellScriptQueue.async { @@ -24,10 +53,6 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem { } } - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - func refreshAndSchedule() { // Execute script and get result let scriptResult = execute(source) @@ -45,7 +70,7 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem { if (newBackgoundColor != self?.backgroundColor) { // performance optimization because of reinstallButton self?.backgroundColor = newBackgoundColor } - self?.attributedTitle = title + self?.setAttributedTitle(title) self?.forceHideConstraint.isActive = scriptResult == "" } diff --git a/MTMR/SwipeItem.swift b/MTMR/SwipeItem.swift index baf7949..399b102 100644 --- a/MTMR/SwipeItem.swift +++ b/MTMR/SwipeItem.swift @@ -9,12 +9,25 @@ import Foundation import Foundation -class SwipeItem: NSCustomTouchBarItem { +class SwipeItem: CustomTouchBarItem { private var scriptApple: NSAppleScript? private var scriptBash: String? private var direction: String private var fingers: Int private var minOffset: Float + + private enum CodingKeys: String, CodingKey { + case sourceApple + case sourceBash + case direction + case fingers + case minOffset + } + + override class var typeIdentifier: String { + return "swipe" + } + init?(identifier: NSTouchBarItem.Identifier, direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) { self.direction = direction self.fingers = fingers @@ -28,6 +41,19 @@ class SwipeItem: NSCustomTouchBarItem { fatalError("init(coder:) has not been implemented") } + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.scriptApple = try container.decodeIfPresent(Source.self, forKey: .sourceApple)?.appleScript + self.scriptBash = try container.decodeIfPresent(Source.self, forKey: .sourceBash)?.string + self.direction = try container.decode(String.self, forKey: .direction) + self.fingers = try container.decode(Int.self, forKey: .fingers) + self.minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0 + + + try super.init(from: decoder) + } + func processEvent(offset: CGFloat, fingers: Int) { if direction == "right" && Float(offset) > self.minOffset && self.fingers == fingers { self.execute() diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 9bf1a78..1ec6f74 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -16,52 +16,6 @@ struct ExactItem { 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 .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." - } - } -} extension NSTouchBarItem.Identifier { static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip") @@ -73,12 +27,7 @@ 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] = [] - var centerIdentifiers: [NSTouchBarItem.Identifier] = [] - var rightIdentifiers: [NSTouchBarItem.Identifier] = [] + var items: [CustomTouchBarItem] = [] var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString)) var basicView: BasicView? var swipeItems: [SwipeItem] = [] @@ -90,14 +39,6 @@ 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 - (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: NSImage.stopProgressFreestandingTemplateName))!)]) - } blacklistAppIdentifiers = AppSettings.blacklistedAppIds @@ -108,21 +49,23 @@ class TouchBarController: NSObject, NSTouchBarDelegate { reloadStandardConfig() } - func createAndUpdatePreset(newJsonItems: [BarItemDefinition]) { + func createAndUpdatePreset(newItems: [BarItemDefinition]) { if let oldBar = self.touchBar { minimizeSystemModal(oldBar) } touchBar = NSTouchBar() - jsonItems = newJsonItems - itemDefinitions = [:] - items = [:] + (items, swipeItems) = getItems(newItems: newItems) - loadItemDefinitions(jsonItems: jsonItems) - createItems() - - let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in - items[identifier] + let leftItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .left ? item : nil }) + let centerItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .center ? item : nil + }) + let rightItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .right ? item : nil + }) + let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) @@ -130,15 +73,17 @@ class TouchBarController: NSObject, NSTouchBarDelegate { touchBar.delegate = self touchBar.defaultItemIdentifiers = [basicViewIdentifier] - let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in - items[identifier] - }) - let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in - items[identifier] - }) - - basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems) + basicView = BasicView(identifier: basicViewIdentifier, items: leftItems + [scrollArea] + rightItems, swipeItems: swipeItems) basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures + + // it seems that we need to set width only after we added them to the view + // so lets reset width here + for item in items { + item.setWidth(value: item.getWidth()) + if item is CustomButtonTouchBarItem { + (item as! CustomButtonTouchBarItem).reinstallButton() + } + } updateActiveApp() } @@ -166,41 +111,26 @@ class TouchBarController: NSObject, NSTouchBarDelegate { 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: [:])] - 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 reloadPreset(path: String?) { + if path != nil { + lastPresetPath = path! } + + let items = lastPresetPath.fileData?.barItemDefinitions() ?? [BarItemDefinition(obj: CustomButtonTouchBarItem(title: "bad preset"))] + createAndUpdatePreset(newItems: items) } - func createItems() { - for (identifier, definition) in itemDefinitions { - let item = createItem(forIdentifier: identifier, definition: definition) - if item is SwipeItem { - swipeItems.append(item as! SwipeItem) + func getItems(newItems: [BarItemDefinition]) -> ([CustomTouchBarItem], [SwipeItem]) { + var items: [CustomTouchBarItem] = [] + var swipeItems: [SwipeItem] = [] + for item in newItems { + if item.obj is SwipeItem { + swipeItems.append(item.obj as! SwipeItem) } else { - items[identifier] = item + items.append(item.obj) } } + return (items, swipeItems) } @objc func setupControlStripPresence() { @@ -224,7 +154,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } } - @objc private func dismissTouchBar() { + @objc func dismissTouchBar() { minimizeSystemModal(touchBar) updateControlStripPresence() } @@ -241,198 +171,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { return nil } - - func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? { - var barItem: NSTouchBarItem! - switch item.type { - case let .staticButton(title: title): - barItem = CustomButtonTouchBarItem(identifier: identifier, title: title) - case let .appleScriptTitledButton(source: source, refreshInterval: interval, alternativeImages: alternativeImages): - barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, alternativeImages: alternativeImages) - case let .shellScriptTitledButton(source: source, refreshInterval: interval): - barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval) - case let .timeButton(formatTemplate: template, timeZone: timeZone, locale: locale): - barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale) - case .battery: - barItem = BatteryBarItem(identifier: identifier) - 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): - barItem = NetworkBarItem(identifier: identifier, flip: flip) - case .darkMode: - barItem = DarkModeBarItem(identifier: identifier) - case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash): - barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash) - } - - if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { - item.tapClosure = action - } - if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem { - item.longTapClosure = longAction - } - 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 action(forItem item: BarItemDefinition) -> (() -> Void)? { - switch item.action { - 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 { - DispatchQueue.appleScriptQueue.async { - var error: NSDictionary? - appleScript.executeAndReturnError(&error) - if let error = error { - print("error \(error) when handling \(item) ") - } - } - } - case let .shellScript(executable: executable, parameters: parameters): - return { - let task = Process() - task.launchPath = executable - task.arguments = parameters - task.launch() - } - case let .openUrl(url: url): - return { - if let url = URL(string: url), NSWorkspace.shared.open(url) { - #if DEBUG - print("URL was successfully opened") - #endif - } else { - print("error", url) - } - } - case let .custom(closure: closure): - return closure - case .none: - return nil - } - } - - func longAction(forItem item: BarItemDefinition) -> (() -> Void)? { - switch item.longAction { - 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 { func setWidth(value: CGFloat) } - -extension NSCustomTouchBarItem: CanSetWidth { - func setWidth(value: CGFloat) { - view.widthAnchor.constraint(equalToConstant: value).isActive = true - } -} - -extension BarItemDefinition { - var align: Align { - if case let .align(result)? = additionalParameters[.align] { - return result - } - return .center - } -} diff --git a/MTMR/Widgets/AppScrubberTouchBarItem.swift b/MTMR/Widgets/AppScrubberTouchBarItem.swift index adc5541..bc3002e 100644 --- a/MTMR/Widgets/AppScrubberTouchBarItem.swift +++ b/MTMR/Widgets/AppScrubberTouchBarItem.swift @@ -7,7 +7,7 @@ import Cocoa -class AppScrubberTouchBarItem: NSCustomTouchBarItem { +class AppScrubberTouchBarItem: CustomTouchBarItem { private var scrollView = NSScrollView() private var autoResize: Bool = false private var widthConstraint: NSLayoutConstraint? @@ -19,6 +19,20 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem { private var frontmostApplicationIdentifier: String? { return NSWorkspace.shared.frontmostApplication?.bundleIdentifier } + + enum ParsingErrors: Error { + case IncorrectRegex(description: String) + } + + private enum CodingKeys: String, CodingKey { + case autoResize + case filter + } + + override class var typeIdentifier: String { + return "dock" + } + private var applications: [DockItem] = [] private var items: [DockBarItem] = [] @@ -29,6 +43,35 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem { self.autoResize = autoResize view = scrollView + self.setup() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false + let filterRegexString = try container.decodeIfPresent(String.self, forKey: .filter) + + if let filterRegexString = filterRegexString { + let regex = try? NSRegularExpression(pattern: filterRegexString, options: []) + if regex == nil { + throw ParsingErrors.IncorrectRegex(description: "incorrect regex") + } + self.filter = regex + } else { + self.filter = nil + } + + try super.init(from: decoder) + view = scrollView + self.setup() + } + + func setup() { 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) @@ -36,10 +79,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem { persistentAppIdentifiers = AppSettings.dockPersistentAppIds hardReloadItems() } - - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @objc func hardReloadItems() { applications = launchedApplications() @@ -82,12 +122,17 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem { public func createAppButton(for app: DockItem) -> DockBarItem { let item = DockBarItem(app) item.isBordered = false - item.tapClosure = { [weak self] in - self?.switchToApp(app: app) - } - item.longTapClosure = { [weak self] in - self?.handleHalfLongPress(item: app) - } + + item.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.switchToApp(app: app) + } ) + ) + item.setLongTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.handleHalfLongPress(item: app) + } ) + ) item.killAppClosure = {[weak self] in self?.handleLongPress(item: app) } @@ -212,8 +257,8 @@ class DockBarItem: CustomButtonTouchBarItem { super.init(identifier: .init(app.bundleIdentifier), title: "") dotView.wantsLayer = true - image = app.icon - image?.size = NSSize(width: iconWidth, height: iconWidth) + self.setImage(app.icon) + self.getImage()?.size = NSSize(width: iconWidth, height: iconWidth) killGestureRecognizer = LongPressGestureRecognizer(target: self, action: #selector(firePanGestureRecognizer)) killGestureRecognizer.allowedTouchTypes = .direct @@ -243,4 +288,8 @@ class DockBarItem: CustomButtonTouchBarItem { required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) { + fatalError("init(from decoder:) has not been implemented") + } } diff --git a/MTMR/Widgets/BatteryBarItem.swift b/MTMR/Widgets/BatteryBarItem.swift index 377c86b..ca590d7 100644 --- a/MTMR/Widgets/BatteryBarItem.swift +++ b/MTMR/Widgets/BatteryBarItem.swift @@ -12,21 +12,35 @@ import IOKit.ps class BatteryBarItem: CustomButtonTouchBarItem { private let batteryInfo = BatteryInfo() + override class var typeIdentifier: String { + return "battery" + } + init(identifier: NSTouchBarItem.Identifier) { super.init(identifier: identifier, title: " ") + self.setup() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setup() + } + + func setup() { batteryInfo.start { [weak self] in self?.refresh() } refresh() } - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - func refresh() { - attributedTitle = batteryInfo.formattedInfo() + setAttributedTitle(batteryInfo.formattedInfo()) } deinit { diff --git a/MTMR/Widgets/BrightnessDownBarItem.swift b/MTMR/Widgets/BrightnessDownBarItem.swift new file mode 100644 index 0000000..1575f43 --- /dev/null +++ b/MTMR/Widgets/BrightnessDownBarItem.swift @@ -0,0 +1,27 @@ +// +// BrightnessDownBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class BrightnessDownBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "brightnessDown" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(#imageLiteral(resourceName: "brightnessDown")) + self.setTapAction(EventAction().setKeyPressClosure(keycode: 145)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/BrightnessUpBarItem.swift b/MTMR/Widgets/BrightnessUpBarItem.swift new file mode 100644 index 0000000..7738ac9 --- /dev/null +++ b/MTMR/Widgets/BrightnessUpBarItem.swift @@ -0,0 +1,27 @@ +// +// BrightnessUpBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class BrightnessUpBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "brightnessUp" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(#imageLiteral(resourceName: "brightnessUp")) + self.setTapAction(EventAction().setKeyPressClosure(keycode: 144)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/BrightnessViewController.swift b/MTMR/Widgets/BrightnessViewController.swift index ac83201..0f592bc 100644 --- a/MTMR/Widgets/BrightnessViewController.swift +++ b/MTMR/Widgets/BrightnessViewController.swift @@ -3,12 +3,39 @@ import AVFoundation import Cocoa import CoreAudio -class BrightnessViewController: NSCustomTouchBarItem { +class BrightnessViewController: CustomTouchBarItem { private(set) var sliderItem: CustomSlider! + override class var typeIdentifier: String { + return "brightness" + } + + private enum CodingKeys: String, CodingKey { + case image + case refreshInterval + } + init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) { super.init(identifier: identifier) + self.setup(image: nil, interval: refreshInterval) + } + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let image = try container.decodeIfPresent(Source.self, forKey: .image)?.image + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 + + try super.init(from: decoder) + + self.setup(image: image, interval: interval) + } + + + func setup(image: NSImage?, interval: Double) { if image == nil { sliderItem = CustomSlider() } else { @@ -22,14 +49,10 @@ class BrightnessViewController: NSCustomTouchBarItem { view = sliderItem - let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true) + let timer = Timer.scheduledTimer(timeInterval: interval, 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) } diff --git a/MTMR/Widgets/CloseBarItem.swift b/MTMR/Widgets/CloseBarItem.swift new file mode 100644 index 0000000..a30e062 --- /dev/null +++ b/MTMR/Widgets/CloseBarItem.swift @@ -0,0 +1,48 @@ +// +// CloseBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/30/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class CloseBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "close" + } + + init(identifier: NSTouchBarItem.Identifier) { + super.init(identifier: identifier, title: "") + + if self.title == "" { + self.title = "close" + } + + self.setTapAction(EventAction.init({_ in + + TouchBarController.shared.reloadPreset(path: nil) + + } )) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + if self.title == "" { + self.title = "close" + } + + self.setTapAction(EventAction.init({_ in + + TouchBarController.shared.reloadPreset(path: nil) + + } )) + } + +} diff --git a/MTMR/Widgets/CurrencyBarItem.swift b/MTMR/Widgets/CurrencyBarItem.swift index 71e5599..f8047aa 100644 --- a/MTMR/Widgets/CurrencyBarItem.swift +++ b/MTMR/Widgets/CurrencyBarItem.swift @@ -58,6 +58,18 @@ class CurrencyBarItem: CustomButtonTouchBarItem { "LTC": 2, "ETH": 2, ] + + override class var typeIdentifier: String { + return "currency" + } + + private enum CodingKeys: String, CodingKey { + case refreshInterval + case from + case to + case full + } + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) { activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") @@ -87,7 +99,51 @@ class CurrencyBarItem: CustomButtonTouchBarItem { super.init(identifier: identifier, title: "⏳") + self.setup() + } + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + 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 + + activity = NSBackgroundActivityScheduler(identifier: CustomTouchBarItem.createIdentifier("Currency.updatecheck").rawValue) + 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 + } + + try super.init(from: decoder) + self.setup() + } + + func setup() { activity.repeats = true activity.qualityOfService = .utility activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in @@ -97,10 +153,6 @@ class CurrencyBarItem: CustomButtonTouchBarItem { 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)")!) @@ -153,10 +205,10 @@ class CurrencyBarItem: CustomButtonTouchBarItem { title = String(format: "%@%.2f", prefix, value) } - let regularFont = attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15) + let regularFont = getAttributedTitle().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 + setAttributedTitle(newTitle) } deinit { diff --git a/MTMR/Widgets/DarkModeBarItem.swift b/MTMR/Widgets/DarkModeBarItem.swift index ceaba78..7beedbd 100644 --- a/MTMR/Widgets/DarkModeBarItem.swift +++ b/MTMR/Widgets/DarkModeBarItem.swift @@ -1,26 +1,43 @@ import Foundation -class DarkModeBarItem: CustomButtonTouchBarItem, Widget { - static var name: String = "darkmode" - static var identifier: String = "com.toxblh.mtmr.darkmode" - +class DarkModeBarItem: CustomButtonTouchBarItem { private var timer: Timer! + + override class var typeIdentifier: String { + return "darkMode" + } init(identifier: NSTouchBarItem.Identifier) { super.init(identifier: identifier, title: "") - isBordered = false - setWidth(value: 24) - - tapClosure = { [weak self] in self?.DarkModeToggle() } - - timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) - - refresh() + + self.setup() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setup() + } + + func setup() { + if getWidth() == 0.0 { + setWidth(value: 24) + } + + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.DarkModeToggle() + } ) + ) + + timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) + + refresh() + } func DarkModeToggle() { DarkMode.isEnabled = !DarkMode.isEnabled @@ -28,7 +45,7 @@ class DarkModeBarItem: CustomButtonTouchBarItem, Widget { } @objc func refresh() { - image = DarkMode.isEnabled ? #imageLiteral(resourceName: "dark-mode-on") : #imageLiteral(resourceName: "dark-mode-off") + self.setImage(DarkMode.isEnabled ? #imageLiteral(resourceName: "dark-mode-on") : #imageLiteral(resourceName: "dark-mode-off")) } } diff --git a/MTMR/Widgets/DeleteBarItem.swift b/MTMR/Widgets/DeleteBarItem.swift new file mode 100644 index 0000000..0ff3030 --- /dev/null +++ b/MTMR/Widgets/DeleteBarItem.swift @@ -0,0 +1,27 @@ +// +// DeleteBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class DeleteBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "delete" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + print("DeleteBarItem.init") + self.title = "del" + self.setTapAction(EventAction().setKeyPressClosure(keycode: 117)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/MTMR/Widgets/DisplaySleepBarItem.swift b/MTMR/Widgets/DisplaySleepBarItem.swift new file mode 100644 index 0000000..f7b7c8a --- /dev/null +++ b/MTMR/Widgets/DisplaySleepBarItem.swift @@ -0,0 +1,27 @@ +// +// DisplaySleep.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class DisplaySleepBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "displaySleep" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.title = "☕️" + self.setTapAction(EventAction().setShellScriptClosure(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"])) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/DnDBarItem.swift b/MTMR/Widgets/DnDBarItem.swift index 18344ca..4c6f150 100644 --- a/MTMR/Widgets/DnDBarItem.swift +++ b/MTMR/Widgets/DnDBarItem.swift @@ -11,21 +11,39 @@ import Foundation class DnDBarItem: CustomButtonTouchBarItem { private var timer: Timer! + override class var typeIdentifier: String { + return "dnd" + } + init(identifier: NSTouchBarItem.Identifier) { super.init(identifier: identifier, title: "") - isBordered = false - setWidth(value: 32) - - tapClosure = { [weak self] in self?.DnDToggle() } - - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) - - refresh() + self.setup() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + self.setup() + } + + func setup() { + if getWidth() == 0.0 { + setWidth(value: 32) + } + + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.DnDToggle() + } ) + ) + + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) + + refresh() + } func DnDToggle() { DoNotDisturb.isEnabled = !DoNotDisturb.isEnabled @@ -33,7 +51,7 @@ class DnDBarItem: CustomButtonTouchBarItem { } @objc func refresh() { - image = DoNotDisturb.isEnabled ? #imageLiteral(resourceName: "dnd-on") : #imageLiteral(resourceName: "dnd-off") + self.setImage(DoNotDisturb.isEnabled ? #imageLiteral(resourceName: "dnd-on") : #imageLiteral(resourceName: "dnd-off")) } } diff --git a/MTMR/Widgets/EscapeBarItem.swift b/MTMR/Widgets/EscapeBarItem.swift new file mode 100644 index 0000000..3d6c364 --- /dev/null +++ b/MTMR/Widgets/EscapeBarItem.swift @@ -0,0 +1,27 @@ +// +// EscapeBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class EscapeBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "escape" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.title = "escape" + self.setTapAction(EventAction().setKeyPressClosure(keycode: 53)) + self.align = .left + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/MTMR/Widgets/ExitTouchbarBarItem.swift b/MTMR/Widgets/ExitTouchbarBarItem.swift new file mode 100644 index 0000000..f54412d --- /dev/null +++ b/MTMR/Widgets/ExitTouchbarBarItem.swift @@ -0,0 +1,50 @@ +// +// ExitTouchbarBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/30/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class ExitTouchbarBarItem: CustomButtonTouchBarItem { + private var timer: Timer! + + override class var typeIdentifier: String { + return "exitTouchbar" + } + + init(identifier: NSTouchBarItem.Identifier) { + super.init(identifier: identifier, title: "") + + if self.title == "" { + self.title = "exit" + } + + self.setTapAction(EventAction.init({_ in + + TouchBarController.shared.dismissTouchBar() + + } )) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + if self.title == "" { + self.title = "exit" + } + + self.setTapAction(EventAction.init({_ in + + TouchBarController.shared.dismissTouchBar() + + } )) + } + +} diff --git a/MTMR/Widgets/GroupBarItem.swift b/MTMR/Widgets/GroupBarItem.swift index b7ed226..0e732c2 100644 --- a/MTMR/Widgets/GroupBarItem.swift +++ b/MTMR/Widgets/GroupBarItem.swift @@ -7,50 +7,96 @@ // import Cocoa -class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate { - var jsonItems: [BarItemDefinition] +class GroupBarItem: CustomTouchBarItem, NSTouchBarDelegate { + private(set) var popoverItem: NSPopoverTouchBarItem! + var jsonItems: [BarItemDefinition] = [] + + override class var typeIdentifier: String { + return "group" + } + + private enum CodingKeys: String, CodingKey { + case items + case title + } + var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:] - var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:] + var items: [CustomTouchBarItem] = [] var leftIdentifiers: [NSTouchBarItem.Identifier] = [] var centerIdentifiers: [NSTouchBarItem.Identifier] = [] var centerItems: [NSTouchBarItem] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = [] var scrollArea: NSCustomTouchBarItem? - var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) + var swipeItems: [SwipeItem] = [] + var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.GroupScrollArea.".appending(UUID().uuidString)) + var basicView: BasicView? + var basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.GroupScrollView.".appending(UUID().uuidString)) - init(identifier: NSTouchBarItem.Identifier, items: [BarItemDefinition]) { + + init(identifier: NSTouchBarItem.Identifier, items: [BarItemDefinition], title: String) { jsonItems = items + popoverItem = NSPopoverTouchBarItem(identifier: identifier) super.init(identifier: identifier) - popoverTouchBar.delegate = self + + self.setup(title: title) } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + popoverItem = NSPopoverTouchBarItem(identifier: identifier) + + let container = try decoder.container(keyedBy: CodingKeys.self) + self.jsonItems = try container.decode([BarItemDefinition].self, forKey: .items) + let title = try container.decodeIfPresent(String.self, forKey: .title) ?? " " + + self.setup(title: title) + } + + + func setup(title: String) { + let button = NSButton(title: title, target: self, + action: #selector(GroupBarItem.showPopover(_:))) + + // Use the built-in gesture recognizer for tap and hold to open our popover's NSTouchBar. + let gestureRecognizer = popoverItem.makeStandardActivatePopoverGestureRecognizer() + button.addGestureRecognizer(gestureRecognizer) + + popoverItem.collapsedRepresentation = button + + view = button - deinit {} + if getWidth() == 0.0 { + setWidth(value: 60) + } + } - @objc override func showPopover(_: Any?) { - itemDefinitions = [:] - items = [:] - leftIdentifiers = [] - centerItems = [] - rightIdentifiers = [] - - loadItemDefinitions(jsonItems: jsonItems) - createItems() - - centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in - items[identifier] + @objc func showPopover(_: Any?) { + items = getItems(newItems: jsonItems) + + let leftItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .left || item.align == .center ? item : nil + }) + let centerItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .center && false ? item : nil + }) + let rightItems = items.compactMap({ (item) -> CustomTouchBarItem? in + item.align == .right ? item : nil }) - centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) - scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) - + let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) + let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) + TouchBarController.shared.touchBar.delegate = self - TouchBarController.shared.touchBar.defaultItemIdentifiers = [] - TouchBarController.shared.touchBar.defaultItemIdentifiers = leftIdentifiers + [centerScrollArea] + rightIdentifiers + TouchBarController.shared.touchBar.defaultItemIdentifiers = [basicViewIdentifier] + + basicView = BasicView(identifier: basicViewIdentifier, items: leftItems + [scrollArea] + rightItems, swipeItems: swipeItems) + basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures + if AppSettings.showControlStripState { presentSystemModal(TouchBarController.shared.touchBar, systemTrayItemIdentifier: .controlStripItem) @@ -60,41 +106,23 @@ class GroupBarItem: NSPopoverTouchBarItem, NSTouchBarDelegate { } func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { - if identifier == centerScrollArea { - return scrollArea + if identifier == basicViewIdentifier { + return basicView } - guard let item = self.items[identifier], - let definition = self.itemDefinitions[identifier], - definition.align != .center else { - return nil - } - return item + return nil } - - func 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 getItems(newItems: [BarItemDefinition]) -> [CustomTouchBarItem] { + var items: [CustomTouchBarItem] = [] + for item in newItems { + if item.obj is SwipeItem { + swipeItems.append(item.obj as! SwipeItem) + } else { + items.append(item.obj) } } - } - - func createItems() { - for (identifier, definition) in itemDefinitions { - items[identifier] = TouchBarController.shared.createItem(forIdentifier: identifier, definition: definition) - } + return items } } + diff --git a/MTMR/Widgets/IlluminationDownBarItem.swift b/MTMR/Widgets/IlluminationDownBarItem.swift new file mode 100644 index 0000000..a9b7099 --- /dev/null +++ b/MTMR/Widgets/IlluminationDownBarItem.swift @@ -0,0 +1,27 @@ +// +// illuminationDownBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class IlluminationDownBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "illuminationDown" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(#imageLiteral(resourceName: "ill_down")) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_ILLUMINATION_DOWN)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/IlluminationUpBarItem.swift b/MTMR/Widgets/IlluminationUpBarItem.swift new file mode 100644 index 0000000..0abaa1b --- /dev/null +++ b/MTMR/Widgets/IlluminationUpBarItem.swift @@ -0,0 +1,27 @@ +// +// illuminationUpBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class IlluminationUpBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "illuminationUp" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(#imageLiteral(resourceName: "ill_up")) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_ILLUMINATION_UP)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/InputSourceBarItem.swift b/MTMR/Widgets/InputSourceBarItem.swift index 6e8b15b..6fcc66e 100644 --- a/MTMR/Widgets/InputSourceBarItem.swift +++ b/MTMR/Widgets/InputSourceBarItem.swift @@ -11,22 +11,39 @@ import Cocoa class InputSourceBarItem: CustomButtonTouchBarItem { fileprivate var notificationCenter: CFNotificationCenter let buttonSize = NSSize(width: 21, height: 21) + + override class var typeIdentifier: String { + return "inputsource" + } init(identifier: NSTouchBarItem.Identifier) { notificationCenter = CFNotificationCenterGetDistributedCenter() super.init(identifier: identifier, title: "⏳") - - observeIputSourceChangedNotification() - textInputSourceDidChange() - tapClosure = { [weak self] in - self?.switchInputSource() - } + + self.setup() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + notificationCenter = CFNotificationCenterGetDistributedCenter() + try super.init(from: decoder) + + self.setup() + } + func setup() { + observeIputSourceChangedNotification() + textInputSourceDidChange() + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.switchInputSource() + } + )) + } + deinit { CFNotificationCenterRemoveEveryObserver(notificationCenter, UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())) } @@ -45,7 +62,7 @@ class InputSourceBarItem: CustomButtonTouchBarItem { if let iconImage = iconImage { iconImage.size = buttonSize - image = iconImage + self.setImage(iconImage) title = "" } else { title = currentSource.name diff --git a/MTMR/Widgets/MusicBarItem.swift b/MTMR/Widgets/MusicBarItem.swift index 23b2029..b5bde38 100644 --- a/MTMR/Widgets/MusicBarItem.swift +++ b/MTMR/Widgets/MusicBarItem.swift @@ -33,17 +33,36 @@ class MusicBarItem: CustomButtonTouchBarItem { private var songTitle: String? private var timer: Timer? private let iconSize = NSSize(width: 21, height: 21) - + + private enum CodingKeys: String, CodingKey { + case refreshInterval + case disableMarquee + } + + override class var typeIdentifier: String { + return "music" + } init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, disableMarquee: Bool) { self.interval = interval self.disableMarquee = disableMarquee super.init(identifier: identifier, title: "⏳") - isBordered = false - tapClosure = { [weak self] in self?.playPause() } - longTapClosure = { [weak self] in self?.nextTrack() } + self.setup() + } + + func setup() { + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.playPause() + } ) + ) + self.setLongTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.nextTrack() + } ) + ) refreshAndSchedule() } @@ -60,11 +79,23 @@ class MusicBarItem: CustomButtonTouchBarItem { required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0 + self.disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false + + try super.init(from: decoder) + self.setup() + } @objc func playPause() { for ident in playerBundleIdentifiers { + print("checking \(ident)") if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) { + print("musicPlayer \(musicPlayer)") if musicPlayer.isRunning { + print("musicPlayer.isRunning \(musicPlayer.isRunning)") if ident == .Spotify { let mp = (musicPlayer as SpotifyApplication) mp.playpause!() @@ -194,6 +225,7 @@ class MusicBarItem: CustomButtonTouchBarItem { for ident in playerBundleIdentifiers { if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) { if musicPlayer.isRunning { + print("musicPlayer \(musicPlayer)") var tempTitle = "" if ident == .Spotify { tempTitle = (musicPlayer as SpotifyApplication).title @@ -268,7 +300,7 @@ class MusicBarItem: CustomButtonTouchBarItem { let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: ident.rawValue) { let image = NSWorkspace.shared.icon(forFile: appPath) image.size = self.iconSize - self.image = image + self.setImage(image) iconUpdated = true } break @@ -278,7 +310,7 @@ class MusicBarItem: CustomButtonTouchBarItem { DispatchQueue.main.async { if !iconUpdated { - self.image = nil + self.setImage(nil) } if !titleUpdated { diff --git a/MTMR/Widgets/MuteBarItem.swift b/MTMR/Widgets/MuteBarItem.swift new file mode 100644 index 0000000..b8594bd --- /dev/null +++ b/MTMR/Widgets/MuteBarItem.swift @@ -0,0 +1,27 @@ +// +// MuteBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class MuteBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "mute" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_MUTE)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/NetworkBarItem.swift b/MTMR/Widgets/NetworkBarItem.swift index 57de0bf..3056933 100644 --- a/MTMR/Widgets/NetworkBarItem.swift +++ b/MTMR/Widgets/NetworkBarItem.swift @@ -8,12 +8,17 @@ import Foundation -class NetworkBarItem: CustomButtonTouchBarItem, Widget { - static var name: String = "network" - static var identifier: String = "com.toxblh.mtmr.network" - +class NetworkBarItem: CustomButtonTouchBarItem { private let flip: Bool + private enum CodingKeys: String, CodingKey { + case flip + } + + override class var typeIdentifier: String { + return "network" + } + init(identifier: NSTouchBarItem.Identifier, flip: Bool = false) { self.flip = flip super.init(identifier: identifier, title: " ") @@ -23,6 +28,14 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget { required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.flip = try container.decodeIfPresent(Bool.self, forKey: .flip) ?? false + + try super.init(from: decoder) + startMonitoringProcess() + } func startMonitoringProcess() { var pipe: Pipe @@ -144,6 +157,6 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget { } - self.attributedTitle = newTitle + self.setAttributedTitle(newTitle) } } diff --git a/MTMR/Widgets/NextBarItem.swift b/MTMR/Widgets/NextBarItem.swift new file mode 100644 index 0000000..797d30c --- /dev/null +++ b/MTMR/Widgets/NextBarItem.swift @@ -0,0 +1,27 @@ +// +// NextBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class NextBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "next" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarFastForwardTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_NEXT)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/NightShiftBarItem.swift b/MTMR/Widgets/NightShiftBarItem.swift index 517a8e7..a3347a9 100644 --- a/MTMR/Widgets/NightShiftBarItem.swift +++ b/MTMR/Widgets/NightShiftBarItem.swift @@ -11,6 +11,10 @@ import Foundation class NightShiftBarItem: CustomButtonTouchBarItem { private let nsclient = CBBlueLightClient() private var timer: Timer! + + override class var typeIdentifier: String { + return "nightShift" + } private var blueLightStatus: Status { var status: Status = Status() @@ -28,19 +32,33 @@ class NightShiftBarItem: CustomButtonTouchBarItem { init(identifier: NSTouchBarItem.Identifier) { super.init(identifier: identifier, title: "") - isBordered = false - setWidth(value: 28) - - tapClosure = { [weak self] in self?.nightShiftAction() } - - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) - - refresh() + self.setup() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + self.setup() + } + + func setup() { + if getWidth() == 0.0 { + setWidth(value: 28) + } + + self.setTapAction( + EventAction( { [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.nightShiftAction() + } ) + ) + + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) + + refresh() + } func nightShiftAction() { setNightShift(state: !isNightShiftEnabled) @@ -48,6 +66,6 @@ class NightShiftBarItem: CustomButtonTouchBarItem { } @objc func refresh() { - image = isNightShiftEnabled ? #imageLiteral(resourceName: "nightShiftOn") : #imageLiteral(resourceName: "nightShiftOff") + self.setImage(isNightShiftEnabled ? #imageLiteral(resourceName: "nightShiftOn") : #imageLiteral(resourceName: "nightShiftOff")) } } diff --git a/MTMR/Widgets/PlayBarItem.swift b/MTMR/Widgets/PlayBarItem.swift new file mode 100644 index 0000000..7143115 --- /dev/null +++ b/MTMR/Widgets/PlayBarItem.swift @@ -0,0 +1,27 @@ +// +// PlayBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class PlayBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "play" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarPlayPauseTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_PLAY)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/PomodoroBarItem.swift b/MTMR/Widgets/PomodoroBarItem.swift index 7e081f7..2557f61 100644 --- a/MTMR/Widgets/PomodoroBarItem.swift +++ b/MTMR/Widgets/PomodoroBarItem.swift @@ -8,25 +8,14 @@ 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), - action: .none, - longAction: .none, - parameters: [:] - ) +class PomodoroBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "pomodoro" + } + + private enum CodingKeys: String, CodingKey { + case workTime + case restTime } private enum TimeTypes { @@ -45,18 +34,44 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget { 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) - tapClosure = { [weak self] in self?.startStopWork() } - longTapClosure = { [weak self] in self?.startStopRest() } + + self.setup() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0 + self.restTime = try container.decodeIfPresent(Double.self, forKey: .restTime) ?? 600.0 + + try super.init(from: decoder) + self.title = defaultTitle + + self.setup() + } + + func setup() { + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.startStopWork() + } ) + ) + self.setLongTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.startStopRest() + } ) + ) + } deinit { timer?.cancel() diff --git a/MTMR/Widgets/PreviousBarItem.swift b/MTMR/Widgets/PreviousBarItem.swift new file mode 100644 index 0000000..e440d34 --- /dev/null +++ b/MTMR/Widgets/PreviousBarItem.swift @@ -0,0 +1,27 @@ +// +// PreviousBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class PreviousBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "previous" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarRewindTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_PREVIOUS)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/SleepBarItem.swift b/MTMR/Widgets/SleepBarItem.swift new file mode 100644 index 0000000..e988ac5 --- /dev/null +++ b/MTMR/Widgets/SleepBarItem.swift @@ -0,0 +1,27 @@ +// +// SleepBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class SleepBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "sleep" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.title = "☕️" + self.setTapAction(EventAction().setShellScriptClosure(executable: "/usr/bin/pmset", parameters: ["sleepnow"])) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/TimeTouchBarItem.swift b/MTMR/Widgets/TimeTouchBarItem.swift index 4a1df35..4bf659c 100644 --- a/MTMR/Widgets/TimeTouchBarItem.swift +++ b/MTMR/Widgets/TimeTouchBarItem.swift @@ -4,7 +4,40 @@ class TimeTouchBarItem: CustomButtonTouchBarItem { private let dateFormatter = DateFormatter() private var timer: Timer! + private enum CodingKeys: String, CodingKey { + case formatTemplate + case timeZone + case locale + } + + override class var typeIdentifier: String { + return "timeButton" + } + init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, timeZone: String? = nil, locale: String? = nil) { + super.init(identifier: identifier, title: " ") + self.setupFormatter(formatTemplate: formatTemplate, timeZone: timeZone, locale: locale) + self.setupTimer() + updateTime() + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + 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 + + try super.init(from: decoder) + self.setupFormatter(formatTemplate: template, timeZone: timeZone, locale: locale) + self.setupTimer() + updateTime() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupFormatter(formatTemplate: String, timeZone: String? = nil, locale: String? = nil) { dateFormatter.dateFormat = formatTemplate if let locale = locale { dateFormatter.locale = Locale(identifier: locale) @@ -12,14 +45,10 @@ class TimeTouchBarItem: CustomButtonTouchBarItem { 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") + + func setupTimer() { + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) } @objc func updateTime() { diff --git a/MTMR/Widgets/VolumeDownBarItem.swift b/MTMR/Widgets/VolumeDownBarItem.swift new file mode 100644 index 0000000..31f2806 --- /dev/null +++ b/MTMR/Widgets/VolumeDownBarItem.swift @@ -0,0 +1,27 @@ +// +// volumeDownBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class VolumeDownBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "volumeDown" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarVolumeDownTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_SOUND_DOWN)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/VolumeUpBarItem.swift b/MTMR/Widgets/VolumeUpBarItem.swift new file mode 100644 index 0000000..abbc0f6 --- /dev/null +++ b/MTMR/Widgets/VolumeUpBarItem.swift @@ -0,0 +1,27 @@ +// +// VolumeUpBarItem.swift +// MTMR +// +// Created by Fedor Zaitsev on 4/27/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// + +import Foundation + +class VolumeUpBarItem: CustomButtonTouchBarItem { + override class var typeIdentifier: String { + return "volumeUp" + } + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + self.setImage(NSImage(named: NSImage.touchBarVolumeUpTemplateName)!) + self.setTapAction(EventAction().setHidKeyClosure(keycode: NX_KEYTYPE_SOUND_UP)) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/MTMR/Widgets/VolumeViewController.swift b/MTMR/Widgets/VolumeViewController.swift index b46f0f5..abf9f18 100644 --- a/MTMR/Widgets/VolumeViewController.swift +++ b/MTMR/Widgets/VolumeViewController.swift @@ -3,12 +3,33 @@ import AVFoundation import Cocoa import CoreAudio -class VolumeViewController: NSCustomTouchBarItem { +class VolumeViewController: CustomTouchBarItem { private(set) var sliderItem: CustomSlider! + + override class var typeIdentifier: String { + return "volume" + } + + private enum CodingKeys: String, CodingKey { + case image + } init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) { super.init(identifier: identifier) + self.setup(image: image) + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let image = try container.decodeIfPresent(Source.self, forKey: .image)?.image + + try super.init(from: decoder) + + self.setup(image: image) + } + + func setup(image: NSImage?) { var forPropertyAddress = AudioObjectPropertyAddress( mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, mScope: kAudioDevicePropertyScopeOutput, @@ -36,6 +57,7 @@ class VolumeViewController: NSCustomTouchBarItem { self.sliderItem.floatValue = self.getInputGain() * 100 } } + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") diff --git a/MTMR/Widgets/WeatherBarItem.swift b/MTMR/Widgets/WeatherBarItem.swift index 61ffeac..addd48e 100644 --- a/MTMR/Widgets/WeatherBarItem.swift +++ b/MTMR/Widgets/WeatherBarItem.swift @@ -21,10 +21,21 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { private var iconsSource: Dictionary private var manager: CLLocationManager! + + override class var typeIdentifier: String { + return "weather" + } + + private enum CodingKeys: String, CodingKey { + case refreshInterval + case units + case api_key + case icon_type + } init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String, icon_type: String? = "text") { - activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") - activity.interval = interval + self.activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") + self.activity.interval = interval self.units = units self.api_key = api_key @@ -43,7 +54,44 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { } super.init(identifier: identifier, title: "⏳") + + self.setup() + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" + + + self.activity = NSBackgroundActivityScheduler(identifier: CustomTouchBarItem.createIdentifier("Weather.updatecheck").rawValue) + self.activity.interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + self.units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric" + self.api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" + if self.units == "metric" { + units_str = "°C" + } + + if self.units == "imperial" { + units_str = "°F" + } + + if icon_type == "images" { + iconsSource = iconsImages + } else { + iconsSource = iconsText + } + + try super.init(from: decoder) + + self.setup() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup() { let status = CLLocationManager.authorizationStatus() if status == .restricted || status == .denied { print("User permission not given") @@ -69,10 +117,6 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { manager.startUpdatingLocation() } - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - @objc func updateWeather() { if location != nil { let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(units)&appid=\(api_key)")!) diff --git a/MTMR/Widgets/YandexWeatherBarItem.swift b/MTMR/Widgets/YandexWeatherBarItem.swift index 740ea2c..9c34b56 100644 --- a/MTMR/Widgets/YandexWeatherBarItem.swift +++ b/MTMR/Widgets/YandexWeatherBarItem.swift @@ -19,13 +19,38 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate private var location: CLLocation! private var prevLocation: CLLocation! private var manager: CLLocationManager! + + override class var typeIdentifier: String { + return "weather" + } + + private enum CodingKeys: String, CodingKey { + case refreshInterval + } init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) { activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") activity.interval = interval super.init(identifier: identifier, title: "⏳") + self.setup() + } + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.activity = NSBackgroundActivityScheduler(identifier: CustomTouchBarItem.createIdentifier("YandexWeather.updatecheck").rawValue) + self.activity.interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + + try super.init(from: decoder) + self.setup() + } + + func setup() { let status = CLLocationManager.authorizationStatus() if status == .restricted || status == .denied { print("User permission not given") @@ -50,11 +75,12 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate manager.desiredAccuracy = kCLLocationAccuracyHundredMeters manager.startUpdatingLocation() - tapClosure = tapClosure ?? defaultTapAction - } - - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") + + self.setTapAction( + EventAction({ [weak self] (_ caller: CustomButtonTouchBarItem) in + self?.defaultTapAction() + } ) + ) } @objc func updateWeather() {