1
0
mirror of https://github.com/Toxblh/MTMR.git synced 2026-01-10 00:58:37 +00:00

Implement double tap and new actions array in config

This commit is contained in:
Matteo Piccina 2020-07-29 20:52:00 +02:00
parent 87141e381b
commit 420614b7ba
3 changed files with 281 additions and 51 deletions

View File

@ -9,17 +9,28 @@
import Cocoa
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
var tapClosure: (() -> Void)?
var tapClosure: (() -> Void)? {
didSet {
actions[.singleTap] = tapClosure
}
}
var longTapClosure: (() -> Void)? {
didSet {
longClick.isEnabled = longTapClosure != nil
actions[.longTap] = longTapClosure
}
}
typealias TriggerClosure = (() -> Void)?
var actions: [Action.Trigger: TriggerClosure] = [:] {
didSet {
singleAndDoubleClick.isDoubleClickEnabled = actions[.doubleTap] != nil
longClick.isEnabled = actions[.longTap] != nil
}
}
var finishViewConfiguration: ()->() = {}
private var button: NSButton!
private var singleClick: HapticClickGestureRecognizer!
private var longClick: LongPressGestureRecognizer!
private var singleAndDoubleClick: DoubleClickGestureRecognizer!
init(identifier: NSTouchBarItem.Identifier, title: String) {
attributedTitle = title.defaultTouchbarAttributedString
@ -31,10 +42,11 @@ 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
singleAndDoubleClick = DoubleClickGestureRecognizer(target: self, action: #selector(handleGestureSingleTap), doubleAction: #selector(handleGestureDoubleTap))
singleAndDoubleClick.allowedTouchTypes = .direct
singleAndDoubleClick.delegate = self
singleAndDoubleClick.isDoubleClickEnabled = false
reinstallButton()
button.attributedTitle = attributedTitle
@ -100,33 +112,35 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
view = button
view.addGestureRecognizer(longClick)
view.addGestureRecognizer(singleClick)
// view.addGestureRecognizer(singleClick)
view.addGestureRecognizer(singleAndDoubleClick)
finishViewConfiguration()
}
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick
|| gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it
if gestureRecognizer == singleAndDoubleClick && otherGestureRecognizer == longClick
|| gestureRecognizer == longClick && otherGestureRecognizer == singleAndDoubleClick // need it
{
return false
}
return true
}
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
switch gr.state {
case .ended:
tapClosure?()
break
default:
break
}
@objc func handleGestureSingleTap() {
guard let singleTap = self.actions[.singleTap] else { return }
singleTap?()
}
@objc func handleGestureDoubleTap() {
guard let doubleTap = self.actions[.doubleTap] else { return }
doubleTap?()
}
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
switch gr.state {
case .possible: // tiny hack because we're calling action manually
(self.longTapClosure ?? self.tapClosure)?()
guard let longTap = self.actions[.longTap] else { return }
longTap?()
break
default:
break
@ -176,15 +190,62 @@ class CustomButtonCell: NSButtonCell {
}
}
class HapticClickGestureRecognizer: NSClickGestureRecognizer {
// Thanks to https://stackoverflow.com/a/49843893
final class DoubleClickGestureRecognizer: NSClickGestureRecognizer {
private let _action: Selector
private let _doubleAction: Selector
private var _clickCount: Int = 0
public var isDoubleClickEnabled = 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) {
_action = action
_doubleAction = doubleAction
super.init(target: target, action: nil)
}
required init?(coder: NSCoder) {
fatalError("init(target:action:doubleAction) 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
guard isDoubleClickEnabled else {
_ = target?.perform(_action)
return
}
let delayThreshold = 0.20 // fine tune this as needed
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
if _clickCount == 2 {
_ = target?.perform(_doubleAction)
}
}
@objc private func _resetAndPerformActionIfNecessary() {
if _clickCount == 1 {
_ = target?.perform(_action)
}
_clickCount = 0
}
}

View File

@ -3,7 +3,7 @@ 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)!)
}
}
@ -51,25 +51,31 @@ class SupportedTypesHolder {
private var supportedTypes: [String: ParametersDecoder] = [
"escape": { _ in (
item: .staticButton(title: "esc"),
action: .keyPress(keycode: 53),
action: .none,
longAction: .none,
parameters: [.align: .align(.left)]
parameters: [.align: .align(.left), .actions: .actions([
Action(trigger: .singleTap, value: .keyPress(keycode: 53))
])]
) },
"delete": { _ in (
item: .staticButton(title: "del"),
action: .keyPress(keycode: 117),
action: .none,
longAction: .none,
parameters: [:]
parameters: [.actions: .actions([
Action(trigger: .singleTap, value: .keyPress(keycode: 117))
])]
) },
"brightnessUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
return (
item: .staticButton(title: ""),
action: .keyPress(keycode: 144),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .keyPress(keycode: 144))
])]
)
},
@ -77,9 +83,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
return (
item: .staticButton(title: ""),
action: .keyPress(keycode: 145),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .keyPress(keycode: 145))
])]
)
},
@ -87,9 +95,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_up"))
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_UP))
])]
)
},
@ -97,9 +107,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "ill_down"))
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_ILLUMINATION_DOWN))
])]
)
},
@ -107,9 +119,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeDownTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN))
])]
)
},
@ -117,9 +131,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarVolumeUpTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_SOUND_UP))
])]
)
},
@ -127,9 +143,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarAudioOutputMuteTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_MUTE),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_MUTE))
])]
)
},
@ -137,9 +155,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarRewindTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PREVIOUS))
])]
)
},
@ -147,9 +167,11 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarPlayPauseTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_PLAY),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_PLAY))
])]
)
},
@ -157,24 +179,30 @@ class SupportedTypesHolder {
let imageParameter = GeneralParameter.image(source: NSImage(named: NSImage.touchBarFastForwardTemplateName)!)
return (
item: .staticButton(title: ""),
action: .hidKey(keycode: NX_KEYTYPE_NEXT),
action: .none,
longAction: .none,
parameters: [.image: imageParameter]
parameters: [.image: imageParameter, .actions: .actions([
Action(trigger: .singleTap, value: .hidKey(keycode: NX_KEYTYPE_NEXT))
])]
)
},
"sleep": { _ in (
item: .staticButton(title: "☕️"),
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]),
action: .none,
longAction: .none,
parameters: [:]
parameters: [.actions: .actions([
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]))
])]
) },
"displaySleep": { _ in (
item: .staticButton(title: "☕️"),
action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]),
action: .none,
longAction: .none,
parameters: [:]
parameters: [.actions: .actions([
Action(trigger: .singleTap, value: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]))
])]
) },
]
@ -393,6 +421,92 @@ enum ItemType: 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 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 ActionType: Decodable {
case none
case hidKey(keycode: Int32)
@ -516,6 +630,7 @@ enum GeneralParameter {
case bordered(_: Bool)
case background(_: NSColor)
case title(_: String)
case actions(_: [Action])
}
struct GeneralParameters: Decodable {
@ -528,6 +643,7 @@ struct GeneralParameters: Decodable {
case bordered
case background
case title
case actions
}
init(from decoder: Decoder) throws {
@ -556,6 +672,10 @@ struct GeneralParameters: Decodable {
if let title = try container.decodeIfPresent(String.self, forKey: .title) {
result[.title] = .title(title)
}
if let actions = try container.decodeIfPresent([Action].self, forKey: .actions) {
result[.actions] = .actions(actions)//.compactMap { $0.base })
}
parameters = result
}

View File

@ -313,6 +313,11 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
if let longAction = self.longAction(forItem: item), let item = barItem as? CustomButtonTouchBarItem {
item.longTapClosure = longAction
}
if case let .actions(actions)? = item.additionalParameters[.actions], let item = barItem as? CustomButtonTouchBarItem {
for action in actions {
item.actions[action.trigger] = self.action(for: action)
}
}
if case let .bordered(bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
item.isBordered = bordered
}
@ -334,6 +339,50 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
}
return barItem
}
func action(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 {