Reputation: 95
I want to detect keystrokes in python in English and also in Hebrew ,I am using Python version 3.7. I've already tried a lot of methods but but nothing works. The problem is that no matter what I do it does not recognize the letters in a language other than English. And if I write in Hebrew it's still appears in the text file in English. I need that even if I press Alt+Shift to changes my input to another language, it detect that language.
IMPORTANT: I need the Windows version.
Suppose this simple example:
# coding: utf-8
from pynput import keyboard
def on_press(key):
with open('keys.txt', 'a',encoding ='utf-8') as file:
file.write("{0}\n".format( str(key)))
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
Thanks.
Upvotes: 3
Views: 1608
Reputation: 30113
ToUnicodeEx
function.def ToUn
), you need to know the active input locale identifier (formerly called the keyboard layout). This isn't an easy task, and its solution is based on my own research, see the def get_current_keyboard_layout()
. The main problem is that the GetKeyboardLayout
function returns right value for the current thread merely under some shell/terminal (e.g. under Python IDLE or Visual Studio Code or Visual Studio 2019, …) and fails if launched from the Run
dialog box (WinKey+R) or from cmd.exe
or from pwsh.exe
or from any app which calls the conhost.exe
helper process.You can easy identify above three parts in the following (partially commented) script:
import os
import sys
if os.name != 'nt':
raise Exception("IMPORTANT: I need the Windows version.")
_verbose = ((len(sys.argv) > 1) and bool(sys.argv[1]))
### 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):
kst = create_string_buffer(256)
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 list_parents(pid, proclist):
'''For verbose output'''
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")]
list_parents(aux[0][1], proclist)
print('parent', aux[0], auxcon if (len(auxcon) == 0) else auxcon[0])
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]
if _verbose:
list_parents(os.getpid(), proc_list)
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()
if _verbose:
print( 'threads', 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:
if _verbose:
print('thread', thread)
keyboardLayout = aux
break
return keyboardLayout
### improved original code
# Detect keyboard input with support of other languages from English
# https://stackoverflow.com/questions/71431386/
###
# written for extensively verbose output
###
import unicodedata
from pynput import keyboard
last_key = keyboard.Key.media_next # improbable last key pressed
def on_press(key):
global last_key
if isinstance(key, keyboard.Key):
if key == keyboard.Key.space:
c_hkl = get_current_keyboard_layout()
chklp = f'{(c_hkl & 0xFFFFFFFF):08x}'
# 0x39 = SpaceBarScanCode https://kbdlayout.info/kbduk/scancodes
print(key.value.char,
f'{key.value.vk} ({key.value.vk:02x})',
f'{0x39} ({0x39:02x})',
f'{c_hkl:10} ({chklp})',
unicodedata.name(key.value.char, '?')
)
else:
if last_key != key:
print( f'{"":39}', key.value, key.name )
last_key = key
else:
c_hkl = get_current_keyboard_layout()
chklp = f'{(c_hkl & 0xFFFFFFFF):08x}'
c_char = ToUn(key.vk, key._scan, 0, c_hkl)
print(c_char,
f'{key.vk} ({key.vk:02x})',
f'{key._scan} ({key._scan:02x})',
f'{c_hkl:10} ({chklp})',
unicodedata.name(c_char[0], '?')
)
def on_release(key):
if key == keyboard.Key.esc:
return False # Stop listener
print('\n vk_code scancode HKL dec (HKL hexa) character name')
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
x=listener.join()
Output (tested mainly the F key for different keyboard layouts (input languages) switched cyclically by LeftAltShift): \SO\71431386.py
vk_code scancode HKL dec (HKL hexa) character name
f 70 (46) 33 (21) 67438601 (04050809) LATIN SMALL LETTER F
<164> alt_l
<160> shift
ф 70 (46) 33 (21) -265092071 (f0330419) CYRILLIC SMALL LETTER EF
<164> alt_l
<160> shift
f 70 (46) 33 (21) 67437573 (04050405) LATIN SMALL LETTER F
<164> alt_l
<160> shift
φ 70 (46) 33 (21) -266992632 (f0160408) GREEK SMALL LETTER PHI
<164> alt_l
<160> shift
f 70 (46) 33 (21) 67438601 (04050809) LATIN SMALL LETTER F
32 (20) 57 (39) 67438601 (04050809) SPACE
<27> esc
Upvotes: 5