Compare commits

..
29 Commits
Author SHA1 Message Date
toxblh 7100a6e6b8 New version. Layouts 2018-04-13 21:20:45 +01:00
Anton Palgunov 347bad8058 Merge pull request #13 from Toxblh/align
Align (left/center/right)
2018-04-13 21:18:29 +01:00
toxblh d72fb0ef7a Update my preset to actual. Fix xcode error 2018-04-13 21:15:02 +01:00
Serg ccc3a2269b Merge branch 'master' into align 2018-04-14 03:00:58 +07:00
Serg 6d6048c49c build quickfix 2018-04-14 03:00:38 +07:00
Serg 055c5d77ef Merge remote-tracking branch 'ad/master' 2018-04-14 02:54:03 +07:00
Serg f64c705c0d update example preset, update mine preset 2018-04-14 02:47:05 +07:00
ad ca2ae30873 + configurable refresh interval for brightness 2018-04-13 22:42:53 +03:00
Serg 00fae08da0 mention change in docs 2018-04-14 02:33:34 +07:00
ad f09b9155c4 + listener for changes of brightness (timer 0.5 sec)
Can't find any listener
2018-04-13 22:17:12 +03:00
Serg 9ef185d214 remove flexSpace 2018-04-14 02:09:34 +07:00
Serg 12f9220e34 implement scrolling view at the center 2018-04-14 01:58:07 +07:00
Serg ba753ff331 Merge branch 'master' into align
# Conflicts:
#	MTMR/ItemsParsing.swift
2018-04-14 01:46:44 +07:00
Serg aba1aea334 Merge pull request #12 from ad/master
+ listener for changes of sytem volume
2018-04-14 01:42:39 +07:00
ad 849b5b6db0 * fixed bug with mute at 0.0 2018-04-13 21:40:34 +03:00
ad 621cb3242b + listener for changes of sytem volume
known bug: sound not mutes at value 0.0
2018-04-13 21:27:59 +03:00
Serg dd0ecf047d additional parameters as dictionary for easier mapping 2018-04-14 00:53:22 +07:00
Serg bbc3d9dcfe filter center items, prepare for scrollView 2018-04-14 00:32:12 +07:00
Serg 85785018f4 create all items in advance 2018-04-14 00:23:47 +07:00
Serg 7b9f894bf3 parse align 2018-04-14 00:23:19 +07:00
toxblh 3a57293206 add brightness icons like original Touch Bar 2018-04-13 17:21:51 +01:00
Serg 9afa9d7470 Merge branch 'tries/button-image'
# Conflicts:
#	MTMR.xcodeproj/project.pbxproj
2018-04-13 22:31:07 +07:00
Anton Palgunov a86f7eb304 Update README.md 2018-04-13 14:54:44 +01:00
toxblh 5013262740 v0.6.1 2018-04-13 14:46:38 +01:00
toxblh e131f92fb5 del trim() in parser 2018-04-13 09:25:55 +01:00
toxblh 69a8d81bce del trim() for aScript 2018-04-13 09:22:03 +01:00
Serg 7789407766 no staticImageButton in readme 2018-04-13 14:00:21 +07:00
toxblh 6c331619d0 Added 2 Support function (trim and resize). Image and title close forever :) resize input image to good size 2018-04-13 01:15:11 +01:00
Serg 79eba1a281 image as generic parameter 2018-04-13 00:00:33 +07:00
18 changed files with 485 additions and 242 deletions
+8
View File
@@ -7,6 +7,7 @@
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 */; };
@@ -15,6 +16,7 @@
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 */; };
@@ -48,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>"; };
@@ -57,6 +60,7 @@
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>"; };
@@ -150,6 +154,7 @@
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
B082B258205C7D8000BC04DC /* Main.storyboard */,
B082B25B205C7D8000BC04DC /* Info.plist */,
B082B25C205C7D8000BC04DC /* MTMR.entitlements */,
@@ -157,6 +162,7 @@
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
);
path = MTMR;
sourceTree = "<group>";
@@ -317,6 +323,7 @@
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 */,
@@ -325,6 +332,7 @@
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+2 -2
View File
@@ -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

+10 -1
View File
@@ -6,7 +6,7 @@ import CoreAudio
class BrightnessViewController: NSCustomTouchBarItem {
private(set) var sliderItem: NSSlider!
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
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(_:)))
@@ -16,12 +16,21 @@ class BrightnessViewController: NSCustomTouchBarItem {
}
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)
+2 -7
View File
@@ -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
View File
@@ -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>
+105 -51
View File
@@ -12,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
@@ -28,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)
@@ -40,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 }
@@ -64,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: [:])
}
}
@@ -84,19 +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()
case brightness(refreshInterval: Double)
private enum CodingKeys: String, CodingKey {
case type
@@ -109,10 +135,8 @@ enum ItemType: Decodable {
enum ItemTypeRaw: String, Decodable {
case staticButton
case staticImageButton
case appleScriptTitledButton
case timeButton
case flexSpace
case volume
case brightness
}
@@ -125,27 +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:
self = .brightness()
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
self = .brightness(refreshInterval: interval)
}
}
}
@@ -154,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: ()->())
@@ -201,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
@@ -230,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?) {
@@ -269,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
}
@@ -283,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
}
+19
View File
@@ -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")
}
}
+52
View File
@@ -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
}
}
+75 -41
View File
@@ -14,53 +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():
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),
@@ -69,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)
@@ -88,50 +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:
barItem = BrightnessViewController(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):
@@ -176,3 +202,11 @@ extension NSCustomTouchBarItem: CanSetWidth {
}
}
extension BarItemDefinition {
var align: Align {
if case .align(let result)? = self.additionalParameters[.align] {
return result
}
return .center
}
}
+52 -2
View File
@@ -16,6 +16,39 @@ class VolumeViewController: NSCustomTouchBarItem {
}
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) {
@@ -24,7 +57,7 @@ class VolumeViewController: NSCustomTouchBarItem {
@objc func sliderValueChanged(_ sender: Any) {
if let sliderItem = sender as? NSSlider {
setInputGain(Float32(sliderItem.intValue)/100.0)
_ = setInputGain(Float32(sliderItem.intValue)/100.0)
}
}
@@ -52,13 +85,30 @@ class VolumeViewController: NSCustomTouchBarItem {
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.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
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 {
-1
View File
@@ -11,7 +11,6 @@
"keycode": 113,
"width": 36,
},
{ "type": "flexSpace" },
{ "type": "volumeDown", "width": 44 },
{ "type": "volumeUp", "width": 44 },
{ "type": "previous" },
+29 -49
View File
@@ -25,7 +25,7 @@ My the idea is to create the program like a platform for plugins for customizati
- [x] Status menu: "preferences", "quit"
- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/`
- [x] Custom buttons size, actions by click
- [ ] Layout: [always left, NSSliderView for center, always right]
- [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/)
@@ -70,6 +70,11 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
- sleep
- displaySleep
## Built-in slider types:
- brightness
- volume
### You can also make a custom buttons using these types
- `staticButton`
```json
@@ -77,13 +82,6 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
"title": "esc",
```
- `staticImageButton`
```json
"type": "staticImageButton",
"image": "StringInbase64"
"title": "Finder",
```
- `appleScriptTitledButton`
```js
"type": "appleScriptTitledButton",
@@ -103,11 +101,6 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
"formatTemplate": "HH:mm" //optional
```
- `flexSpace` – to easily split touch bar in two parts: left and right
```json
"type": "flexSpace"
```
## Actions:
- `hidKey`
```json
@@ -145,17 +138,24 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
"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" },
{ "type": "exitTouchbar", "align": "left" },
{
"type": "brightnessUp",
"align": "left",
"width": 36
},
{
"type": "staticButton",
"align": "left",
"title": "🔆",
"action": "keyPress",
"keycode": 113,
@@ -170,9 +170,9 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
"refreshInterval": 1
},
{
"type": "staticImageButton",
"title": "Finder",
"image": "%base64Finder%",
"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"
@@ -186,18 +186,17 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
},
"refreshInterval": 1
},
{ "type": "flexSpace" },
{ "type": "previous", "width": 36 },
{ "type": "play", "width": 36 },
{ "type": "next", "width": 36 },
{ "type": "sleep", "width": 36 },
{ "type": "displaySleep" },
{ "type": "weather", "refreshInterval": 1800, "width": 70 },
{ "type": "volumeDown", "width": 36 },
{ "type": "volumeUp", "width": 36 },
{ "type": "battery", "refreshInterval": 60 },
{ "type": "appleScriptTitledButton", "refreshInterval": 1800, "source": { "filePath": "/Users/redetection/Library/Application Support/MTMR/Weather.scpt"} },
{ "type": "timeButton", "formatTemplate": "HH:mm", "width": 64 }
{ "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" }
]
```
@@ -206,26 +205,7 @@ File for customize your preset for MTMR: `open ~/Library/Application Support/MTM
[@Toxblh preset](Resources/toxblh.json)
[@ReDetection preset](Resources/ReDetection.json):
```json
[
{ "type": "escape", "width": 110 },
{ "type": "exitTouchbar" },
{ "type": "brightnessDown", "width": 40 },
{ "type": "brightnessUp", "width": 40 },
{ "type": "appleScriptTitledButton", "refreshInterval": 15, "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 \"\"" },
"action": "appleScript", "actionAppleScript": {"inline": "if application \"Safari\" is running then\r\ttell application \"Safari\"\r\t\trepeat with w in windows\r\t\t\trepeat with t in tabs of w\r\t\t\t\ttell t\r\t\t\t\t\tif URL starts with \"https:\/\/music.yandex.ru\" and name does not end with \"на Яндекс.Музыке\" then --последнее условие проверяет, запущена ли музыка\r\t\t\t\t\t\tactivate\r\t\t\t\t\t\tset index of w to 1\r\t\t\t\t\t\tdelay 0.1\r\t\t\t\t\t\tset current tab of w to t\r\t\t\t\t\tend if\r\t\t\t\tend tell\r\t\t\tend repeat\r\t\tend repeat\r\tend tell\rend if"},
},
{ "type": "appleScriptTitledButton", "source": { "inline": "tell application \"Reminders\"\r\tset activeReminders to name of (reminders of list \"Напоминания\" whose completed is false)\r\tif activeReminders is not {} then\r\t\treturn first item of activeReminders\r\telse\r\t\treturn \"\"\r\tend if\rend tell" }, "refreshInterval": 30},
{ "type": "flexSpace" },
{ "type": "appleScriptTitledButton", "source": { "inline": "if application \"iTunes\" is running then\r\ttell application \"iTunes\"\r\t\tif player state is not stopped then return \"\"\r\tend tell\rend if\rif 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 \"\"\r\t\t\t\tend if\r\t\t\tend tell\r\t\tend repeat\r\tend tell\rend if\rreturn \"▶\"" }, "refreshInterval": 30, "width": 40},
{ "type": "volumeDown", "width": 44 },
{ "type": "volumeUp", "width": 44 },
{ "type": "displaySleep" },
{ "type": "appleScriptTitledButton", "refreshInterval": 1800, "source": { "filePath": "/Users/redetection/Library/Application Support/MTMR/Weather.scpt"} },
{ "type": "timeButton" },
]
```
[@ReDetection preset](Resources/ReDetection.json)
## Credits
File diff suppressed because one or more lines are too long
+62 -41
View File
File diff suppressed because one or more lines are too long