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() {