Skip to content

Commit

Permalink
#3857 poll for keyboard layout changes
Browse files Browse the repository at this point in the history
we don't seem to be getting the WM_INPUTLANGCHANGE message reliably, so we have to use polling.
and we have to use a different thread (we use the main thread via GLib) because otherwise we get stale values for the keyboard layout
  • Loading branch information
totaam committed May 19, 2023
1 parent 994a377 commit 41e6981
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
2 changes: 1 addition & 1 deletion xpra/client/gui/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ def get_keyboard_caps(self):
log("keyboard capabilities: %s", caps)
return caps

def window_keyboard_layout_changed(self, window):
def window_keyboard_layout_changed(self, window=None):
#win32 can change the keyboard mapping per window...
keylog("window_keyboard_layout_changed(%s)", window)
if self.keyboard_helper:
Expand Down
36 changes: 31 additions & 5 deletions xpra/platform/win32/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from ctypes.wintypes import HWND, DWORD, WPARAM, LPARAM, MSG, POINT, RECT, HGDIOBJ, LPCWSTR
from ctypes.util import find_library
from gi.repository import GLib

from xpra.client.gui import mixin_features
from xpra.exit_codes import ExitCode
Expand All @@ -37,6 +38,7 @@
GetIntSystemParametersInfo,
GetUserObjectInformationA, OpenInputDesktop, CloseDesktop,
GetMonitorInfo,
GetKeyboardLayoutName,
)
from xpra.common import KeyEvent
from xpra.util import AdHocStruct, csv, envint, envbool, typedict
Expand Down Expand Up @@ -94,6 +96,7 @@
#GTK3 is fixed, so we don't need this hook:
MAX_SIZE_HINT = WINDOW_HOOKS and envbool("XPRA_WIN32_MAX_SIZE_HINT", False)
LANGCHANGE = WINDOW_HOOKS and envbool("XPRA_WIN32_LANGCHANGE", True)
POLL_LAYOUT = envint("XPRA_WIN32_POLL_LAYOUT", 10)

FORWARD_WINDOWS_KEY = envbool("XPRA_FORWARD_WINDOWS_KEY", True)
WHEEL = envbool("XPRA_WHEEL", True)
Expand Down Expand Up @@ -569,7 +572,7 @@ def add_window_hooks(window):

if LANGCHANGE:
def inputlangchange(_hwnd, _event, wParam, lParam):
log("WM_INPUTLANGCHANGE: character set: %i, input locale identifier: %i", wParam, lParam)
keylog("WM_INPUTLANGCHANGE: character set: %i, input locale identifier: %i", wParam, lParam)
window.keyboard_layout_changed("WM_INPUTLANGCHANGE", wParam, lParam)
win32hooks.add_window_event_handler(win32con.WM_INPUTLANGCHANGE, inputlangchange)

Expand Down Expand Up @@ -1050,6 +1053,8 @@ def log_screensaver():
log.error("Error: cannot register focus and power callbacks:")
log.estr(e)
self.keyboard_hook_id = None
self.keyboard_poll_timer : int = 0
self.keyboard_id : int = self.get_keyboard_layout_id()
if FORWARD_WINDOWS_KEY and mixin_features.windows:
from xpra.make_thread import start_thread
start_thread(self.init_keyboard_listener, "keyboard-listener", daemon=True)
Expand Down Expand Up @@ -1080,12 +1085,33 @@ def cleanup(self):
log("ClientExtras.cleanup() ended")
#self.client = None

def get_keyboard_layout_id(self):
name_buf = create_string_buffer(win32con.KL_NAMELENGTH)
if not GetKeyboardLayoutName(name_buf):
return 0
log.warn(f"layout-name={name_buf.value}")
try:
#win32 API returns a hex string
return int(name_buf.value, 16)
except ValueError:
log.warn("Warning: failed to parse keyboard layout code '%s'", name_buf.value)
return 0

def poll_layout(self):
self.keyboard_poll_timer = 0
klid = self.get_keyboard_layout_id()
if klid and klid!=self.keyboard_id:
self.keyboard_id = klid
self.client.window_keyboard_layout_changed()

def init_keyboard_listener(self):
class KBDLLHOOKSTRUCT(Structure):
_fields_ = [("vk_code", DWORD),
("scan_code", DWORD),
("flags", DWORD),
("time", c_int),]
def toString(self):
return f"KBDLLHOOKSTRUCT({self.vk_code}, {self.scan_code}, {self.flags:x}, {self.time})"
DOWN = [win32con.WM_KEYDOWN, win32con.WM_SYSKEYDOWN]
#UP = [win32con.WM_KEYUP, win32con.WM_SYSKEYUP]
ALL_KEY_EVENTS = {win32con.WM_KEYDOWN : "KEYDOWN",
Expand All @@ -1095,6 +1121,8 @@ class KBDLLHOOKSTRUCT(Structure):
}
def low_level_keyboard_handler(nCode, wParam, lParam):
log("WH_KEYBOARD_LL: %s", (nCode, wParam, lParam))
if POLL_LAYOUT and self.keyboard_poll_timer==0:
self.keyboard_poll_timer = GLib.timeout_add(POLL_LAYOUT, self.poll_layout)
if nCode<0:
#docs say we should not process this event:
return CallNextHookEx(0, nCode, wParam, lParam)
Expand Down Expand Up @@ -1226,12 +1254,12 @@ def session_change_event(self, event, session):
log("will unfreeze all the windows")
#don't unfreeze directly from here,
#as the system may not be fully usable yet (see #997)
from gi.repository import GLib # @UnresolvedImport
GLib.idle_add(c.unfreeze)


def inputlangchange(self, wParam, lParam):
log("WM_INPUTLANGCHANGE: %i, %i", wParam, lParam)
keylog("WM_INPUTLANGCHANGE: %i, %i", wParam, lParam)
self.poll_layout()

def inichange(self, wParam, lParam):
if lParam:
Expand Down Expand Up @@ -1301,8 +1329,6 @@ def main():
log.enable_debug()
win32_event_logger.enable_debug()

from gi.repository import GLib # @UnresolvedImport

log.info("Event loop is running")
loop = GLib.MainLoop()

Expand Down

0 comments on commit 41e6981

Please sign in to comment.