mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-11-01 11:08:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7100a6e6b8 | ||
|
|
347bad8058 | ||
|
|
d72fb0ef7a | ||
|
|
ccc3a2269b | ||
|
|
6d6048c49c | ||
|
|
055c5d77ef | ||
|
|
f64c705c0d | ||
|
|
ca2ae30873 | ||
|
|
00fae08da0 | ||
|
|
f09b9155c4 | ||
|
|
9ef185d214 | ||
|
|
12f9220e34 | ||
|
|
ba753ff331 | ||
|
|
aba1aea334 | ||
|
|
849b5b6db0 | ||
|
|
621cb3242b | ||
|
|
dd0ecf047d | ||
|
|
bbc3d9dcfe | ||
|
|
85785018f4 | ||
|
|
7b9f894bf3 | ||
|
|
3a57293206 | ||
|
|
9afa9d7470 | ||
|
|
a86f7eb304 | ||
|
|
5013262740 | ||
|
|
106f297f57 | ||
|
|
115ea1372c | ||
|
|
f45aad4fad | ||
|
|
e131f92fb5 | ||
|
|
69a8d81bce | ||
|
|
7789407766 | ||
|
|
6c331619d0 | ||
|
|
9393a4e484 | ||
|
|
4b480dddc3 | ||
|
|
a6f27a1fef | ||
|
|
79eba1a281 | ||
|
|
2ad89bd8c8 | ||
|
|
ec990e8147 | ||
|
|
16d8380830 | ||
|
|
9e43a3b561 | ||
|
|
080bc67c54 | ||
|
|
81131ec390 | ||
|
|
cd6054af03 |
@@ -7,12 +7,16 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */; };
|
||||
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */; };
|
||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */; };
|
||||
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
|
||||
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
|
||||
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
|
||||
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
|
||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
|
||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
|
||||
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
|
||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; };
|
||||
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; };
|
||||
B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; };
|
||||
@@ -46,6 +50,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewItem.swift; sourceTree = "<group>"; };
|
||||
36BDC22F207CDA8600FCFEBE /* TECHNICAL_DEBT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TECHNICAL_DEBT.md; sourceTree = "<group>"; };
|
||||
36C2ECD2207B3B1D003CDA33 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
@@ -53,6 +58,9 @@
|
||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
|
||||
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
|
||||
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
|
||||
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = "<group>"; };
|
||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; };
|
||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; };
|
||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarPrivateApi.h; sourceTree = "<group>"; };
|
||||
@@ -146,11 +154,15 @@
|
||||
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
|
||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
||||
B082B258205C7D8000BC04DC /* Main.storyboard */,
|
||||
B082B25B205C7D8000BC04DC /* Info.plist */,
|
||||
B082B25C205C7D8000BC04DC /* MTMR.entitlements */,
|
||||
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
|
||||
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
|
||||
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
|
||||
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
|
||||
);
|
||||
path = MTMR;
|
||||
sourceTree = "<group>";
|
||||
@@ -311,12 +323,16 @@
|
||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */,
|
||||
36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */,
|
||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
||||
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
||||
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
|
||||
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
|
||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
||||
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
|
||||
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */,
|
||||
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
|
||||
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
|
||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
|
||||
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -27,8 +27,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
|
||||
@objc func testFn(_ sender: Any?) {
|
||||
let quoteText = "You click on menu"
|
||||
print(quoteText)
|
||||
let task = Process()
|
||||
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
|
||||
let presetPath = appSupportDirectory.appending("/items.json")
|
||||
task.launchPath = "/usr/bin/open"
|
||||
task.arguments = [presetPath]
|
||||
task.launch()
|
||||
}
|
||||
|
||||
func createMenu() {
|
||||
|
||||
@@ -5,7 +5,7 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
private let interval: TimeInterval
|
||||
private var forceHideConstraint: NSLayoutConstraint!
|
||||
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: Source, interval: TimeInterval, onTap: @escaping ()->()) {
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, onTap: @escaping ()->()) {
|
||||
self.interval = interval
|
||||
super.init(identifier: identifier, title: "compile", onTap: onTap)
|
||||
self.forceHideConstraint = self.view.widthAnchor.constraint(equalToConstant: 0)
|
||||
@@ -56,7 +56,7 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
|
||||
}
|
||||
|
||||
extension Source {
|
||||
extension SourceProtocol {
|
||||
var appleScript: NSAppleScript? {
|
||||
guard let source = self.string else { return nil }
|
||||
return NSAppleScript(source: source)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "brightnessDown.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "brightnessUp.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,54 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import AVFoundation
|
||||
import CoreAudio
|
||||
|
||||
class BrightnessViewController: NSCustomTouchBarItem {
|
||||
private(set) var sliderItem: NSSlider!
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, refreshInterval: Double, image: NSImage? = nil) {
|
||||
super.init(identifier: identifier)
|
||||
let brightness:Double = Double(getBrightness())
|
||||
sliderItem = NSSlider(value: brightness*100.0, minValue: 0.0, maxValue: 100.0, target: self, action:#selector(BrightnessViewController.sliderValueChanged(_:)))
|
||||
|
||||
if (image != nil) {
|
||||
sliderItem.cell = CustomSliderCell(knob: image!)
|
||||
}
|
||||
|
||||
self.view = sliderItem
|
||||
|
||||
let timer = Timer.scheduledTimer(timeInterval: refreshInterval, target: self, selector: #selector(BrightnessViewController.updateBrightnessSlider), userInfo: nil, repeats: true)
|
||||
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func updateBrightnessSlider() {
|
||||
DispatchQueue.main.async {
|
||||
self.sliderItem.floatValue = self.getBrightness() * 100
|
||||
}
|
||||
}
|
||||
|
||||
@objc func sliderValueChanged(_ sender: Any) {
|
||||
if let sliderItem = sender as? NSSlider {
|
||||
setBrightness(level: Float32(sliderItem.intValue)/100.0)
|
||||
}
|
||||
}
|
||||
|
||||
private func getBrightness() -> Float32 {
|
||||
var level: Float32 = 0.5
|
||||
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
|
||||
|
||||
IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &level)
|
||||
return level
|
||||
}
|
||||
|
||||
private func setBrightness(level: Float) {
|
||||
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
|
||||
|
||||
IODisplaySetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, level)
|
||||
IOObjectRelease(service)
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,10 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem {
|
||||
let tapClosure: () -> ()
|
||||
private(set) var button: NSButton!
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping () -> (), image: NSImage? = nil) {
|
||||
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping () -> ()) {
|
||||
self.tapClosure = callback
|
||||
super.init(identifier: identifier)
|
||||
if let image = image {
|
||||
button = NSButton(title: title, image: image, target: self, action: #selector(didTapped))
|
||||
button.bezelColor = .clear
|
||||
} else {
|
||||
button = NSButton(title: title, target: self, action: #selector(didTapped))
|
||||
}
|
||||
button = NSButton(title: title, target: self, action: #selector(didTapped))
|
||||
self.view = button
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.6</string>
|
||||
<string>0.8</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
+112
-49
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
extension Data {
|
||||
|
||||
@@ -11,13 +12,13 @@ extension Data {
|
||||
struct BarItemDefinition: Decodable {
|
||||
let type: ItemType
|
||||
let action: ActionType
|
||||
let additionalParameters: [GeneralParameter]
|
||||
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
}
|
||||
|
||||
init(type: ItemType, action: ActionType, additionalParameters: [GeneralParameter]) {
|
||||
init(type: ItemType, action: ActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) {
|
||||
self.type = type
|
||||
self.action = action
|
||||
self.additionalParameters = additionalParameters
|
||||
@@ -27,9 +28,10 @@ struct BarItemDefinition: Decodable {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
|
||||
let additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||
if let result = try? parametersDecoder(decoder),
|
||||
case let (itemType, action) = result {
|
||||
case let (itemType, action, parameters) = result {
|
||||
parameters.forEach { additionalParameters[$0] = $1 }
|
||||
self.init(type: itemType, action: action, additionalParameters: additionalParameters)
|
||||
} else {
|
||||
self.init(type: .staticButton(title: "unknown"), action: .none, additionalParameters: additionalParameters)
|
||||
@@ -39,23 +41,50 @@ struct BarItemDefinition: Decodable {
|
||||
}
|
||||
|
||||
class SupportedTypesHolder {
|
||||
typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType)
|
||||
typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter])
|
||||
private var supportedTypes: [String: ParametersDecoder] = [
|
||||
"escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53)) },
|
||||
"brightnessUp": { _ in return (item: .staticButton(title: "🔆"), action: .keyPress(keycode: 113)) },
|
||||
"brightnessDown": { _ in return (item: .staticButton(title: "🔅"), action: .keyPress(keycode: 107)) },
|
||||
"volumeDown": { _ in return (item: .staticImageButton(title: "", image: NSImage(named: .touchBarVolumeDownTemplate)!), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN)) },
|
||||
"volumeUp": { _ in return (item: .staticImageButton(title: "", image: NSImage(named: .touchBarVolumeUpTemplate)!), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP)) },
|
||||
"previous": { _ in return (item: .staticImageButton(title: "", image: NSImage(named: .touchBarRewindTemplate)!), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS)) },
|
||||
"play": { _ in return (item: .staticImageButton(title: "", image: NSImage(named: .touchBarPlayPauseTemplate)!), action: .hidKey(keycode: NX_KEYTYPE_PLAY)) },
|
||||
"next": { _ in return (item: .staticImageButton(title: "", image: NSImage(named: .touchBarFastForwardTemplate)!), action: .hidKey(keycode: NX_KEYTYPE_NEXT)) },
|
||||
"escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), parameters: [.align: .align(.left)]) },
|
||||
"brightnessUp": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
||||
return (item: .staticButton(title: ""), action: .keyPress(keycode: 113), parameters: [.image: imageParameter])
|
||||
},
|
||||
"brightnessDown": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
||||
return (item: .staticButton(title: ""), action: .keyPress(keycode: 107), parameters: [.image: imageParameter])
|
||||
},
|
||||
"volumeDown": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!)
|
||||
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), parameters: [.image: imageParameter])
|
||||
},
|
||||
"volumeUp": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!)
|
||||
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), 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])
|
||||
},
|
||||
"play": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!)
|
||||
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), parameters: [.image: imageParameter])
|
||||
},
|
||||
"next": { _ in
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!)
|
||||
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), parameters: [.image: imageParameter])
|
||||
},
|
||||
"weather": { 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: "Weather", ofType: "scpt")!
|
||||
let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0)
|
||||
return (item: item, action: .none)
|
||||
return (item: item, action: .none, parameters: [:])
|
||||
},
|
||||
"brightness": { 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)
|
||||
return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, parameters: [:])
|
||||
},
|
||||
"battery": { decoder in
|
||||
enum CodingKeys: String, CodingKey { case refreshInterval }
|
||||
@@ -63,17 +92,17 @@ class SupportedTypesHolder {
|
||||
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)
|
||||
return (item: item, action: .none)
|
||||
return (item: item, action: .none, parameters: [:])
|
||||
},
|
||||
"sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]) ) },
|
||||
"displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]) ) },
|
||||
"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: [:]) },
|
||||
]
|
||||
|
||||
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))
|
||||
return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), parameters: [:])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +112,17 @@ class SupportedTypesHolder {
|
||||
|
||||
func register(typename: String, item: ItemType, action: ActionType) {
|
||||
register(typename: typename) { _ in
|
||||
return (item: item, action: action)
|
||||
return (item: item, action: action, parameters: [:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ItemType: Decodable {
|
||||
case staticButton(title: String)
|
||||
case staticImageButton(title: String, image: NSImage)
|
||||
case appleScriptTitledButton(source: Source, refreshInterval: Double)
|
||||
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||
case timeButton(formatTemplate: String)
|
||||
case flexSpace()
|
||||
case volume()
|
||||
case brightness(refreshInterval: Double)
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
@@ -106,10 +135,10 @@ enum ItemType: Decodable {
|
||||
|
||||
enum ItemTypeRaw: String, Decodable {
|
||||
case staticButton
|
||||
case staticImageButton
|
||||
case appleScriptTitledButton
|
||||
case timeButton
|
||||
case flexSpace
|
||||
case volume
|
||||
case brightness
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@@ -120,23 +149,17 @@ enum ItemType: Decodable {
|
||||
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 .staticImageButton:
|
||||
let title = try container.decode(String.self, forKey: .title)
|
||||
let imageRaw = try container.decode(String.self, forKey: .image)
|
||||
if let decodedImageData = Data(base64Encoded: imageRaw, options: .ignoreUnknownCharacters) {
|
||||
let decImage = NSImage(data: decodedImageData)!
|
||||
self = .staticImageButton(title: title, image: decImage)
|
||||
} else {
|
||||
self = .staticButton(title: title)
|
||||
}
|
||||
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 .flexSpace:
|
||||
self = .flexSpace()
|
||||
case .volume:
|
||||
self = .volume()
|
||||
case .brightness:
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
|
||||
self = .brightness(refreshInterval: interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +168,7 @@ enum ActionType: Decodable {
|
||||
case none
|
||||
case hidKey(keycode: Int)
|
||||
case keyPress(keycode: Int)
|
||||
case appleSctipt(source: Source)
|
||||
case appleSctipt(source: SourceProtocol)
|
||||
case shellScript(executable: String, parameters: [String])
|
||||
case custom(closure: ()->())
|
||||
|
||||
@@ -192,8 +215,6 @@ func ==(lhs: ItemType, rhs: ItemType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.staticButton(a), .staticButton(b)):
|
||||
return a == b
|
||||
case let (.flexSpace(a), .flexSpace(b)):
|
||||
return a == b
|
||||
case let (.appleScriptTitledButton(a, b), .appleScriptTitledButton(c, d)):
|
||||
return a == c && b == d
|
||||
|
||||
@@ -221,34 +242,56 @@ func ==(lhs: ActionType, rhs: ActionType) -> Bool {
|
||||
|
||||
enum GeneralParameter {
|
||||
case width(_: CGFloat)
|
||||
case image(source: SourceProtocol)
|
||||
case align(_: Align)
|
||||
}
|
||||
|
||||
struct GeneralParameters: Decodable {
|
||||
let parameters: [GeneralParameter]
|
||||
let parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||
|
||||
fileprivate enum CodingKeys: String, CodingKey {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case width
|
||||
case image
|
||||
case align
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
var result: [GeneralParameter] = []
|
||||
var result: [GeneralParameters.CodingKeys: GeneralParameter] = [:]
|
||||
if let value = try container.decodeIfPresent(CGFloat.self, forKey: .width) {
|
||||
result.append(.width(value))
|
||||
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)
|
||||
parameters = result
|
||||
}
|
||||
}
|
||||
|
||||
struct Source: Decodable {
|
||||
protocol SourceProtocol {
|
||||
var data: Data? { get }
|
||||
var string: String? { get }
|
||||
var image: NSImage? { get }
|
||||
}
|
||||
struct Source: Decodable, SourceProtocol {
|
||||
let filePath: String?
|
||||
let base64: String?
|
||||
let inline: String?
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case filePath
|
||||
case base64
|
||||
case inline
|
||||
}
|
||||
|
||||
var data: Data? {
|
||||
return base64?.base64Data ?? inline?.data(using: .utf8) ?? filePath?.fileData
|
||||
}
|
||||
var string: String? {
|
||||
return inline ?? self.data?.base64Content
|
||||
return inline ?? self.data?.utf8string
|
||||
}
|
||||
var image: NSImage? {
|
||||
return data?.image
|
||||
}
|
||||
|
||||
private init(filePath: String?, base64: String?, inline: String?) {
|
||||
@@ -260,8 +303,19 @@ struct Source: Decodable {
|
||||
self.init(filePath: filePath, base64: nil, inline: nil)
|
||||
}
|
||||
}
|
||||
extension Source: Equatable {}
|
||||
func ==(left: Source, right: Source) -> Bool {
|
||||
extension NSImage: SourceProtocol {
|
||||
var data: Data? {
|
||||
return nil
|
||||
}
|
||||
var string: String? {
|
||||
return nil
|
||||
}
|
||||
var image: NSImage? {
|
||||
return self
|
||||
}
|
||||
}
|
||||
extension SourceProtocol where Self: Equatable {}
|
||||
func ==(left: SourceProtocol, right: SourceProtocol) -> Bool {
|
||||
return left.data == right.data
|
||||
}
|
||||
|
||||
@@ -274,7 +328,16 @@ extension String {
|
||||
}
|
||||
}
|
||||
extension Data {
|
||||
var base64Content: String? {
|
||||
var utf8string: String? {
|
||||
return String(data: self, encoding: .utf8)
|
||||
}
|
||||
var image: NSImage? {
|
||||
return NSImage(data: self)?.resize(maxSize: NSSize(width: 24, height: 24))
|
||||
}
|
||||
}
|
||||
|
||||
enum Align: String, Decodable {
|
||||
case left
|
||||
case center
|
||||
case right
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
|
||||
class ScrollViewItem: NSCustomTouchBarItem {
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem]) {
|
||||
super.init(identifier: identifier)
|
||||
let views = items.flatMap { $0.view }
|
||||
let stackView = NSStackView(views: views)
|
||||
stackView.orientation = .horizontal
|
||||
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
|
||||
scrollView.documentView = stackView
|
||||
self.view = scrollView
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// SupportHelpers.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by Anton Palgunov on 13/04/2018.
|
||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func trim() -> String {
|
||||
return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSImage {
|
||||
func resize(maxSize:NSSize) -> NSImage {
|
||||
var ratio:Float = 0.0
|
||||
let imageWidth = Float(self.size.width)
|
||||
let imageHeight = Float(self.size.height)
|
||||
let maxWidth = Float(maxSize.width)
|
||||
let maxHeight = Float(maxSize.height)
|
||||
|
||||
// Get ratio (landscape or portrait)
|
||||
if (imageWidth > imageHeight) {
|
||||
// Landscape
|
||||
ratio = maxWidth / imageWidth;
|
||||
}
|
||||
else {
|
||||
// Portrait
|
||||
ratio = maxHeight / imageHeight;
|
||||
}
|
||||
|
||||
// Calculate new size based on the ratio
|
||||
let newWidth = imageWidth * ratio
|
||||
let newHeight = imageHeight * ratio
|
||||
|
||||
// Create a new NSSize object with the newly calculated size
|
||||
let newSize:NSSize = NSSize(width: Int(newWidth), height: Int(newHeight))
|
||||
|
||||
// Cast the NSImage to a CGImage
|
||||
var imageRect:NSRect = NSMakeRect(0, 0, self.size.width, self.size.height)
|
||||
let imageRef = self.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
|
||||
|
||||
// Create NSImage from the CGImage using the new size
|
||||
let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize)
|
||||
|
||||
// Return the new image
|
||||
return imageWithNewSize
|
||||
}
|
||||
}
|
||||
@@ -14,49 +14,59 @@ struct ExactItem {
|
||||
}
|
||||
|
||||
extension ItemType {
|
||||
|
||||
|
||||
var identifierBase: String {
|
||||
switch self {
|
||||
case .staticButton(title: _):
|
||||
return "com.toxblh.mtmr.staticButton."
|
||||
case .staticImageButton(title: _):
|
||||
return "com.toxblh.mtmr.staticImageButton."
|
||||
case .appleScriptTitledButton(source: _):
|
||||
return "com.toxblh.mtmr.appleScriptButton."
|
||||
case .timeButton(formatTemplate: _):
|
||||
return "com.toxblh.mtmr.timeButton."
|
||||
case .flexSpace():
|
||||
return "NSTouchBarItem.Identifier.flexibleSpace"
|
||||
case .volume():
|
||||
return "com.toxblh.mtmr.volume"
|
||||
case .brightness(refreshInterval: _):
|
||||
return "com.toxblh.mtmr.brightness"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
static let shared = TouchBarController()
|
||||
|
||||
|
||||
let touchBar = NSTouchBar()
|
||||
|
||||
var items: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
|
||||
|
||||
|
||||
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
|
||||
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
|
||||
var leftIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||
var centerItems: [NSTouchBarItem] = []
|
||||
var rightIdentifiers: [NSTouchBarItem.Identifier] = []
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in
|
||||
self?.dismissTouchBar()
|
||||
}))
|
||||
|
||||
loadItems()
|
||||
|
||||
|
||||
loadItemDefinitions()
|
||||
createItems()
|
||||
centerItems = self.itemDefinitions.flatMap { (identifier, definition) -> NSTouchBarItem? in
|
||||
return definition.align == .center ? items[identifier] : nil
|
||||
}
|
||||
|
||||
touchBar.delegate = self
|
||||
touchBar.defaultItemIdentifiers = self.leftIdentifiers + [.centerScrollArea] + self.rightIdentifiers
|
||||
self.presentTouchBar()
|
||||
}
|
||||
|
||||
func loadItems() {
|
||||
|
||||
func loadItemDefinitions() {
|
||||
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
|
||||
let presetPath = appSupportDirectory.appending("/items.json")
|
||||
if !FileManager.default.fileExists(atPath: presetPath),
|
||||
@@ -65,18 +75,27 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath)
|
||||
}
|
||||
let jsonData = presetPath.fileData
|
||||
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, additionalParameters: [])]
|
||||
|
||||
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, additionalParameters: [:])]
|
||||
|
||||
for item in jsonItems {
|
||||
let identifierString = item.type.identifierBase.appending(UUID().uuidString)
|
||||
let identifier = item.type == ItemType.flexSpace()
|
||||
? NSTouchBarItem.Identifier.flexibleSpace
|
||||
: NSTouchBarItem.Identifier(identifierString)
|
||||
items[identifier] = item
|
||||
touchBar.defaultItemIdentifiers += [identifier]
|
||||
let identifier = NSTouchBarItem.Identifier(identifierString)
|
||||
itemDefinitions[identifier] = item
|
||||
if item.align == .left {
|
||||
leftIdentifiers.append(identifier)
|
||||
}
|
||||
if item.align == .right {
|
||||
rightIdentifiers.append(identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func createItems() {
|
||||
for (identifier, definition) in self.itemDefinitions {
|
||||
self.items[identifier] = self.createItem(forIdentifier: identifier, definition: definition)
|
||||
}
|
||||
}
|
||||
|
||||
func setupControlStripPresence() {
|
||||
DFRSystemModalShowsCloseBoxWhenFrontMost(false)
|
||||
let item = NSCustomTouchBarItem(identifier: .controlStripItem)
|
||||
@@ -84,46 +103,61 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
NSTouchBarItem.addSystemTrayItem(item)
|
||||
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
|
||||
}
|
||||
|
||||
|
||||
func updateControlStripPresence() {
|
||||
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
|
||||
}
|
||||
|
||||
|
||||
@objc private func presentTouchBar() {
|
||||
NSTouchBar.presentSystemModalFunctionBar(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
|
||||
}
|
||||
|
||||
|
||||
@objc private func dismissTouchBar() {
|
||||
NSTouchBar.minimizeSystemModalFunctionBar(touchBar)
|
||||
}
|
||||
|
||||
|
||||
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||
guard let item = self.items[identifier] else {
|
||||
if identifier == .centerScrollArea {
|
||||
return ScrollViewItem(identifier: identifier, items: centerItems)
|
||||
}
|
||||
|
||||
guard let item = self.items[identifier],
|
||||
let definition = self.itemDefinitions[identifier],
|
||||
definition.align != .center else {
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
|
||||
let action = self.action(forItem: item)
|
||||
|
||||
var barItem: NSTouchBarItem!
|
||||
switch item.type {
|
||||
case .staticButton(title: let title):
|
||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title, onTap: action)
|
||||
case .staticImageButton(title: let title, image: let image):
|
||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title, onTap: action, image: image)
|
||||
case .appleScriptTitledButton(source: let source, refreshInterval: let interval):
|
||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action)
|
||||
case .timeButton(formatTemplate: let template):
|
||||
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template)
|
||||
case .flexSpace:
|
||||
barItem = nil
|
||||
case .volume:
|
||||
barItem = VolumeViewController(identifier: identifier)
|
||||
case .brightness(refreshInterval: let interval):
|
||||
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval)
|
||||
}
|
||||
for parameter in item.additionalParameters {
|
||||
if case .width(let value) = parameter, let widthBarItem = barItem as? CanSetWidth {
|
||||
widthBarItem.setWidth(value: value)
|
||||
}
|
||||
if case .width(let value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth {
|
||||
widthBarItem.setWidth(value: value)
|
||||
}
|
||||
if case .image(let source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
|
||||
let button = item.button!
|
||||
button.image = source.image
|
||||
button.imagePosition = .imageLeading
|
||||
button.imageHugsTitle = true
|
||||
button.bezelColor = .clear
|
||||
}
|
||||
return barItem
|
||||
}
|
||||
|
||||
|
||||
|
||||
func action(forItem item: BarItemDefinition) -> ()->() {
|
||||
switch item.action {
|
||||
case .hidKey(keycode: let keycode):
|
||||
@@ -168,3 +202,11 @@ extension NSCustomTouchBarItem: CanSetWidth {
|
||||
}
|
||||
}
|
||||
|
||||
extension BarItemDefinition {
|
||||
var align: Align {
|
||||
if case .align(let result)? = self.additionalParameters[.align] {
|
||||
return result
|
||||
}
|
||||
return .center
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import AVFoundation
|
||||
import CoreAudio
|
||||
|
||||
class VolumeViewController: NSCustomTouchBarItem {
|
||||
private(set) var sliderItem: NSSlider!
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
|
||||
super.init(identifier: identifier)
|
||||
let volume:Double = Double(getInputGain())
|
||||
sliderItem = NSSlider(value: volume*100, minValue: 0.0, maxValue: 100.0, target: self, action:#selector(VolumeViewController.sliderValueChanged(_:)))
|
||||
|
||||
if (image != nil) {
|
||||
sliderItem.cell = CustomSliderCell(knob: image!)
|
||||
}
|
||||
|
||||
self.view = sliderItem
|
||||
|
||||
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
mScope: kAudioDevicePropertyScopeOutput,
|
||||
mElement: kAudioObjectPropertyElementMaster)
|
||||
|
||||
addListenerBlock(listenerBlock: audioObjectPropertyListenerBlock,
|
||||
onAudioObjectID: defaultDeviceID,
|
||||
forPropertyAddress: &forPropertyAddress)
|
||||
}
|
||||
|
||||
func addListenerBlock( listenerBlock: @escaping AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, forPropertyAddress: UnsafePointer<AudioObjectPropertyAddress>) {
|
||||
|
||||
if (kAudioHardwareNoError != AudioObjectAddPropertyListenerBlock(onAudioObjectID, forPropertyAddress, nil, listenerBlock)) {
|
||||
print("Error calling: AudioObjectAddPropertyListenerBlock") }
|
||||
}
|
||||
|
||||
func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
|
||||
var index: UInt32 = 0
|
||||
while index < numberAddresses {
|
||||
let address: AudioObjectPropertyAddress = addresses[Int(index)]
|
||||
switch address.mSelector {
|
||||
case kAudioHardwareServiceDeviceProperty_VirtualMasterVolume:
|
||||
DispatchQueue.main.async {
|
||||
self.sliderItem.floatValue = self.getInputGain() * 100
|
||||
}
|
||||
default:
|
||||
|
||||
print("We didn't expect this!")
|
||||
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func sliderValueChanged(_ sender: Any) {
|
||||
if let sliderItem = sender as? NSSlider {
|
||||
_ = setInputGain(Float32(sliderItem.intValue)/100.0)
|
||||
}
|
||||
}
|
||||
|
||||
private var defaultDeviceID: AudioObjectID {
|
||||
var deviceID: AudioObjectID = AudioObjectID(0)
|
||||
var size: UInt32 = UInt32(MemoryLayout<AudioObjectID>.size)
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice)
|
||||
address.mScope = AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID)
|
||||
return deviceID
|
||||
}
|
||||
|
||||
private func getInputGain() -> Float32 {
|
||||
var volume: Float32 = 0.5
|
||||
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume))
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume)
|
||||
return volume
|
||||
}
|
||||
|
||||
private func setInputGain(_ volume: Float32) -> OSStatus {
|
||||
var inputVolume: Float32 = volume
|
||||
|
||||
if inputVolume == 0.0 {
|
||||
_ = setMute( mute: 1)
|
||||
} else {
|
||||
_ = setMute( mute: 0)
|
||||
}
|
||||
|
||||
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: inputVolume))
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume)
|
||||
}
|
||||
|
||||
private func setMute( mute: Int) -> OSStatus {
|
||||
var muteVal: Int = mute
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioDevicePropertyMute)
|
||||
let size: UInt32 = UInt32(MemoryLayout.size(ofValue: muteVal))
|
||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &muteVal)
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSliderCell: NSSliderCell {
|
||||
var knobImage:NSImage!
|
||||
private var _currentKnobRect:NSRect!
|
||||
private var _barRect:NSRect!
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(knob:NSImage) {
|
||||
knobImage = knob;
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func drawKnob(_ knobRect: NSRect) {
|
||||
|
||||
if (knobImage == nil) {
|
||||
super.drawKnob(knobRect)
|
||||
return;
|
||||
}
|
||||
|
||||
_currentKnobRect = knobRect;
|
||||
drawBar(inside: _barRect, flipped: true)
|
||||
self.controlView?.lockFocus()
|
||||
|
||||
knobImage.size.width = knobRect.size.width
|
||||
knobImage.size.height = knobRect.size.height
|
||||
|
||||
let newOriginX:CGFloat = knobRect.origin.x *
|
||||
(_barRect.size.width - (knobImage.size.width - knobRect.size.width)) / _barRect.size.width;
|
||||
|
||||
knobImage.draw(at: NSPoint(x: newOriginX, y: knobRect.origin.y), from: NSRect(x: 0, y: 0, width: knobImage.size.width, height: knobImage.size.height), operation: NSCompositingOperation.sourceOver, fraction: 1)
|
||||
|
||||
self.controlView?.unlockFocus()
|
||||
|
||||
}
|
||||
|
||||
override func drawBar(inside aRect: NSRect, flipped: Bool) {
|
||||
_barRect = aRect
|
||||
|
||||
var rect = aRect
|
||||
rect.size.height = CGFloat(3)
|
||||
let barRadius = CGFloat(2.5)
|
||||
let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
|
||||
let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 8))
|
||||
var leftRect = rect
|
||||
leftRect.size.width = finalWidth
|
||||
let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
|
||||
NSColor.lightGray.setFill()
|
||||
bg.fill()
|
||||
let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius)
|
||||
NSColor.darkGray.setFill()
|
||||
active.fill()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
[
|
||||
{ "type": "escape" },
|
||||
{ "type": "exitTouchbar" },
|
||||
{ "type": "volume", "width": 80 },
|
||||
{ "type": "brightness", "width": 80 },
|
||||
{ "type": "brightnessDown" },
|
||||
{
|
||||
"type": "staticButton",
|
||||
@@ -9,7 +11,6 @@
|
||||
"keycode": 113,
|
||||
"width": 36,
|
||||
},
|
||||
{ "type": "flexSpace" },
|
||||
{ "type": "volumeDown", "width": 44 },
|
||||
{ "type": "volumeUp", "width": 44 },
|
||||
{ "type": "previous" },
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[](https://github.com/Toxblh/MTMR/releases)
|
||||
|
||||
<p align="center">
|
||||
<img src="Resources/TouchBar-v0.3.png">
|
||||
<img src="Resources/TouchBar-v0.6.png">
|
||||
</p>
|
||||
|
||||
My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :)
|
||||
@@ -24,8 +24,8 @@ My the idea is to create the program like a platform for plugins for customizati
|
||||
- [x] Hide from Dock
|
||||
- [x] Status menu: "preferences", "quit"
|
||||
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
|
||||
- [ ] Layout: [always left, NSSliderView for center, always right]
|
||||
- [ ] Custom buttons size, actions by click in settings
|
||||
- [x] Custom buttons size, actions by click
|
||||
- [x] Layout: [always left, NSSliderView for center, always right]
|
||||
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
||||
- [ ] System for autoupdate (maybe https://sparkle-project.org/)
|
||||
|
||||
@@ -38,3 +38,175 @@ Settings:
|
||||
|
||||
Maybe:
|
||||
- [ ] Refactoring the application on packages (AppleScript, JavaScript? and Swift?)
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
## Preset
|
||||
|
||||
File for customize your preset for MTMR: `open ~/Library/Application Support/MTMR/items.json`
|
||||
|
||||
## Built-in button types:
|
||||
|
||||
- escape
|
||||
- exitTouchbar
|
||||
- brightnessUp
|
||||
- brightnessDown
|
||||
- volumeDown
|
||||
- volumeUp
|
||||
|
||||
> Media Keys
|
||||
- previous
|
||||
- play
|
||||
- next
|
||||
|
||||
> AppleScript plugins
|
||||
- weather
|
||||
- battery
|
||||
- sleep
|
||||
- displaySleep
|
||||
|
||||
## Built-in slider types:
|
||||
|
||||
- brightness
|
||||
- volume
|
||||
|
||||
### You can also make a custom buttons using these types
|
||||
- `staticButton`
|
||||
```json
|
||||
"type": "staticButton",
|
||||
"title": "esc",
|
||||
```
|
||||
|
||||
- `appleScriptTitledButton`
|
||||
```js
|
||||
"type": "appleScriptTitledButton",
|
||||
"refreshInterval": 60, //optional
|
||||
"source": {
|
||||
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt",
|
||||
// or
|
||||
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell",
|
||||
// or
|
||||
"base64": "StringInbase64"
|
||||
},
|
||||
```
|
||||
|
||||
- `timeButton`
|
||||
```js
|
||||
"type": "timeButton",
|
||||
"formatTemplate": "HH:mm" //optional
|
||||
```
|
||||
|
||||
## Actions:
|
||||
- `hidKey`
|
||||
```json
|
||||
"action": "hidKey",
|
||||
"keycode": 53,
|
||||
```
|
||||
|
||||
- `keyPress`
|
||||
```json
|
||||
"action": "keyPress",
|
||||
"keycode": 1,
|
||||
```
|
||||
|
||||
- `appleScript`
|
||||
```js
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell"
|
||||
// "filePath" or "base64" will work as well
|
||||
},
|
||||
```
|
||||
|
||||
- `shellScript`
|
||||
```js
|
||||
"action": "shellScript",
|
||||
"executablePath": "/usr/bin/pmset",
|
||||
"shellArguments": "sleepnow", // optional
|
||||
|
||||
```
|
||||
|
||||
## Additional paramaters:
|
||||
|
||||
- `width` allow to easily restrict how much room a particular button will take
|
||||
```json
|
||||
"width": 34
|
||||
```
|
||||
|
||||
- `align` can stick the time to the side. default is center
|
||||
```js
|
||||
"align": "left" //or "right" or "center"
|
||||
```
|
||||
|
||||
## Example configuration:
|
||||
```json
|
||||
[
|
||||
{ "type": "escape", "width": 110 },
|
||||
{ "type": "exitTouchbar", "align": "left" },
|
||||
{
|
||||
"type": "brightnessUp",
|
||||
"align": "left",
|
||||
"width": 36
|
||||
},
|
||||
{
|
||||
"type": "staticButton",
|
||||
"align": "left",
|
||||
"title": "🔆",
|
||||
"action": "keyPress",
|
||||
"keycode": 113,
|
||||
"width": 36
|
||||
},
|
||||
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
"source": {
|
||||
"filePath": "/Users/toxblh/Library/Application Support/MTMR/iTunes.nowPlaying.scpt"
|
||||
},
|
||||
"refreshInterval": 1
|
||||
},
|
||||
{
|
||||
"type": "staticButton",
|
||||
"align": "left",
|
||||
"image": { "base64" : "%base64Finder%"},
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell"
|
||||
},
|
||||
"width": 36
|
||||
},
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
"source": {
|
||||
"inline": "if application \"Safari\" is running then\r\ttell application \"Safari\"\r\t\trepeat with t in tabs of windows\r\t\t\ttell t\r\t\t\t\tif URL starts with \"https:\/\/music.yandex.ru\" and name does not end with \"на Яндекс.Музыке\" then\r\t\t\t\t\treturn name of t as text\r\t\t\t\tend if\r\t\t\tend tell\r\t\tend repeat\r\tend tell\rend if\rreturn \"\""
|
||||
},
|
||||
"refreshInterval": 1
|
||||
},
|
||||
{ "type": "previous", "width": 36, "align": "right" },
|
||||
{ "type": "play", "width": 36, "align": "right" },
|
||||
{ "type": "next", "width": 36, "align": "right" },
|
||||
{ "type": "sleep", "width": 36 , "align": "right"},
|
||||
{ "type": "displaySleep", "align": "right" },
|
||||
{ "type": "weather", "refreshInterval": 1800, "width": 70, "align": "right" },
|
||||
{ "type": "volumeDown", "width": 36 , "align": "right"},
|
||||
{ "type": "volumeUp", "width": 36 , "align": "right"},
|
||||
{ "type": "battery", "refreshInterval": 60 , "align": "right"},
|
||||
{ "type": "appleScriptTitledButton", "refreshInterval": 1800, "source": { "filePath": "/Users/redetection/Library/Application Support/MTMR/Weather.scpt"} , "align": "right"},
|
||||
{ "type": "timeButton", "formatTemplate": "HH:mm", "width": 64, "align": "right" }
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### Author's presets
|
||||
|
||||
[@Toxblh preset](Resources/toxblh.json)
|
||||
|
||||
[@ReDetection preset](Resources/ReDetection.json)
|
||||
|
||||
## Credits
|
||||
|
||||
Built by [@Toxblh](https://patreon.com/toxblh) and [@ReDetection](http://patreon.com/ReDetection).
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user