Convert Virtual Key Code to unicode string - macos

I have some code I've been using to get the current keyboard layout and convert a virtual key code into a string. This works great in most situations, but I'm having trouble with some specific cases. The one that brought this to light is the accent key next to the backspace key on german QWERTZ keyboards. http://en.wikipedia.org/wiki/File:KB_Germany.svg
That key generates the VK code I'd expect kVK_ANSI_Equal but when using a QWERTZ keyboard layout I get no description back. Its ending up as a dead key because its supposed to be composed with another key. Is there any way to catch these cases and do the proper conversion?
My current code is below.
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if(keyboardLayout)
{
UInt32 deadKeyState = 0;
UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
OSStatus status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, 0,
LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if(actualStringLength > 0 && status == noErr)
return [[NSString stringWithCharacters:unicodeString length:(NSInteger)actualStringLength] uppercaseString];
}

That key is a dead key, as you can see if you try it yourself or look at the Keyboard Viewer with the German layout active.
On the Mac, the way to enter a dead key's actual character, without composing it with another character, is to press a space after it. So try that: Turn off kUCKeyTranslateNoDeadKeysBit, and if UCKeyTranslate sets the dead-key state, translate a space after it.
EDIT (added by asker)
Just for future people, here is the fixed code with the right solution.
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if(keyboardLayout)
{
UInt32 deadKeyState = 0;
UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
OSStatus status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, 0,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if (actualStringLength == 0 && deadKeyState)
{
status = UCKeyTranslate(keyboardLayout,
kVK_Space, kUCKeyActionDown, 0,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
}
if(actualStringLength > 0 && status == noErr)
return [[NSString stringWithCharacters:unicodeString length:(NSUInteger)actualStringLength] uppercaseString];
}

Related

Unable to get all hard disks information with kernel mode driver on windows 11

I am fairly new to kernel programming and I have a little problem getting all disk drives information like name,serialnumber from kernel mode. I use below code to get all disks symbolic links which works perfectly fine.
static VOID DeviceInterfaceTest_Func() {
NTSTATUS Status;
PWSTR SymbolicLinkList;
PWSTR SymbolicLinkListPtr;
GUID Guid = {
0x53F5630D,
0xB6BF,
0x11D0,
{
0x94,
0xF2,
0x00,
0xA0,
0xC9,
0x1E,
0xFB,
0x8B
}
}; //Defined in mountmgr.h
Status = IoGetDeviceInterfaces( &
Guid,
NULL,
0, &
SymbolicLinkList);
if (!NT_SUCCESS(Status)) {
return;
}
KdPrint(("IoGetDeviceInterfaces results:\n"));
for (SymbolicLinkListPtr = SymbolicLinkList; SymbolicLinkListPtr[0] != 0 && SymbolicLinkListPtr[1] != 0; SymbolicLinkListPtr += wcslen(SymbolicLinkListPtr) + 1) {
KdPrint(("Symbolic Link: %S\n", SymbolicLinkListPtr));
PUNICODE_STRING PTarget {};
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
if (s == STATUS_SUCCESS) {
//KdPrint(("%S\n", PTarget->Buffer));
KdPrint(("Finished!\n"));
}
}
ExFreePool(SymbolicLinkList);
}
However when i try to use InitializeObjectAttributes function to extract data of symbolic link inside for loop I checking their names with KdPrint and all them are null as a result i can't use ZwOpenSymbolicLinkObject, because when i use it i get BSOD. What am I doing wrong? Is my method valid to get disk information or I should use another method? Below is the code of SymbolicLinkTarget
NTSTATUS SymbolicLinkTarget(_In_ PUNICODE_STRING SymbolicLinkStr, _Out_ PUNICODE_STRING PTarget) {
OBJECT_ATTRIBUTES ObjectAtiribute {};
NTSTATUS Status = 0;
HANDLE Handle = nullptr;
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
KdPrint(("Object length:%u \n", ObjectAtiribute.Length));
KdPrint(("Object name:%s \n", ObjectAtiribute.ObjectName - > Buffer));
Status = ZwOpenSymbolicLinkObject(&Handle, GENERIC_READ, &ObjectAtiribute);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwOpenSymbolicLinkObject failed (0x%08X)\n", Status));
return Status;
}
UNREFERENCED_PARAMETER(PTarget);
ULONG Tag1 = 'Tag1';
PTarget->MaximumLength = 200 * sizeof(WCHAR);
PTarget->Length = 0;
PTarget->Buffer = (PWCH)ExAllocatePool2(PagedPool, PTarget->MaximumLength, Tag1);
if (!PTarget->Buffer)
{
ZwClose(Handle);
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ZwQuerySymbolicLinkObject(Handle, PTarget, NULL);
ZwClose(Handle);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwQuerySymbolicLinkObject failed (0x%08X)\n", Status));
ExFreePool(PTarget->Buffer);
return Status;
}
return STATUS_SUCCESS;
}
Thank you very much for helping.
There are multiple problems in your functions. Let start with he main one:
In SymbolicLinkTarget():
OBJECT_ATTRIBUTES ObjectAtiribute {};
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
You are going to initialize ObjectAtiribute from SymbolicLinkStr (and the other parameters) but in DeviceInterfaceTest_Func() you actually never set Input to contain a string!
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
Input.Length
This is wrong:
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.Length will be set to the size of a pointer. According to the UNICODE_STRING (ntdef.h; subauth.h) the length is:
Specifies the length, in bytes, of the string pointed to by the Buffer member, not including the terminating NULL character, if any.
So:
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
Notice the wcslen() is already in the init-statement of the for loop, I would train to extract it to have it in the loop body.
Input.MaximumLength
Input.MaximumLength = 200 * sizeof(WCHAR);
What if the string is more lager than 200 characters?
MaximumLength is defined as such:
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.
Thus it's safe to just do:
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
The allocation for the Buffer member can be kept in place. Now you need to copy the string into the buffer.
UNICODE_STRING init
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0); // note: you should define a Tag for your Driver.
if(Input.buffer == NULL) {
// not enough memory.
return;
}
status = RtlStringCbCopyW(Input.Buffer, max_length_bytes, SymbolicLinkListPtr);
// TODO: check status
Now that you know how to do it manually, throw your code and use RtlUnicodeStringInit
Other things & hints
Always checks the return status / value of the functions you use. In kernel mode, this is super important.
NTSTATUS check is always done using one of the status macros (usually NT_SUCCESS)
Use string safe functions.
nitpicking: A success return value of IoGetDeviceInterfaces may also indicate an empty buffer. Although you check that in the for loop init-statement, I would have checked that right after the function so the intent is clearer.
KdPrint(("Object name:%s \n", ObjectAtiribute.ObjectName - > Buffer));
It's %S (wide char) not %s (char); see format specification. you can pass a UNICODE_STRING and use the %Z formatter. Also be wary of - > which is strange (you probably meant ->).
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
Use OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE if the resulting handle is not meant to cross the kernel <-> user-mode boundary (in your case, it doesn't have to cross that boundary). Otherwise you leak a kernel handle to user-mode, which has security implications.
This is also required when you call ZwOpenSymbolicLinkObject and you are not running in a system thread:
If the caller is not running in a system thread context, it must set the OBJ_KERNEL_HANDLE attribute when it calls InitializeObjectAttributes.
You can define GUIDs with DEFINE_GUID; see Defining and Exporting New GUIDs and Including GUIDs in Driver Code. In your case you don't need to export it.
This is probably nitpicking, but use nullptr (c++) or NULL (c) instead of 0 to convey the idea that you are checking for a pointer and not just the integral value of 0.

How can I force all events from one device to be handled by one window, while allowing all other events from all other devices to be handled normally?

I have an application that is used as a control system for a presentation, under Linux and using X11. I have a USB presentation remote that acts as a very miniature keyboard (four buttons: Page Up, Page Down, and two others) which can be used to advance and go back in the presentation. I would like to have my presentation application to receive all of the events from this remote regardless of where the mouse focus is. But I would also like to be able to receive the normal mouse and keyboard events if the current window focus is on the presentation application. Using XIGrabDevice() I was able to receive all events from the remote in the presentation application regardless of the current focus but I was not able to receive any events from the mouse or keyboard while the grab was active.
I ended up setting up a separate program to capture the remote's keys, then I relay those keys to my main program. I did it this way because the original program was using the older XInput extension, and I needed to use the newer XInput2 extension, and they do not exist well together. Here's some C++ code (it doesn't do any error checking, but this should be done in a real program):
// Open connection to X Server
Display *dpy = XOpenDisplay(NULL);
// Get opcode for XInput Extension; we'll need it for processing events
int xi_opcode = -1, event, error;
XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error);
// Allow user to select a device
int num_devices;
XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
for (int i = 0; i < num_devices; ++i)
{
XIDeviceInfo *dev = &info[i];
std::cout << dev->deviceid << " " << dev->name << "\n";
}
XIFreeDeviceInfo(info);
std::cout << "Enter the device number: ";
std::string input;
std::cin >> input;
int deviceid = -1;
std::istringstream istr(input);
istr >> deviceid;
// Create an InputOnly window that is just used to grab events from this device
XSetWindowAttributes attrs;
long attrmask = 0;
memset(&attrs, 0, sizeof(attrs));
attrs.override_redirect = True; // Required to grab device
attrmask |= CWOverrideRedirect;
Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, attrmask, &attrs);
// Make window without decorations
PropMotifWmHints hints;
hints.flags = 2;
hints.decorations = 0;
Atom property = XInternAtom(dpy, "_MOTIF_WM_HINTS", True);
XChangeProperty(dpy, win, property, property, 32, PropModeReplace, (unsigned char *)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
// We are interested in key presses and hierarchy changes. We also need to get key releases or else we get an infinite stream of key presses.
XIEventMask evmasks[1];
unsigned char mask0[XIMaskLen(XI_LASTEVENT)];
memset(mask0, 0, sizeof(mask0));
XISetMask(mask0, XI_KeyPress);
XISetMask(mask0, XI_KeyRelease);
XISetMask(mask0, XI_HierarchyChanged);
evmasks[0].deviceid = XIAllDevices;
evmasks[0].mask_len = sizeof(mask0);
evmasks[0].mask = mask0;
XISelectEvents(dpy, win, evmasks, 1);
XMapWindow(dpy, win);
XFlush(dpy);
XEvent ev;
bool grab_success = false, grab_changed;
while (1)
{
grab_changed = false;
if (!grab_success)
{
XIEventMask masks[1];
unsigned char mask0[XIMaskLen(XI_LASTEVENT)];
memset(mask0, 0, sizeof(mask0));
XISetMask(mask0, XI_KeyPress);
masks[0].deviceid = deviceid;
masks[0].mask_len = sizeof(mask0);
masks[0].mask = mask0;
XIGrabDevice(dpy, deviceid, win, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, masks);
}
XNextEvent(dpy, &ev);
XGenericEventCookie *cookie = &ev.xcookie;
if (cookie->type == GenericEvent && cookie->extension == xi_opcode && XGetEventData(dpy, cookie))
{
if (cookie->evtype == XI_KeyPress)
{
XIDeviceEvent *de = (XIDeviceEvent*)cookie->data;
std::cout << "found XI_KeyPress event: keycode " << de->detail << "\n";
}
else if (cookie->evtype == XI_HierarchyChanged)
{
// Perhaps a device was unplugged. The client is expected to re-read the list of devices to find out what changed.
std::cout << "found XI_HierarchyChanged event.\n";
grab_changed = true;
}
XFreeEventData(dpy, cookie);
}
if (grab_changed)
{
XIUngrabDevice(dpy, deviceid, CurrentTime);
grab_success = false;
break;
}
}
I found the following links helpful:
Peter Hutterer's 6-part blog on XInput2: 1 2 3 4 5 6
This blog entry was useful to determine which class to cast the cookie->data pointer to, depending on the cookie->evtype: 7

Screen size in inches on Windows

I am developing a multi-platform game that runs on iOS as well as desktops (Windows, Mac, Linux). I want the game to be able to resize certain UI elements depending on the resolution of the screen in inches. The idea is that if a button should be, say, around 1/2 inch across in any interface, it will be scaled automatically that size.
Now for iOS devices this problem is reasonably well solvable using brute force techniques. You can look up the type of the device and use a hard-coded table to determine the screen size in inches for each device. Not the most elegant solution, but sufficient.
Desktops are the tricky ones. What I wish and hope exists is a mechanism by which (some?) monitors report to operating systems their actual screen size in inches. If that mechanism exists and I can access it somehow, I can get good numbers at least for some monitors. But I've never come across any such concept in any of the major OS APIs.
Is there a way to ask for the screen size in inches in Win32? If so, are there monitors that actually provide this information?
(And if the answer is no: Gosh, doesn't this seem awfully useful?)
For Windows, first see SetProcessDPIAware() for a discussion on turning off automatic scaling, and then call GetDeviceCaps( LOGPIXELSX ) and GetDeviceCaps( LOGPIXELSY ) on your HDC to determine the monitor's DPI. Divide the screen resolution on your active monitor by those settings and you've got the size.
Also see this article for a similar discussion on DPI aware apps.
Here is a method I found at the web address "https://ofekshilon.com/2011/11/13/reading-monitor-physical-dimensions-or-getting-the-edid-the-right-way/".
Authour says that measurement is in millimeters.
Does not give you the precise width and height but better approximation than HORSIZE and VERTSIZE. In which I tried on two different monitors and got a max difference of 38 cm (measured screen size - calculated screen size).
#include <SetupApi.h>
#pragma comment(lib, "setupapi.lib")
#define NAME_SIZE 128
const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18};
// Assumes hDevRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, short& WidthMm, short& HeightMm)
{
DWORD dwType, AcutalValueNameLength = NAME_SIZE;
TCHAR valueName[NAME_SIZE];
BYTE EDIDdata[1024];
DWORD edidsize=sizeof(EDIDdata);
for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
{
retValue = RegEnumValueA ( hDevRegKey, i, &valueName[0],
&AcutalValueNameLength, NULL, &dwType,
EDIDdata, // buffer
&edidsize); // buffer size
if (retValue != ERROR_SUCCESS || 0 != strcmp(valueName,"EDID"))
continue;
WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
return true; // valid EDID found
}
return false; // EDID not found
}
// strange! Authour requires TargetDevID argument but does not use it
bool GetSizeForDevID(const char *TargetDevID, short& WidthMm, short& HeightMm)
{
HDEVINFO devInfo = SetupDiGetClassDevsExA(
&GUID_CLASS_MONITOR, //class GUID
NULL, //enumerator
NULL, //HWND
DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
NULL, // device info, create a new one.
NULL, // machine name, local machine
NULL);// reserved
if (NULL == devInfo) return false;
bool bRes = false;
for (ULONG i=0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
{
SP_DEVINFO_DATA devInfoData;
memset(&devInfoData,0,sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);
if (SetupDiEnumDeviceInfo(devInfo,i,&devInfoData))
{
HKEY hDevRegKey = SetupDiOpenDevRegKey(devInfo,&devInfoData,DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if(!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) continue;
bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);
RegCloseKey(hDevRegKey);
}
}
SetupDiDestroyDeviceInfoList(devInfo);
return bRes;
}
int main(int argc, CHAR* argv[])
{
short WidthMm, HeightMm;
DISPLAY_DEVICE dd;
dd.cb = sizeof(dd);
DWORD dev = 0; // device index
int id = 1; // monitor number, as used by Display Properties > Settings
char DeviceID[1024];
bool bFoundDevice = false;
while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice)
{
DISPLAY_DEVICE ddMon = {sizeof(ddMon)};
DWORD devMon = 0;
while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) && !bFoundDevice)
{
if (ddMon.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP &&
!(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
{
sprintf(DeviceID,"%s", ddMon.DeviceID+8);
for(auto it=DeviceID; *it; ++it)
if(*it == '\\') { *it = 0; break; }
bFoundDevice = GetSizeForDevID(DeviceID, WidthMm, HeightMm);
}
devMon++;
ZeroMemory(&ddMon, sizeof(ddMon));
ddMon.cb = sizeof(ddMon);
}
ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
dev++;
}
return 0;
}

ToAscii/ToUnicode in a keyboard hook destroys dead keys

It seems that if you call ToAscii() or ToUnicode() while in a global WH_KEYBOARD_LL hook, and a dead-key is pressed, it will be 'destroyed'.
For example, say you've configured your input language in Windows as Spanish, and you want to type an accented letter á in a program. Normally, you'd press the single-quote key (the dead key), then the letter "a", and then on the screen an accented á would be displayed, as expected.
But this doesn't work if you call ToAscii() or ToUnicode() in a low-level keyboard hook function. It seems that the dead key is destroyed, and so no accented letter á shows up on screen. Removing a call to the above functions resolves the issue... but unfortunately, I need to be able to call those functions.
I Googled for a while, and while a lot of people seemed to have this issue, no good solution was provided.
Any help would be much appreciated!
EDIT: I'm calling ToAscii() to convert the virtual-key code and scan code received in my LowLevelKeyboardProc hook function into the resulting character that will be displayed on screen for the user.
I tried MapVirtualKey(kbHookData->vkCode, 2), but this isn't as "complete" a function as ToAscii(); for example, if you press Shift + 2, you'll get '2', not '#' (or whatever Shift + 2 will produce for the user's keyboard layout/language).
ToAscii() is perfect... until a dead-key is pressed.
EDIT2: Here's the hook function, with irrelevant info removed:
LRESULT CALLBACK keyboard_LL_hook_func(int code, WPARAM wParam, LPARAM lParam) {
LPKBDLLHOOKSTRUCT kbHookData = (LPKBDLLHOOKSTRUCT)lParam;
BYTE keyboard_state[256];
if (code < 0) {
return CallNextHookEx(keyHook, code, wParam, lParam);
}
WORD wCharacter = 0;
GetKeyboardState(&keyboard_state);
int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
keyboard_state, &wCharacter, 0);
/* If ta == -1, a dead-key was pressed. The dead-key will be "destroyed"
* and you'll no longer be able to create any accented characters. Remove
* the call to ToAscii() above, and you can then create accented characters. */
return CallNextHookEx(keyHook, code, wParam, lParam);
}
Quite an old thread. Unfortunately it didn't contain the answer I was looking for and none of the answers seemed to work properly. I finally solved the problem by checking the MSB of the MapVirtualKey function, before calling ToUnicode / ToAscii. Seems to be working like a charm:
if(!(MapVirtualKey(kbHookData->vkCode, MAPVK_VK_TO_CHAR)>>(sizeof(UINT)*8-1) & 1)) {
ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
keyboard_state, &wCharacter, 0);
}
Quoting MSDN on the return value of MapVirtualKey, if MAPVK_VK_TO_CHAR is used:
[...] Dead keys (diacritics) are indicated by setting the top bit of the return value. [...]
stop using ToAscii() and use ToUncode()
remember that ToUnicode may return you nothing on dead keys - this is why they are called dead keys.
Any key will have a scancode or a virtual key code but not necessary a character.
You shouldn't combine the buttons with characters - assuming that any key/button has a text representation (Unicode) is wrong.
So:
for input text use the characters reported by Windows
for checking button pressed (ex. games) use scancodes or virtual keys (probably virtual keys are better).
for keyboard shortcuts use virtual key codes.
Call 'ToAscii' function twice for a correct processing of dead-key, like in:
int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
keyboard_state, &wCharacter, 0);
int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
keyboard_state, &wCharacter, 0);
If (ta == -1)
...
Calling the ToAscii or ToUnicode twice is the answer.
I found this and converted it for Delphi, and it works!
cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0);
cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0); //yes call it twice
I encountered this issue while creating a key logger in C# and none of the above answers worked for me.
After a deep blog searching, I stumbled across this keyboard listener which handles dead keys perfectly.
Here is a full code which covers dead keys and shortcut keys using ALT + NUMPAD, basically a full implementation of a TextField input handling:
[DllImport("user32.dll")]
public static extern int ToUnicode(uint virtualKeyCode, uint scanCode, byte[] keyboardState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder receivingBuffer, int bufferSize, uint flags);
private StringBuilder _pressCharBuffer = new StringBuilder(256);
private byte[] _pressCharKeyboardState = new byte[256];
public bool PreFilterMessage(ref Message m)
{
var handled = false;
if (m.Msg == 0x0100 || m.Msg == 0x0102)
{
bool isShiftPressed = (ModifierKeys & Keys.Shift) != 0;
bool isControlPressed = (ModifierKeys & Keys.Control) != 0;
bool isAltPressed = (ModifierKeys & Keys.Alt) != 0;
bool isAltGrPressed = (ModifierKeys & Keys.RMenu) != 0;
for (int i = 0; i < 256; i++)
_pressCharKeyboardState[i] = 0;
if (isShiftPressed)
_pressCharKeyboardState[(int)Keys.ShiftKey] = 0xff;
if (isAltGrPressed)
{
_pressCharKeyboardState[(int)Keys.ControlKey] = 0xff;
_pressCharKeyboardState[(int)Keys.Menu] = 0xff;
}
if (Control.IsKeyLocked(Keys.CapsLock))
_pressCharKeyboardState[(int)Keys.CapsLock] = 0xff;
Char chr = (Char)0;
int ret = ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);
if (ret == 0)
chr = Char.ConvertFromUtf32(m.WParam.ToInt32())[0];
if (ret == -1)
ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);
else if (_pressCharBuffer.Length > 0)
chr = _pressCharBuffer[0];
if (m.Msg == 0x0102 && Char.IsWhiteSpace(chr))
chr = (Char)0;
if (ret >= 0 && chr > 0)
{
//DO YOUR STUFF using either "chr" as special key (UP, DOWN, etc..)
//either _pressCharBuffer.ToString()(can contain more than one character if dead key was pressed before)
//and don't forget to set the "handled" to true, so nobody else can use the message afterwards
}
}
return handled;
}
It is known that ToUnicode() and its older counterpart ToAscii() can change keyboard state of the current thread and thus mess with dead keys and ALT+NUMPAD keystrokes:
As ToUnicodeEx translates the virtual-key code, it also changes the
state of the kernel-mode keyboard buffer. This state-change affects
dead keys, ligatures, alt+numpad key entry, and so on. It might also
cause undesired side-effects if used in conjunction with
TranslateMessage (which also changes the state of the kernel-mode
keyboard buffer).
To avoid that you can do your ToUnicode() call in a separate thread (it will have a separate keyboard state) or use a special flag in wFlags param that is documented in ToUnicode() docs:
If bit 2 is set, keyboard state is not changed (Windows 10, version
1607 and newer)
Or you can prepare sc->char mapping table beforehand and update it on language change event.
I think it should work with ToAscii() too but better not use this old ANSI codepage-dependant method. Use ToUnicode() API instead that can even return ligatures and UTF-16 surrogate pairs - if keyboard layout have them. Some do.
See Asynchronous input vs synchronous input, a quick introduction
for the reason behind this.
I copy the vkCode in a queue and do the conversion from another thread
#HOOKPROC
def keyHookKFunc(code,wParam,lParam):
global gkeyQueue
gkeyQueue.append((code,wParam,kbd.vkCode))
return windll.user32.CallNextHookEx(0,code,wParam,lParam)
This has the advantage of not delaying key processing by the os
This works for me
byte[] keyState = new byte[256];
//Remove this if using
//GetKeyboardState(keyState);
//Add only the Keys you want
keysDown[(int)Keys.ShiftKey] = 0x80; // SHIFT down
keysDown[(int)Keys.Menu] = 0x80; // ALT down
keysDown[(int)Keys.ControlKey] = 0x80; // CONTROL down
//ToAscii should work fine
if (ToAscii(myKeyboardStruct.VirtualKeyCode, myKeyboardStruct.ScanCode, keyState, inBuffer, myKeyboardStruct.Flags) == 1)
{
//do something
}

Jump to listbox item by typing first few characters

I have a list of items (potentially large) from which the user must select one. I'd like to allow the user to type the first few letters of the desired item to jump to the correct place in the list. By default, each keypress jumps to the first item starting with that letter, so you can't type the first several letters. Is there any straightforward way to do this? Any CodeProject or other such example?
I've looked for hours, and found any number of samples for IAutocomplete, but that won't help here because I need to guarantee that the result is in the list.
The only way I can think to do this is to derive from CListBox, capture the keystrokes myself, find the item, run a timer so that new keystrokes after a sufficient pause will start a new search... since I'm not an MFC jock, this is daunting. Any tips much appreciated.
One clarifying note: my ultimate goal is actually to get this keyboard behavior for a ComboBox of DropDownList style (i.e. no edit box). The lack of an edit box rules out most autocomplete code, and the need for ComboBox functionality means I can't use CListCtrl by itself.
After much unnecessary pain, I've discovered that the real correct answer is simply to use LBS_SORT. Simply by specifying this style, the basic vanilla listbox supports the incremental search keyboard shortcut style I wanted. Without LBS_SORT (or CBS_SORT for a combobox), you get the irritating and almost-useless jump-to-first-letter-only behavior. I didn't try LBS_SORT because my list contents were added in sorted order anyway.
So the dozen or so hours of investigating custom controls, etc., all for naught because the Microsoft documentation makes no mention of this important behavioral difference in the description of LBS_SORT!!
Thanks to everyone who contributed.
I've implemented such a functionality in core Win32. Heres the code.
Somewhere in your message loop that processes the list box insert:
switch(message)
{
case WM_CHAR:
if(HandleListBoxKeyStrokes(hwnd, wParam) == FALSE)
return FALSE;
....
Heres the code (propably not fully complete):
/* ======================================================================== */
/* ======================================================================== */
#define RETURNr(a, b) // homegrown asserts
BOOLEAN HandleListBoxKeyStrokes(HWND hwnd, UINT theKey)
{
#define MAXCHARCACHEINTERVALL 600.0 // Max. milisecs time offset to consider as typed 'at once'
static char sgLastChars[255] = {'0'};
static double sgLastCharTime = 0.;
static HWND sgLasthwnd = NULL;
if(GetSecs() - sgLastCharTime > MAXCHARCACHEINTERVALL ||
sgLasthwnd != hwnd)
*sgLastChars = 0;
if(theKey == ' ' && *sgLastChars == 0)
return TRUE;
sgLastCharTime = GetSecs();
sgLasthwnd = hwnd;
AppendChar(sgLastChars, toupper(theKey));
if(strlen(sgLastChars) > 1)
{
LONG l = GetWindowLong(hwnd, GWL_STYLE);
Char255 tx;
GetClassName(hwnd, tx, sizeof(tx));
if( (! stricmp(tx, "Listbox") &&
! (l & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) ) ||
(! stricmp(tx, "ComboBox") && // combo Box support
l & CBS_DROPDOWNLIST &&
! (l & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) )
{
long Count, l, BestMatch = - 1, BestMatchOff = 0;
long LBcmdSet[] = {LB_GETCOUNT, LB_GETTEXTLEN , LB_GETTEXT};
long CBcmdSet[] = {CB_GETCOUNT, CB_GETLBTEXTLEN, CB_GETLBTEXT};
long *cmdSet = (! stricmp(tx, "ComboBox")) ? CBcmdSet : LBcmdSet;
RETURNr((Count = SendMessage(hwnd, cmdSet[0], 0, 0)) != LB_ERR, 0);
for(int i = 0; i < Count; i++)
{
RETURNr((l = SendMessage(hwnd, cmdSet[1], i, 0)) != LB_ERR, TRUE);
RETURNr( l < sizeof(tx), TRUE);
RETURNr((l = SendMessage(hwnd, cmdSet[2], i, (LPARAM)&tx)) != LB_ERR, TRUE);
strupr(tx);
if(! strncmp(tx, sgLastChars, strlen(sgLastChars)))
{
SelListBoxAndNotify(hwnd, i);
return FALSE;
}
char *p;
if(p = strstr(tx, sgLastChars))
{
int off = p - tx;
if(BestMatch == -1 || off < BestMatchOff)
{
BestMatch = i;
BestMatchOff = off;
}
}
}
// If text not found at start of string see if it matches some part inside the string
if(BestMatch != -1)
SelListBoxAndNotify(hwnd, BestMatch);
// Nothing found - dont process
return FALSE;
}
}
return TRUE;
}
/* ======================================================================== */
/* ======================================================================== */
void SelListBoxAndNotify(HWND hwnd, int index)
{
// i am sorry here - this is some XVT-toolkit specific code.
// it has to be replaced with something similar for native Win32
WINDOW win = xvtwi_hwnd_to_window(hwnd);
WINDOW parent = xvt_vobj_get_parent(win);
xvt_list_set_sel(win, index, 1);
EVENT evt;
memset(&evt, 0, sizeof(evt));
evt.type = E_CONTROL;
evt.v.ctl.id = GetDlgCtrlID(hwnd);
evt.v.ctl.ci.v.lbox.dbl_click = FALSE;
xvt_win_dispatch_event(parent, &evt);
}
/* ======================================================================== */
/* ======================================================================== */
double GetSecs(void)
{
struct timeb timebuffer;
ftime(&timebuffer);
return (double)timebuffer.millitm +
((double)timebuffer.time * 1000.) - // Timezone needed for DbfGetToday
((double)timebuffer.timezone * 60. * 1000.);
}
/* ======================================================================== */
/* ======================================================================== */
char AppendChar(char *tx, char C)
{ int i;
i = strlen(tx);
tx[i ] = C;
tx[i + 1] = 0;
return(C);
}
Can you use a CListView CListCtrl instead? They work like that by default.

Resources