diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index e2b9d50..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index d2b0a60..80192d8 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; }; 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; + B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; }; B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; }; @@ -71,6 +72,7 @@ 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = ""; }; 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = ""; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = ""; }; + B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryBarItem.swift; sourceTree = ""; }; B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = ""; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = ""; }; B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = ""; }; @@ -176,6 +178,7 @@ 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */, 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */, B05600D22083E9BB00EB218D /* CustomSlider.swift */, + B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */, 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */, 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */, ); @@ -355,6 +358,7 @@ 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */, 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */, 368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */, + B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MTMR/AppDelegate.swift b/MTMR/AppDelegate.swift index 850031a..19b3dd7 100644 --- a/MTMR/AppDelegate.swift +++ b/MTMR/AppDelegate.swift @@ -13,7 +13,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength) func applicationDidFinishLaunching(_ aNotification: Notification) { - // Insert code here to initialize your application TouchBarController.shared.setupControlStripPresence() if let button = statusItem.button { @@ -23,7 +22,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application + } @objc func openPrefereces(_ sender: Any?) { diff --git a/MTMR/AppleScriptTouchBarItem.swift b/MTMR/AppleScriptTouchBarItem.swift index bbd214a..87c876a 100644 --- a/MTMR/AppleScriptTouchBarItem.swift +++ b/MTMR/AppleScriptTouchBarItem.swift @@ -18,7 +18,9 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { DispatchQueue.main.async { var error: NSDictionary? guard script.compileAndReturnError(&error) else { -// print(error?.description ?? "unknown error") + #if DEBUG + print(error?.description ?? "unknown error") + #endif DispatchQueue.main.async { self.button.title = "error" } @@ -33,7 +35,9 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { } func refreshAndSchedule() { -// print("refresh happened") + #if DEBUG + print("refresh happened") + #endif let scriptResult = self.execute() DispatchQueue.main.async { self.button.title = scriptResult diff --git a/MTMR/BatteryBarItem.swift b/MTMR/BatteryBarItem.swift new file mode 100644 index 0000000..69de2ce --- /dev/null +++ b/MTMR/BatteryBarItem.swift @@ -0,0 +1,137 @@ +// +// BatteryBarItem.swift +// MTMR +// +// Created by Anton Palgunov on 18/04/2018. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +import IOKit.ps +import Foundation + +class BatteryBarItem: NSCustomTouchBarItem { + private var timer: Timer! + private let button = NSButton(title: "", target: nil, action: nil) + + override init(identifier: NSTouchBarItem.Identifier) { + super.init(identifier: identifier) + self.view = button + button.bezelColor = .clear + + let batteryInfo = BatteryInfo(button: button) + batteryInfo.start() + batteryInfo.updateInfo() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class BatteryInfo: NSObject { + var current: Int = 0 + var timeToEmpty: Int = 0 + var timeToFull: Int = 0 + var isCharged: Bool = false + var isCharging: Bool = false + var ACPower: String = "" + var timeRemaining: String = "" + + var button: NSButton? + var loop:CFRunLoopSource? + + init(button: NSButton) { + super.init() + + self.button = button + self.start() + } + + func start() { + let opaque = Unmanaged.passRetained(self).toOpaque() + let context = UnsafeMutableRawPointer(opaque) + loop = IOPSNotificationCreateRunLoopSource({ (context) in + guard let ctx = context else { + return + } + + let watcher = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + watcher.updateInfo() + }, context).takeRetainedValue() as CFRunLoopSource + CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode) + } + + func stop() { + if !(self.loop != nil) { + return + } + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), self.loop, CFRunLoopMode.defaultMode) + self.loop = nil + } + + func getPSInfo() { + let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef] + + for ps in psList { + if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] { + let current = psDesc[kIOPSCurrentCapacityKey] + if (current != nil) { + self.current = current as! Int + } + + let timeToEmpty = psDesc[kIOPSTimeToEmptyKey] + if (timeToEmpty != nil) { + self.timeToEmpty = timeToEmpty as! Int + } + + let timeToFull = psDesc[kIOPSTimeToFullChargeKey] + if (timeToFull != nil) { + self.timeToFull = timeToFull as! Int + } + + let isCharged = psDesc[kIOPSIsChargedKey] + if (isCharged != nil) { + self.isCharged = isCharged as! Bool + } + + let isCharging = psDesc[kIOPSIsChargingKey] + if (isCharging != nil) { + self.isCharging = isCharging as! Bool + } + + let ACPower = psDesc[kIOPSPowerSourceStateKey] + if (ACPower != nil) { + self.ACPower = ACPower as! String + } + } + } + } + + func getFormattedTime(time: Int) -> String { + if (time > 0) { + let timeFormatted = NSString(format: " (%d:%02d)", time / 60, time % 60) as String + return timeFormatted + } else if (time == 0) { + return "" + } + + return " (?)" + } + + public func updateInfo() { + var title = "" + self.getPSInfo() + + if ACPower == "AC Power" { + title += "⚡️" + timeRemaining = getFormattedTime(time: timeToFull) + } else { + timeRemaining = getFormattedTime(time: timeToEmpty) + } + + title += String(current) + "%" + timeRemaining + button?.title = title + } + +} diff --git a/MTMR/CurrencyBarItem.swift b/MTMR/CurrencyBarItem.swift index 8d2ed32..440d3b1 100644 --- a/MTMR/CurrencyBarItem.swift +++ b/MTMR/CurrencyBarItem.swift @@ -16,7 +16,7 @@ class CurrencyBarItem: CustomButtonTouchBarItem { private var from: String private var to: String private var oldValue: Float32! - + private let currencies = [ "USD": "$", "EUR": "€", @@ -31,44 +31,47 @@ class CurrencyBarItem: CustomButtonTouchBarItem { "IDR": "Rp", "MXN": "$", "SGD": "$", - "CHF": "Fr." + "CHF": "Fr.", + "BTC": "฿", + "LTC": "Ł", + "ETH": "Ξ", ] - + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, onTap: @escaping () -> ()) { self.interval = interval self.from = from self.to = to - + if let prefix = currencies[from] { self.prefix = prefix } else { self.prefix = from } - + super.init(identifier: identifier, title: "⏳", onTap: onTap) - + button.bezelColor = .clear self.view = button - + timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(updateCurrency), userInfo: nil, repeats: true) - + updateCurrency() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + @objc func updateCurrency() { let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!) - + let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in if error == nil { do { let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject] - + var value: Float32! - + if let data_array = json["data"] as? [String : AnyObject] { if let rates = data_array["rates"] as? [String : AnyObject] { if let item = rates["\(self.to)"] as? String { @@ -89,21 +92,21 @@ class CurrencyBarItem: CustomButtonTouchBarItem { task.resume() } - + func setCurrency(value: Float32) { var color = NSColor.white - + if let oldValue = self.oldValue { if oldValue < value { - color = NSColor(red: 95.0/255.0, green: 185.0/255.0, blue: 50.0/255.0, alpha: 1.0) + color = NSColor.green } else if oldValue > value { - color = NSColor(red: 185.0/255.0, green: 95.0/255.0, blue: 50.0/255.0, alpha: 1.0) + color = NSColor.red } } self.oldValue = value - + button.title = String(format: "%@%.2f", self.prefix, value) - + let textRange = NSRange(location: 0, length: button.title.count) let newTitle = NSMutableAttributedString(string: button.title) newTitle.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: textRange) diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index a01af0c..2a4b1eb 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -60,6 +60,10 @@ class SupportedTypesHolder { let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), parameters: [.image: imageParameter]) }, + "mute": { _ in + let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarAudioOutputMuteTemplate)!) + return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_MUTE), parameters: [.image: imageParameter]) + }, "previous": { _ in let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), parameters: [.image: imageParameter]) @@ -113,15 +117,6 @@ class SupportedTypesHolder { return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, parameters: [:]) } }, - "battery": { decoder in - enum CodingKeys: String, CodingKey { case refreshInterval } - let container = try decoder.container(keyedBy: CodingKeys.self) - let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) - let scriptPath = Bundle.main.path(forResource: "Battery", ofType: "scpt")! - let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0) - let action = try ActionType(from: decoder) - return (item: item, action: action, parameters: [:]) - }, "sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), parameters: [:]) }, "displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), parameters: [:]) }, ] @@ -149,6 +144,7 @@ enum ItemType: Decodable { case staticButton(title: String) case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case timeButton(formatTemplate: String) + case battery() case dock() case volume() case brightness(refreshInterval: Double) @@ -174,6 +170,7 @@ enum ItemType: Decodable { case staticButton case appleScriptTitledButton case timeButton + case battery case dock case volume case brightness @@ -195,6 +192,8 @@ enum ItemType: Decodable { 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: @@ -209,7 +208,7 @@ enum ItemType: Decodable { 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) ?? 1800.0 + 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) diff --git a/MTMR/KeyPress.swift b/MTMR/KeyPress.swift index 5421d97..fe0f109 100644 --- a/MTMR/KeyPress.swift +++ b/MTMR/KeyPress.swift @@ -58,6 +58,7 @@ func HIDPostAuxKey(_ key: Int) { // hidsystem/ev_keymap.h let NX_KEYTYPE_SOUND_UP = 0 let NX_KEYTYPE_SOUND_DOWN = 1 +let NX_KEYTYPE_MUTE = 7 let NX_KEYTYPE_BRIGHTNESS_UP = 2 let NX_KEYTYPE_BRIGHTNESS_DOWN = 3 @@ -69,3 +70,4 @@ let NX_KEYTYPE_PREVIOUS = 18 + diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 290b483..92f74fa 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -23,6 +23,8 @@ extension ItemType { return "com.toxblh.mtmr.appleScriptButton." case .timeButton(formatTemplate: _): return "com.toxblh.mtmr.timeButton." + case .battery(): + return "com.toxblh.mtmr.battery." case .dock(): return "com.toxblh.mtmr.dock" case .volume(): @@ -40,7 +42,6 @@ extension ItemType { extension NSTouchBarItem.Identifier { static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip") - static let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea") } class TouchBarController: NSObject, NSTouchBarDelegate { @@ -54,6 +55,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { var leftIdentifiers: [NSTouchBarItem.Identifier] = [] var centerItems: [NSTouchBarItem] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = [] + var scrollArea: NSCustomTouchBarItem? + var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) private override init() { super.init() @@ -81,8 +84,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate { return definition.align == .center ? items[identifier] : nil } + self.centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString)) + self.scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems) + touchBar.delegate = self - touchBar.defaultItemIdentifiers = self.leftIdentifiers + [.centerScrollArea] + self.rightIdentifiers + touchBar.defaultItemIdentifiers = [] + touchBar.defaultItemIdentifiers = self.leftIdentifiers + [centerScrollArea] + self.rightIdentifiers self.presentTouchBar() } @@ -141,8 +148,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { - if identifier == .centerScrollArea { - return ScrollViewItem(identifier: identifier, items: centerItems) + if identifier == centerScrollArea { + return self.scrollArea } guard let item = self.items[identifier], @@ -164,6 +171,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action) case .timeButton(formatTemplate: let template): barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template) + case .battery(): + barItem = BatteryBarItem(identifier: identifier) case .dock: barItem = AppScrubberTouchBarItem(identifier: identifier) case .volume: @@ -225,7 +234,9 @@ class TouchBarController: NSObject, NSTouchBarDelegate { case .openUrl(url: let url): return { if let url = URL(string: url), NSWorkspace.shared.open(url) { -// print("URL was successfully opened") + #if DEBUG + print("URL was successfully opened") + #endif } else { print("error", url) } diff --git a/MTMR/WeatherBarItem.swift b/MTMR/WeatherBarItem.swift index 8951853..7140bd8 100644 --- a/MTMR/WeatherBarItem.swift +++ b/MTMR/WeatherBarItem.swift @@ -33,6 +33,10 @@ class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate { units_str = "°C" } + if self.units == "imperial" { + units_str = "°F" + } + if icon_type == "images" { iconsSource = iconsImages } else { diff --git a/MTMR/defaultPreset.json b/MTMR/defaultPreset.json index cac4fa2..f041f37 100644 --- a/MTMR/defaultPreset.json +++ b/MTMR/defaultPreset.json @@ -66,8 +66,10 @@ { "type": "previous", "width": 44 }, { "type": "play", "width": 44 }, { "type": "next", "width": 44 }, - { "type": "weather", "refreshInterval": 1800 }, + { "type": "weather", "icon_type": "images", "units": "metric" }, + { "type": "currency", "from": "BTC", "to": "USD" }, { "type": "sleep", "width": 44 }, + { "type": "mute", "width": 40, "align": "right" }, { "type": "volumeDown", "width": 34, "align": "right" }, { "type": "volume", "width": 60, "align": "right" }, { "type": "volumeUp", "width": 34, "align": "right" }, diff --git a/README.md b/README.md index 4225998..4f406c5 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ Maybe: ## Installation 1. Download last release [Releases](https://github.com/Toxblh/MTMR/releases) -2. Unzip 3. Open MTMR 4. Open preset `open ~/Library/Application\ Support/MTMR/items.json` and customize it. Restart MTMR to apply changes. @@ -62,16 +61,20 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT - brightnessDown - volumeDown - volumeUp +- mute - dock +> Native Plugins +- battery +- currency +- weather + > Media Keys - previous - play - next > AppleScript plugins -- weather -- battery - sleep - displaySleep @@ -106,8 +109,30 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT "formatTemplate": "HH:mm" //optional ``` +## Native plugins +- `weather` +> Provider: https://openweathermap.org Need allowance location service +```js + "type": "weather", + "refreshInterval": 600, + "units": "metric", // or imperial + "icon_type": "text" // or images + "api_key": "" // you can get the key on openweather +``` + +- `currency` +> Provider: https://coinbase.com +```js + "type": "currency", + "refreshInterval": 600, + "align": "right", + "from": "BTC", + "to": "USD", +``` + ## Actions: - `hidKey` +> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers ```json "action": "hidKey", "keycode": 53, @@ -132,10 +157,16 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT ```js "action": "shellScript", "executablePath": "/usr/bin/pmset", - "shellArguments": "sleepnow", // optional + "shellArguments": ["sleepnow"], // optional ``` +- `openUrl` +```js + "action": "openUrl", + "url": "https://google.com", +``` + ## Additional paramaters: - `width` allow to restrict how much room a particular button will take