-
-
Notifications
You must be signed in to change notification settings - Fork 191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add as SwiftUI .keyboardShortcut()
helper
#101
Comments
For anyone interested I needed it because I wanted to display shortcuts in my menu bar items so I implemented it manually thanks to this answer on StackOverflow https://stackoverflow.com/a/35138823. Maybe there's a cleaner way to implement the View extension and import KeyboardShortcuts
import SwiftUI
import Carbon
extension View {
public func keyboardShortcut(_ shortcut: KeyboardShortcuts.Name) -> some View {
if let shortcut = shortcut.shortcut {
if let keyEquivalent = shortcut.toKeyEquivalent() {
return AnyView(self.keyboardShortcut(keyEquivalent, modifiers: shortcut.toEventModifiers()))
}
}
return AnyView(self)
}
}
extension KeyboardShortcuts.Shortcut {
func toKeyEquivalent() -> KeyEquivalent? {
let carbonKeyCode = UInt16(self.carbonKeyCode)
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<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data
let osStatus = layoutData.withUnsafeBytes {
UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, carbonKeyCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
}
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", carbonKeyCode, osStatus);
return nil
}
return KeyEquivalent(Character(String(utf16CodeUnits: nameBuffer, count: nameLength)))
}
func toEventModifiers() -> SwiftUI.EventModifiers {
var modifiers: SwiftUI.EventModifiers = []
if self.modifiers.contains(NSEvent.ModifierFlags.command) {
modifiers.update(with: EventModifiers.command)
}
if self.modifiers.contains(NSEvent.ModifierFlags.control) {
modifiers.update(with: EventModifiers.control)
}
if self.modifiers.contains(NSEvent.ModifierFlags.option) {
modifiers.update(with: EventModifiers.option)
}
if self.modifiers.contains(NSEvent.ModifierFlags.shift) {
modifiers.update(with: EventModifiers.shift)
}
if self.modifiers.contains(NSEvent.ModifierFlags.capsLock) {
modifiers.update(with: EventModifiers.capsLock)
}
if self.modifiers.contains(NSEvent.ModifierFlags.numericPad) {
modifiers.update(with: EventModifiers.numericPad)
}
return modifiers
}
} Example implementation : struct SomeView: View {
var body: some View {
return Button("Shortcut") {
print("clicked")
}.keyboardShortcut(KeyboardShortcuts.Name("..."))
}
} |
Use solution from sindresorhus#101 (comment) The old solution creates thousands associatedShortcut, it isn't good
Thanks, @mbenoukaiss! 🙏 How would you extend this so the menu bar item is updated dynamically? You currently have to restart the app for changes to take effect. |
This is a necessity when using this package with |
I agree, this is exactly where I'm at as well |
i found a short fix, you could notify the user, whenever a change in the keyboard-shortcut is made the app would restart, and you could programatically restart like this:- func relaunch(afterDelay seconds: TimeInterval = 0.5) -> Never {
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "sleep \(seconds); open \"\(Bundle.main.bundlePath)\""]
task.launch()
NSApp.terminate(self)
exit(0)
} |
Use solution from sindresorhus#101 (comment) The old solution creates thousands associatedShortcut, it isn't good
A little late to the party, but I've just raised a PR that solves this (same idea, different SwiftUI wrapper, which auto updates based on changes to the Shortcut state) see: #181 |
For anyone who needs it quickly! I've forked this project because I wanted this change this weekend 🙂 -> https://github.com/aueangpanit/KeyboardShortcuts Example usage: In case it's helpful, all of the changes for this feature in this file: https://github.com/aueangpanit/KeyboardShortcuts/blob/main/Sources/KeyboardShortcuts/View%2B%2B.swift Hopefully, we have it in the main project soon! ❤️ |
Hi @sindresorhus , thank you so much for providing a fix here. Just want to double check. This is how I use the MenuBarExtra right now. But, when I change the shortcut for that it is not reflected in the Button label. It works as expected, but the label still shows the previous shortcut until I restart the app. Is there anything that I have to do in addition to make this work?
|
I just tested this on my app and the key shortcut changes are reflected instantly. No need for restart. My code looks the same as yours. I am using macOS 15.1. Btw, this feature made this library perfect. Thanks, @sindresorhus. |
@rampatra thanks for checking. Just to double check: On which macOS are you? |
I have just checked on my macOS 13 and macOS 15 virtual machines. No issue there. My dev env is still on macOS 14. I guess this is some set-up related issue on my side. |
#69
The text was updated successfully, but these errors were encountered: