I'm using a local monitor to observe key events. However, I get an incorrect keycode when the modifiers ⌥ and ⌘ are active and the key with code 42 is pressed simultaneously. The keyCode in the event is 8, which is C on my keyboard. The event monitor is used within a subclass of NSTextField to intercept key events.
This is the only case where I get an incorrect keycode so far. I also tried using a CGEventTap to reproduce this, but the keyCode is always correct using a CGEventTap.
Has anybody ever experienced this? Is this a known bug?
import Foundation
import Cocoa
class FooTextField : NSTextField {
func initMonitor() {
NSEvent.addLocalMonitorForEvents(matching: [.keyUp, .keyDown, .flagsChanged], handler: onKeyEvent)
}
private func onKeyEvent(_ e: NSEvent) -> NSEvent? {
//prints 8 when key with keycode 42 is pressed and (⌥,⌘) are active.
//the textfield seems to receives the correct keycode since a # appears for German keyboards
print(e.keyCode)
return e
}
}
Related
I have an NSTextField in my window and 4 menu items with key equivalents ←↑→↓.
When the text field is selected and I press an arrow key, I would expect the cursor to move in the text field but instead the corresponding menu item action is performed.
So there has to be an issue in the responder chain. To figure out what's wrong I've watched WWDC 2010 Session 145 – Key Event Handling in Cocoa Applications mentioned in this NSMenuItem KeyEquivalent space " " bug thread.
The event flow for keys (hotkeys) is shown in the session as follows:
So I checked the call stack with a menu item which has keyEquivalent = K (just any normal key) and for a menu item which has keyEquivalent = → (right arrow key)
First: K key event call stack; Second: Right arrow key event call stack
So when pressing an arrow key, the event is sent directly to mainMenu.performKeyEquivalent, but it should actually be sent to the keyWindow right?
Why is that and how can I fix this behavior so that my NSTextField receives the arrow key events before the mainMenu does?
Interesting observation about the call stack difference. Since arrow keys play the most important role in navigation they are probably handled differently from the rest of keys, like you saw in the NSMenuItem KeyEquivalent space " " bug thread. Again, it's one of those cases when AppKit takes care of everything behind the scenes to make your life easier in 99.9% situations.
You can see the actual difference in behaviour by pressing k while textfield has the focus. Unlike with arrows, the menu item's key equivalent doesn't get triggered and input goes directly into the control.
For your situation you can use NSMenuItemValidation protocol to override the default action of enabling or disabling a specific menu item. AFAIK this can go into any responder in a chain, e.g., view controller, window, or application. So, you can enable/disable your menu items in a single place when the window's first responder is a textfield or any other control that uses these events to properly operate.
extension ViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
// Filter menu item by it's assigned action, just as an exampe.
if menuItem.action != #selector(ViewController.menuActionLeftArrowKey(_:)) { return true }
Swift.print("Validating menu item:", menuItem)
// Disable the menu item if first responder is text view.
let isTextView = self.view.window?.firstResponder is NSTextView
return !isTextView
}
}
This will get invoked prior displaying the menu in order to update item state, prior invoking menu item key equivalent in order to check if action needs sending or not, and probably in other cases when AppKit needs to check the item's state – can't think of any from the top of my head.
P.S. Above the first responder check is done against NSTextView not NSTextField, here's why.
This is the solution I've chosen, which resulted from the comments from #Willeke.
I've created a subclass of NSWindow and overridden the keyDown(with:) method. Every Window in my application (currently 2) subclass this new NavigationWindow, so that you can use the arrow keys in every window.
class NavigationWindow: NSWindow {
override func keyDown(with event: NSEvent) {
if event.keyCode == 123 || event.keyCode == 126 || event.specialKey == NSEvent.SpecialKey.pageUp {
print("navigate back")
} else if event.keyCode == 124 || event.keyCode == 125 || event.specialKey == NSEvent.SpecialKey.pageDown {
print("navigate forward")
} else {
super.keyDown(with: event)
}
}
}
This implementation registers all four arrow keys plus the page up and down keys for navigation.
These are the key codes
123: right arrow
124: left arrow
125: down arrow
126: up arrow
I am reading the keyboard shortcuts typed inside a NSTextField.
When I press, for example, shift + option + F2, this method
class func convertToString(event:NSEvent) -> String {
var shortcut = ""
let intersection = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
if intersection.contains(.control) {
shortcut.append("^ ")
}
if intersection.contains(.option) {
shortcut.append("⌥ ")
}
if intersection.contains(.command) {
shortcut.append("⌘ ")
}
if intersection.contains(.shift) {
shortcut.append("⇧ ")
}
if intersection.contains(.function) {
shortcut.append("fn ")
}
let character = keysCodesToCharacters[event.keyCode]!
shortcut.append(character)
return shortcut
}
will evaluate true for
if intersection.contains(.function)
In theory this tests for the function key but the most strange part is that event.keycode comes as 120 that corresponds to F2.
So I don't need to do this test at all to test for the function keys F1 to F12.
The big question is: what is the purpose of this .function. What is this testing?
I thought of the fn keys on the mac keyboard but these keys are not detectable and when I press any key from F1 to F12, this method gives me the keycode of the key plus this function true.
What I mean is this: when I press F2, for example, I receive event.keycode, meaning that F2 was pressed and also functionKey (63) as true? Why the redundancy?
Apple's documentation isn't what is used to be.
If the documentation doesn't help, check the header. Copied from NSEvents.h:
NSEventModifierFlagFunction = 1 << 23, // Set if any function key is pressed.
NSEventModifierFlagFunction was NSFunctionKeyMask, documentation:
NSFunctionKeyMask
Set if any function key is pressed. The function keys include the F keys at the top of most keyboards (F1, F2, and so on) and the navigation keys in the center of most keyboards (Help, Forward Delete, Home, End, Page Up, Page Down, and the arrow keys).
Apparently the .function flag is also set when the fn-key is pressed (on my Apple keyboard, my Logitech keyboard handles the fn-key internally).
I am developing an application to detect keyboard type for macOS.
I have seen several functions which reading the documentation are supposed to return keyboard id.
However when I test those on my laptop it always print 59.
Can someone tell me where does this 59 value come from and its meaning ??
So far I have tried with oncreen keyboard and built-in keyboard. I have also tried with different layouts but I keep getting that 59
This is my code:
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
uint32_t kbdType = LMGetKbdType();
NSLog(#"Testing LMGetKbdType ----------> %d", kbdType);
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
NSEventType type = [event type];
if(type==NSKeyDown || type==NSKeyUp) {
int64_t val = CGEventGetIntegerValueField(cgEvent, kCGKeyboardEventKeyboardType);
NSLog(#"CGEventGetIntegerValueField: %lld",val);
EventRef ce = (EventRef)[event eventRef];
if(ce) {
unsigned kbt;
GetEventParameter(
ce,
kEventParamKeyboardType,
typeUInt32, NULL,
sizeof kbt, NULL,
& kbt
);
NSLog(#"CARBON Keyboard type: %d",kbt);
}
CGEventSourceRef evSrc = CGEventCreateSourceFromEvent( cgEvent );
if(evSrc) {
unsigned kbt = (NSUInteger) CGEventSourceGetKeyboardType( evSrc );
CFRelease(evSrc);
NSLog(#"COCOA: %d",kbt);
}
}
}
I think these are undocumented values with no external meaning. They are only useful for passing back into other APIs that need a keyboard type (e.g. UCKeyTranslate()).
I think that they are of the same kind that used to be documented in <CoreServices/CarbonCore/Gestalt.h>, under gestaltKeyboardType. However, that header is no longer being updated and doesn't list a type 59.
What exactly are you trying to figure out about the keyboard? If it's general layout, you can use KBGetLayoutType() to learn if it's ANSI, JIS, or ISO. You pass in the keyboard type, like the one you're getting from LMGetKbdType().
The active keyboard layout (e.g. U.S. vs. French vs. Dvorak) should not affect the keyboard type. The keyboard type is an aspect of the hardware and doesn't change as the layout (the interpretation of keys into characters) changes.
I'm trying to create a small WASD demo game in macOS. I'm using NSEvent for handling the key events. To detect the key presses, I'm searching for keyDown events. Here's what I have:
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
(keyEvent) -> NSEvent? in
if self.keyDown(with: keyEvent) {
return nil
} else {
return keyEvent
}
}
func keyDown(with event: NSEvent) -> Bool {
userInt.keyDown(key: event.characters)
return true
}
So here, I'm holding the keys down (as you'd expect in a game), and I'm getting some very slow movement. Like, when I'm holding it down, it's very janky. Upon further inspection, I saw that the key repeat interval was 0.1s, which was set in my system preferences. This means that it's skipping frames. However, in a game, I don't want this setting to affect the movement. So how can I detect a key holding event without being held up by the key repeat interval?
You should ignore key-repeat events (with isARepeat true). Instead, when you get a key-down event, start a timer that fires however often you want to advance your game state. Advance the game state in that timer's firing code. When you get a key-up event, stop the timer.
Im trying to implement a keyboard class in my game that has two modes. The game mode takes input that uses lowercase, unmodified keys (unmodified meaning if I type a '0' with the shift it still returns '0' instead of ')'). I have tracked it down as far as using the charactersIgnoringModifiers method of the NSEvent class but this method excludes all the modifier keys except for the shift key.
You can use -[NSEvent keyCode] and then translate the key code to a character without using any modifiers. Doing the latter is easier said than done. Here's a long mailing list thread on the techniques and gotchas.
The best option I could find so far for ignoring the <Shift> modifier is by using NSEvent.characters(byApplyingModifiers:) with a modifier that doesn't change the key glyph, i.e. .numericPad:
func onKeyDown(event: NSEvent) {
let characters = event.characters(byApplyingModifiers: .numericPad)
print("Key pressed: \(characters)")
}
Ideally you'd be able to pass in a mask that represents no modifiers at all, but the API doesn't seem to support it.
For completeness, here's how you could start writing a function that takes a UInt16 (CGKeyCode) and returns a string representation according to the user's keyboard:
func keyCodeToString(code: UInt16) -> String {
switch code {
// Keys that are the same across keyboards
// TODO: Fill in the rest
case 0x7A: return "<F1>"
case 0x24: return "<Enter>"
case 0x35: return "<Escape>"
// Keys that change between keyboards
default:
let cgEvent = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: true)!
let nsEvent = NSEvent(cgEvent: cgEvent)!
let characters = nsEvent.characters(byApplyingModifiers: .numericPad)
return String(characters?.uppercased() ?? "<KeyCode: \(code)>")
}
}
The goal being for the F1 key to display <F1>, but the ";" key to display ; on US keyboards but Ñ on Spanish keyboards.