mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-10-31 10:28:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe901a572 | ||
|
|
54eaa3fd9f | ||
|
|
6660bb2d8f | ||
|
|
7a1800252c | ||
|
|
588e6ae09b | ||
|
|
87141e381b | ||
|
|
14282b86a9 | ||
|
|
810cdeed36 | ||
|
|
a65613acaf | ||
|
|
aa69d5f592 | ||
|
|
2f00c9ffb3 | ||
|
|
1def53878d | ||
|
|
3e5fa14494 | ||
|
|
2e2f556daf |
@@ -71,6 +71,7 @@
|
||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; };
|
||||
BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; };
|
||||
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; };
|
||||
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -163,6 +164,7 @@
|
||||
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = "<group>"; };
|
||||
BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = "<group>"; };
|
||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = "<group>"; };
|
||||
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextScrubberTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -322,6 +324,7 @@
|
||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */,
|
||||
B08126F0217BE19000A98970 /* WidgetProtocol.swift */,
|
||||
B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */,
|
||||
F29F6A2424BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
@@ -488,6 +491,7 @@
|
||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
||||
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
||||
F29F6A2524BC7148004FF8E4 /* UpNextScrubberTouchBarItem.swift in Sources */,
|
||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
||||
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
|
||||
|
||||
+11
-11
@@ -15,41 +15,41 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
var fourfingers: NSPanGestureRecognizer!
|
||||
var swipeItems: [SwipeItem] = []
|
||||
var prevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
||||
|
||||
|
||||
// legacy gesture positions
|
||||
// by legacy I mean gestures to increse/decrease volume/brigtness which can be checked from app menu
|
||||
var legacyPrevPositions: [Int: CGFloat] = [2:0, 3:0, 4:0]
|
||||
var legacyGesturesEnabled = false
|
||||
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, items: [NSTouchBarItem], swipeItems: [SwipeItem]) {
|
||||
super.init(identifier: identifier)
|
||||
self.swipeItems = swipeItems
|
||||
let views = items.compactMap { $0.view }
|
||||
let stackView = NSStackView(views: views)
|
||||
stackView.spacing = 1
|
||||
stackView.spacing = 8
|
||||
stackView.orientation = .horizontal
|
||||
view = stackView
|
||||
|
||||
|
||||
twofingers = NSPanGestureRecognizer(target: self, action: #selector(twofingersHandler(_:)))
|
||||
twofingers.numberOfTouchesRequired = 2
|
||||
twofingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(twofingers)
|
||||
|
||||
|
||||
threefingers = NSPanGestureRecognizer(target: self, action: #selector(threefingersHandler(_:)))
|
||||
threefingers.numberOfTouchesRequired = 3
|
||||
threefingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(threefingers)
|
||||
|
||||
|
||||
fourfingers = NSPanGestureRecognizer(target: self, action: #selector(fourfingersHandler(_:)))
|
||||
fourfingers.numberOfTouchesRequired = 4
|
||||
fourfingers.allowedTouchTypes = .direct
|
||||
view.addGestureRecognizer(fourfingers)
|
||||
}
|
||||
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
func gestureHandler(position: CGFloat, fingers: Int, state: NSGestureRecognizer.State) {
|
||||
switch state {
|
||||
case .began:
|
||||
@@ -72,9 +72,9 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
let prevPos = legacyPrevPositions[fingers]!
|
||||
if ((position - prevPos) > 15) || ((prevPos - position) > 15) {
|
||||
if position > prevPos {
|
||||
GenericKeyPress(keyCode: CGKeyCode(144)).send()
|
||||
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_UP)
|
||||
} else if position < prevPos {
|
||||
GenericKeyPress(keyCode: CGKeyCode(145)).send()
|
||||
HIDPostAuxKey(NX_KEYTYPE_BRIGHTNESS_DOWN)
|
||||
}
|
||||
legacyPrevPositions[fingers] = position
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class BasicView: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func twofingersHandler(_ sender: NSGestureRecognizer?) {
|
||||
let position = (sender?.location(in: sender?.view).x)!
|
||||
self.gestureHandler(position: position, fingers: 2, state: sender!.state)
|
||||
|
||||
@@ -8,18 +8,32 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
struct ItemAction {
|
||||
typealias TriggerClosure = (() -> Void)?
|
||||
|
||||
let trigger: Action.Trigger
|
||||
let closure: TriggerClosure
|
||||
|
||||
init(trigger: Action.Trigger, _ closure: TriggerClosure) {
|
||||
self.trigger = trigger
|
||||
self.closure = closure
|
||||
}
|
||||
}
|
||||
|
||||
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
|
||||
var tapClosure: (() -> Void)?
|
||||
var longTapClosure: (() -> Void)? {
|
||||
|
||||
var actions: [ItemAction] = [] {
|
||||
didSet {
|
||||
longClick.isEnabled = longTapClosure != nil
|
||||
multiClick.isDoubleClickEnabled = actions.filter({ $0.trigger == .doubleTap }).count > 0
|
||||
multiClick.isTripleClickEnabled = actions.filter({ $0.trigger == .tripleTap }).count > 0
|
||||
longClick.isEnabled = actions.filter({ $0.trigger == .longTap }).count > 0
|
||||
}
|
||||
}
|
||||
var finishViewConfiguration: ()->() = {}
|
||||
|
||||
private var button: NSButton!
|
||||
private var singleClick: HapticClickGestureRecognizer!
|
||||
private var longClick: LongPressGestureRecognizer!
|
||||
private var multiClick: MultiClickGestureRecognizer!
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, title: String) {
|
||||
attributedTitle = title.defaultTouchbarAttributedString
|
||||
@@ -31,10 +45,17 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
||||
longClick.isEnabled = false
|
||||
longClick.allowedTouchTypes = .direct
|
||||
longClick.delegate = self
|
||||
|
||||
singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
|
||||
singleClick.allowedTouchTypes = .direct
|
||||
singleClick.delegate = self
|
||||
|
||||
multiClick = MultiClickGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(handleGestureSingleTap),
|
||||
doubleAction: #selector(handleGestureDoubleTap),
|
||||
tripleAction: #selector(handleGestureTripleTap)
|
||||
)
|
||||
multiClick.allowedTouchTypes = .direct
|
||||
multiClick.delegate = self
|
||||
multiClick.isDoubleClickEnabled = false
|
||||
multiClick.isTripleClickEnabled = false
|
||||
|
||||
reinstallButton()
|
||||
button.attributedTitle = attributedTitle
|
||||
@@ -100,33 +121,43 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
||||
view = button
|
||||
|
||||
view.addGestureRecognizer(longClick)
|
||||
view.addGestureRecognizer(singleClick)
|
||||
// view.addGestureRecognizer(singleClick)
|
||||
view.addGestureRecognizer(multiClick)
|
||||
finishViewConfiguration()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick
|
||||
|| gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it
|
||||
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
|
||||
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
|
||||
{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
|
||||
switch gr.state {
|
||||
case .ended:
|
||||
tapClosure?()
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
func callActions(for trigger: Action.Trigger) {
|
||||
let itemActions = self.actions.filter { $0.trigger == trigger }
|
||||
for itemAction in itemActions {
|
||||
itemAction.closure?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleGestureSingleTap() {
|
||||
callActions(for: .singleTap)
|
||||
}
|
||||
|
||||
@objc func handleGestureDoubleTap() {
|
||||
callActions(for: .doubleTap)
|
||||
}
|
||||
|
||||
@objc func handleGestureTripleTap() {
|
||||
callActions(for: .tripleTap)
|
||||
}
|
||||
|
||||
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
|
||||
switch gr.state {
|
||||
case .possible: // tiny hack because we're calling action manually
|
||||
(self.longTapClosure ?? self.tapClosure)?()
|
||||
callActions(for: .longTap)
|
||||
break
|
||||
default:
|
||||
break
|
||||
@@ -176,15 +207,78 @@ class CustomButtonCell: NSButtonCell {
|
||||
}
|
||||
}
|
||||
|
||||
class HapticClickGestureRecognizer: NSClickGestureRecognizer {
|
||||
// Thanks to https://stackoverflow.com/a/49843893
|
||||
final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
|
||||
|
||||
private let _action: Selector
|
||||
private let _doubleAction: Selector
|
||||
private let _tripleAction: Selector
|
||||
private var _clickCount: Int = 0
|
||||
|
||||
public var isDoubleClickEnabled = true
|
||||
public var isTripleClickEnabled = true
|
||||
|
||||
override var action: Selector? {
|
||||
get {
|
||||
return nil /// prevent base class from performing any actions
|
||||
} set {
|
||||
if newValue != nil { // if they are trying to assign an actual action
|
||||
fatalError("Only use init(target:action:doubleAction) for assigning actions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) {
|
||||
_action = action
|
||||
_doubleAction = doubleAction
|
||||
_tripleAction = tripleAction
|
||||
super.init(target: target, action: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(target:action:doubleAction:tripleAction) is only support atm")
|
||||
}
|
||||
|
||||
override func touchesBegan(with event: NSEvent) {
|
||||
HapticFeedback.shared?.tap(strong: 2)
|
||||
super.touchesBegan(with: event)
|
||||
}
|
||||
|
||||
|
||||
override func touchesEnded(with event: NSEvent) {
|
||||
HapticFeedback.shared?.tap(strong: 1)
|
||||
super.touchesEnded(with: event)
|
||||
_clickCount += 1
|
||||
|
||||
var delayThreshold: TimeInterval // fine tune this as needed
|
||||
|
||||
guard isDoubleClickEnabled || isTripleClickEnabled else {
|
||||
_ = target?.perform(_action)
|
||||
return
|
||||
}
|
||||
|
||||
if (isTripleClickEnabled) {
|
||||
delayThreshold = 0.4
|
||||
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
|
||||
if _clickCount == 3 {
|
||||
_ = target?.perform(_tripleAction)
|
||||
}
|
||||
} else {
|
||||
delayThreshold = 0.3
|
||||
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
|
||||
if _clickCount == 2 {
|
||||
_ = target?.perform(_doubleAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func _resetAndPerformActionIfNecessary() {
|
||||
if _clickCount == 1 {
|
||||
_ = target?.perform(_action)
|
||||
}
|
||||
if isTripleClickEnabled && _clickCount == 2 {
|
||||
_ = target?.perform(_doubleAction)
|
||||
}
|
||||
_clickCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.26</string>
|
||||
<string>0.27</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>401</string>
|
||||
<string>429</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
+195
-48
@@ -3,47 +3,52 @@ import Foundation
|
||||
|
||||
extension Data {
|
||||
func barItemDefinitions() -> [BarItemDefinition]? {
|
||||
return try? JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
|
||||
return try! JSONDecoder().decode([BarItemDefinition].self, from: utf8string!.stripComments().data(using: .utf8)!)
|
||||
}
|
||||
}
|
||||
|
||||
struct BarItemDefinition: Decodable {
|
||||
let type: ItemType
|
||||
let action: ActionType
|
||||
let longAction: LongActionType
|
||||
let actions: [Action]
|
||||
let legacyAction: LegacyActionType
|
||||
let legacyLongAction: LegacyLongActionType
|
||||
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case actions
|
||||
}
|
||||
|
||||
init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
||||
init(type: ItemType, actions: [Action], action: LegacyActionType, legacyLongAction: LegacyLongActionType, additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]) {
|
||||
self.type = type
|
||||
self.action = action
|
||||
self.longAction = longAction
|
||||
self.actions = actions
|
||||
self.legacyAction = action
|
||||
self.legacyLongAction = legacyLongAction
|
||||
self.additionalParameters = additionalParameters
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
|
||||
let actions = try container.decodeIfPresent([Action].self, forKey: .actions)
|
||||
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type, actions: actions ?? [])
|
||||
var additionalParameters = try GeneralParameters(from: decoder).parameters
|
||||
|
||||
if let result = try? parametersDecoder(decoder),
|
||||
case let (itemType, action, longAction, parameters) = result {
|
||||
case let (itemType, actions, action, longAction, parameters) = result {
|
||||
parameters.forEach { additionalParameters[$0] = $1 }
|
||||
self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters)
|
||||
self.init(type: itemType, actions: actions, action: action, legacyLongAction: longAction, additionalParameters: additionalParameters)
|
||||
} else {
|
||||
self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
|
||||
self.init(type: .staticButton(title: "unknown"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: additionalParameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias ParametersDecoder = (Decoder) throws -> (
|
||||
item: ItemType,
|
||||
action: ActionType,
|
||||
longAction: LongActionType,
|
||||
actions: [Action],
|
||||
legacyAction: LegacyActionType,
|
||||
legacyLongAction: LegacyLongActionType,
|
||||
parameters: [GeneralParameters.CodingKeys: GeneralParameter]
|
||||
)
|
||||
|
||||
@@ -51,15 +56,21 @@ class SupportedTypesHolder {
|
||||
private var supportedTypes: [String: ParametersDecoder] = [
|
||||
"escape": { _ in (
|
||||
item: .staticButton(title: "esc"),
|
||||
action: .keyPress(keycode: 53),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.align: .align(.left)]
|
||||
) },
|
||||
|
||||
"delete": { _ in (
|
||||
item: .staticButton(title: "del"),
|
||||
action: .keyPress(keycode: 117),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [:]
|
||||
) },
|
||||
|
||||
@@ -67,8 +78,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .keyPress(keycode: 144),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_UP))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -77,8 +91,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .keyPress(keycode: 145),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_BRIGHTNESS_DOWN))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -87,8 +104,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -97,8 +117,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -107,8 +130,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -117,8 +143,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -127,8 +156,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_MUTE),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -137,8 +169,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -147,8 +182,11 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_PLAY),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
@@ -157,23 +195,32 @@ class SupportedTypesHolder {
|
||||
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
|
||||
return (
|
||||
item: .staticButton(title: ""),
|
||||
action: .hidKey(keycode: NX_KEYTYPE_NEXT),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.image: imageParameter]
|
||||
)
|
||||
},
|
||||
|
||||
"sleep": { _ in (
|
||||
item: .staticButton(title: "☕️"),
|
||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [:]
|
||||
) },
|
||||
|
||||
"displaySleep": { _ in (
|
||||
item: .staticButton(title: "☕️"),
|
||||
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]),
|
||||
longAction: .none,
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [:]
|
||||
) },
|
||||
|
||||
@@ -181,11 +228,12 @@ class SupportedTypesHolder {
|
||||
|
||||
static let sharedInstance = SupportedTypesHolder()
|
||||
|
||||
func lookup(by type: String) -> ParametersDecoder {
|
||||
func lookup(by type: String, actions: [Action]) -> ParametersDecoder {
|
||||
return supportedTypes[type] ?? { decoder in (
|
||||
item: try ItemType(from: decoder),
|
||||
action: try ActionType(from: decoder),
|
||||
longAction: try LongActionType(from: decoder),
|
||||
actions: actions,
|
||||
legacyAction: try LegacyActionType(from: decoder),
|
||||
legacyLongAction: try LegacyLongActionType(from: decoder),
|
||||
parameters: [:]
|
||||
) }
|
||||
}
|
||||
@@ -194,12 +242,13 @@ class SupportedTypesHolder {
|
||||
supportedTypes[typename] = decoder
|
||||
}
|
||||
|
||||
func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
|
||||
func register(typename: String, item: ItemType, actions: [Action], legacyAction: LegacyActionType, legacyLongAction: LegacyLongActionType) {
|
||||
register(typename: typename) { _ in
|
||||
(
|
||||
item: item,
|
||||
action,
|
||||
longAction,
|
||||
actions,
|
||||
legacyAction,
|
||||
legacyLongAction,
|
||||
parameters: [:]
|
||||
)
|
||||
}
|
||||
@@ -227,6 +276,7 @@ enum ItemType: Decodable {
|
||||
case network(flip: Bool)
|
||||
case darkMode
|
||||
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
|
||||
case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool)
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
@@ -258,6 +308,7 @@ enum ItemType: Decodable {
|
||||
case direction
|
||||
case fingers
|
||||
case minOffset
|
||||
case maxToShow
|
||||
}
|
||||
|
||||
enum ItemTypeRaw: String, Decodable {
|
||||
@@ -281,6 +332,7 @@ enum ItemType: Decodable {
|
||||
case network
|
||||
case darkMode
|
||||
case swipe
|
||||
case upnext
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@@ -378,11 +430,106 @@ enum ItemType: Decodable {
|
||||
let fingers = try container.decode(Int.self, forKey: .fingers)
|
||||
let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0
|
||||
self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
||||
|
||||
case .upnext:
|
||||
let from = try container.decodeIfPresent(Double.self, forKey: .from) ?? 0 // Lower bounds of period of time in hours to search for events
|
||||
let to = try container.decodeIfPresent(Double.self, forKey: .to) ?? 12 // Upper bounds of period of time in hours to search for events
|
||||
let maxToShow = try container.decodeIfPresent(Int.self, forKey: .maxToShow) ?? 3 // 1 indexed array. Get the 1st, 2nd, 3rd event to display multiple notifications
|
||||
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 60.0
|
||||
self = .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ActionType: Decodable {
|
||||
struct FailableDecodable<Base : Decodable> : Decodable {
|
||||
|
||||
let base: Base?
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.base = try? container.decode(Base.self)
|
||||
}
|
||||
}
|
||||
|
||||
struct Action: Decodable {
|
||||
enum Trigger: String, Decodable {
|
||||
case singleTap
|
||||
case doubleTap
|
||||
case tripleTap
|
||||
case longTap
|
||||
}
|
||||
|
||||
enum Value {
|
||||
case none
|
||||
case hidKey(keycode: Int32)
|
||||
case keyPress(keycode: Int)
|
||||
case appleScript(source: SourceProtocol)
|
||||
case shellScript(executable: String, parameters: [String])
|
||||
case custom(closure: () -> Void)
|
||||
case openUrl(url: String)
|
||||
}
|
||||
|
||||
private enum ActionTypeRaw: String, Decodable {
|
||||
case hidKey
|
||||
case keyPress
|
||||
case appleScript
|
||||
case shellScript
|
||||
case openUrl
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case trigger
|
||||
case action
|
||||
case keycode
|
||||
case actionAppleScript
|
||||
case executablePath
|
||||
case shellArguments
|
||||
case url
|
||||
}
|
||||
|
||||
let trigger: Trigger
|
||||
let value: Value
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
trigger = try container.decode(Trigger.self, forKey: .trigger)
|
||||
let type = try container.decodeIfPresent(ActionTypeRaw.self, forKey: .action)
|
||||
|
||||
switch type {
|
||||
case .some(.hidKey):
|
||||
let keycode = try container.decode(Int32.self, forKey: .keycode)
|
||||
value = .hidKey(keycode: keycode)
|
||||
|
||||
case .some(.keyPress):
|
||||
let keycode = try container.decode(Int.self, forKey: .keycode)
|
||||
value = .keyPress(keycode: keycode)
|
||||
|
||||
case .some(.appleScript):
|
||||
let source = try container.decode(Source.self, forKey: .actionAppleScript)
|
||||
value = .appleScript(source: source)
|
||||
|
||||
case .some(.shellScript):
|
||||
let executable = try container.decode(String.self, forKey: .executablePath)
|
||||
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
|
||||
value = .shellScript(executable: executable, parameters: parameters)
|
||||
|
||||
case .some(.openUrl):
|
||||
let url = try container.decode(String.self, forKey: .url)
|
||||
value = .openUrl(url: url)
|
||||
case .none:
|
||||
value = .none
|
||||
}
|
||||
}
|
||||
|
||||
init(trigger: Trigger, value: Value) {
|
||||
self.trigger = trigger
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
enum LegacyActionType: Decodable {
|
||||
case none
|
||||
case hidKey(keycode: Int32)
|
||||
case keyPress(keycode: Int)
|
||||
@@ -440,7 +587,7 @@ enum ActionType: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
enum LongActionType: Decodable {
|
||||
enum LegacyLongActionType: Decodable {
|
||||
case none
|
||||
case hidKey(keycode: Int32)
|
||||
case keyPress(keycode: Int)
|
||||
|
||||
@@ -59,6 +59,8 @@ extension ItemType {
|
||||
return DarkModeBarItem.identifier
|
||||
case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _):
|
||||
return "com.toxblh.mtmr.swipe."
|
||||
case .upnext(from: _, to: _, maxToShow: _, autoResize: _):
|
||||
return "com.connorgmeehan.mtmrup.next."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,17 +92,32 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar() }), longAction: .none)
|
||||
SupportedTypesHolder.sharedInstance.register(
|
||||
typename: "exitTouchbar",
|
||||
item: .staticButton(title: "exit"),
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in self?.dismissTouchBar() }))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none
|
||||
)
|
||||
|
||||
SupportedTypesHolder.sharedInstance.register(typename: "close") { _ in
|
||||
(item: .staticButton(title: ""), action: .custom(closure: { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.reloadPreset(path: self.lastPresetPath)
|
||||
}), longAction: .none, parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
||||
(
|
||||
item: .staticButton(title: ""),
|
||||
actions: [
|
||||
Action(trigger: .singleTap, value: .custom(closure: { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.reloadPreset(path: self.lastPresetPath)
|
||||
}))
|
||||
],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [.width: .width(30), .image: .image(source: (NSImage(named: NSImage.stopProgressFreestandingTemplateName))!)])
|
||||
}
|
||||
|
||||
blacklistAppIdentifiers = AppSettings.blacklistedAppIds
|
||||
|
||||
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||
@@ -123,20 +140,20 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
|
||||
|
||||
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||
|
||||
touchBar.delegate = self
|
||||
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
||||
|
||||
|
||||
let leftItems = leftIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
let rightItems = rightIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
|
||||
|
||||
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
||||
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
||||
|
||||
@@ -168,7 +185,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
|
||||
func reloadPreset(path: String) {
|
||||
lastPresetPath = path
|
||||
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
|
||||
let items = path.fileData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), actions: [], action: .none, legacyLongAction: .none, additionalParameters: [:])]
|
||||
createAndUpdatePreset(newJsonItems: items)
|
||||
}
|
||||
|
||||
@@ -301,13 +318,21 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
barItem = DarkModeBarItem(identifier: identifier)
|
||||
case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash):
|
||||
barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash)
|
||||
case let .upnext(from: from, to: to, maxToShow: maxToShow, autoResize: autoResize):
|
||||
barItem = UpNextScrubberTouchBarItem(identifier: identifier, interval: 60, from: from, to: to, maxToShow: maxToShow, autoResize: autoResize)
|
||||
}
|
||||
|
||||
if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||
item.tapClosure = action
|
||||
item.actions.append(ItemAction(trigger: .singleTap, action))
|
||||
}
|
||||
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
|
||||
item.longTapClosure = longAction
|
||||
item.actions.append(ItemAction(trigger: .longTap, longAction))
|
||||
}
|
||||
|
||||
if let touchBarItem = barItem as? CustomButtonTouchBarItem {
|
||||
for action in item.actions {
|
||||
touchBarItem.actions.append(ItemAction(trigger: action.trigger, self.closure(for: action)))
|
||||
}
|
||||
}
|
||||
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
|
||||
item.isBordered = bordered
|
||||
@@ -330,9 +355,53 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
return barItem
|
||||
}
|
||||
|
||||
func closure(for action: Action) -> (() -> Void)? {
|
||||
switch action.value {
|
||||
case let .hidKey(keycode: keycode):
|
||||
return { HIDPostAuxKey(keycode) }
|
||||
case let .keyPress(keycode: keycode):
|
||||
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
|
||||
case let .appleScript(source: source):
|
||||
guard let appleScript = source.appleScript else {
|
||||
print("cannot create apple script for item \(action)")
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
DispatchQueue.appleScriptQueue.async {
|
||||
var error: NSDictionary?
|
||||
appleScript.executeAndReturnError(&error)
|
||||
if let error = error {
|
||||
print("error \(error) when handling \(action) ")
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .shellScript(executable: executable, parameters: parameters):
|
||||
return {
|
||||
let task = Process()
|
||||
task.launchPath = executable
|
||||
task.arguments = parameters
|
||||
task.launch()
|
||||
}
|
||||
case let .openUrl(url: url):
|
||||
return {
|
||||
if let url = URL(string: url), NSWorkspace.shared.open(url) {
|
||||
#if DEBUG
|
||||
print("URL was successfully opened")
|
||||
#endif
|
||||
} else {
|
||||
print("error", url)
|
||||
}
|
||||
}
|
||||
case let .custom(closure: closure):
|
||||
return closure
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func action(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||
switch item.action {
|
||||
switch item.legacyAction {
|
||||
case let .hidKey(keycode: keycode):
|
||||
return { HIDPostAuxKey(keycode) }
|
||||
case let .keyPress(keycode: keycode):
|
||||
@@ -376,7 +445,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
|
||||
func longAction(forItem item: BarItemDefinition) -> (() -> Void)? {
|
||||
switch item.longAction {
|
||||
switch item.legacyLongAction {
|
||||
case let .hidKey(keycode: keycode):
|
||||
return { HIDPostAuxKey(keycode) }
|
||||
case let .keyPress(keycode: keycode):
|
||||
@@ -428,6 +497,12 @@ extension NSCustomTouchBarItem: CanSetWidth {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSPopoverTouchBarItem: CanSetWidth {
|
||||
func setWidth(value: CGFloat) {
|
||||
view?.widthAnchor.constraint(equalToConstant: value).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
extension BarItemDefinition {
|
||||
var align: Align {
|
||||
if case let .align(result)? = additionalParameters[.align] {
|
||||
|
||||
@@ -82,12 +82,14 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem {
|
||||
public func createAppButton(for app: DockItem) -> DockBarItem {
|
||||
let item = DockBarItem(app)
|
||||
item.isBordered = false
|
||||
item.tapClosure = { [weak self] in
|
||||
self?.switchToApp(app: app)
|
||||
}
|
||||
item.longTapClosure = { [weak self] in
|
||||
self?.handleHalfLongPress(item: app)
|
||||
}
|
||||
item.actions.append(contentsOf: [
|
||||
ItemAction(trigger: .singleTap) { [weak self] in
|
||||
self?.switchToApp(app: app)
|
||||
},
|
||||
ItemAction(trigger: .longTap) { [weak self] in
|
||||
self?.handleHalfLongPress(item: app)
|
||||
}
|
||||
])
|
||||
item.killAppClosure = {[weak self] in
|
||||
self?.handleLongPress(item: app)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class DarkModeBarItem: CustomButtonTouchBarItem, Widget {
|
||||
isBordered = false
|
||||
setWidth(value: 24)
|
||||
|
||||
tapClosure = { [weak self] in self?.DarkModeToggle() }
|
||||
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DarkModeToggle() })
|
||||
|
||||
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class DnDBarItem: CustomButtonTouchBarItem {
|
||||
isBordered = false
|
||||
setWidth(value: 32)
|
||||
|
||||
tapClosure = { [weak self] in self?.DnDToggle() }
|
||||
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.DnDToggle() })
|
||||
|
||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ class InputSourceBarItem: CustomButtonTouchBarItem {
|
||||
|
||||
observeIputSourceChangedNotification()
|
||||
textInputSourceDidChange()
|
||||
tapClosure = { [weak self] in
|
||||
|
||||
actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
||||
self?.switchInputSource()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
|
||||
@@ -41,9 +41,12 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
|
||||
super.init(identifier: identifier, title: "⏳")
|
||||
isBordered = false
|
||||
|
||||
tapClosure = { [weak self] in self?.playPause() }
|
||||
longTapClosure = { [weak self] in self?.nextTrack() }
|
||||
|
||||
actions = [
|
||||
ItemAction(trigger: .singleTap) { [weak self] in self?.playPause() },
|
||||
ItemAction(trigger: .doubleTap) { [weak self] in self?.previousTrack() },
|
||||
ItemAction(trigger: .longTap) { [weak self] in self?.nextTrack() }
|
||||
]
|
||||
|
||||
refreshAndSchedule()
|
||||
}
|
||||
@@ -177,6 +180,31 @@ class MusicBarItem: CustomButtonTouchBarItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func previousTrack() {
|
||||
for ident in playerBundleIdentifiers {
|
||||
if let musicPlayer = SBApplication(bundleIdentifier: ident.rawValue) {
|
||||
if musicPlayer.isRunning {
|
||||
if ident == .Spotify {
|
||||
let mp = (musicPlayer as SpotifyApplication)
|
||||
mp.previousTrack!()
|
||||
updatePlayer()
|
||||
return
|
||||
} else if ident == .iTunes {
|
||||
let mp = (musicPlayer as iTunesApplication)
|
||||
mp.previousTrack!()
|
||||
updatePlayer()
|
||||
return
|
||||
} else if ident == .Music {
|
||||
let mp = (musicPlayer as MusicApplication)
|
||||
mp.previousTrack!()
|
||||
updatePlayer()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshAndSchedule() {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -30,8 +30,8 @@ class NightShiftBarItem: CustomButtonTouchBarItem {
|
||||
super.init(identifier: identifier, title: "")
|
||||
isBordered = false
|
||||
setWidth(value: 28)
|
||||
|
||||
tapClosure = { [weak self] in self?.nightShiftAction() }
|
||||
|
||||
actions.append(ItemAction(trigger: .singleTap) { [weak self] in self?.nightShiftAction() })
|
||||
|
||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(refresh), userInfo: nil, repeats: true)
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
||||
|
||||
return (
|
||||
item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300),
|
||||
action: .none,
|
||||
longAction: .none,
|
||||
actions: [],
|
||||
legacyAction: .none,
|
||||
legacyLongAction: .none,
|
||||
parameters: [:]
|
||||
)
|
||||
}
|
||||
@@ -50,8 +51,10 @@ class PomodoroBarItem: CustomButtonTouchBarItem, Widget {
|
||||
self.workTime = workTime
|
||||
self.restTime = restTime
|
||||
super.init(identifier: identifier, title: defaultTitle)
|
||||
tapClosure = { [weak self] in self?.startStopWork() }
|
||||
longTapClosure = { [weak self] in self?.startStopRest() }
|
||||
actions.append(contentsOf: [
|
||||
ItemAction(trigger: .singleTap) { [weak self] in self?.startStopWork() },
|
||||
ItemAction(trigger: .longTap) { [weak self] in self?.startStopRest() }
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
//
|
||||
// UpNextScrubberTouchBarItems.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by Connor Meehan on 13/7/20.
|
||||
// Copyright © 2020 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import EventKit
|
||||
|
||||
class UpNextScrubberTouchBarItem: NSCustomTouchBarItem {
|
||||
// Dependencies
|
||||
private let scrollView = NSScrollView()
|
||||
private let activity: NSBackgroundActivityScheduler // Update scheduler
|
||||
private var eventSources : [IUpNextSource] = []
|
||||
private var items: [UpNextItem] = []
|
||||
|
||||
// Settings
|
||||
private var futureSearchCutoff: Double
|
||||
private var pastSearchCutoff: Double
|
||||
private var maxToShow: Int
|
||||
private var widthConstraint: NSLayoutConstraint?
|
||||
private var autoResize: Bool = false
|
||||
|
||||
/// <#Description#>
|
||||
/// - Parameters:
|
||||
/// - identifier: Unique identifier of widget
|
||||
/// - interval: Update view interval in seconds
|
||||
/// - from: Relative to current time, how far back we search for events in hours
|
||||
/// - to: Relative to current time, how far forward we search for events in hours
|
||||
/// - maxToShow: Which event to show (1 is first, 2 is second, and so on)
|
||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, maxToShow: Int, autoResize: Bool) {
|
||||
// Initialise member properties
|
||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck")
|
||||
pastSearchCutoff = from * 3600
|
||||
futureSearchCutoff = to * 3600
|
||||
self.maxToShow = maxToShow
|
||||
self.autoResize = autoResize
|
||||
UpNextItem.df.dateFormat = "HH:mm"
|
||||
// Error handling
|
||||
if (maxToShow <= 0) {
|
||||
fatalError("Error on UpNext bar item. maxToShow property must be greater than 0.")
|
||||
}
|
||||
// Init super
|
||||
super.init(identifier: identifier)
|
||||
view = scrollView
|
||||
// Add event sources
|
||||
// Can optionally pass an update view callback to an event source to redraw element
|
||||
self.eventSources.append(UpNextCalenderSource(updateCallback: self.updateView))
|
||||
// Fallback interactivity via interval
|
||||
activity.interval = interval
|
||||
activity.repeats = true
|
||||
activity.qualityOfService = .utility
|
||||
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||
self.updateView()
|
||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||
}
|
||||
updateView()
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateView() -> Void {
|
||||
items = []
|
||||
var upcomingEvents = self.getUpcomingEvents()
|
||||
upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending})
|
||||
var index = 1
|
||||
DispatchQueue.main.async {
|
||||
for event in upcomingEvents {
|
||||
// Create UpNextItem
|
||||
let item = UpNextItem(event: event)
|
||||
item.backgroundColor = self.getBackgroundColor(startDate: event.startDate)
|
||||
// Bind tap event
|
||||
item.actions.append(ItemAction(trigger: .singleTap) { [weak self] in
|
||||
self?.switchToApp(event: event)
|
||||
})
|
||||
// Add to view
|
||||
self.items.append(item)
|
||||
// Check if should display any more
|
||||
if (index == self.maxToShow) {
|
||||
break;
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
self.reloadData()
|
||||
self.updateSize()
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadData() {
|
||||
let stackView = NSStackView(views: items.compactMap { $0.view })
|
||||
stackView.spacing = 5
|
||||
stackView.orientation = .horizontal
|
||||
let visibleRect = self.scrollView.documentVisibleRect
|
||||
self.scrollView.documentView = stackView
|
||||
stackView.scroll(visibleRect.origin)
|
||||
}
|
||||
|
||||
func updateSize() {
|
||||
if self.autoResize {
|
||||
self.widthConstraint?.isActive = false
|
||||
|
||||
let width = self.scrollView.documentView?.fittingSize.width ?? 0
|
||||
self.widthConstraint = self.scrollView.widthAnchor.constraint(equalToConstant: width)
|
||||
self.widthConstraint!.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func getUpcomingEvents() -> [UpNextEventModel] {
|
||||
var upcomingEvents: [UpNextEventModel] = []
|
||||
|
||||
// Calculate the range we're going to search for events in
|
||||
let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff)
|
||||
let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff)
|
||||
|
||||
// Get all events from all sources
|
||||
for eventSource in self.eventSources {
|
||||
if (eventSource.hasPermission) {
|
||||
let events = eventSource.getUpcomingEvents(dateLowerBounds: dateLowerBounds, dateUpperBounds: dateUpperBounds)
|
||||
upcomingEvents.append(contentsOf: events)
|
||||
}
|
||||
}
|
||||
|
||||
return upcomingEvents
|
||||
}
|
||||
|
||||
public func switchToApp(event: UpNextEventModel) {
|
||||
var bundleIdentifier: String
|
||||
switch(event.sourceType) {
|
||||
case .iCalendar:
|
||||
bundleIdentifier = UpNextCalenderSource.bundleIdentifier
|
||||
}
|
||||
|
||||
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
|
||||
|
||||
// NB: if you can't open app which on another space, try to check mark
|
||||
// "When switching to an application, switch to a Space with open windows for the application"
|
||||
// in Mission control settings
|
||||
}
|
||||
|
||||
|
||||
func getBackgroundColor(startDate: Date) -> NSColor {
|
||||
let distance = startDate.timeIntervalSinceReferenceDate/60 - Date().timeIntervalSinceReferenceDate/60 // Get time difference in minutes
|
||||
if (distance < 0 as TimeInterval) { // If it's in the past, draw as blue
|
||||
return NSColor.systemBlue
|
||||
} else if (distance < 30 as TimeInterval) { // Less than 30 minutes, backround is red
|
||||
return NSColor.systemRed
|
||||
}
|
||||
return NSColor.clear
|
||||
}
|
||||
}
|
||||
|
||||
private class UpNextItem : CustomButtonTouchBarItem {
|
||||
static public let df = DateFormatter()
|
||||
|
||||
init(event: UpNextEventModel) {
|
||||
let identifier = UpNextItem.getIdentifier(event: event)
|
||||
let title = UpNextItem.getTitle(event: event)
|
||||
super.init(identifier: NSTouchBarItem.Identifier(rawValue: identifier), title: title)
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private static func getTitle(event: UpNextEventModel) -> String {
|
||||
var title = ""
|
||||
let startDateString = UpNextItem.df.string(for: event.startDate)
|
||||
switch event.sourceType {
|
||||
case .iCalendar:
|
||||
title = String.init(format: "🗓 %@ - %@ ", event.title, startDateString!)
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
private static func getIdentifier(event: UpNextEventModel) -> String {
|
||||
var identifier : String
|
||||
switch event.sourceType {
|
||||
case .iCalendar:
|
||||
identifier = "com.mtmr.iCalendarEvent"
|
||||
}
|
||||
return identifier + "." + event.title
|
||||
}
|
||||
}
|
||||
|
||||
enum UpNextSourceType {
|
||||
case iCalendar
|
||||
}
|
||||
|
||||
// Model for events to be displayed in dock
|
||||
struct UpNextEventModel {
|
||||
let title: String
|
||||
let startDate: Date
|
||||
let sourceType: UpNextSourceType
|
||||
}
|
||||
|
||||
|
||||
// Interface for any event source
|
||||
protocol IUpNextSource {
|
||||
static var bundleIdentifier: String { get }
|
||||
var hasPermission : Bool { get }
|
||||
var updateCallback : () -> Void { get set }
|
||||
|
||||
init(updateCallback: @escaping () -> Void)
|
||||
func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel]
|
||||
}
|
||||
|
||||
class UpNextCalenderSource : IUpNextSource {
|
||||
static public let bundleIdentifier: String = "com.apple.iCal"
|
||||
|
||||
public var hasPermission: Bool = false
|
||||
private var eventStore : EKEventStore
|
||||
internal var updateCallback: () -> Void
|
||||
|
||||
required init(updateCallback: @escaping () -> Void = {}) {
|
||||
self.updateCallback = updateCallback
|
||||
eventStore = EKEventStore()
|
||||
NotificationCenter.default.addObserver(forName: .EKEventStoreChanged, object: eventStore, queue: nil, using: handleUpdate)
|
||||
let authStatus = EKEventStore.authorizationStatus(for: .event)
|
||||
if (authStatus != .authorized) {
|
||||
eventStore.requestAccess(to: .event){ granted, error in
|
||||
self.hasPermission = granted;
|
||||
self.handleUpdate()
|
||||
if(!granted) {
|
||||
NSLog("Error: MTMR UpNextBarWidget not given calendar access.")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.handleUpdate()
|
||||
}
|
||||
|
||||
}
|
||||
public func handleUpdate() {
|
||||
self.handleUpdate(note: Notification(name: Notification.Name("refresh view")))
|
||||
}
|
||||
public func handleUpdate(note: Notification) {
|
||||
self.updateCallback()
|
||||
}
|
||||
|
||||
public func getUpcomingEvents(dateLowerBounds: Date, dateUpperBounds: Date) -> [UpNextEventModel] {
|
||||
var upcomingEvents: [UpNextEventModel] = []
|
||||
let calendars = self.eventStore.calendars(for: .event)
|
||||
let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: calendars)
|
||||
let events = self.eventStore.events(matching: predicate)
|
||||
for event in events {
|
||||
upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate, sourceType: UpNextSourceType.iCalendar))
|
||||
}
|
||||
return upcomingEvents
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,13 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
private let activity: NSBackgroundActivityScheduler
|
||||
private let unitsStr = "°C"
|
||||
private let iconsSource = [
|
||||
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
||||
"Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫"
|
||||
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь с грозой": "⛈", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
||||
"Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Thunderstorm with rain": "⛈", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫"
|
||||
]
|
||||
private var location: CLLocation!
|
||||
private var prevLocation: CLLocation!
|
||||
private var manager: CLLocationManager!
|
||||
private var updateWeatherTask: URLSessionDataTask?
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval) {
|
||||
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
|
||||
@@ -49,8 +50,13 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||
manager.startUpdatingLocation()
|
||||
|
||||
tapClosure = tapClosure ?? defaultTapAction
|
||||
|
||||
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
|
||||
actions.append(ItemAction(
|
||||
trigger: .singleTap,
|
||||
defaultTapAction
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
@@ -61,7 +67,8 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
var urlRequest = URLRequest(url: URL(string: getWeatherUrl())!)
|
||||
urlRequest.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36", forHTTPHeaderField: "user-agent") // important for the right format
|
||||
|
||||
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
|
||||
updateWeatherTask?.cancel()
|
||||
updateWeatherTask = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
|
||||
guard error == nil, let response = data?.utf8string else {
|
||||
return
|
||||
}
|
||||
@@ -86,12 +93,12 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
updateWeatherTask?.resume()
|
||||
}
|
||||
|
||||
func getWeatherUrl() -> String {
|
||||
if location != nil {
|
||||
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)?lang=ru"
|
||||
return "https://yandex.ru/pogoda/?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&lang=ru"
|
||||
} else {
|
||||
return "https://yandex.ru/pogoda/?lang=ru" // Yandex will try to determine your location by default
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class ParseConfig: XCTestCase {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
guard case .none? = result?.first?.action else {
|
||||
guard result?.first?.actions.count == 0 else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
@@ -18,14 +18,29 @@ class ParseConfig: XCTestCase {
|
||||
|
||||
func testButtonKeyCodeAction() {
|
||||
let buttonKeycodeFixture = """
|
||||
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123} ]
|
||||
[ { "type": "staticButton", "title": "Pew", "actions": [ { "trigger": "singleTap", "action": "hidKey", "keycode": 123 } ] } ]
|
||||
""".data(using: .utf8)!
|
||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
guard case .hidKey(keycode: 123)? = result?.first?.action else {
|
||||
guard case .hidKey(keycode: 123)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testButtonKeyCodeLegacyAction() {
|
||||
let buttonKeycodeFixture = """
|
||||
[ { "type": "staticButton", "title": "Pew", "action": "hidKey", "keycode": 123 } ]
|
||||
""".data(using: .utf8)!
|
||||
let result = try? JSONDecoder().decode([BarItemDefinition].self, from: buttonKeycodeFixture)
|
||||
guard case .staticButton("Pew")? = result?.first?.type else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
guard case .hidKey(keycode: 123)? = result?.first?.legacyAction else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
@@ -40,7 +55,7 @@ class ParseConfig: XCTestCase {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
||||
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
@@ -55,7 +70,7 @@ class ParseConfig: XCTestCase {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
guard case .keyPress(keycode: 53)? = result?.first?.action else {
|
||||
guard case .keyPress(keycode: 53)? = result?.first?.actions.filter({ $0.trigger == .singleTap }).first?.value else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
- darkMode
|
||||
- pomodoro
|
||||
- network
|
||||
- upnext (Calendar events)
|
||||
|
||||
> Media Keys
|
||||
|
||||
@@ -116,7 +117,7 @@ You can add custom actions for two/three/four finger swipes. To do it, you need
|
||||
"type": "swipe",
|
||||
"fingers": 2, // number of fingers required (2,3 or 4)
|
||||
"direction": "right", // direction of swipe (right/left)
|
||||
"minOffset" 10, // optional: minimal required offset for gesture to emit event
|
||||
"minOffset": 10, // optional: minimal required offset for gesture to emit event
|
||||
"sourceApple": { // optional: apple script to run
|
||||
"inline": "beep"
|
||||
},
|
||||
@@ -197,10 +198,15 @@ Example of "CPU load" button which also changes color based on load value.
|
||||
"source": {
|
||||
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
|
||||
},
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"trigger": "singleTap",
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
|
||||
}
|
||||
}
|
||||
],
|
||||
"align": "right",
|
||||
"image": {
|
||||
// Or you can specify a filePath here.
|
||||
@@ -343,8 +349,46 @@ To close a group, use the button:
|
||||
},
|
||||
```
|
||||
|
||||
#### `upnext`
|
||||
|
||||
> Calender next event plugin
|
||||
Displays upcoming events from MacOS Calendar. Does not display current event.
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "upnext",
|
||||
"from": 0, // Lower bound of search range for next event in hours. Default 0 (current time)(can be negative to view events in the past)
|
||||
"to": 12, // Upper bounds of search range for next event in hours. Default 12 (12 hours in the future)
|
||||
"maxToShow": 3 // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events)
|
||||
"autoResize": false // If true, widget will expand to display all events. Default false (scrollable view within "width")
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Actions:
|
||||
|
||||
### Example:
|
||||
|
||||
```js
|
||||
"actions": [
|
||||
{
|
||||
"trigger": "singleTap",
|
||||
"action": "hidKey",
|
||||
"keycode": 53
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Triggers:
|
||||
|
||||
- `singleTap`
|
||||
- `doubleTap`
|
||||
- `tripleTap`
|
||||
- `longTap`
|
||||
|
||||
### Types
|
||||
|
||||
- `hidKey`
|
||||
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
|
||||
|
||||
@@ -386,22 +430,6 @@ To close a group, use the button:
|
||||
"url": "https://google.com",
|
||||
```
|
||||
|
||||
## LongActions
|
||||
|
||||
If you want to longPress for some operations, it is similar to the configuration for Actions but with additional parameters, for example:
|
||||
|
||||
```js
|
||||
"longAction": "hidKey",
|
||||
"longKeycode": 53,
|
||||
```
|
||||
|
||||
- longAction
|
||||
- longKeycode
|
||||
- longActionAppleScript
|
||||
- longExecutablePath
|
||||
- longShellArguments
|
||||
- longUrl
|
||||
|
||||
## Additional parameters:
|
||||
|
||||
- `width` restrict how much room a particular button will take
|
||||
@@ -446,40 +474,21 @@ by using background with color "#000000" and bordered == false you can create bu
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Roadmap
|
||||
#### If you can't open preferences:
|
||||
- Opening another program which can't edit text
|
||||
1. Open Terminal.app
|
||||
2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press <kbd>Enter</kbd>
|
||||
|
||||
|
||||
#### Buttons or gestures doesn't work:
|
||||
- "After the last update my mtmr is not working anymore!"
|
||||
- "Buttons sometimes do not trigger action"
|
||||
- "ESC don't work"
|
||||
- "Gestures don't work"
|
||||
|
||||
- [x] Create the first prototype with TouchBar in Storyboard
|
||||
- [x] Put in stripe menu on startup the application
|
||||
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
||||
- [x] Time in touchbar!
|
||||
- [x] First the weather plugin
|
||||
- [x] Find how to open full-screen TouchBar without the cross and stripe menu
|
||||
- [x] Find how to add haptic feedback
|
||||
- [x] Add icon and menu in StatusBar
|
||||
- [x] Hide from Dock
|
||||
- [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
|
||||
- [x] Layout: [always left, NSSliderView for center, always right]
|
||||
- [x] System for autoupdate (https://sparkle-project.org/)
|
||||
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
||||
- [ ] Custom settings for paddings and margins for buttons
|
||||
- [ ] XPC Service for scripts
|
||||
- [ ] UI for settings
|
||||
- [ ] Import config from BTT
|
||||
|
||||
Settings:
|
||||
|
||||
- [ ] Interface for plugins and export like presets
|
||||
- [x] Startup at login
|
||||
- [ ] Show on/off in Dock
|
||||
- [ ] Show on/off in StatusBar
|
||||
- [ x] On/off Haptic Feedback
|
||||
|
||||
Maybe:
|
||||
|
||||
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
||||
Re-tick or check a tick for access 🍏→ System Preferences → Security and Privacy → tab Privacy → Accessibility → MTMR
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -4,3 +4,38 @@
|
||||
* try move away from enums when parse preset – enums are hard to extend
|
||||
* find better way to hide bar items
|
||||
* extract bar items creating from TouchBarController to separate class, cover with tests
|
||||
|
||||
|
||||
### Roadmap
|
||||
|
||||
- [x] Create the first prototype with TouchBar in Storyboard
|
||||
- [x] Put in stripe menu on startup the application
|
||||
- [x] Find how to simulate real buttons like brightness, volume, night shift and etc.
|
||||
- [x] Time in touchbar!
|
||||
- [x] First the weather plugin
|
||||
- [x] Find how to open full-screen TouchBar without the cross and stripe menu
|
||||
- [x] Find how to add haptic feedback
|
||||
- [x] Add icon and menu in StatusBar
|
||||
- [x] Hide from Dock
|
||||
- [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
|
||||
- [x] Layout: [always left, NSSliderView for center, always right]
|
||||
- [x] System for autoupdate (https://sparkle-project.org/)
|
||||
- [ ] Overwrite default values from item types (e.g. title for brightness)
|
||||
- [ ] Custom settings for paddings and margins for buttons
|
||||
- [ ] XPC Service for scripts
|
||||
- [ ] UI for settings
|
||||
- [ ] Import config from BTT
|
||||
|
||||
Settings:
|
||||
|
||||
- [ ] Interface for plugins and export like presets
|
||||
- [x] Startup at login
|
||||
- [ ] Show on/off in Dock
|
||||
- [ ] Show on/off in StatusBar
|
||||
- [x] On/off Haptic Feedback
|
||||
|
||||
Maybe:
|
||||
|
||||
- [ ] Refactoring the application into packages (AppleScript, JavaScript? and Swift?)
|
||||
|
||||
Reference in New Issue
Block a user