How to capture Unicode from key events without an NSTextView - cocoa

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

Related

How to extract the character from WM_KEYDOWN in PreTranslateMessage(MSG*pMsg)

In a MFC application within PreTranslateMessage(MSG *pMsg) inherited from a CView, I have this:
if (pMsg->message == WM_KEYDOWN) ...
The fields in a WM_KEYDOWN are documented here. The virtual key VK_ value is in pMsg->wParam and pMsg->lParam contains several field, of which bits 16-23 is the keyboard scan code.
So in my code I use:
const int virtualKey = pMsg->wParam;
const int hardwareScanCode = (pMsg->lParam >> 16) & 0x00ff; // bits 16-23
On my non-US keyboard for example, when I press the "#" character, I get the following:
virtualKey == 0xde --> VK_OEM_7 "Used for miscellaneous characters; it can vary by keyboard."
hardwareScanCode == 0x29 (41 decimal)
The character I'd like to "capture" or process differently is ASCII "#", 0x23 (35 decimal).
MY QUESTION
How do I translate the WM_KEYDOWN information to get something I can compare against, regardless of language or keyboard layout? I need to identify the # key whether the user has a standard US keyboard, or something different.
For example, I've been looking at the following functions such as:
MapVirtualKey(virtualkey, MAPVK_VSC_TO_VK);
// previous line is useless, the key VK_OEM_7 doesn't map to anything without the scan code
ToAscii(virtualKey, hardwareScanCode, nullptr, &word, 0);
// previous line returns zero, and zero is written to `word`
Edit:
Long story short: On a U.S. keyboard, SHIFT+3 = #, while on a French keyboard SHIFT+3 = /. So I don't want to look at individual keys, instead I want to know about the character.
When handling WM_KEYDOWN, how do I translate lParam and wParam (the "keys") to find out the character which the keyboard and Windows is about to generate?
I believe this is a better solution. This one was tested with both the standard U.S. keyboard layout and the Canadian-French keyboard layout.
const int wParam = pMsg->wParam;
const int lParam = pMsg->lParam;
const int keyboardScanCode = (lParam >> 16) & 0x00ff;
const int virtualKey = wParam;
BYTE keyboardState[256];
GetKeyboardState(keyboardState);
WORD ascii = 0;
const int len = ToAscii(virtualKey, keyboardScanCode, keyboardState, &ascii, 0);
if (len == 1 && ascii == '#')
{
// ...etc...
}
Even though the help page seems to hint that keyboardState is optional for the call to ToAscii(), I found that it was required with the character I was trying to detect.
Found the magic API call that gets me what I need: GetKeyNameText()
if (pMsg->message == WM_KEYDOWN)
{
char buffer[20];
const int len = GetKeyNameTextA(pMsg->lParam, buffer, sizeof(buffer));
if (len == 1 && buffer[0] == '#')
{
// ...etc...
}
}
Nope, that code only works on keyboard layouts that have an explicit '#' key. Doesn't work on layouts like the standard U.S. layout where '#' is a combination of other keys like SHIFT + 3.
I'm not an MFC expert, but here's roughly what I believe its message loop looks like:
while (::GetMessage(&msg, NULL, 0, 0) > 0) {
if (!app->PreTranslateMessage(&msg)) { // the hook you want to use
TranslateMessage(&msg); // where WM_CHAR messages are generated
DispatchMessage(&msg); // where the original message is dispatched
}
}
Suppose a U.S. user (for whom 3 and # are on the same key) presses that key.
The PreTranslateMessage hook will see the WM_KEYDOWN message.
If it allows the message to pass through, then TranslateMessage will generate a WM_CHAR message (or something from that family of messages) and dispatch it directly. PreTranslateMessage will never see the WM_CHAR.
Whether that WM_CHAR is a '3' or a '#' depends on the keyboard state, specifically whether a Shift key is currently pressed. But the WM_KEYDOWN message doesn't contain all the keyboard state. TranslateMessage keeps track of the state by taking notes on the keyboard messages that pass through it, so it knows whether the Shift (or Ctrl or Alt) is already down.
Then DispatchMessage will dispatch the original WM_KEYDOWN message.
If you want to catch only the '#' and not the '3's, then you have two options:
Make your PreTranslateMessage hook keep track of all the keyboard state (like TranslateMessage would normally do). It would have to watch for all of the keyboard messages to track the keyboard state and use that in conjunction with the keyboard layout to figure whether the current message would normally generate a '#'. You'd then have to manually dispatch the WM_KEYDOWN message and return TRUE (so that the normal translate/dispatch doesn't happen). You'd also have to be careful to also filter the corresponding WM_KEYUP messages so that you don't confuse TranslateMessage's internal state. That's a lot of work and lots to test.
Find a place to intercept the WM_CHAR messages that TranslateMessage generates.
For that second option, you could subclass the destination window, have it intercept WM_CHAR messages when the character is '#' and pass everything else through. That seems a lot simpler and well targeted.

How to Keep Static Prefix in UITextField When Pasting String

I have a UITextField that handles input of currency. The initial state of the text field has a set value of $0.00 but changes to just $ when it becomes the first responder.
I have successfully disabled the user from deleting the $ prefix of the text field's text by adding this:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == priceTextField {
if range.length == 1 && count(string) == 0 {
// Deleting text
if range.location <= 0 {
return false
}
}
}
return true
}
However, this only works when the user enters text with keys and then deletes text by pressing the backspace key. If the user copies text, highlights the $ prefix and then pastes in the text, the $ prefix gets replaced with the pasted text.
If the user selects the location before the $ prefix and pastes the text, I move the $ prefix to the replacement string by handling it with:
if range.location == 0 && count(string) >= 1 {
textField.text = "$\(string)"
return false
}
I realize that I need to create some logic for using the range of the text in the text field, but I'm not sure about where to start.
I'm not asking for a handout of a code snippet, but rather if someone can point me in the direction of what sort of logic I am needing to implement so that the $ prefix is not editable and always at the beginning of the text (even when selected and pasted over)?
I would suggest you to,
1.If textfield is empty, use placeholder property of UITextField to indicate what needs to be entered.
2.If the textfield has a value,
i) in textFieldDidBeginEditing delegate, remove the $ symbol from the value, i.e. when textfield is active.
ii) in textFieldDidEndEditing delegate, add the $ symbol to the value entered, i.e. when textfield is inactive.

theEvent charactersIgnoringModifiers - Get characters without modifiers

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.

How to redirect a WM_KEYDOWN message to another control in MFC?

I'm on a roll today with MFC! :D
I have a text box and a list view control.
When the user presses the VK_UP and VK_DOWN keys in the text box, I would like this to happen:
Do some stuff.
Have the list view control also process the message (to highlight the previous/next item).
I want the list view to wrap around the current selection, if the key press is the first in its sequence.
Do some more stuff.
I tried subclassing my edit box in my dialog:
class MyEditBox : public CWnd
{
bool allowWrap;
afx_msg void OnKeyUp(UINT, UINT, UINT) { this->allowWrap = true; }
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CListCtrl &listView = static_cast<CListView *>(
this->GetParent()->GetDlgItem(IDC_LIST_VIEW))->GetListCtrl();
if (nChar == VK_UP || nChar == VK_DOWN)
{
int iSelBefore = listView.GetNextItem(-1, LVNI_SELECTED);
this->GetParent()->GetDlgItem(IDC_LIST_VIEW)
->OnKeyDown(nChar, nRepCnt, nFlags); //Oops! Protected member :(
int iSelAfter = listView.GetNextItem(-1, LVNI_SELECTED);
if (iSelBefore == iSelAfter && // Did the selection reach an end?
this->allowWrap) // If so, can we wrap it around?
{
int i = a == 0 ? listView.GetItemCount() - 1 : 0;
listView.SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED,
LVIS_SELECTED | LVIS_FOCUSED);
}
}
this->allowWrap = false;
}
}
but OnKeyDown() is a protected member, so I can't just call it on another control.
Is there a better way to solve this than manually sending the command with SendMessage? Should I change my design, e.g. subclass something else, etc.?
Your intention is to select previous or next item in list control, right? Then you should call the method to do that directly instaed of asking the CListCtrl to "process" your message.
You may call CListCtrl::SetSelectionMark and CListCtrl::SetItemState to select next or previous keystroke. Example:
cListCtrl.SetSelectionMark(nIndex);
cListCtrl.SetItemState(nIndex, LVIS_SELECTED | LVIS_FOCUSED, 0xFF);
You can handle Key Down, Key Up as well as Page Down, Page Up, End, Home or any any key from edit box. You need to do calculation, though.
Or you can just SendMessage. There is no need to call OnKeyDown directly. Let the framework call it for you when you send the message.
I am seeing also other ways to solve this:
Derive a class from CListCtrl called MyListCtrl and choose one of two things:
1.1 Declare MyEditBox as a friend and now you can call the protected methods on MyEditBox
1.2 Add public methods CallOnKeyDown(...) and CallOnKeyup(...) to it that only do what is needed.
And when creating the control, instance a MyListCtrl instead of a CListCtrl. Also replace the listView variable you have shown here to be a MyListCtrl and use the methods you have now available
Use PreTranslateMessage(...). I think this "hammer" solution is worse than sending a message.

Suppress a wxWidgets event at specific point to prevent mutual handling

I have a WxWidget Panel with two TextControls for user input. One TextControl input changes the value of the other input field. I used an EVT_COMMAND_TEXT_UPDATE event and bound it to a function like "OnValueChanged" ...
mTextCtrl1->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MyClass::OnTextCtrlChanged1), NULL, this);
mTextCtrl2->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MyClass::OnTextCtrlChanged2), NULL, this);
void MyClass::OnTextCtrlChanged1(wxCommandEvent &event) {
// ...
mTextCtrl2->SetValue(...); // Set a Hex value of entered Value in text ctrl 1
}
void MyClass::OnTextCtrlChanged2(wxCommandEvent &event) {
// ...
mTextCtrl1->SetValue(...); // Set a Integer value of entered Value in text ctrl 2
// at this point, MyClass::OnTextCtrl1 is handled,
// but should not, only if user himself enters something
}
Problem is, when Text in one TextControl is changed, it changes the value of the other correctly. But, as soon as text is changed in other input, it rises its own TEXT_UPDATE event and updates the current users inputs, resulting in funny curser jumping etc.
Is it possible to provide the execution of these events while changing the value of the other TextControl, so that it does not rise its TEXT_UPDATE event? If the user makes some input for himself to this text control, it should work as usual.
Maybe you can use wxTextCtrl::ChangeValue
virtual void ChangeValue(const wxString& value)
Sets the text value and marks the control as not-modified (which means that IsModified would return false immediately after the call to SetValue).
Note that this function will not generate the wxEVT_COMMAND_TEXT_UPDATED event. This is the only difference with SetValue. See this topic for more information.

Resources