Skip to content

Commit

Permalink
much improved keyboard support for win32 shadow servers:
Browse files Browse the repository at this point in the history
* move fake_key to keyboard module
* explicitly skip some button events we have no correspondence for
* partial handling for some modifier key synchronization
* fix backspace, escape mapping
* fix letter virtual key mapping (must rely on caps/shift only)
etc

git-svn-id: https://xpra.org/svn/Xpra/trunk@7794 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Sep 24, 2014
1 parent 0abac57 commit e389cb8
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 21 deletions.
87 changes: 80 additions & 7 deletions src/xpra/platform/win32/keyboard_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@
from xpra.server.keyboard_config_base import KeyboardConfigBase


MAPVK_VK_TO_VSC = 0
def fake_key(keycode, press):
if keycode<=0:
log.warn("no keycode found for %s", keycode)
return
#KEYEVENTF_SILENT = 0X4
flags = 0
if not press:
flags |= win32con.KEYEVENTF_KEYUP
#get the scancode:
scancode = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_VSC)
#see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646304(v=vs.85).aspx
log("fake_key(%s, %s) calling keybd_event(%s, %s, %s, 0)", keycode, press, keycode, scancode, flags)
win32api.keybd_event(keycode, scancode, flags, 0)


class KeyboardConfig(KeyboardConfigBase):

def __init__(self):
Expand All @@ -35,24 +51,62 @@ def get_keycode(self, client_keycode, keyname, modifiers):

def make_keymask_match(self, modifier_list, ignored_modifier_keycode=None, ignored_modifier_keynames=None):
log("make_keymask_match%s", (modifier_list, ignored_modifier_keycode, ignored_modifier_keynames))
mods = self.get_modifiers_state()
log("modifiers_state=%s", mods)
log("GetKeyboardState=%s", [int(x!=0) for x in win32api.GetKeyboardState()])
log("keys pressed=%s", ",".join(str(VK_NAMES.get(i, i)) for i in range(256) if win32api.GetAsyncKeyState(i)>0))
current = set(self.get_current_mask())
wanted = set(modifier_list or [])
log("make_keymask_match: current mask=%s, wanted=%s, ignoring=%s/%s", current, wanted, ignored_modifier_keycode, ignored_modifier_keynames)
if current==wanted:
return
def is_ignored(modifier):
if not ignored_modifier_keynames:
return False
for keyname in ignored_modifier_keynames: #ie: ["Control_R"]
keycode = KEYCODES.get(keyname) #ie: "Control_R" -> VK_RCONTROL
if keycode>0:
key_mod = MOD_KEYS.get(keycode) #ie: "control"
if key_mod==modifier:
return True
return False #not found

def change_mask(modifiers, press, info):
for modifier in modifiers:
if is_ignored(modifier):
log("change_mask: ignoring %s", modifier)
continue
#find the keycode:
for k,v in MOD_KEYS.items():
if ignored_modifier_keycode and ignored_modifier_keycode==k:
log("change_mask: ignoring %s / %s", VK_NAMES.get(k, k), v)
continue
if v==modifier:
#figure out if this is the one that needs toggling:
is_pressed = win32api.GetAsyncKeyState(k)
log("make_keymask_match: %s pressed=%s", k, is_pressed)
if bool(is_pressed)!=press:
log("make_keymask_match: using %s to %s %s", VK_NAMES.get(k, k), info, modifier)
fake_key(k, press)
break
change_mask(current.difference(wanted), False, "remove")
change_mask(wanted.difference(current), True, "add")

def get_modifiers_state(self):
def get_current_mask(self):
mods = set()
for vk, mod in MOD_KEYS.items():
if win32api.GetAsyncKeyState(vk)!=0:
mods.add(mod)
return list(mods)


MOD_KEYS = {
win32con.VK_LSHIFT : "shift",
win32con.VK_RSHIFT : "shift",
win32con.VK_LCONTROL : "control",
win32con.VK_RCONTROL : "control",
win32con.VK_CAPITAL : "lock",
win32con.VK_LMENU : "mod1", #Alt_L
win32con.VK_RMENU : "mod1", #Alt_R
win32con.VK_CAPITAL : "lock",
win32con.VK_NUMLOCK : "num",
}

#we currently assume that all key events are sent using X11 names,
Expand Down Expand Up @@ -92,7 +146,9 @@ def get_modifiers_state(self):
"OEM_ATTN" : "Oem",
"OEM_AUTO" : "Auto",
"OEM_AX" : "Ax",
"OEM_BACKTAB" : "BackSpace",
#"OEM_BACKTAB" : "BackSpace",
"BACK" : "BackSpace",
"ESCAPE" : "Escape",
"OEM_CLEAR" : "OemClr",
"OEM_COMMA" : "OEM_COMMA",
"OEM_COPY" : "Copy",
Expand Down Expand Up @@ -121,6 +177,7 @@ def get_modifiers_state(self):
"SELECT" : "Select",
"SEPARATOR" : "Separator",
"SPACE" : "Space",
"SPACE" : "space",
"SUBTRACT" : "minus",
"TAB" : "Tab",
"ZOOM" : "Zoom",
Expand Down Expand Up @@ -258,6 +315,12 @@ def get_modifiers_state(self):
"OEM_BACKTAB" : 0xF5,
}

VK_NAMES = {}
for name in dir(win32con):
if name.startswith("VK_"):
VK_NAMES[getattr(win32con, name)] = name
log("VK_NAMES=%s", VK_NAMES)

#lookup the constants:
KEYCODES = {}
for vk, name in VIRTUAL_KEYS.items():
Expand All @@ -269,9 +332,19 @@ def get_modifiers_state(self):
KEYCODES[name] = DEFS[vk]
else:
log.warn("missing key constant: %s", vk_name)
for c in "abcdefghijklmnopqrstuvwxyz":
KEYCODES.update({
"Shift_L" : win32con.VK_LSHIFT,
"Shift_R" : win32con.VK_RSHIFT,
"Control_L" : win32con.VK_LCONTROL,
"Control_R" : win32con.VK_RCONTROL,
"Caps_Lock" : win32con.VK_CAPITAL,
"Num_Lock" : win32con.VK_NUMLOCK,
"Scroll_Lock" : win32con.VK_SCROLL,
})

for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
KEYCODES[c] = ord(c)
KEYCODES[c.upper()] = ord(c.upper())
KEYCODES[c.lower()] = ord(c)
for c in "0123456789":
KEYCODES[c] = ord(c)
log("KEYCODES: %s", KEYCODES)
23 changes: 9 additions & 14 deletions src/xpra/platform/win32/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import win32con #@UnresolvedImport
from xpra.log import Logger
log = Logger("shadow", "win32")
keylog = Logger("keyboard", "shadow", "win32")

from xpra.server.gtk_root_window_model import GTKRootWindowModel
from xpra.server.gtk_server_base import GTKServerBase
from xpra.server.shadow_server_base import ShadowServerBase
from xpra.platform.win32.keyboard_config import KeyboardConfig
from xpra.platform.win32.keyboard_config import KeyboardConfig, fake_key

NOEVENT = object()
BUTTON_EVENTS = {
#(button,up-or-down) : win-event-name
(1, True) : (win32con.MOUSEEVENTF_LEFTDOWN, 0),
Expand All @@ -23,9 +25,12 @@
(3, True) : (win32con.MOUSEEVENTF_RIGHTDOWN, 0),
(3, False) : (win32con.MOUSEEVENTF_RIGHTUP, 0),
(4, True) : (win32con.MOUSEEVENTF_WHEEL, win32con.WHEEL_DELTA),
(4, False) : NOEVENT,
(5, True) : (win32con.MOUSEEVENTF_WHEEL, -win32con.WHEEL_DELTA),
(5, False) : NOEVENT,
}


class ShadowServer(ShadowServerBase, GTKServerBase):

def __init__(self):
Expand All @@ -48,19 +53,7 @@ def get_keyboard_config(self, props):
return KeyboardConfig()

def fake_key(self, keycode, press):
if keycode<=0:
log.warn("no keycode found for %s", keycode)
return
#KEYEVENTF_SILENT = 0X4;
flags = 0 #KEYEVENTF_SILENT
if press:
flags |= win32con.KEYEVENTF_KEYUP
#get the scancode:
MAPVK_VK_TO_VSC = 0
scancode = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_VSC)
#see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646304(v=vs.85).aspx
log("fake_key(%s, %s) calling keybd_event(%s, %s, %s, 0)", keycode, press, keycode, scancode, flags)
win32api.keybd_event(keycode, scancode, flags, 0)
fake_key(keycode, press)

def _process_button_action(self, proto, packet):
wid, button, pressed, pointer, modifiers = packet[1:6]
Expand All @@ -70,6 +63,8 @@ def _process_button_action(self, proto, packet):
if event is None:
log.warn("no matching event found for button=%s, pressed=%s", button, pressed)
return
elif event is NOEVENT:
return
x, y = pointer
dwFlags, dwData = event
win32api.mouse_event(dwFlags, x, y, dwData, 0)
Expand Down

0 comments on commit e389cb8

Please sign in to comment.