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.
Related
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 building a NSTouchBar for my app using storyboard.
I want to replace the ESC button with something else.
As usual, there is no doc telling you how to do that.
I have searched the web and I have found vague informations like
You can change the content of "esc" to something else, though, like
"done" or anything, even an icon, by using
escapeKeyReplacementItemIdentifier with the NSTouchBarItem.
But this is too vague to understand.
Any ideas?
This is what I did so far.
I have added a button to NSTouchBar on storyboard and changed its identifier to newESC. I added this line programmatically:
self.touchBar.escapeKeyReplacementItemIdentifier = #"newESC";
When I run the App the ESC key is now invisible but still occupies its space on the bar. The button that was supposed to replace it, appears next to it. So that bar that was
`ESC`, `NEW_ESC`, `BUTTON1`, `BUTTON2`, ...
is now
`ESC` (invisible), `NEW_ESC`, `BUTTON1`, `BUTTON2`, ...
The old ESC is still occupying its space on the bar.
This is done by creating a touch bar item, let's say a NSCustomTouchBarItem containing a NSButton, and associating this item with its own identifier.
Then with another identifier you do your usual logic but you add the previously created identifier as the ESC replacement.
Quick example in Swift:
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
switch identifier {
case NSTouchBarItemIdentifier.identifierForESCItem:
let item = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: "Button!", target: self, action: #selector(escTapped))
item.view = button
return item
case NSTouchBarItemIdentifier.yourUsualIdentifier:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSTextField(labelWithString: "Example")
touchBar.escapeKeyReplacementItemIdentifier = .identifierForESCItem
return item
default:
return nil
}
}
func escTapped() {
// do additional logic when user taps ESC (optional)
}
I also suggest making an extension (category) for the identifiers, it avoids making typos with string literals:
#available(OSX 10.12.2, *)
extension NSTouchBarItemIdentifier {
static let identifierForESCItem = NSTouchBarItemIdentifier("com.yourdomain.yourapp.touchBar.identifierForESCItem")
static let yourUsualIdentifier = NSTouchBarItemIdentifier("com.yourdomain.yourapp.touchBar.yourUsualIdentifier")
}
I want to restrict character input on NSTextField, i.e. so that disallowed characters aren't even appearing. Most of what I found about this topic were solutions that only validate after text input finished or using NSFormatter which still allows the character to appear.
So far I came up with this solution, sub-classing NSTextField:
class RestrictedTextField : NSTextField
{
static let VALID_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'-.& ";
override func textDidChange(notification:NSNotification)
{
for c in stringValue
{
if (RestrictedTextField.VALID_CHARACTERS.rangeOfString("\(c)") == nil)
{
stringValue = stringValue.stringByReplacingOccurrencesOfString("\(c)", withString: "", options: .LiteralSearch, range: nil);
break;
}
}
}
}
It works but isn't really optimal because the textcursor still moves by one space if an invalid character is tried to be entered. I also think the loop shouldn't be necessary so I wonder does somebody know a more elegant solution for this?
You have complete control with a subclass of NSFormatter. I'm not sure why you think you don't.
Override isPartialStringValid(_:proposedSelectedRange:originalString:originalSelectedRange:errorDescription:) and implement the desired logic. From the docs (with some minor edits by me):
In a subclass implementation, evaluate [the string pointed to by *partialStringPtr] according to the context. Return YES if partialStringPtr is acceptable and NO if partialStringPtr is unacceptable. Assign a new string to partialStringPtr and a new range to proposedSelRangePtr and return NO if you want to replace the string and change the selection range.
So, if the user tries to insert disallowed characters, you can either reject their edit in its entirety or modify it to strip those disallowed characters. (Remember that user changes can include pasting, so it's not necessarily just a single typed character.) To reject the change entirely, assign origString to *partialStringPtr and origSelRange to *proposedSelRangePtr.
I'd like to capture key events from any window in the application and interpret them as Unicode. For example, if the user types Option-e-e (on a default-configured US English keyboard), I would like to recognize that as "é".
I tried capturing keypress events and calling -[NSEvent characters]. However, as it says in the documentation, "This method returns an empty string for dead keys, such as Option-e." If I type Option-e-e, then it gives me nothing for the Option-e and plain "e" for the second e.
Is there a way to combine a series of keycodes (from -[NSEvent keyCode]) into a Unicode character?
Or a way to receive an event for each Unicode character typed (like Java's key-typed event)?
Here's a way to take a series of key press events and get the Unicode character(s) they'd type.
Basically, call UCKeyTranslate() for each key press event received. Use its deadKeyState argument to capture a dead key and pass it along to the subsequent call.
Example:
Receive key press event for Option-e.
Call UCKeyTranslate() with the virtual key code (for e), the modifier key state (for Option), and a variable to store the dead key state.
UCKeyTranslate() outputs an empty string and updates the dead key state.
Receive key press event for e.
Call UCKeyTranlate() with the virtual key code (for e) and the variable that holds the dead key state.
UCKeyTranslate() outputs "é".
Sample code (the function to call for each key press event):
/**
* Returns the Unicode characters that would be typed by a key press.
*
* #param event A key press event.
* #param deadKeyState To capture multi-keystroke characters (e.g. Option-E-E for "é"), pass a reference to the same
* variable on consecutive calls to this function. Before the first call, you should initialize the variable to 0.
* #return One or more Unicode characters.
*/
CFStringRef getCharactersForKeyPress(NSEvent *event, UInt32 *deadKeyState)
{
// http://stackoverflow.com/questions/12547007/convert-key-code-into-key-equivalent-string
// http://stackoverflow.com/questions/8263618/convert-virtual-key-code-to-unicode-string
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
CGEventFlags flags = [event modifierFlags];
UInt32 modifierKeyState = (flags >> 16) & 0xFF;
const size_t unicodeStringLength = 4;
UniChar unicodeString[unicodeStringLength];
UniCharCount realLength;
UCKeyTranslate(keyboardLayout,
[event keyCode],
kUCKeyActionDown,
modifierKeyState,
LMGetKbdType(),
0,
deadKeyState,
unicodeStringLength,
&realLength,
unicodeString);
CFRelease(currentKeyboard);
return CFStringCreateWithCharacters(kCFAllocatorDefault, unicodeString, realLength);
}
subclass the view/window you want to capture the "é" event in and add this instance variable
BOOL optionE_Pressed;
then, override keyDown: with this
-(void) keyDown:(NSEvent *)theEvent {
NSString *chars = theEvent.charactersIgnoringModifiers;
unichar aChar = [chars characterAtIndex: 0];
if (aChar==101 && [theEvent modifierFlags]&NSAlternateKeyMask) {
optionE_Pressed=YES;
}
else if (aChar==101 && optionE_Pressed) {
NSLog(#"spanish é pressed");
}
else {
optionE_Pressed=NO;
}
[super keyDown:theEvent];
}
The Boolean variable "optionE_Pressed" is activated when the user holds down the option and e keys. If the next key that is pressed is e, meaning that they have effectively created a spanish é, then it will log "spanish é pressed." Otherwise, the Boolien is switched back to NO. The "super" call at the end allows the user to still be able to type all events as normal
RAWINPUT provides two flags (RI_KEY_E0 and RI_KEY_E1) to check whether the left or right version of a key is pressed. This works great for CTRL, but not for left and right shift. In fact, the flags are the same for both, and the VKey is also the same (VK_SHIFT). How can I find out which shift was pressed? I'm working on Windows 7. Interestingly, the flags/vkey values are exactly the same no matter which shift key I'm pressing.
Windows 7, and I only get VK_SHIFT, never the L/R variants
Which is part of the explanation why this doesn't work the way you think it should do. There's ancient history behind this. The keyboard controller was redesigned for the IBM AT, again for the Enhanced keyboard. It started sending out 0xe0 and 0xe1 prefixes for keys that were added to the keyboard layout. Like the right Ctrl and Alt keys.
But keyboards always had two shift keys. The original IBM PC didn't consider them special keys, they simply have a different scan code. Which was maintained in later updates. Accordingly, you don't get the RI_KEY_E0 or E1 flags for them. You have to distinguish them by the RAWKEYBOARD.MakeCode value. The left shift key has makecode 0x2a, the right key is 0x36.
Note that the left Ctrl and Alt keys don't have the flags either. They match the corresponding keys on the old PC keyboard layout. The description of the flags in the MSDN Library article is not very accurate.
You can distinguish left-right SHIFT/CONTROL/ALT VK codes like this:
case WM_INPUT:
{
HRAWINPUT dataHandle = reinterpret_cast<HRAWINPUT>(lParam);
RAWINPUT input;
UINT size = sizeof(input);
::GetRawInputData(dataHandle, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER));
if (input.header.dwType != RIM_TYPEKEYBOARD)
break;
const RAWKEYBOARD& keyboard = input.data.keyboard;
// Ignore key overrun state
if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE)
return;
// Ignore keys not mapped to any VK code
// This effectively filters out scan code pre/postfix for some keys like PrintScreen.
if (keyboard.VKey >= 0xff/*VK__none_*/)
return;
uint16_t scanCode = keyboard.MakeCode;
// Scan codes could contain 0xe0 or 0xe1 one-byte prefix.
// See https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
scanCode |= (keyboard.Flags & RI_KEY_E0) ? 0xe000 : 0;
scanCode |= (keyboard.Flags & RI_KEY_E1) ? 0xe100 : 0;
uint16_t vkCode = keyboard.VKey;
switch (vkCode)
{
case VK_SHIFT: // -> VK_LSHIFT or VK_RSHIFT
case VK_CONTROL: // -> VK_LCONTROL or VK_RCONTROL
case VK_MENU: // -> VK_LMENU or VK_RMENU
vkCode = LOWORD(MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX));
break;
}
//...
return 0;
}
This code should work at least from Vista.
But please note that gamedev programmers are usually manually mapping scancodes to internal game engine specific keycodes - because VK codes are tend to change on different keyboard layouts. For example if you use usual VK_W/VK_A/VK_S/VK_D for movement in QWERTY layout - it could turn into VK_Z/VK_Q/VK_S/VK_D in AZERTY keyboard layout. VK codes are primarily handy in Win32 GUI programming.
You can grab decent scancode<->USB HID Usage conversion table here: https://source.chromium.org/chromium/chromium/src/+/main:ui/events/keycodes/dom/dom_code_data.inc