0

In Python 3.10.5 on Windows 11 employing the wxPython 4.2.0 package, I have created a wx.ListBox widget. Now I want to listen to key presses when the list box is focused. But I need the character codes returned by the key press handler to respect the current keyboard layout, that is, for instance, whenever I press with the Czech keyboard layout the keys in the second key row that produce numbers 1, 2, 3, etc. in the English keyboard layout, I need not to receive character codes which when converted to characters using the chr() Python function result in numbers 1, 2, 3, etc., but instead I need to receive unicode char codes which when converted with chr() result in the Czech accented characters ě, š, č, etc.

The code snippet I am using so far is the following:

listbox = wx.ListBox(panel, size=(1200, 500), choices=[], style=wx.LB_SINGLE)
listbox.Bind(wx.EVT_CHAR_HOOK, onListboxCharHook)

...

def onListboxCharHook(self, event):
    unicodeKey = event.GetUnicodeKey()
    print(chr(unicodeKey))

In other words, the code above prints 1, 2, 3, 4, etc. when keys in the number row are pressed, but instead I need +, ě, š, č, etc. if the Czech keyboard layout is active.

I've also tried using EVT_KEY_DOWN event type instead of EVT_CHAR_HOOK, but the result is the same. Note that the wx.TextCtrl has a EVT_CHAR event type. When this event type is used, the chr(event.GetUnicodeKey()) correctly returns the characterss respecting the current keyboard layout as I need. But the problem is that wx.ListBox does not support the EVT_CHAR event type, so I am not able to get those actually pressed characters using wx.ListBox.

Therefore, is there some way using the wx.ListBox widget to translate the EVT_CHAR_HOOK event key codes into the actually pressed characters respecting the current keyboard layout?

Adam
  • 1,926
  • 23
  • 21
  • 1
    Does this answer your question? [Detect keyboard input with support of other languages from English](https://stackoverflow.com/questions/71431386/detect-keyboard-input-with-support-of-other-languages-from-english) – JosefZ Dec 04 '22 at 19:29
  • Looks like it is what I am looking for. Could you please modify that ansewr to use wxPython instead of pynput, and post it as the anser to my question? – Adam Dec 04 '22 at 20:50
  • Please [edit] your question to provide a [mcve] showing us the code on where you stuck solving the task by yourself and explaining why you fail to complete the task. Sorry, StackOverflow is not a free code writing service… – JosefZ Dec 05 '22 at 15:05

1 Answers1

0

Thanks to the answer to a similar question - Detect keyboard input with support of other languages from English, I have put together a working code which on key press prints the pressed character respecting the current keyboard layout as intended by the question. The releveant parts of that code are the following.

import wx

listbox = wx.ListBox(panel, size=(1200, 500), choices=[], style=wx.LB_SINGLE)
listbox.Bind(wx.EVT_KEY_DOWN, self.onListboxKeyDown)

def onListboxKeyDown(self, event):
    rawKey = event.GetRawKeyCode()
    unicodeKey = event.GetUnicodeKey()
    modifiers = event.GetModifiers()
    onlyShiftDown = modifiers == (wx.MOD_SHIFT)

    # Handle the press of any character key without any or only with the Shift modifier
    if (unicodeKey != wx.WXK_NONE) and (not modifiers or onlyShiftDown):
        char = getCurrentLayoutChar(rawKey, onlyShiftDown)
        print(char)
        return

event.Skip()

import os
import sys

### Translate the specified virtual-key code and keyboard state
#   to the corresponding Unicode character or characters.
#   learn.microsoft.com/en-gb/windows/win32/api/winuser/nf-winuser-tounicodeex
### Adapted from
#   the solution to https://stackoverflow.com/questions/38224277/
#   by https://stackoverflow.com/users/235698/mark-tolonen
###

from ctypes import (
    WinDLL, POINTER, create_string_buffer, create_unicode_buffer,
    c_int32, c_uint, c_uint, c_char, c_wchar, c_int, c_uint, c_void_p
    )
_ToUnicodeEx = WinDLL('user32').ToUnicodeEx
_ToUnicodeEx.argtypes = [
        c_uint,           # wVirtKey   virtual-key code to be translated
        c_uint,           # wScanCode  hardware scan code of ˙wVirtKey˙
        POINTER(c_char),  # lpKeyState current keyboard state (256-byte array)
        POINTER(c_wchar), # pwszBuff   buffer that receives translated chars
        c_int,            # cchBuff    size of the `pwszBuff` buffer (in chars)
        c_uint,           # wFlags     behavior of the function
        c_void_p          # dwhkl      input locale identifier
]
_ToUnicodeEx.restype = c_int


def ToUn(vk,sc,wfl,hkid, shiftDown=False):
    kst = create_string_buffer(256)
    if shiftDown:
        kst[16] = 0x80
    b = create_unicode_buffer(5)
    if _ToUnicodeEx(vk,sc,kst,b,5,wfl,hkid):
        return b.value
    else:
        return chr( 0xFFFD) # Replacement Character


### Retrieve the active input locale identifier
#   (formerly called the keyboard layout)
#   https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeyboardlayout
###
#   Method based on my own research; non-optimized, debugged on Windows 10… 
###

from ctypes import WinDLL
user32 = WinDLL('user32', use_last_error=True)

def get_servant_conhost(pid, proclist):
    """Find “attendant” host process (conhost.exe)"""
    aux = [_ for _ in proclist if _[0] == pid]
    if len( aux) > 0:
        auxcon = [x for x in proclist if (
                x[1] == aux[0][0] and x[2] == "conhost.exe")]
        if len( auxcon) == 0:
            auxconret = get_servant_conhost(aux[0][1], proclist)
            return auxconret
        else:
            auxconret = auxcon[0]
            auxconret.append( aux[0][2])
            return auxconret
    else:
        return []


def get_conhost_threads():
    if sys.executable.lower().endswith('\\pythonw.exe'):
        return []
    import wmi
    c = wmi.WMI()
    w_where = ' or '.join([
        'Name like "p%.exe"',  # py.exe|python.exe|pwsh.exe|powershell.exe 
        'Name = "conhost.exe"',
        'Name = "cmd.exe"'
    ])
    w_properties = 'ProcessId, ParentProcessId, Name'
    w_wql = f'SELECT {w_properties} FROM Win32_Process where {w_where}'
    w_wqlaux = c.query(w_wql)
    proc_list = [[wqlitem.__getattr__('ProcessId'),
          wqlitem.__getattr__('ParentProcessId'),
          wqlitem.__getattr__('Name')] for wqlitem in w_wqlaux] 
    servant_conhost = get_servant_conhost(os.getpid(), proc_list)
    if len( servant_conhost) == 0:
        return []
    else:
        try:
            w_where = f'ProcessHandle = {servant_conhost[0]}'
            w_wql = f'SELECT Handle FROM Win32_Thread WHERE {w_where}'
            w_wqlHandle = c.query(w_wql)
            wqlthreads = [x.__getattr__('Handle') for x in w_wqlHandle]
        except:
            wqlthreads = []
    return wqlthreads


# required if run from `cmd` or from the `Run` dialog box (`<WinKey>+R`) 
conhost_threads = get_conhost_threads()
                                    

def get_current_keyboard_layout():
    foregroundWindow  = user32.GetForegroundWindow();
    foregroundProcess = user32.GetWindowThreadProcessId(int(foregroundWindow), 0);
    keyboardLayout    = user32.GetKeyboardLayout(int(foregroundProcess));
    keyboardLayout0   = user32.GetKeyboardLayout(int(0));
    if keyboardLayout == 0  or len(conhost_threads):                 
        if keyboardLayout == 0:
            keyboardLayout = keyboardLayout0
        for thread in conhost_threads:
            aux = user32.GetKeyboardLayout( int(thread))
            if aux != 0 and aux != keyboardLayout0:
                keyboardLayout = aux
                break
    return keyboardLayout

def getCurrentLayoutChar(key, shiftDown):
    c_hkl = get_current_keyboard_layout()
    char = ToUn(key, 0, 0,c_hkl, shiftDown)
    return char
Adam
  • 1,926
  • 23
  • 21