Skip to content

Commit

Permalink
#173:
Browse files Browse the repository at this point in the history
* add "xinput" build option and logging category
* add "input-devices" packet (and add it to large packet exemption list)
* pointer packets (motion and buttons) can now have extra optional arguments, including the deviceid
* move motion_moveresize code to a dedicated method we can re-use
* add "INPUT_DEVICES" feature to platforms so the command line option will only be shown on platforms that support different input device backends (just X11 for now)
* adjust all server code with the extra packet data for pointer packets (ie: shadow servers, desktop server, etc)
* move pointer update code to a class: XTestPointerDevice
* move
* inject the XI2 hooks into the gdk bindings if the client selects "--input-device=xi", disable the default handlers and use the XI callbacks instead
* the XI handler merges the "Raw" events  (if found) into the corresponding event

git-svn-id: https://xpra.org/svn/Xpra/trunk@15802 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed May 11, 2017
1 parent 49f9c31 commit 07564b4
Show file tree
Hide file tree
Showing 20 changed files with 1,125 additions and 128 deletions.
10 changes: 9 additions & 1 deletion src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def is_RH():
client_ENABLED = DEFAULT

x11_ENABLED = DEFAULT and not WIN32 and not OSX
xinput_ENABLED = x11_ENABLED
dbus_ENABLED = DEFAULT and x11_ENABLED and not (OSX or WIN32)
gtk_x11_ENABLED = DEFAULT and not WIN32 and not OSX
gtk2_ENABLED = DEFAULT and client_ENABLED and not PYTHON3
Expand Down Expand Up @@ -218,7 +219,8 @@ def is_RH():
"csc_libyuv",
"bencode", "cython_bencode", "vsock", "mdns",
"clipboard",
"server", "client", "dbus", "x11", "gtk_x11", "service",
"server", "client", "dbus", "x11", "xinput",
"gtk_x11", "service",
"gtk2", "gtk3",
"html5", "minify", "html5_gzip", "html5_brotli",
"pam",
Expand Down Expand Up @@ -916,6 +918,7 @@ def pkgconfig(*pkgs_options, **ekw):
"xpra/x11/bindings/core_bindings.c",
"xpra/x11/bindings/posix_display_source.c",
"xpra/x11/bindings/ximage.c",
"xpra/x11/bindings/xi2_bindings.c",
"xpra/platform/win32/propsys.cpp",
"xpra/platform/darwin/gdk_bindings.c",
"xpra/net/bencode/cython_bencode.c",
Expand Down Expand Up @@ -1740,6 +1743,11 @@ def osx_pkgconfig(*pkgs_options, **ekw):
["xpra/x11/bindings/ximage.pyx"],
**pkgconfig("x11", "xcomposite", "xdamage", "xext")
))
if xinput_ENABLED:
cython_add(Extension("xpra.x11.bindings.xi2_bindings",
["xpra/x11/bindings/xi2_bindings.pyx"],
**pkgconfig("x11", "xi")
))

toggle_packages(gtk_x11_ENABLED, "xpra.x11.gtk_x11")
if gtk_x11_ENABLED:
Expand Down
3 changes: 2 additions & 1 deletion src/xpra/client/client_base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file is part of Xpra.
# Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2010-2017 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
Expand Down Expand Up @@ -234,6 +234,7 @@ def setup_connection(self, conn):
self._protocol.large_packets.append("keymap-changed")
self._protocol.large_packets.append("server-settings")
self._protocol.large_packets.append("logging")
self._protocol.large_packets.append("input-devices")
self._protocol.set_compression_level(self.compression_level)
self._protocol.receive_aliases.update(self._aliases)
self._protocol.enable_default_encoder()
Expand Down
4 changes: 2 additions & 2 deletions src/xpra/client/client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ def _device_info(self, event):
except:
return ""

def _button_action(self, button, event, depressed):
def _button_action(self, button, event, depressed, *args):
if self._client.readonly:
return
pointer, modifiers, buttons = self._pointer_modifiers(event)
Expand All @@ -680,7 +680,7 @@ def _button_action(self, button, event, depressed):
b = sb
server_buttons.append(b)
def send_button(pressed):
self._client.send_button(wid, server_button, pressed, pointer, modifiers, server_buttons)
self._client.send_button(wid, server_button, pressed, pointer, modifiers, server_buttons, *args)
pressed_state = self.button_state.get(button, False)
if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False:
mouselog("button action: simulating a missing mouse-down event for window %s before sending the mouse-up event", wid)
Expand Down
140 changes: 72 additions & 68 deletions src/xpra/client/gtk_base/gtk_client_window_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is part of Xpra.
# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
# Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2010-2017 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
Expand Down Expand Up @@ -934,75 +934,78 @@ def call_action(self, action_type, action, state, pdata):

def do_motion_notify_event(self, event):
if self.moveresize_event:
x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event
dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction)
buttons = self._event_buttons(event)
if start_buttons is None:
#first time around, store the buttons
start_buttons = buttons
self.moveresize_event[4] = buttons
if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons):
geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize", dirstr, button, buttons)
self.moveresize_event = None
else:
x = event.x_root
y = event.y_root
dx = x-x_root
dy = y-y_root
#clamp resizing using size hints,
#or sane defaults: minimum of (1x1) and maximum of (2*15x2*25)
minw = self.geometry_hints.get("min_width", 1)
minh = self.geometry_hints.get("min_height", 1)
maxw = self.geometry_hints.get("max_width", 2**15)
maxh = self.geometry_hints.get("max_height", 2**15)
geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i", dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy)
if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT):
#height will be set to: wh+dy
dy = max(minh-wh, dy)
dy = min(maxh-wh, dy)
elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT):
#height will be set to: wh-dy
dy = min(wh-minh, dy)
dy = max(wh-maxh, dy)
if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT):
#width will be set to: ww+dx
dx = max(minw-ww, dx)
dx = min(maxw-ww, dx)
elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT):
#width will be set to: ww-dx
dx = min(ww-minw, dx)
dx = max(ww-maxw, dx)
#calculate move + resize:
if direction==MOVERESIZE_MOVE:
data = (wx+dx, wy+dy), None
elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT:
data = None, (ww+dx, wh+dy)
elif direction==MOVERESIZE_SIZE_BOTTOM:
data = None, (ww, wh+dy)
elif direction==MOVERESIZE_SIZE_BOTTOMLEFT:
data = (wx+dx, wy), (ww-dx, wh+dy)
elif direction==MOVERESIZE_SIZE_RIGHT:
data = None, (ww+dx, wh)
elif direction==MOVERESIZE_SIZE_LEFT:
data = (wx+dx, wy), (ww-dx, wh)
elif direction==MOVERESIZE_SIZE_TOPRIGHT:
data = (wx, wy+dy), (ww+dx, wh-dy)
elif direction==MOVERESIZE_SIZE_TOP:
data = (wx, wy+dy), (ww, wh-dy)
elif direction==MOVERESIZE_SIZE_TOPLEFT:
data = (wx+dx, wy+dy), (ww-dx, wh-dy)
else:
#not handled yet!
data = None
geomlog("%s for window %ix%i: started at %s, now at %s, delta=%s, button=%s, buttons=%s, data=%s", dirstr, ww, wh, (x_root, y_root), (x, y), (dx, dy), button, buttons, data)
if data:
#modifying the window is slower than moving the pointer,
#do it via a timer to batch things together
self.moveresize_data = data
if self.moveresize_timer is None:
self.moveresize_timer = self.timeout_add(20, self.do_moveresize)
self.motion_moveresize(event)
ClientWindowBase.do_motion_notify_event(self, event)

def motion_moveresize(self, event):
x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event
dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction)
buttons = self._event_buttons(event)
if start_buttons is None:
#first time around, store the buttons
start_buttons = buttons
self.moveresize_event[4] = buttons
if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons):
geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize", dirstr, button, buttons)
self.moveresize_event = None
else:
x = event.x_root
y = event.y_root
dx = x-x_root
dy = y-y_root
#clamp resizing using size hints,
#or sane defaults: minimum of (1x1) and maximum of (2*15x2*25)
minw = self.geometry_hints.get("min_width", 1)
minh = self.geometry_hints.get("min_height", 1)
maxw = self.geometry_hints.get("max_width", 2**15)
maxh = self.geometry_hints.get("max_height", 2**15)
geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i", dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy)
if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT):
#height will be set to: wh+dy
dy = max(minh-wh, dy)
dy = min(maxh-wh, dy)
elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT):
#height will be set to: wh-dy
dy = min(wh-minh, dy)
dy = max(wh-maxh, dy)
if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT):
#width will be set to: ww+dx
dx = max(minw-ww, dx)
dx = min(maxw-ww, dx)
elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT):
#width will be set to: ww-dx
dx = min(ww-minw, dx)
dx = max(ww-maxw, dx)
#calculate move + resize:
if direction==MOVERESIZE_MOVE:
data = (wx+dx, wy+dy), None
elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT:
data = None, (ww+dx, wh+dy)
elif direction==MOVERESIZE_SIZE_BOTTOM:
data = None, (ww, wh+dy)
elif direction==MOVERESIZE_SIZE_BOTTOMLEFT:
data = (wx+dx, wy), (ww-dx, wh+dy)
elif direction==MOVERESIZE_SIZE_RIGHT:
data = None, (ww+dx, wh)
elif direction==MOVERESIZE_SIZE_LEFT:
data = (wx+dx, wy), (ww-dx, wh)
elif direction==MOVERESIZE_SIZE_TOPRIGHT:
data = (wx, wy+dy), (ww+dx, wh-dy)
elif direction==MOVERESIZE_SIZE_TOP:
data = (wx, wy+dy), (ww, wh-dy)
elif direction==MOVERESIZE_SIZE_TOPLEFT:
data = (wx+dx, wy+dy), (ww-dx, wh-dy)
else:
#not handled yet!
data = None
geomlog("%s for window %ix%i: started at %s, now at %s, delta=%s, button=%s, buttons=%s, data=%s", dirstr, ww, wh, (x_root, y_root), (x, y), (dx, dy), button, buttons, data)
if data:
#modifying the window is slower than moving the pointer,
#do it via a timer to batch things together
self.moveresize_data = data
if self.moveresize_timer is None:
self.moveresize_timer = self.timeout_add(20, self.do_moveresize)

def do_moveresize(self):
self.moveresize_timer = None
mrd = self.moveresize_data
Expand Down Expand Up @@ -1323,6 +1326,7 @@ def _get_pointer(self, event):
def _pointer_modifiers(self, event):
x, y = self._get_pointer(event)
pointer = self._pointer(x, y)
#FIXME: state is used for both mods and buttons??
modifiers = self._client.mask_to_names(event.state)
buttons = self._event_buttons(event)
v = pointer, modifiers, buttons
Expand Down
22 changes: 16 additions & 6 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ def __init__(self):
self.server_is_desktop = False
self.server_supports_sharing = False
self.server_supports_window_filters = False
self.server_input_devices = None
#what we told the server about our encoding defaults:
self.encoding_defaults = {}

Expand Down Expand Up @@ -424,6 +425,7 @@ def vinfo(k):
self.client_supports_sharing = opts.sharing
self.log_both = (opts.remote_logging or "").lower()=="both"
self.client_supports_remote_logging = self.log_both or parse_bool("remote-logging", opts.remote_logging)
self.input_devices = opts.input_devices
#mouse wheel:
mw = (opts.mousewheel or "").lower().replace("-", "")
if mw not in FALSE_OPTIONS:
Expand Down Expand Up @@ -1201,17 +1203,17 @@ def init_opengl(self, enable_opengl):
def scale_pointer(self, pointer):
return int(pointer[0]/self.xscale), int(pointer[1]/self.yscale)

def send_button(self, wid, button, pressed, pointer, modifiers, buttons):
def send_button(state):
self.send_positional(["button-action", wid,
button, state,
pointer, modifiers, buttons])
def send_button(self, wid, button, pressed, pointer, modifiers, buttons, *args):
pressed_state = self._button_state.get(button, False)
if PYTHON3 and WIN32 and pressed_state==pressed:
mouselog("button action: unchanged state, ignoring event")
return
self._button_state[button] = pressed
send_button(pressed)
packet = ["button-action", wid,
button, pressed,
pointer, modifiers, buttons] + list(args)
mouselog("button packet: %s", packet)
self.send_positional(packet)


def window_keyboard_layout_changed(self, window):
Expand Down Expand Up @@ -1941,6 +1943,9 @@ def process_ui_capabilities(self):
if self.webcam_option=="on" or self.webcam_option.find("/dev/video")>=0:
self.start_sending_webcam()

#input devices:
self.server_input_devices = c.strget("input-devices")

#sound:
self.server_pulseaudio_id = c.strget("sound.pulseaudio.id")
self.server_pulseaudio_server = c.strget("sound.pulseaudio.server")
Expand Down Expand Up @@ -2144,6 +2149,11 @@ def _process_control(self, packet):
log.warn("received invalid control command from server: %s", command)


def send_input_devices(self, fmt, input_devices):
assert self.server_input_devices
self.send("input-devices", fmt, input_devices)


def start_sending_webcam(self):
with self.webcam_lock:
self.do_start_sending_webcam(self.webcam_option)
Expand Down
3 changes: 2 additions & 1 deletion src/xpra/log.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is part of Xpra.
# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
# Copyright (C) 2012-2016 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2012-2017 Antoine Martin <antoine@devloop.org.uk>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand Down Expand Up @@ -264,6 +264,7 @@ def enable_format(format_string):
])),
("X11", OrderedDict([
("x11" , "All X11 code"),
("xinput" , "XInput bindings"),
("bindings" , "X11 Cython bindings"),
("core" , "X11 core bindings"),
("randr" , "X11 RandR bindings"),
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/platform/darwin/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def stop_refresh(self):
GTKShadowServerBase.stop_refresh(self)


def do_process_mouse_common(self, proto, wid, pointer):
def do_process_mouse_common(self, proto, wid, pointer, *args):
CG.CGWarpMouseCursorPosition(pointer)

def fake_key(self, keycode, press):
Expand Down
5 changes: 4 additions & 1 deletion src/xpra/platform/features.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com>
# Copyright (C) 2011-2015 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2011-2017 Antoine Martin <antoine@devloop.org.uk>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand All @@ -15,6 +15,8 @@
SYSTEM_TRAY_SUPPORTED = False
REINIT_WINDOWS = False

INPUT_DEVICES = ["auto"]

CLIPBOARDS = []
CLIPBOARD_WANT_TARGETS = envbool("XPRA_CLIPBOARD_WANT_TARGETS")
CLIPBOARD_GREEDY = envbool("XPRA_CLIPBOARD_GREEDY")
Expand Down Expand Up @@ -62,6 +64,7 @@
"CLIPBOARD_NATIVE_CLASS",
"UI_THREAD_POLLING",
"CLIENT_MODULES",
"INPUT_DEVICES",
]
from xpra.platform import platform_import
platform_import(globals(), "features", False,
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/platform/win32/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ def refresh(self):
log("refresh()=%s", v)
return v

def do_process_mouse_common(self, proto, wid, pointer):
def do_process_mouse_common(self, proto, wid, pointer, *args):
#adjust pointer position for offset in client:
try:
SetCursorPos(*pointer)
Expand Down
2 changes: 2 additions & 0 deletions src/xpra/platform/xposix/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@

DEFAULT_SSH_CMD = "ssh"
CLIPBOARDS=["CLIPBOARD", "PRIMARY", "SECONDARY"]

INPUT_DEVICES = ["auto", "xi"]
Loading

0 comments on commit 07564b4

Please sign in to comment.