Resolve captured keys by physical position, not by character
Die Tastendruck-Erkennung ging bisher ueber das erzeugte Zeichen (keysym bzw. Virtual-Key). Das dreht die Kette falsch herum: HID-Keycodes SIND physische Tastenpositionen -- das Board sendet eine Position, erst Windows macht daraus ueber das aktive Layout ein Zeichen. Auf deutschem Layout landete deshalb jedes Y auf der Z-Taste des Boards und umgekehrt, und AeOeUe/#/+/ss waren gar nicht erfassbar. versapad_keylayout.py loest das ueber MapVirtualKeyW (Virtual-Key -> Scan-Code) und eine layoutunabhaengige Scan-Code-zu-HID-Tabelle. GetKeyNameTextW liefert dazu den Namen, den eine Taste auf dem aktiven Layout traegt, so dass Dropdown und Grid "Strg+Z" zeigen, wenn Strg+Z gemeint ist. Beides sind passive Layout-Abfragen -- kein Hook, keine Fenstermanipulation, also nicht das, was AGENTS.md verbietet (die offizielle VersaGUI benutzt GetKeyNameText fuer denselben Zweck). Benannte Tasten laufen weiterhin zuerst ueber den Tk-keysym, und das ist kein Schoenheitsfehler: MapVirtualKeyW liefert fuer die Pfeiltasten denselben Scan-Code wie fuer ihre Numpad-Zwillinge (gemessen: VK_LEFT und VK_NUMPAD4 beide 0x4B). Ohne diesen Schritt waeren beide nicht zu unterscheiden. Namen sind zugleich Schluessel (Dropdown, hid_key_code_for_name), muessen also eindeutig bleiben -- auf deutschem Layout heisst HID 0x31 "#", ein Name den bisher HID 0x32 trug. Der Layoutname gewinnt, der verdraengte US-Name wird gekennzeichnet statt verworfen. Bestehende Belegungen aendern damit ihre Anzeige, nicht ihre Daten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8a00b3ae36
commit
42897d553c
2 changed files with 252 additions and 27 deletions
123
versapad_data.py
123
versapad_data.py
|
|
@ -8,6 +8,8 @@ Kein Schreibzugriff auf die JSONs -- reines Lesen/Anzeigen.
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import versapad_keylayout as kl
|
||||||
|
|
||||||
NUM_PROFILES = 3
|
NUM_PROFILES = 3
|
||||||
|
|
||||||
# Groesse der globalen Makrotabelle (SMacroTable, siehe versapad_protocol.
|
# Groesse der globalen Makrotabelle (SMacroTable, siehe versapad_protocol.
|
||||||
|
|
@ -228,29 +230,41 @@ def tk_event_to_hid(keysym, keycode, state, held_modifier=0):
|
||||||
Modifier (siehe TK_MODIFIER_KEYSYMS) -- beides wird verodert, damit ein
|
Modifier (siehe TK_MODIFIER_KEYSYMS) -- beides wird verodert, damit ein
|
||||||
verlorenes KeyRelease die Erkennung nicht verfaelscht.
|
verlorenes KeyRelease die Erkennung nicht verfaelscht.
|
||||||
|
|
||||||
Bei gehaltenem Shift zaehlt zuerst der Virtual-Key-Code: der keysym ist
|
Aufloesungsreihenfolge, und warum genau so:
|
||||||
dann das *verschobene* Zeichen (deutsches Layout: Shift+7 -> "slash",
|
|
||||||
was sonst faelschlich auf die Taste 0x38 zeigen wuerde), der VK-Code
|
|
||||||
bleibt derselbe wie ohne Shift.
|
|
||||||
|
|
||||||
Layout-Vorbehalt (derselbe wie bei den Dropdown-Labels, siehe
|
1. **Benannte Tasten ueber den keysym** (Enter, Pfeile, F-Tasten,
|
||||||
_SPECIAL_KEYS): HID-Keycodes sind US-Tastenpositionen, Tk liefert aber
|
Numpad, Entf ...). Die sind layoutunabhaengig eindeutig -- und der
|
||||||
nur Zeichen/VK-Codes des aktiven Layouts -- die physische Position
|
Weg ueber den Scan-Code waere hier sogar gefaehrlich: Windows liefert
|
||||||
(Scan-Code) waere dafuer noetig und ist ohne WinAPI nicht zu bekommen.
|
fuer die Pfeiltasten denselben Scan-Code wie fuer ihre
|
||||||
Auf deutschem Layout landen Y und Z deshalb vertauscht auf dem Board.
|
Numpad-Zwillinge (gemessen: VK_LEFT und VK_NUMPAD4 beide 0x4B).
|
||||||
Das Ergebnis ist im Dialog sichtbar (Dropdown + Modifier-Checkboxen
|
2. **Zeichentasten ueber die physische Position**
|
||||||
werden gefuellt) und laesst sich dort von Hand korrigieren."""
|
(`versapad_keylayout.hid_for_vk()`, Virtual-Key -> Scan-Code -> HID).
|
||||||
shifted = bool(state & 0x0001) or bool(held_modifier & 0x02)
|
HID-Keycodes SIND Positionen; alles, was ueber das erzeugte Zeichen
|
||||||
if shifted:
|
geht, ist auf nicht-US-Layouts falsch. Genau hier lag der Fehler, der
|
||||||
code = _WIN_VK_TO_HID.get(keycode)
|
auf deutschem Layout Y und Z vertauscht hat und AeOeUe/#/+ gar nicht
|
||||||
|
erfassbar machte. Der Virtual-Key ist ausserdem unabhaengig davon, ob
|
||||||
|
Shift oder AltGr mitgehalten wird.
|
||||||
|
3. **Naeherung ohne WinAPI** (keysym-Zeichentabelle, dann VK-Tabelle) --
|
||||||
|
nur relevant, wenn `versapad_keylayout` nicht verfuegbar ist
|
||||||
|
(Nicht-Windows, kein ctypes). Auf dieser Ebene bleibt es bei der
|
||||||
|
US-Layout-Naeherung inklusive vertauschtem Y/Z; bei gehaltenem Shift
|
||||||
|
zaehlt dort zuerst der VK-Code, weil der keysym dann das verschobene
|
||||||
|
Zeichen ist (deutsch: Shift+7 -> "slash").
|
||||||
|
"""
|
||||||
|
if keysym in TK_MODIFIER_KEYSYMS:
|
||||||
|
return None # Modifier sind nie das Ziel, sie setzen nur Bits
|
||||||
|
|
||||||
|
code = _TK_NAMED_KEYSYMS.get(keysym)
|
||||||
if code is None:
|
if code is None:
|
||||||
code = tk_keysym_to_hid(keysym)
|
code = kl.hid_for_vk(keycode)
|
||||||
|
if code is None:
|
||||||
|
if state & 0x0001 or held_modifier & 0x02:
|
||||||
|
code = _WIN_VK_TO_HID.get(keycode) or tk_keysym_to_hid(keysym)
|
||||||
else:
|
else:
|
||||||
code = tk_keysym_to_hid(keysym)
|
code = tk_keysym_to_hid(keysym) or _WIN_VK_TO_HID.get(keycode)
|
||||||
if code is None:
|
|
||||||
code = _WIN_VK_TO_HID.get(keycode)
|
|
||||||
if code is None:
|
if code is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
modifier = held_modifier
|
modifier = held_modifier
|
||||||
for bit, mod in TK_STATE_MODIFIER_BITS:
|
for bit, mod in TK_STATE_MODIFIER_BITS:
|
||||||
if state & bit:
|
if state & bit:
|
||||||
|
|
@ -269,9 +283,55 @@ ANIM_LABELS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_display_name_cache = None
|
||||||
|
|
||||||
|
|
||||||
|
def _display_key_names():
|
||||||
|
"""{hid_code: Anzeigename} -- Zeichentasten mit dem Namen des aktiven
|
||||||
|
Windows-Layouts, alles andere mit der gepflegten deutschen Bezeichnung
|
||||||
|
aus _SPECIAL_KEYS ("Enter", "Bild↑", "Num5"); die liest sich besser als
|
||||||
|
das, was Windows liefert ("EINGABE", "4 (ZEHNERTASTATUR)").
|
||||||
|
|
||||||
|
Einmal ermittelt und behalten -- ein Layoutwechsel zur Laufzeit wird
|
||||||
|
bewusst nicht nachgezogen (siehe versapad_keylayout.key_name()).
|
||||||
|
|
||||||
|
Die Namen sind gleichzeitig Schluessel im Dropdown und in
|
||||||
|
hid_key_code_for_name(), muessen also eindeutig bleiben. Kollisionen
|
||||||
|
entstehen real: auf deutschem Layout heisst HID 0x31 (US-Backslash-
|
||||||
|
Position) schlicht "#" -- und diesen Namen trug bisher HID 0x32
|
||||||
|
(Non-US-#). Der Layoutname gewinnt, der verdraengte US-Name wird
|
||||||
|
gekennzeichnet statt verworfen, damit die Taste ansprechbar bleibt."""
|
||||||
|
global _display_name_cache
|
||||||
|
if _display_name_cache is None:
|
||||||
|
names = dict(_SPECIAL_KEYS)
|
||||||
|
layout = {}
|
||||||
|
for code in sorted(_SPECIAL_KEYS):
|
||||||
|
if code not in kl.CHARACTER_HID_CODES:
|
||||||
|
continue
|
||||||
|
name = kl.key_name(code)
|
||||||
|
if name:
|
||||||
|
layout[code] = name
|
||||||
|
names.update(layout)
|
||||||
|
claimed = set(layout.values())
|
||||||
|
for code, name in list(names.items()):
|
||||||
|
if code not in layout and name in claimed:
|
||||||
|
names[code] = f"{name} (US-Layout)"
|
||||||
|
elif code not in layout:
|
||||||
|
claimed.add(name)
|
||||||
|
_display_name_cache = names
|
||||||
|
return _display_name_cache
|
||||||
|
|
||||||
|
|
||||||
|
def hid_key_name(keycode):
|
||||||
|
"""Anzeigename einer Taste, layoutrichtig wo es darauf ankommt
|
||||||
|
(deutsch: 0x1C -> "Z", 0x34 -> "ä"). Ohne verfuegbare Layout-Abfrage
|
||||||
|
(Nicht-Windows) bleibt es bei der US-Naeherung aus _SPECIAL_KEYS."""
|
||||||
|
return _display_key_names().get(keycode, f"0x{keycode:02X}")
|
||||||
|
|
||||||
|
|
||||||
def hid_key_choices():
|
def hid_key_choices():
|
||||||
"""Sortierte [(keycode, name), ...] fuer Dropdown-Auswahl beim Editieren."""
|
"""Sortierte [(keycode, name), ...] fuer Dropdown-Auswahl beim Editieren."""
|
||||||
return sorted(_SPECIAL_KEYS.items())
|
return [(code, hid_key_name(code)) for code in sorted(_SPECIAL_KEYS)]
|
||||||
|
|
||||||
|
|
||||||
def consumer_choices():
|
def consumer_choices():
|
||||||
|
|
@ -279,15 +339,28 @@ def consumer_choices():
|
||||||
return sorted(_CONSUMER_NAMES.items())
|
return sorted(_CONSUMER_NAMES.items())
|
||||||
|
|
||||||
|
|
||||||
_KEY_CODE_BY_NAME = {name: code for code, name in _SPECIAL_KEYS.items()}
|
|
||||||
_CONSUMER_ID_BY_NAME = {name: cid for cid, name in _CONSUMER_NAMES.items()}
|
_CONSUMER_ID_BY_NAME = {name: cid for cid, name in _CONSUMER_NAMES.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def _key_code_by_name():
|
||||||
|
"""Namen -> Keycode, Layoutnamen haben Vorrang vor den US-Namen.
|
||||||
|
|
||||||
|
Beide Schreibweisen bleiben gueltig, damit aeltere Aufrufe nicht brechen.
|
||||||
|
Bei einer echten Kollision (deutsch: "Z" ist US-Position 0x1D *und*
|
||||||
|
Layoutname von 0x1C) gewinnt bewusst das Layout: wer "Z" sagt, will die
|
||||||
|
Taste, die auf dieser Tastatur ein Z tippt -- alles andere waere genau
|
||||||
|
der Fehler, der hier gerade behoben wurde."""
|
||||||
|
by_name = {name: code for code, name in _SPECIAL_KEYS.items()}
|
||||||
|
by_name.update({name: code for code, name in _display_key_names().items()})
|
||||||
|
return by_name
|
||||||
|
|
||||||
|
|
||||||
def hid_key_code_for_name(name):
|
def hid_key_code_for_name(name):
|
||||||
"""z.B. 'S' -> 0x16. Wirft ValueError mit Vorschlaegen bei unbekanntem Namen."""
|
"""z.B. 'S' -> 0x16. Wirft ValueError mit Vorschlaegen bei unbekanntem Namen."""
|
||||||
if name not in _KEY_CODE_BY_NAME:
|
by_name = _key_code_by_name()
|
||||||
raise ValueError(f"Unbekannte Taste {name!r}. Gueltige Namen: {sorted(_KEY_CODE_BY_NAME)}")
|
if name not in by_name:
|
||||||
return _KEY_CODE_BY_NAME[name]
|
raise ValueError(f"Unbekannte Taste {name!r}. Gueltige Namen: {sorted(by_name)}")
|
||||||
|
return by_name[name]
|
||||||
|
|
||||||
|
|
||||||
def consumer_id_for_name(name):
|
def consumer_id_for_name(name):
|
||||||
|
|
@ -312,7 +385,7 @@ def macro_step_label(step):
|
||||||
"""step: {'keycode','modifier'} -> z.B. 'Strg+S'. Reine Keycode/Modifier-Variante
|
"""step: {'keycode','modifier'} -> z.B. 'Strg+S'. Reine Keycode/Modifier-Variante
|
||||||
von hid_key_label() (dort steckt beides in einem 16-Bit data-Feld, hier getrennt)."""
|
von hid_key_label() (dort steckt beides in einem 16-Bit data-Feld, hier getrennt)."""
|
||||||
mods = [name for bit, name in MODIFIER_BITS if step["modifier"] & bit]
|
mods = [name for bit, name in MODIFIER_BITS if step["modifier"] & bit]
|
||||||
key = _SPECIAL_KEYS.get(step["keycode"], f"0x{step['keycode']:02X}")
|
key = hid_key_name(step["keycode"])
|
||||||
return "+".join(mods + [key]) if mods else key
|
return "+".join(mods + [key]) if mods else key
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -340,7 +413,7 @@ def hid_key_label(data):
|
||||||
keycode = data & 0xFF
|
keycode = data & 0xFF
|
||||||
modifier = (data >> 8) & 0xFF
|
modifier = (data >> 8) & 0xFF
|
||||||
mods = [name for bit, name in MODIFIER_BITS if modifier & bit]
|
mods = [name for bit, name in MODIFIER_BITS if modifier & bit]
|
||||||
key = _SPECIAL_KEYS.get(keycode, f"0x{keycode:02X}")
|
key = hid_key_name(keycode)
|
||||||
return "+".join(mods + [key]) if mods else key
|
return "+".join(mods + [key]) if mods else key
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
152
versapad_keylayout.py
Normal file
152
versapad_keylayout.py
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
"""
|
||||||
|
Abfragen ans AKTIVE Windows-Tastaturlayout: physische Tastenposition
|
||||||
|
(Scan-Code) und der Name, den diese Taste auf dem aktuellen Layout traegt.
|
||||||
|
|
||||||
|
Warum ueberhaupt WinAPI, wo dieses Projekt sonst konsequent darauf
|
||||||
|
verzichtet: HID-Keycodes bezeichnen **physische Tastenpositionen** (das
|
||||||
|
Board sendet eine Position, erst Windows macht daraus ein Zeichen). Aus
|
||||||
|
einem Tk-Event kommen aber nur `keysym` und Virtual-Key-Code -- beides ist
|
||||||
|
bereits durch das Layout gefiltert. Auf deutschem Layout landete deshalb
|
||||||
|
jedes Y auf der Z-Taste des Boards und umgekehrt, und AeOeUe/#/+ waren gar
|
||||||
|
nicht erfassbar. Die Position ist ohne `MapVirtualKeyW` schlicht nicht zu
|
||||||
|
bekommen.
|
||||||
|
|
||||||
|
Abgrenzung zu den Domaenenregeln in AGENTS.md: verboten sind dort
|
||||||
|
**Fenstermanipulation** (`SetWindowLongW`/`ShowWindow` aufs eigene Fenster)
|
||||||
|
und **globale Tastaturhooks** (`SetWindowsHookEx`) -- genau die Aufrufe, die
|
||||||
|
in einem anderen Projekt AV-Fehlalarme ausgeloest haben. Hier passiert
|
||||||
|
nichts davon: `MapVirtualKeyW` und `GetKeyNameTextW` sind passive,
|
||||||
|
lesende Layout-Abfragen ohne Fenster-, Prozess- oder Eingabezugriff. Die
|
||||||
|
offizielle VersaGUI (C#) benutzt `GetKeyNameText()` fuer denselben Zweck.
|
||||||
|
|
||||||
|
Alles hier ist optional: auf Nicht-Windows oder wenn `user32` nicht laedt,
|
||||||
|
bleibt AVAILABLE False und alle Funktionen liefern None -- `versapad_data`
|
||||||
|
faellt dann auf seine US-Layout-Naeherung zurueck.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Scan-Code (PS/2 Set 1, wie MapVirtualKeyW ihn liefert) -> HID Usage Page
|
||||||
|
# 0x07. Diese Tabelle ist layoutunabhaengig und damit der eigentliche Kern:
|
||||||
|
# eine physische Taste hat immer denselben Scan-Code und denselben HID-Code,
|
||||||
|
# egal welches Zeichen das Layout daraufschreibt.
|
||||||
|
SCANCODE_TO_HID = {
|
||||||
|
0x01: 0x29, # Esc
|
||||||
|
0x02: 0x1E, 0x03: 0x1F, 0x04: 0x20, 0x05: 0x21, 0x06: 0x22,
|
||||||
|
0x07: 0x23, 0x08: 0x24, 0x09: 0x25, 0x0A: 0x26, 0x0B: 0x27, # 1-9, 0
|
||||||
|
0x0C: 0x2D, 0x0D: 0x2E, # US -/=, deutsch ss/Akut
|
||||||
|
0x0E: 0x2A, 0x0F: 0x2B, # Backspace, Tab
|
||||||
|
0x10: 0x14, 0x11: 0x1A, 0x12: 0x08, 0x13: 0x15, 0x14: 0x17, # Q W E R T
|
||||||
|
0x15: 0x1C, 0x16: 0x18, 0x17: 0x0C, 0x18: 0x12, 0x19: 0x13, # US Y U I O P
|
||||||
|
0x1A: 0x2F, 0x1B: 0x30, # US [/], deutsch Ue/+
|
||||||
|
0x1C: 0x28, # Enter
|
||||||
|
0x1E: 0x04, 0x1F: 0x16, 0x20: 0x07, 0x21: 0x09, 0x22: 0x0A, # A S D F G
|
||||||
|
0x23: 0x0B, 0x24: 0x0D, 0x25: 0x0E, 0x26: 0x0F, # H J K L
|
||||||
|
0x27: 0x33, 0x28: 0x34, # US ;/', deutsch Oe/Ae
|
||||||
|
0x29: 0x35, # US Backtick, deutsch Zirkumflex
|
||||||
|
0x2B: 0x31, # US Backslash, deutsch #
|
||||||
|
0x2C: 0x1D, 0x2D: 0x1B, 0x2E: 0x06, 0x2F: 0x19, 0x30: 0x05, # US Z X C V B
|
||||||
|
0x31: 0x11, 0x32: 0x10, # N M
|
||||||
|
0x33: 0x36, 0x34: 0x37, # Komma, Punkt (in beiden Layouts gleich)
|
||||||
|
0x35: 0x38, # US /, deutsch -
|
||||||
|
0x37: 0x55, # Num *
|
||||||
|
0x39: 0x2C, # Leertaste
|
||||||
|
0x3A: 0x39, # Caps
|
||||||
|
0x45: 0x53, 0x46: 0x47, # NumLock, Rollen
|
||||||
|
0x47: 0x5F, 0x48: 0x60, 0x49: 0x61, 0x4A: 0x56, # Num 7 8 9 -
|
||||||
|
0x4B: 0x5C, 0x4C: 0x5D, 0x4D: 0x5E, 0x4E: 0x57, # Num 4 5 6 +
|
||||||
|
0x4F: 0x59, 0x50: 0x5A, 0x51: 0x5B, # Num 1 2 3
|
||||||
|
0x52: 0x62, 0x53: 0x63, # Num 0 .
|
||||||
|
0x56: 0x64, # ISO-Zusatztaste (deutsch <>|)
|
||||||
|
0x57: 0x44, 0x58: 0x45, # F11, F12
|
||||||
|
}
|
||||||
|
for _i in range(10):
|
||||||
|
SCANCODE_TO_HID[0x3B + _i] = 0x3A + _i # F1-F10
|
||||||
|
|
||||||
|
HID_TO_SCANCODE = {hid: scan for scan, hid in SCANCODE_TO_HID.items()}
|
||||||
|
|
||||||
|
# Nur fuer diese HID-Codes lohnt der Layout-Name: es sind die Tasten, deren
|
||||||
|
# Beschriftung sich zwischen Layouts unterscheidet (Buchstaben, Ziffern,
|
||||||
|
# Satzzeichen). Fuer Enter/F5/Entf/Numpad bleiben die gepflegten deutschen
|
||||||
|
# Namen aus versapad_data._SPECIAL_KEYS besser als das, was Windows liefert
|
||||||
|
# ("EINGABE", "4 (ZEHNERTASTATUR)").
|
||||||
|
CHARACTER_HID_CODES = (
|
||||||
|
frozenset(range(0x04, 0x1E)) # A-Z (US-Positionen)
|
||||||
|
| frozenset(range(0x1E, 0x28)) # 1-9, 0
|
||||||
|
| frozenset(range(0x2D, 0x39)) # Satzzeichen
|
||||||
|
| frozenset({0x64}) # ISO-Zusatztaste
|
||||||
|
)
|
||||||
|
|
||||||
|
MAPVK_VK_TO_VSC_EX = 4
|
||||||
|
|
||||||
|
AVAILABLE = False
|
||||||
|
_user32 = None
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
_user32 = ctypes.WinDLL("user32", use_last_error=True)
|
||||||
|
_user32.MapVirtualKeyW.argtypes = [wintypes.UINT, wintypes.UINT]
|
||||||
|
_user32.MapVirtualKeyW.restype = wintypes.UINT
|
||||||
|
_user32.GetKeyNameTextW.argtypes = [wintypes.LONG, wintypes.LPWSTR, ctypes.c_int]
|
||||||
|
_user32.GetKeyNameTextW.restype = ctypes.c_int
|
||||||
|
AVAILABLE = True
|
||||||
|
except (ImportError, OSError, AttributeError):
|
||||||
|
_user32 = None # z.B. exotische Python-Builds ohne ctypes
|
||||||
|
|
||||||
|
|
||||||
|
def scancode_for_vk(vk):
|
||||||
|
"""Virtual-Key-Code -> Scan-Code der physischen Taste, oder None.
|
||||||
|
|
||||||
|
MAPVK_VK_TO_VSC_EX setzt fuer manche Tasten ein 0xE0-Praefix ins High-Byte
|
||||||
|
(erweiterte Tasten), fuer die Pfeiltasten hier aber gemessen NICHT -- die
|
||||||
|
liefern denselben Scan-Code wie ihre Numpad-Zwillinge. Genau deshalb
|
||||||
|
laeuft die Aufloesung in versapad_data zuerst ueber die eindeutigen
|
||||||
|
Tk-keysyms und benutzt diesen Weg nur fuer Zeichentasten."""
|
||||||
|
if not AVAILABLE:
|
||||||
|
return None
|
||||||
|
scan = _user32.MapVirtualKeyW(vk, MAPVK_VK_TO_VSC_EX)
|
||||||
|
return scan or None
|
||||||
|
|
||||||
|
|
||||||
|
def hid_for_vk(vk):
|
||||||
|
"""Virtual-Key-Code -> HID-Keycode ueber die physische Tastenposition.
|
||||||
|
Das ist der eigentliche Fix gegen vertauschtes Y/Z und nicht erfassbares
|
||||||
|
AeOeUe/#/+: der Umweg ueber das Zeichen entfaellt komplett."""
|
||||||
|
scan = scancode_for_vk(vk)
|
||||||
|
if scan is None:
|
||||||
|
return None
|
||||||
|
if (scan >> 8) == 0xE0:
|
||||||
|
return None # erweiterte Taste -- kommt hier nicht vor, siehe oben
|
||||||
|
return SCANCODE_TO_HID.get(scan & 0xFF)
|
||||||
|
|
||||||
|
|
||||||
|
def key_name(hid_code):
|
||||||
|
"""HID-Keycode -> Beschriftung dieser Taste auf dem aktiven Layout
|
||||||
|
(deutsch: HID 0x1C -> "Z", 0x34 -> "ä"), oder None wenn nicht ermittelbar.
|
||||||
|
|
||||||
|
Achtung: das Ergebnis gilt fuer das Layout, das beim Aufruf aktiv ist.
|
||||||
|
Ein Layoutwechsel zur Laufzeit wird nicht bemerkt -- fuer ein Tool, das
|
||||||
|
eine Tastatur konfiguriert, ist das vertretbar (der Cache in
|
||||||
|
versapad_data haelt entsprechend auch nur eine Fassung)."""
|
||||||
|
if not AVAILABLE:
|
||||||
|
return None
|
||||||
|
scan = HID_TO_SCANCODE.get(hid_code)
|
||||||
|
if scan is None:
|
||||||
|
return None
|
||||||
|
buffer = ctypes.create_unicode_buffer(64)
|
||||||
|
# lParam-Layout von GetKeyNameTextW: Bits 16-23 Scan-Code,
|
||||||
|
# Bit 24 "erweiterte Taste".
|
||||||
|
written = _user32.GetKeyNameTextW((scan & 0xFF) << 16, buffer, 64)
|
||||||
|
if not written:
|
||||||
|
return None
|
||||||
|
name = buffer.value.strip()
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
# Windows liefert Mehrzeichen-Namen in Grossbuchstaben ("AKUT",
|
||||||
|
# "ZIRKUMFLEX") -- als Dropdown-Eintrag zwischen "A" und "ä" liest sich
|
||||||
|
# Titelschreibung deutlich ruhiger.
|
||||||
|
if len(name) > 1 and name == name.upper():
|
||||||
|
return name.capitalize()
|
||||||
|
return name
|
||||||
Loading…
Add table
Add a link
Reference in a new issue