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 00000000..e239d79b Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_1x.png differ 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 00000000..2df83a0c Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_3x.png b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_3x.png new file mode 100644 index 00000000..4cc0f548 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/record.imageset/baseline_radio_button_checked_black_20pt_3x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/stop.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/stop.imageset/Contents.json new file mode 100644 index 00000000..576aa1ce --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/stop.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "baseline_stop_circle_black_20pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "baseline_stop_circle_black_20pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "baseline_stop_circle_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/stop.imageset/baseline_stop_circle_black_20pt_1x.png b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_1x.png new file mode 100644 index 00000000..f20c32e2 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_1x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_2x.png b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_2x.png new file mode 100644 index 00000000..9245d5c2 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_2x.png differ diff --git a/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_3x.png b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_3x.png new file mode 100644 index 00000000..9ac03dcc Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/stop.imageset/baseline_stop_circle_black_20pt_3x.png differ 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 + ]) + } }