diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index e23f093..aaa1dc4 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -3,7 +3,7 @@ import AppKit extension Data { func barItemDefinitions() -> [BarItemDefinition]? { - return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!) + return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!) } } @@ -12,23 +12,24 @@ struct BarItemDefinition: Decodable { let action: ActionType let longAction: LongActionType let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter] - + private enum CodingKeys: String, CodingKey { case type } - + init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) { self.type = type self.action = action self.longAction = longAction self.additionalParameters = additionalParameters } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: .type) let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type) var additionalParameters = try GeneralParameters(from: decoder).parameters + if let result = try? parametersDecoder(decoder), case let (itemType, action, longAction, parameters) = result { parameters.forEach { additionalParameters[$0] = $1 } @@ -37,46 +38,56 @@ struct BarItemDefinition: Decodable { self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters) } } - + } class SupportedTypesHolder { typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, longAction: LongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter]) private var supportedTypes: [String: ParametersDecoder] = [ "escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), longAction: .none, parameters: [.align: .align(.left)]) }, + "delete": { _ in return (item: .staticButton(title: "del"), action: .keyPress(keycode: 117), longAction: .none, parameters: [:])}, + "brightnessUp": { _ in let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp")) return (item: .staticButton(title: ""), action: .keyPress(keycode: 144), longAction: .none, parameters: [.image: imageParameter]) }, + "brightnessDown": { _ in let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown")) return (item: .staticButton(title: ""), action: .keyPress(keycode: 145), longAction: .none, parameters: [.image: imageParameter]) }, + "volumeDown": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), longAction: .none, parameters: [.image: imageParameter]) }, + "volumeUp": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), longAction: .none, parameters: [.image: imageParameter]) }, + "mute": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarAudioOutputMuteTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_MUTE), longAction: .none, parameters: [.image: imageParameter]) }, + "previous": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), longAction: .none, parameters: [.image: imageParameter]) }, + "play": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), longAction: .none, parameters: [.image: imageParameter]) }, + "next": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), longAction: .none, parameters: [.image: imageParameter]) }, + "weather": { decoder in enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key ; case icon_type } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -88,6 +99,7 @@ class SupportedTypesHolder { let longAction = try LongActionType(from: decoder) return (item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"), action: action, longAction: longAction, parameters: [:]) }, + "currency": { decoder in enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -98,12 +110,15 @@ class SupportedTypesHolder { let longAction = try LongActionType(from: decoder) return (item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD"), action: action, longAction: longAction, parameters: [:]) }, + "dock": { decoder in return (item: .dock(), action: .none, longAction: .none, parameters: [:]) }, + "inputsource": { decoder in return (item: .inputsource(), action: .none, longAction: .none, parameters: [:]) }, + "volume": { decoder in enum CodingKeys: String, CodingKey { case image } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -113,6 +128,7 @@ class SupportedTypesHolder { return (item: .volume(), action: .none, longAction: .none, parameters: [:]) } }, + "brightness": { decoder in enum CodingKeys: String, CodingKey { case refreshInterval; case image } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -123,8 +139,11 @@ class SupportedTypesHolder { return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, longAction: .none, parameters: [:]) } }, + "sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), longAction: .none, parameters: [:]) }, + "displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), longAction: .none, parameters: [:])}, + "music": { decoder in enum CodingKeys: String, CodingKey { case refreshInterval } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -136,6 +155,7 @@ class SupportedTypesHolder { parameters: [:] ) }, + "group": { decoder in enum CodingKeys: CodingKey { case items } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -147,20 +167,20 @@ class SupportedTypesHolder { parameters: [:] ) }, - ] - + ] + static let sharedInstance = SupportedTypesHolder() - + func lookup(by type: String) -> ParametersDecoder { return supportedTypes[type] ?? { decoder in return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), longAction: try LongActionType(from: decoder), parameters: [:]) } } - + func register(typename: String, decoder: @escaping ParametersDecoder) { supportedTypes[typename] = decoder } - + func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) { register(typename: typename) { _ in return (item: item, action: action, longAction: longAction, parameters: [:]) @@ -181,7 +201,7 @@ enum ItemType: Decodable { case inputsource() case music(interval: Double) case groupBar(items: [BarItemDefinition]) - + private enum CodingKeys: String, CodingKey { case type case title @@ -198,7 +218,7 @@ enum ItemType: Decodable { case longUrl case items } - + enum ItemTypeRaw: String, Decodable { case staticButton case appleScriptTitledButton @@ -213,46 +233,58 @@ enum ItemType: Decodable { case music case groupBar } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(ItemTypeRaw.self, forKey: .type) switch type { + case .appleScriptTitledButton: let source = try container.decode(Source.self, forKey: .source) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 self = .appleScriptTitledButton(source: source, refreshInterval: interval) + case .staticButton: let title = try container.decode(String.self, forKey: .title) self = .staticButton(title: title) + case .timeButton: let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm" self = .timeButton(formatTemplate: template) + case .battery: self = .battery() + case .dock: self = .dock() + case .volume: self = .volume() + case .brightness: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 self = .brightness(refreshInterval: interval) + case .weather: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric" let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type) + case .currency: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0 let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB" let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD" self = .currency(interval: interval, from: from, to: to) + case .inputsource: self = .inputsource() + case .music: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 self = .music(interval: interval) + case .groupBar: let items = try container.decode([BarItemDefinition].self, forKey: .items) self = .groupBar(items: items) @@ -264,11 +296,11 @@ enum ActionType: Decodable { case none case hidKey(keycode: Int32) case keyPress(keycode: Int) - case appleSctipt(source: SourceProtocol) + case appleScript(source: SourceProtocol) case shellScript(executable: String, parameters: [String]) case custom(closure: ()->()) case openUrl(url: String) - + private enum CodingKeys: String, CodingKey { case action case keycode @@ -277,7 +309,7 @@ enum ActionType: Decodable { case shellArguments case url } - + private enum ActionTypeRaw: String, Decodable { case hidKey case keyPress @@ -285,29 +317,35 @@ enum ActionType: Decodable { case shellScript case openUrl } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action) + switch type { - case .some(.hidKey): - let keycode = try container.decode(Int32.self, forKey: .keycode) - self = .hidKey(keycode: keycode) - case .some(.keyPress): - let keycode = try container.decode(Int.self, forKey: .keycode) - self = .keyPress(keycode: keycode) - case .some(.appleScript): - let source = try container.decode(Source.self, forKey: .actionAppleScript) - self = .appleSctipt(source: source) - case .some(.shellScript): - let executable = try container.decode(String.self, forKey: .executablePath) - let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] - self = .shellScript(executable: executable, parameters: parameters) - case .some(.openUrl): - let url = try container.decode(String.self, forKey: .url) - self = .openUrl(url: url) - case .none: - self = .none + case .some(.hidKey): + let keycode = try container.decode(Int32.self, forKey: .keycode) + self = .hidKey(keycode: keycode) + + case .some(.keyPress): + let keycode = try container.decode(Int.self, forKey: .keycode) + self = .keyPress(keycode: keycode) + + case .some(.appleScript): + let source = try container.decode(Source.self, forKey: .actionAppleScript) + self = .appleScript(source: source) + + case .some(.shellScript): + let executable = try container.decode(String.self, forKey: .executablePath) + let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] + self = .shellScript(executable: executable, parameters: parameters) + + case .some(.openUrl): + let url = try container.decode(String.self, forKey: .url) + self = .openUrl(url: url) + + case .none: + self = .none } } } @@ -317,17 +355,17 @@ enum LongActionType: Decodable { case none case hidKey(keycode: Int32) case keyPress(keycode: Int) - case appleSctipt(source: SourceProtocol) + case appleScript(source: SourceProtocol) case shellScript(executable: String, parameters: [String]) case custom(closure: ()->()) case openUrl(url: String) private enum CodingKeys: String, CodingKey { case longAction - case keycode - case actionAppleScript - case executablePath - case shellArguments + case longKeycode + case longActionAppleScript + case longExecutablePath + case longShellArguments case longUrl } @@ -342,25 +380,31 @@ enum LongActionType: Decodable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction) + switch longType { - case .some(.hidKey): - let keycode = try container.decode(Int32.self, forKey: .keycode) - self = .hidKey(keycode: keycode) - case .some(.keyPress): - let keycode = try container.decode(Int.self, forKey: .keycode) - self = .keyPress(keycode: keycode) - case .some(.appleScript): - let source = try container.decode(Source.self, forKey: .actionAppleScript) - self = .appleSctipt(source: source) - case .some(.shellScript): - let executable = try container.decode(String.self, forKey: .executablePath) - let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] - self = .shellScript(executable: executable, parameters: parameters) - case .some(.openUrl): - let longUrl = try container.decode(String.self, forKey: .longUrl) - self = .openUrl(url: longUrl) - case .none: - self = .none + case .some(.hidKey): + let keycode = try container.decode(Int32.self, forKey: .longKeycode) + self = .hidKey(keycode: keycode) + + case .some(.keyPress): + let keycode = try container.decode(Int.self, forKey: .longKeycode) + self = .keyPress(keycode: keycode) + + case .some(.appleScript): + let source = try container.decode(Source.self, forKey: .longActionAppleScript) + self = .appleScript(source: source) + + case .some(.shellScript): + let executable = try container.decode(String.self, forKey: .longExecutablePath) + let parameters = try container.decodeIfPresent([String].self, forKey: .longShellArguments) ?? [] + self = .shellScript(executable: executable, parameters: parameters) + + case .some(.openUrl): + let longUrl = try container.decode(String.self, forKey: .longUrl) + self = .openUrl(url: longUrl) + + case .none: + self = .none } } } @@ -377,7 +421,7 @@ enum GeneralParameter { struct GeneralParameters: Decodable { let parameters: [GeneralParameters.CodingKeys: GeneralParameter] - + enum CodingKeys: String, CodingKey { case width case image @@ -386,35 +430,45 @@ struct GeneralParameters: Decodable { case background case title } + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:] + if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) { result[.width] = .width(value) } + if let imageSource = try container.decodeIfPresent(Source.self, forKey: .image) { result[.image] = .image(source: imageSource) } + let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center result[.align] = .align(align) + if let borderedFlag = try container.decodeIfPresent(Bool.self, forKey: .bordered) { result[.bordered] = .bordered(borderedFlag) } + if let backgroundColor = try container.decodeIfPresent(String.self, forKey: .background)?.hexColor { result[.background] = .background(backgroundColor) } + if let title = try container.decodeIfPresent(String.self, forKey: .title) { result[.title] = .title(title) } + parameters = result } } + protocol SourceProtocol { var data: Data? { get } var string: String? { get } var image: NSImage? { get } var appleScript: NSAppleScript? { get } } + struct Source: Decodable, SourceProtocol { let filePath: String? let base64: String? @@ -429,35 +483,43 @@ struct Source: Decodable, SourceProtocol { var data: Data? { return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData } + var string: String? { return inline ?? filePath?.fileString } + var image: NSImage? { return data?.image } + var appleScript: NSAppleScript? { return filePath?.fileURL.appleScript ?? self.string?.appleScript } - + private init(filePath: String?, base64: String?, inline: String?) { self.filePath = filePath self.base64 = base64 self.inline = inline } + init(filePath: String) { self.init(filePath: filePath, base64: nil, inline: nil) } } + extension NSImage: SourceProtocol { var data: Data? { return nil } + var string: String? { return nil } + var image: NSImage? { return self } + var appleScript: NSAppleScript? { return nil } @@ -467,6 +529,7 @@ extension String { var base64Data: Data? { return Data(base64Encoded: self) } + var fileData: Data? { return try? Data(contentsOf: URL(fileURLWithPath: self)) } @@ -477,10 +540,12 @@ extension String { } } + extension Data { var utf8string: String? { return String(data: self, encoding: .utf8) } + var image: NSImage? { return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24)) } @@ -496,6 +561,7 @@ extension String { var fileURL: URL { return URL(fileURLWithPath: self) } + var appleScript: NSAppleScript? { return NSAppleScript(source: self) } diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index c62085f..ce0bac3 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -315,7 +315,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { return { HIDPostAuxKey(keycode) } case .keyPress(keycode: let keycode): return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() } - case .appleSctipt(source: let source): + case .appleScript(source: let source): guard let appleScript = source.appleScript else { print("cannot create apple script for item \(item)") return {} @@ -360,7 +360,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { return { HIDPostAuxKey(keycode) } case .keyPress(keycode: let keycode): return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() } - case .appleSctipt(source: let source): + case .appleScript(source: let source): guard let appleScript = source.appleScript else { print("cannot create apple script for item \(item)") return {} diff --git a/README.md b/README.md index 0cdfb61..f112164 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,20 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT "url": "https://google.com", ``` +## LongActions +This then you want to use longPress for some operations is will the same values like for Actions but different additional parameters, example: +```js + "longAction": "hidKey", + "longKeycode": 53, +``` + +- longAction +- longKeycode +- longActionAppleScript +- longExecutablePath +- longShellArguments +- longUrl + ## Additional parameters: - `width` allow to restrict how much room a particular button will take