From 58ad6c568b1c2cdf0a86d0befb26f8b844df0816 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Mon, 20 Jan 2025 17:04:55 +0100 Subject: [PATCH] feat: added feature to set keyboard shortcut to open/close popup window (#1976) --- Kit/extensions.swift | 152 +++++++++++++++++- Kit/module/module.swift | 6 +- Kit/module/popup.swift | 22 +++ Kit/plugins/Store.swift | 8 + Modules/Battery/popup.swift | 13 +- Modules/Bluetooth/popup.swift | 2 +- Modules/CPU/popup.swift | 15 +- Modules/Clock/popup.swift | 13 +- Modules/Disk/popup.swift | 13 +- Modules/GPU/config.plist | 2 +- Modules/GPU/popup.swift | 17 +- Modules/Net/popup.swift | 13 +- Modules/RAM/popup.swift | 13 +- Modules/Sensors/popup.swift | 9 +- Stats/AppDelegate.swift | 7 + .../record.imageset/Contents.json | 26 +++ ...ine_radio_button_checked_black_20pt_1x.png | Bin 0 -> 233 bytes ...ine_radio_button_checked_black_20pt_2x.png | Bin 0 -> 450 bytes ...ine_radio_button_checked_black_20pt_3x.png | Bin 0 -> 636 bytes .../stop.imageset/Contents.json | 26 +++ .../baseline_stop_circle_black_20pt_1x.png | Bin 0 -> 153 bytes .../baseline_stop_circle_black_20pt_2x.png | Bin 0 -> 248 bytes .../baseline_stop_circle_black_20pt_3x.png | Bin 0 -> 343 bytes Stats/Views/CombinedView.swift | 7 + Stats/helpers.swift | 21 +++ 25 files changed, 346 insertions(+), 39 deletions(-) create mode 100644 Stats/Supporting Files/Assets.xcassets/record.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_3x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/stop.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_3x.png diff --git a/Kit/extensions.swift b/Kit/extensions.swift index 13b0dbcf..4e331a1c 100644 --- a/Kit/extensions.swift +++ b/Kit/extensions.swift @@ -10,6 +10,7 @@ // import Cocoa +import Carbon extension String: @retroactive LocalizedError { public var errorDescription: String? { return self } @@ -312,9 +313,9 @@ public extension NSView { return s } - func buttonIconView(_ action: Selector, icon: NSImage) -> NSButton { + func buttonIconView(_ action: Selector, icon: NSImage, height: CGFloat = 22) -> NSButton { let button = NSButton() - button.heightAnchor.constraint(equalToConstant: 22).isActive = true + button.heightAnchor.constraint(equalToConstant: height).isActive = true button.bezelStyle = .regularSquare button.translatesAutoresizingMaskIntoConstraints = false button.imageScaling = .scaleNone @@ -564,3 +565,150 @@ extension CGFloat { return ceil(self / 10) * 10 } } + +public class KeyboardShartcutView: NSStackView { + private let callback: (_ value: [UInt16]) -> Void + + private var startIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "record.circle", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("record"))! + } + private var stopIcon: NSImage { + if #available(macOS 12.0, *), let icon = iconFromSymbol(name: "stop.circle.fill", scale: .large) { + return icon + } + return NSImage(named: NSImage.Name("stop"))! + } + + private var valueField: NSTextField? = nil + private var startButton: NSButton? = nil + private var stopButton: NSButton? = nil + + private var recording: Bool = false + private var keyCodes: [UInt16] = [] + private var value: [UInt16] = [] + private var interaction: Bool = false + + public init(callback: @escaping (_ value: [UInt16]) -> Void, value: [UInt16]) { + self.callback = callback + self.value = value + + super.init(frame: NSRect.zero) + self.orientation = .horizontal + + let stringValue = value.isEmpty ? localizedString("Disabled") : self.parseValue(value) + let valueField: NSTextField = LabelField(stringValue) + valueField.font = NSFont.systemFont(ofSize: 13, weight: .regular) + valueField.textColor = .textColor + valueField.alignment = .center + + let startButton = buttonIconView(#selector(self.startListening), icon: self.startIcon, height: 15) + let stopButton = buttonIconView(#selector(self.stopListening), icon: self.stopIcon, height: 15) + + self.addArrangedSubview(valueField) + self.addArrangedSubview(startButton) + + self.valueField = valueField + self.startButton = startButton + self.stopButton = stopButton + + NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .flagsChanged]) { [weak self] event in + self?.handleKeyEvent(event) + return event + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func startListening() { + guard AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary) else { return } + if let btn = self.stopButton { + self.startButton?.removeFromSuperview() + self.addArrangedSubview(btn) + } + self.valueField?.stringValue = localizedString("Listening...") + self.keyCodes = [] + self.recording = true + } + + @objc private func stopListening() { + if let btn = self.startButton { + self.stopButton?.removeFromSuperview() + self.addArrangedSubview(btn) + } + + if self.keyCodes.isEmpty && !self.interaction { + self.value = [] + self.valueField?.stringValue = localizedString("Disabled") + } + + self.recording = false + self.interaction = false + self.callback(self.value) + } + + private func handleKeyEvent(_ event: NSEvent) { + guard self.recording else { return } + self.interaction = true + + if event.type == .flagsChanged { + self.keyCodes = [] + if event.modifierFlags.contains(.control) { self.keyCodes.append(59) } + if event.modifierFlags.contains(.shift) { self.keyCodes.append(60) } + if event.modifierFlags.contains(.command) { self.keyCodes.append(55) } + if event.modifierFlags.contains(.option) { self.keyCodes.append(58) } + } else if event.type == .keyDown { + self.keyCodes.append(event.keyCode) + self.value = self.keyCodes + } + + let list = self.keyCodes.isEmpty ? self.value : self.keyCodes + self.valueField?.stringValue = self.parseValue(list) + } + + private func parseValue(_ list: [UInt16]) -> String { + return list.compactMap { self.keyName(virtualKeyCode: $0) }.joined(separator: " + ") + } + + private func keyName(virtualKeyCode: UInt16) -> String? { + if virtualKeyCode == 59 { + return "Control" + } else if virtualKeyCode == 60 { + return "Shift" + } else if virtualKeyCode == 55 { + return "Command" + } else if virtualKeyCode == 58 { + return "Option" + } + + let maxNameLength = 4 + var nameBuffer = [UniChar](repeating: 0, count: maxNameLength) + var nameLength = 0 + + let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock + var deadKeys: UInt32 = 0 + let keyboardType = UInt32(LMGetKbdType()) + + let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue() + guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else { + NSLog("Could not get keyboard layout data") + return nil + } + let layoutData = Unmanaged.fromOpaque(ptr).takeUnretainedValue() as Data + let osStatus = layoutData.withUnsafeBytes { + UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, virtualKeyCode, UInt16(kUCKeyActionDown), + modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask), + &deadKeys, maxNameLength, &nameLength, &nameBuffer) + } + guard osStatus == noErr else { + NSLog("Code: 0x%04X Status: %+i", virtualKeyCode, osStatus) + return nil + } + + return String(utf16CodeUnits: nameBuffer, count: nameLength) + } +} diff --git a/Kit/module/module.swift b/Kit/module/module.swift index 615874a5..13fa2111 100644 --- a/Kit/module/module.swift +++ b/Kit/module/module.swift @@ -78,10 +78,14 @@ open class Module { } public var combinedPosition: Int { get { Store.shared.int(key: "\(self.name)_position", defaultValue: 0) } - set { Store.shared.set(key: "\(self.name)_position", value: newValue) } + set { Store.shared.set(key: "\(self.name)_position", value: newValue) } } public var userDefaults: UserDefaults? = UserDefaults(suiteName: "\(Bundle.main.object(forInfoDictionaryKey: "TeamId") as! String).eu.exelban.Stats.widgets") + public var popupKeyboardShortcut: [UInt16] { + return self.popupView?.keyboardShortcut ?? [] + } + private var moduleType: ModuleType private var settingsView: Settings_v? = nil diff --git a/Kit/module/popup.swift b/Kit/module/popup.swift index 86dda9bc..a3167246 100644 --- a/Kit/module/popup.swift +++ b/Kit/module/popup.swift @@ -12,18 +12,40 @@ import Cocoa public protocol Popup_p: NSView { + var keyboardShortcut: [UInt16] { get } var sizeCallback: ((NSSize) -> Void)? { get set } + func settings() -> NSView? func appear() func disappear() + func setKeyboardShortcut(_ binding: [UInt16]) } open class PopupWrapper: NSStackView, Popup_p { + public var title: String + public var keyboardShortcut: [UInt16] = [] open var sizeCallback: ((NSSize) -> Void)? = nil + + public init(_ typ: ModuleType, frame: NSRect) { + self.title = typ.rawValue + self.keyboardShortcut = Store.shared.array(key: "\(typ.rawValue)_popup_keyboardShortcut", defaultValue: []) as? [UInt16] ?? [] + + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + open func settings() -> NSView? { return nil } open func appear() {} open func disappear() {} + + open func setKeyboardShortcut(_ binding: [UInt16]) { + self.keyboardShortcut = binding + Store.shared.set(key: "\(self.title)_popup_keyboardShortcut", value: binding) + } } public class PopupWindow: NSWindow, NSWindowDelegate { diff --git a/Kit/plugins/Store.swift b/Kit/plugins/Store.swift index 38a0029f..d7f86258 100644 --- a/Kit/plugins/Store.swift +++ b/Kit/plugins/Store.swift @@ -37,6 +37,10 @@ public class Store { return (!self.exist(key: key) ? value : defaults.integer(forKey: key)) } + public func array(key: String, defaultValue value: [Any]) -> [Any] { + return (!self.exist(key: key) ? value : defaults.array(forKey: key)!) + } + public func data(key: String) -> Data? { return defaults.data(forKey: key) } @@ -57,6 +61,10 @@ public class Store { self.defaults.set(value, forKey: key) } + public func set(key: String, value: [Any]) { + self.defaults.set(value, forKey: key) + } + public func reset() { self.defaults.dictionaryRepresentation().keys.forEach { key in self.defaults.removeObject(forKey: key) diff --git a/Modules/Battery/popup.swift b/Modules/Battery/popup.swift index 4849b7e5..f556081d 100644 --- a/Modules/Battery/popup.swift +++ b/Modules/Battery/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private var grid: NSGridView? = nil private let dashboardHeight: CGFloat = 90 @@ -65,9 +63,7 @@ internal class Popup: PopupWrapper { } public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect( + super.init(module, frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, @@ -337,6 +333,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Colorize battery"), component: switchView( action: #selector(self.toggleColor), diff --git a/Modules/Bluetooth/popup.swift b/Modules/Bluetooth/popup.swift index dec00375..b043a575 100644 --- a/Modules/Bluetooth/popup.swift +++ b/Modules/Bluetooth/popup.swift @@ -16,7 +16,7 @@ internal class Popup: PopupWrapper { private let emptyView: EmptyView = EmptyView(height: 30, isHidden: false, msg: localizedString("No Bluetooth devices are available")) public init() { - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 30)) + super.init(ModuleType.bluetooth, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 30)) self.orientation = .vertical self.spacing = Constants.Popup.margins diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift index 216b06d5..8aa22517 100644 --- a/Modules/CPU/popup.swift +++ b/Modules/CPU/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private let dashboardHeight: CGFloat = 90 private let chartHeight: CGFloat = 120 + Constants.Popup.separatorHeight private var detailsHeight: CGFloat { @@ -125,14 +123,10 @@ internal class Popup: PopupWrapper { } public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(module, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.spacing = 0 self.orientation = .vertical -// self.setAccessibilityElement(true) -// self.toolTip = self.title self.systemColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_systemColor", defaultValue: self.systemColorState.key)) self.userColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_userColor", defaultValue: self.userColorState.key)) @@ -546,6 +540,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("System color"), component: selectView( action: #selector(self.toggleSystemColor), diff --git a/Modules/Clock/popup.swift b/Modules/Clock/popup.swift index c3624c18..df573d68 100644 --- a/Modules/Clock/popup.swift +++ b/Modules/Clock/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private let orderTableView: OrderTableView = OrderTableView() private var list: [Clock_t] = [] @@ -22,9 +20,7 @@ internal class Popup: PopupWrapper { private var calendarState: Bool = true public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(module, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.orientation = .vertical self.spacing = Constants.Popup.margins @@ -87,6 +83,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Calendar"), component: switchView( action: #selector(self.toggleCalendarState), diff --git a/Modules/Disk/popup.swift b/Modules/Disk/popup.swift index 5175203b..8258262c 100644 --- a/Modules/Disk/popup.swift +++ b/Modules/Disk/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private var readColorState: SColor = .secondBlue private var readColor: NSColor { self.readColorState.additional as? NSColor ?? NSColor.systemRed } private var writeColorState: SColor = .secondRed @@ -43,9 +41,7 @@ internal class Popup: PopupWrapper { private var lastList: [String] = [] public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(module, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.readColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_readColor", defaultValue: self.readColorState.key)) self.writeColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_writeColor", defaultValue: self.writeColorState.key)) @@ -193,6 +189,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Write color"), component: selectView( action: #selector(self.toggleWriteColor), diff --git a/Modules/GPU/config.plist b/Modules/GPU/config.plist index 18dbe1dc..03418b57 100644 --- a/Modules/GPU/config.plist +++ b/Modules/GPU/config.plist @@ -76,7 +76,7 @@ Settings popup - + notifications diff --git a/Modules/GPU/popup.swift b/Modules/GPU/popup.swift index d9f36785..ae274fcc 100644 --- a/Modules/GPU/popup.swift +++ b/Modules/GPU/popup.swift @@ -14,7 +14,7 @@ import Kit internal class Popup: PopupWrapper { public init() { - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(ModuleType.GPU, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.orientation = .vertical self.spacing = Constants.Popup.margins @@ -51,6 +51,21 @@ internal class Popup: PopupWrapper { self.sizeCallback?(self.frame.size) } } + + // MARK: - Settings + + public override func settings() -> NSView? { + let view = SettingsContainerView() + + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + + return view + } } private class GPUView: NSStackView { diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift index 5b848f7e..15b9aa8a 100644 --- a/Modules/Net/popup.swift +++ b/Modules/Net/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private var uploadContainerView: NSView? = nil private var uploadView: NSView? = nil private var uploadValue: Int64 = 0 @@ -105,9 +103,7 @@ internal class Popup: PopupWrapper { } public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(module, frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.spacing = 0 self.orientation = .vertical @@ -563,6 +559,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Color of download"), component: selectView( action: #selector(self.toggleDownloadColor), diff --git a/Modules/RAM/popup.swift b/Modules/RAM/popup.swift index 0c25c8c5..d55601b2 100644 --- a/Modules/RAM/popup.swift +++ b/Modules/RAM/popup.swift @@ -13,8 +13,6 @@ import Cocoa import Kit internal class Popup: PopupWrapper { - private var title: String - private var grid: NSGridView? = nil private let dashboardHeight: CGFloat = 90 @@ -69,9 +67,7 @@ internal class Popup: PopupWrapper { private var chartColor: NSColor { self.chartColorState.additional as? NSColor ?? NSColor.systemBlue } public init(_ module: ModuleType) { - self.title = module.rawValue - - super.init(frame: NSRect( + super.init(module, frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, @@ -289,6 +285,13 @@ internal class Popup: PopupWrapper { public override func settings() -> NSView? { let view = SettingsContainerView() + view.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + view.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("App color"), component: selectView( action: #selector(toggleAppColor), diff --git a/Modules/Sensors/popup.swift b/Modules/Sensors/popup.swift index 108730a8..862e9c0b 100644 --- a/Modules/Sensors/popup.swift +++ b/Modules/Sensors/popup.swift @@ -46,7 +46,7 @@ internal class Popup: PopupWrapper { } public init() { - super.init(frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, height: 0)) + super.init(ModuleType.sensors, frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.fanValueState = FanValue(rawValue: Store.shared.string(key: "Sensors_popup_fanValue", defaultValue: self.fanValueState.rawValue)) ?? .percentage @@ -57,6 +57,13 @@ internal class Popup: PopupWrapper { self.settingsView.orientation = .vertical self.settingsView.spacing = Constants.Settings.margin + self.settingsView.addArrangedSubview(PreferencesSection([ + PreferencesRow(localizedString("Keyboard shortcut"), component: KeyboardShartcutView( + callback: self.setKeyboardShortcut, + value: self.keyboardShortcut + )) + ])) + self.settingsView.addArrangedSubview(PreferencesSection([ PreferencesRow(localizedString("Fan value"), component: selectView( action: #selector(self.toggleFanValue), diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index d89ab2de..d8e10b48 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -74,6 +74,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele self.icon() NotificationCenter.default.addObserver(self, selector: #selector(listenForAppPause), name: .pause, object: nil) + NSEvent.addGlobalMonitorForEvents(matching: [.keyDown, .flagsChanged]) { [weak self] event in + self?.handleKeyEvent(event) + } + NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .flagsChanged]) { [weak self] event in + self?.handleKeyEvent(event) + return event + } info("Stats started in \((startingPoint.timeIntervalSinceNow * -1).rounded(toPlaces: 4)) seconds") self.startTS = Date() diff --git a/Stats/Supporting Files/Assets.xcassets/record.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/record.imageset/Contents.json new file mode 100644 index 00000000..69a4a6ba --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/record.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "baseline_radio_button_checked_black_20pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "baseline_radio_button_checked_black_20pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "baseline_radio_button_checked_black_20pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_1x.png b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..e239d79b9ac628a3506079d54ded8a8fa393eca3 GIT binary patch literal 233 zcmVl$vV?5@9Z}}AAd(}Wu^Xw zly*k($k9&u4O^Oo!QR^1-XKhtpV-++r${e_&dK>5txS%ouQEy2jwaO$+zK6N^g`%0 zpcA$)kvRIYE}RfUbvW`eV&XAd)bJsAt1 jeZmY=WY{djxIg0t&m@9@>&Qg800000NkvXXu0mjf#8O@S literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_2x.png b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2df83a0c603b15e5d1dabe3a64c5be81cfca470a GIT binary patch literal 450 zcmV;z0X_bSP)NE)K76WRw0yGSJXa- zec=TGn0n_qe?hpXlL4Y8-l;lxiQ3%<9=K zDZ~z3V5w)BeJz_Rkx%CBugTym_4Jjbt^|t^u1u3AgWe$|#P3yG5G%W*{A9B0s!mF9?FGBl(Eg zJ!YBLIu}8>Jn!*_l0IxajNN)e!|L})BM9sw2xlWjhgjgjNW&RH=mvoU1Yx7UOR+dX zC~55mK^O#qV+3KQzgusCEv=m)2xH5@#Fw0SQWCdK;>J#V>sdG)5Pg1`xK188uCM1zHB zkPyLyD(m%n?RX7nD6-pm-t6=K%-h+4S(~+g($=WcW`NHVW5zt;GoVeKwZEcV=aPHI z8U3D1)_<3Dn?7MC>77>6XI?r z9t*jB7w8$Yc3AK$&2hwu^ERC@(nh$-f084oH}(r1%zfUpQEK_(HVU8*s)b{s%7+$Q zIpVsb{po26W{zEsIp>^Xc9}~-ooXX>rqS+bD%G_jw(yLT8{n~J#db9{+sP*GE9f(e z3F{(Wie`s+R{XO<^s{I;_*B3~!n%o{DU&{%37>|tezK8He4(jSIYIk6RiR%K)MZW0 zF4Bqb6sVb4G(Xqn;3pQmscd)Y*+sN0tA@?XeJ^>2=c=}SDOpNgQ&=+*2y+vMi9{e$ z{komFtpKgU^R>;nr7Ueb@vQR5IMN&jeM7pz@PS&AtGl@T2^N8Wi)vyFn4$euR8SELUf*c z=nu~2hx}8tRBmN0vUM)`ARzld#YK^`v*{cILk&+!@6KMSFF?B(JYD@<);T3K0RRvL BG*Fy*5Nn}%;aN6io9*!M&;)xPZ%gn~c zOpB+?#~LQ&U`#aWm}5A^-ebU}a1~3C6NH5-p4%@uLD=I+7qPm}Dla!)&_{EE&{KrL zI*Aj6It-3Klh5LN&KOQ(Nd_g9>OKqf=U|rwcCjZI3kFVc%>vgOLG>-GUy6F#s3*4i y(=#Xz>g7wlz#6mwgC=FrMhzOh$yelwe~A~FOwwE_{fzVg0000LdXy!2-J4?g;|WDFV7jY-b3iAVf6#VGqzU_2#Yl_#NQS^XB$1d7kHa zp64Yf@Is9@ddOFgH)_04AVGBHs4zo*G^0We^WD)vlr*@*Tq#Oy5M>)mq%cv27ILx{ z8G`n}0J+G3N64`Y2nIi)Rvp3M8ftYB4DO)T5W(O9 zYON6rUVX4XY5%nREPdLw10nzb0D^)2TMHlo_e;T;3Lc`R@}M3P027Lb#G`4CM7*iR pm}V|Dh`T&EwYV)grU3u|6aoEZAOTp|k{JL1002ovPDHLkV1hQoh+qH! literal 0 HcmV?d00001 diff --git a/Stats/Views/CombinedView.swift b/Stats/Views/CombinedView.swift index 56737248..a48e3dce 100644 --- a/Stats/Views/CombinedView.swift +++ b/Stats/Views/CombinedView.swift @@ -208,9 +208,12 @@ internal class CombinedView: NSObject, NSGestureRecognizerDelegate { } private class Popup: NSStackView, Popup_p { + fileprivate var keyboardShortcut: [UInt16] = [] fileprivate var sizeCallback: ((NSSize) -> Void)? = nil init() { + self.keyboardShortcut = Store.shared.array(key: "CombinedModules_popup_keyboardShortcut", defaultValue: []) as? [UInt16] ?? [] + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) self.orientation = .vertical @@ -234,6 +237,10 @@ private class Popup: NSStackView, Popup_p { fileprivate func settings() -> NSView? { return nil } fileprivate func appear() {} fileprivate func disappear() {} + fileprivate func setKeyboardShortcut(_ binding: [UInt16]) { + self.keyboardShortcut = binding + Store.shared.set(key: "CombinedModules_popup_keyboardShortcut", value: binding) + } @objc private func reinit() { self.subviews.forEach({ $0.removeFromSuperview() }) diff --git a/Stats/helpers.swift b/Stats/helpers.swift index ea3b3515..6e874b69 100644 --- a/Stats/helpers.swift +++ b/Stats/helpers.swift @@ -265,4 +265,25 @@ extension AppDelegate { @objc internal func openSettings() { NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": "Dashboard"]) } + + internal func handleKeyEvent(_ event: NSEvent) { + var keyCodes: [UInt16] = [] + if event.modifierFlags.contains(.control) { keyCodes.append(59) } + if event.modifierFlags.contains(.shift) { keyCodes.append(60) } + if event.modifierFlags.contains(.command) { keyCodes.append(55) } + if event.modifierFlags.contains(.option) { keyCodes.append(58) } + keyCodes.append(event.keyCode) + + guard !keyCodes.isEmpty, + let module = modules.first(where: { $0.enabled && $0.popupKeyboardShortcut == keyCodes }), + let widget = module.menuBar.widgets.filter({ $0.isActive }).first, + let window = widget.item.window else { return } + + NotificationCenter.default.post(name: .togglePopup, object: nil, userInfo: [ + "module": module.name, + "widget": widget.type, + "origin": window.frame.origin, + "center": window.frame.width/2 + ]) + } }