xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #173: xi-events-v10.patch

File xi-events-v10.patch, 79.9 KB (added by Antoine Martin, 4 years ago)

use uinput to inject events

  • etc/xpra/xorg.conf

     
    77  Option "DontVTSwitch" "true"
    88  Option "AllowMouseOpenFail" "true"
    99  Option "PciForceNone" "true"
    10   Option "AutoEnableDevices" "false"
    11   Option "AutoAddDevices" "false"
     10  Option "AutoEnableDevices" "true"
     11  Option "AutoAddDevices" "true"
    1212EndSection
    1313
    1414Section "Device"
  • setup.py

     
    914914                   "xpra/x11/bindings/core_bindings.c",
    915915                   "xpra/x11/bindings/posix_display_source.c",
    916916                   "xpra/x11/bindings/ximage.c",
     917                   "xpra/x11/bindings/xi2_bindings.c",
    917918                   "xpra/platform/win32/propsys.cpp",
    918919                   "xpra/platform/darwin/gdk_bindings.c",
    919920                   "xpra/net/bencode/cython_bencode.c",
     
    17341735                ["xpra/x11/bindings/ximage.pyx"],
    17351736                **pkgconfig("x11", "xcomposite", "xdamage", "xext")
    17361737                ))
     1738    cython_add(Extension("xpra.x11.bindings.xi2_bindings",
     1739                ["xpra/x11/bindings/xi2_bindings.pyx"],
     1740                **pkgconfig("x11", "xi")
     1741                ))
    17371742
    17381743toggle_packages(gtk_x11_ENABLED, "xpra.x11.gtk_x11")
    17391744if gtk_x11_ENABLED:
  • xpra/client/client_base.py

     
    234234        self._protocol.large_packets.append("keymap-changed")
    235235        self._protocol.large_packets.append("server-settings")
    236236        self._protocol.large_packets.append("logging")
     237        self._protocol.large_packets.append("input-devices")
    237238        self._protocol.set_compression_level(self.compression_level)
    238239        self._protocol.receive_aliases.update(self._aliases)
    239240        self._protocol.enable_default_encoder()
  • xpra/client/client_window_base.py

     
    644644        #overriden in GTKClientWindowBase
    645645        return self._id
    646646
     647    def do_xi_motion(self, *args):
     648        log.warn("do_xi_motion%s", args)
     649
     650
    647651    def do_motion_notify_event(self, event):
    648652        if self._client.readonly:
    649653            return
     
    659663        except:
    660664            return ""
    661665
    662     def _button_action(self, button, event, depressed):
     666    def _button_action(self, button, event, depressed, *args):
    663667        if self._client.readonly:
    664668            return
    665669        pointer, modifiers, buttons = self._pointer_modifiers(event)
     
    680684                b = sb
    681685            server_buttons.append(b)
    682686        def send_button(pressed):
    683             self._client.send_button(wid, server_button, pressed, pointer, modifiers, server_buttons)
     687            self._client.send_button(wid, server_button, pressed, pointer, modifiers, server_buttons, *args)
    684688        pressed_state = self.button_state.get(button, False)
    685689        if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False:
    686690            mouselog("button action: simulating a missing mouse-down event for window %s before sending the mouse-up event", wid)
  • xpra/client/gtk_base/gtk_client_window_base.py

     
    3636                       MOVERESIZE_SIZE_LEFT, MOVERESIZE_MOVE)
    3737
    3838from xpra.gtk_common.gobject_compat import import_gtk, import_gdk, import_cairo, import_pixbufloader, get_xid
    39 from xpra.gtk_common.gobject_util import no_arg_signal
     39from xpra.gtk_common.gobject_util import no_arg_signal, one_arg_signal
    4040from xpra.gtk_common.gtk_util import (get_pixbuf_from_data, get_default_root_window, is_realized,
    4141    WINDOW_POPUP, WINDOW_TOPLEVEL, GRAB_STATUS_STRING, GRAB_SUCCESS, SCROLL_UP, SCROLL_DOWN, SCROLL_LEFT, SCROLL_RIGHT)
    4242from xpra.gtk_common.keymap import KEY_TRANSLATIONS
     
    145145
    146146    __common_gsignals__ = {
    147147        "state-updated"         : no_arg_signal,
     148        "xi-motion"             : one_arg_signal,
    148149        }
    149150
    150151    #maximum size of the actual window:
     
    933934
    934935
    935936    def do_motion_notify_event(self, event):
     937        log.info("do_motion_notify_event")
    936938        if self.moveresize_event:
    937             x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event
    938             dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction)
    939             buttons = self._event_buttons(event)
    940             if start_buttons is None:
    941                 #first time around, store the buttons
    942                 start_buttons = buttons
    943                 self.moveresize_event[4] = buttons
    944             if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons):
    945                 geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize", dirstr, button, buttons)
    946                 self.moveresize_event = None
    947             else:
    948                 x = event.x_root
    949                 y = event.y_root
    950                 dx = x-x_root
    951                 dy = y-y_root
    952                 #clamp resizing using size hints,
    953                 #or sane defaults: minimum of (1x1) and maximum of (2*15x2*25)
    954                 minw = self.geometry_hints.get("min_width", 1)
    955                 minh = self.geometry_hints.get("min_height", 1)
    956                 maxw = self.geometry_hints.get("max_width", 2**15)
    957                 maxh = self.geometry_hints.get("max_height", 2**15)
    958                 geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i", dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy)
    959                 if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT):
    960                     #height will be set to: wh+dy
    961                     dy = max(minh-wh, dy)
    962                     dy = min(maxh-wh, dy)
    963                 elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT):
    964                     #height will be set to: wh-dy
    965                     dy = min(wh-minh, dy)
    966                     dy = max(wh-maxh, dy)
    967                 if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT):
    968                     #width will be set to: ww+dx
    969                     dx = max(minw-ww, dx)
    970                     dx = min(maxw-ww, dx)
    971                 elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT):
    972                     #width will be set to: ww-dx
    973                     dx = min(ww-minw, dx)
    974                     dx = max(ww-maxw, dx)
    975                 #calculate move + resize:
    976                 if direction==MOVERESIZE_MOVE:
    977                     data = (wx+dx, wy+dy), None
    978                 elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT:
    979                     data = None, (ww+dx, wh+dy)
    980                 elif direction==MOVERESIZE_SIZE_BOTTOM:
    981                     data = None, (ww, wh+dy)
    982                 elif direction==MOVERESIZE_SIZE_BOTTOMLEFT:
    983                     data = (wx+dx, wy), (ww-dx, wh+dy)
    984                 elif direction==MOVERESIZE_SIZE_RIGHT:
    985                     data = None, (ww+dx, wh)
    986                 elif direction==MOVERESIZE_SIZE_LEFT:
    987                     data = (wx+dx, wy), (ww-dx, wh)
    988                 elif direction==MOVERESIZE_SIZE_TOPRIGHT:
    989                     data = (wx, wy+dy), (ww+dx, wh-dy)
    990                 elif direction==MOVERESIZE_SIZE_TOP:
    991                     data = (wx, wy+dy), (ww, wh-dy)
    992                 elif direction==MOVERESIZE_SIZE_TOPLEFT:
    993                     data = (wx+dx, wy+dy), (ww-dx, wh-dy)
    994                 else:
    995                     #not handled yet!
    996                     data = None
    997                 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)
    998                 if data:
    999                     #modifying the window is slower than moving the pointer,
    1000                     #do it via a timer to batch things together
    1001                     self.moveresize_data = data
    1002                     if self.moveresize_timer is None:
    1003                         self.moveresize_timer = self.timeout_add(20, self.do_moveresize)
     939            self.motion_moveresize(event)
    1004940        ClientWindowBase.do_motion_notify_event(self, event)
    1005941
     942    def motion_moveresize(self, event):
     943        x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event
     944        dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction)
     945        buttons = self._event_buttons(event)
     946        if start_buttons is None:
     947            #first time around, store the buttons
     948            start_buttons = buttons
     949            self.moveresize_event[4] = buttons
     950        if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons):
     951            geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize", dirstr, button, buttons)
     952            self.moveresize_event = None
     953        else:
     954            x = event.x_root
     955            y = event.y_root
     956            dx = x-x_root
     957            dy = y-y_root
     958            #clamp resizing using size hints,
     959            #or sane defaults: minimum of (1x1) and maximum of (2*15x2*25)
     960            minw = self.geometry_hints.get("min_width", 1)
     961            minh = self.geometry_hints.get("min_height", 1)
     962            maxw = self.geometry_hints.get("max_width", 2**15)
     963            maxh = self.geometry_hints.get("max_height", 2**15)
     964            geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i", dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy)
     965            if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT):
     966                #height will be set to: wh+dy
     967                dy = max(minh-wh, dy)
     968                dy = min(maxh-wh, dy)
     969            elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT):
     970                #height will be set to: wh-dy
     971                dy = min(wh-minh, dy)
     972                dy = max(wh-maxh, dy)
     973            if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT):
     974                #width will be set to: ww+dx
     975                dx = max(minw-ww, dx)
     976                dx = min(maxw-ww, dx)
     977            elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT):
     978                #width will be set to: ww-dx
     979                dx = min(ww-minw, dx)
     980                dx = max(ww-maxw, dx)
     981            #calculate move + resize:
     982            if direction==MOVERESIZE_MOVE:
     983                data = (wx+dx, wy+dy), None
     984            elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT:
     985                data = None, (ww+dx, wh+dy)
     986            elif direction==MOVERESIZE_SIZE_BOTTOM:
     987                data = None, (ww, wh+dy)
     988            elif direction==MOVERESIZE_SIZE_BOTTOMLEFT:
     989                data = (wx+dx, wy), (ww-dx, wh+dy)
     990            elif direction==MOVERESIZE_SIZE_RIGHT:
     991                data = None, (ww+dx, wh)
     992            elif direction==MOVERESIZE_SIZE_LEFT:
     993                data = (wx+dx, wy), (ww-dx, wh)
     994            elif direction==MOVERESIZE_SIZE_TOPRIGHT:
     995                data = (wx, wy+dy), (ww+dx, wh-dy)
     996            elif direction==MOVERESIZE_SIZE_TOP:
     997                data = (wx, wy+dy), (ww, wh-dy)
     998            elif direction==MOVERESIZE_SIZE_TOPLEFT:
     999                data = (wx+dx, wy+dy), (ww-dx, wh-dy)
     1000            else:
     1001                #not handled yet!
     1002                data = None
     1003            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)
     1004            if data:
     1005                #modifying the window is slower than moving the pointer,
     1006                #do it via a timer to batch things together
     1007                self.moveresize_data = data
     1008                if self.moveresize_timer is None:
     1009                    self.moveresize_timer = self.timeout_add(20, self.do_moveresize)
     1010
    10061011    def do_moveresize(self):
    10071012        self.moveresize_timer = None
    10081013        mrd = self.moveresize_data
     
    13231328    def _pointer_modifiers(self, event):
    13241329        x, y = self._get_pointer(event)
    13251330        pointer = self._pointer(x, y)
     1331        #FIXME: state is used for both mods and buttons??
    13261332        modifiers = self._client.mask_to_names(event.state)
    13271333        buttons = self._event_buttons(event)
    13281334        v = pointer, modifiers, buttons
  • xpra/client/ui_client_base.py

     
    266266        self.server_is_desktop = False
    267267        self.server_supports_sharing = False
    268268        self.server_supports_window_filters = False
     269        self.server_input_devices = None
    269270        #what we told the server about our encoding defaults:
    270271        self.encoding_defaults = {}
    271272
     
    424425        self.client_supports_sharing = opts.sharing
    425426        self.log_both = (opts.remote_logging or "").lower()=="both"
    426427        self.client_supports_remote_logging = self.log_both or parse_bool("remote-logging", opts.remote_logging)
     428        self.input_devices = opts.input_devices
    427429        #mouse wheel:
    428430        mw = (opts.mousewheel or "").lower().replace("-", "")
    429431        if mw not in FALSE_OPTIONS:
     
    12011203    def scale_pointer(self, pointer):
    12021204        return int(pointer[0]/self.xscale), int(pointer[1]/self.yscale)
    12031205
    1204     def send_button(self, wid, button, pressed, pointer, modifiers, buttons):
    1205         def send_button(state):
    1206             self.send_positional(["button-action", wid,
    1207                                               button, state,
    1208                                               pointer, modifiers, buttons])
     1206    def send_button(self, wid, button, pressed, pointer, modifiers, buttons, *args):
    12091207        pressed_state = self._button_state.get(button, False)
    12101208        if PYTHON3 and WIN32 and pressed_state==pressed:
    12111209            mouselog("button action: unchanged state, ignoring event")
    12121210            return
    12131211        self._button_state[button] = pressed
    1214         send_button(pressed)
     1212        packet =  ["button-action", wid,
     1213                   button, pressed,
     1214                   pointer, modifiers, buttons] + list(args)
     1215        log.info("button packet: %s", packet)
     1216        self.send_positional(packet)
    12151217
    12161218
    12171219    def window_keyboard_layout_changed(self, window):
     
    19411943            if self.webcam_option=="on" or self.webcam_option.find("/dev/video")>=0:
    19421944                self.start_sending_webcam()
    19431945
     1946        #input devices:
     1947        self.server_input_devices = c.strget("input-devices")
     1948
    19441949        #sound:
    19451950        self.server_pulseaudio_id = c.strget("sound.pulseaudio.id")
    19461951        self.server_pulseaudio_server = c.strget("sound.pulseaudio.server")
     
    21442149            log.warn("received invalid control command from server: %s", command)
    21452150
    21462151
     2152    def send_input_devices(self, input_devices):
     2153        assert self.server_input_devices
     2154        self.send("input-devices", input_devices)
     2155
     2156
    21472157    def start_sending_webcam(self):
    21482158        with self.webcam_lock:
    21492159            self.do_start_sending_webcam(self.webcam_option)
  • xpra/log.py

     
    264264                ])),
    265265    ("X11", OrderedDict([
    266266                ("x11"          , "All X11 code"),
     267                ("xinput"       , "XInput bindings"),
    267268                ("bindings"     , "X11 Cython bindings"),
    268269                ("core"         , "X11 core bindings"),
    269270                ("randr"        , "X11 RandR bindings"),
  • xpra/platform/darwin/shadow_server.py

     
    139139        GTKShadowServerBase.stop_refresh(self)
    140140
    141141
    142     def do_process_mouse_common(self, proto, wid, pointer):
     142    def do_process_mouse_common(self, proto, wid, pointer, *args):
    143143        CG.CGWarpMouseCursorPosition(pointer)
    144144
    145145    def fake_key(self, keycode, press):
  • xpra/platform/features.py

     
    1515SYSTEM_TRAY_SUPPORTED = False
    1616REINIT_WINDOWS = False
    1717
     18INPUT_DEVICES = ["auto"]
     19
    1820CLIPBOARDS = []
    1921CLIPBOARD_WANT_TARGETS = envbool("XPRA_CLIPBOARD_WANT_TARGETS")
    2022CLIPBOARD_GREEDY = envbool("XPRA_CLIPBOARD_GREEDY")
     
    6264                   "CLIPBOARD_NATIVE_CLASS",
    6365                   "UI_THREAD_POLLING",
    6466                   "CLIENT_MODULES",
     67                   "INPUT_DEVICES",
    6568                   ]
    6669from xpra.platform import platform_import
    6770platform_import(globals(), "features", False,
  • xpra/platform/win32/shadow_server.py

     
    341341        log("refresh()=%s", v)
    342342        return v
    343343
    344     def do_process_mouse_common(self, proto, wid, pointer):
     344    def do_process_mouse_common(self, proto, wid, pointer, *args):
    345345        #adjust pointer position for offset in client:
    346346        try:
    347347            SetCursorPos(*pointer)
  • xpra/platform/xposix/features.py

     
    2121
    2222DEFAULT_SSH_CMD = "ssh"
    2323CLIPBOARDS=["CLIPBOARD", "PRIMARY", "SECONDARY"]
     24
     25INPUT_DEVICES = ["auto", "xi"]
  • xpra/platform/xposix/gui.py

     
    1616dbuslog = Logger("posix", "dbus")
    1717traylog = Logger("posix", "menu")
    1818menulog = Logger("posix", "menu")
     19mouselog = Logger("posix", "mouse")
    1920
    2021from xpra.os_util import strtobytes, bytestostr
    21 from xpra.util import iround, envbool
     22from xpra.util import iround, envbool, csv
    2223from xpra.gtk_common.gobject_compat import get_xid, is_gtk3
    2324
     25try:
     26    from xpra.x11.bindings.window_bindings import X11WindowBindings
     27    from xpra.x11.bindings.xi2_bindings import X11XI2Bindings   #@UnresolvedImport
     28except Exception as e:
     29    log.error("no X11 bindings", exc_info=True)
     30    X11WindowBindings = None
     31    X11XI2Bindings = None
     32
    2433device_bell = None
    2534GTK_MENUS = envbool("XPRA_GTK_MENUS", False)
    2635RANDR_DPI = envbool("XPRA_RANDR_DPI", True)
     
    93102def _get_X11_window_property(xid, name, req_type):
    94103    try:
    95104        from xpra.gtk_common.error import xsync
    96         from xpra.x11.bindings.window_bindings import X11WindowBindings, PropertyError #@UnresolvedImport
    97         window_bindings = X11WindowBindings()
     105        from xpra.x11.bindings.window_bindings import PropertyError #@UnresolvedImport
    98106        try:
     107            X11Window = X11WindowBindings()
    99108            with xsync:
    100                 prop = window_bindings.XGetWindowProperty(xid, name, req_type)
     109                prop = X11Window.XGetWindowProperty(xid, name, req_type)
    101110            log("_get_X11_window_property(%#x, %s, %s)=%s, len=%s", xid, name, req_type, type(prop), len(prop or []))
    102111            return prop
    103112        except PropertyError as e:
     
    108117    return None
    109118def _get_X11_root_property(name, req_type):
    110119    try:
    111         from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport
    112         window_bindings = X11WindowBindings()
    113         root_xid = window_bindings.getDefaultRootWindow()
     120        X11Window = X11WindowBindings()
     121        root_xid = X11Window.getDefaultRootWindow()
    114122        return _get_X11_window_property(root_xid, name, req_type)
    115123    except Exception as e:
    116124        log.warn("Warning: failed to get X11 root property '%s'", name)
     
    170178
    171179def _get_xsettings():
    172180    try:
    173         from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport
    174         window_bindings = X11WindowBindings()
     181        X11Window = X11WindowBindings()
    175182        selection = "_XSETTINGS_S0"
    176         owner = window_bindings.XGetSelectionOwner(selection)
     183        owner = X11Window.XGetSelectionOwner(selection)
    177184        if not owner:
    178185            return None
    179186        XSETTINGS = "_XSETTINGS_SETTINGS"
    180         data = window_bindings.XGetWindowProperty(owner, XSETTINGS, XSETTINGS)
     187        data = X11Window.XGetWindowProperty(owner, XSETTINGS, XSETTINGS)
    181188        if not data:
    182189            return None
    183190        from xpra.x11.xsettings_prop import get_settings
    184         return get_settings(window_bindings.get_display_name(), data)
     191        return get_settings(X11Window.get_display_name(), data)
    185192    except Exception as e:
    186193        log("_get_xsettings error: %s", e)
    187194    return None
     
    461468    try:
    462469        from xpra.x11.gtk2 import gdk_display_source
    463470        assert gdk_display_source
    464         from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport
     471        from xpra.x11.bindings.window_bindings import constants #@UnresolvedImport
    465472        X11Window = X11WindowBindings()
    466473        root_xid = X11Window.getDefaultRootWindow()
    467474        if window:
     
    499506    _toggle_wm_state(window, "_NET_WM_STATE_SHADED", shaded)
    500507
    501508
     509
     510WINDOW_ADD_HOOKS = []
     511def add_window_hooks(window):
     512    global WINDOW_ADD_HOOKS
     513    for x in WINDOW_ADD_HOOKS:
     514        x(window)
     515    log.warn("add_window_hooks(%s) added %s", window, WINDOW_ADD_HOOKS)
     516
     517WINDOW_REMOVE_HOOKS = []
     518def remove_window_hooks(window):
     519    global WINDOW_REMOVE_HOOKS
     520    for x in WINDOW_REMOVE_HOOKS:
     521        x(window)
     522    log.warn("remove_window_hooks(%s) added %s", window, WINDOW_REMOVE_HOOKS)
     523
     524
    502525def get_info():
    503526    from xpra.platform.gui import get_info_base
    504527    i = get_info_base()
     
    516539    return i
    517540
    518541
     542class XI2_Window(object):
     543    def __init__(self, window):
     544        log.warn("XI2_Window(%s)", window)
     545        self.XI2 = X11XI2Bindings()
     546        self.X11Window = X11WindowBindings()
     547        self.window = window
     548        self.xid = window.get_window().xid
     549        self.windows = ()
     550        window.connect("configure-event", self.configured)
     551        self.configured()
     552        #replace event handlers with XI2 version:
     553        self.do_motion_notify_event = window.do_motion_notify_event
     554        window.do_motion_notify_event = self.noop
     555        window.do_button_press_event = self.noop
     556        window.do_button_release_event = self.noop
     557        window.do_scroll_event = self.noop
     558        window.connect("destroy", self.cleanup)
     559
     560    def noop(self, *args):
     561        pass
     562
     563    def cleanup(self, *args):
     564        for window in self.windows:
     565            self.XI2.disconnect(window)
     566
     567    def configured(self, *args):
     568        self.windows = self.get_parent_windows(self.xid)
     569        for window in self.windows:
     570            self.XI2.connect(window, "XI_Motion", self.do_xi_motion)
     571            self.XI2.connect(window, "XI_ButtonPress", self.do_xi_button)
     572            self.XI2.connect(window, "XI_ButtonRelease", self.do_xi_button)
     573
     574    def get_parent_windows(self, oxid):
     575        windows = [oxid]
     576        root = self.X11Window.getDefaultRootWindow()
     577        xid = oxid
     578        while True:
     579            xid = self.X11Window.getParent(xid)
     580            if xid==0 or xid==root:
     581                break
     582            windows.append(xid)
     583        log("get_parent_windows(%#x)=%s", oxid, csv(hex(x) for x in windows))
     584        return windows
     585
     586
     587    def do_xi_button(self, event):
     588        window = self.window
     589        if self.window._client.readonly:
     590            return
     591        #TODO: server needs to tell us if it handles xinput:
     592        server_input_devices = "auto"
     593        if server_input_devices=="xinput":
     594            #skip synthetic scroll events for two-finger scroll,
     595            #as the server should synthesize them from the motion events
     596            #those have the same serial:
     597            matching_motion = self.XI2.find_event("XI_Motion", event.serial)
     598            #maybe we need more to distinguish?
     599            server_input_devices = "auto"
     600            if matching_motion:
     601                return
     602        button = event.detail
     603        depressed = (event.name == "XI_ButtonPress")
     604        args = self.get_pointer_extra_args(event)
     605        window._button_action(button, event, depressed, *args)
     606
     607    def do_xi_motion(self, event):
     608        window = self.window
     609        if window.moveresize_event:
     610            window.motion_moveresize(event)
     611            self.do_motion_notify_event(event)
     612            return
     613        if window._client.readonly:
     614            return
     615        log("do_motion_notify_event(%s)", event)
     616        #find the motion events in the xi2 event list:
     617        pointer, modifiers, buttons = window._pointer_modifiers(event)
     618        wid = self.window.get_mouse_event_wid(*pointer)
     619        mouselog("do_motion_notify_event(%s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s", event, wid, window._client._focused, self.window._id, event.device, pointer, modifiers, buttons)
     620        packet = ["pointer-position", wid, pointer, modifiers, buttons] + self.get_pointer_extra_args(event)
     621        mouselog.info("motion packet: %s", packet)
     622        window._client.send_mouse_position(packet)
     623
     624    def get_pointer_extra_args(self, event):
     625        def intscaled(f):
     626            return int(f*1000000), 1000000
     627        def dictscaled(v):
     628            return dict((k,intscaled(v)) for k,v in v.items())
     629        raw_valuators = {}
     630        raw_event_name = event.name.replace("XI_", "XI_Raw")    #ie: XI_Motion -> XI_RawMotion
     631        raw = self.XI2.find_event(raw_event_name, event.serial)
     632        #log.info("raw(%s)=%s", raw_event_name, raw)
     633        if raw:
     634            raw_valuators = raw.raw_valuators
     635        args = [event.device]
     636        for x in ("x", "y", "x_root", "y_root"):
     637            args.append(intscaled(getattr(event, x)))
     638        for v in [event.valuators, raw_valuators]:
     639            args.append(dictscaled(v))
     640        return args
     641
     642
    519643class ClientExtras(object):
    520644    def __init__(self, client, opts):
    521645        self.client = client
     
    528652        self.x11_filter = None
    529653        if client.xsettings_enabled:
    530654            self.setup_xprops()
     655        if client.input_devices=="xi":
     656            self.setup_xi()
    531657        self.setup_dbus_signals()
    532658
    533659    def ready(self):
     
    565691                bus._clean_up_signal_match(self.upower_sleeping_match)
    566692            if self.login1_match:
    567693                bus._clean_up_signal_match(self.login1_match)
     694        global WINDOW_METHOD_OVERRIDES
     695        WINDOW_METHOD_OVERRIDES = {}
    568696
    569697    def resuming_callback(self, *args):
    570698        eventlog("resuming_callback%s", args)
     
    647775        except ImportError as e:
    648776            log.error("failed to load X11 properties/settings bindings: %s - root window properties will not be propagated", e)
    649777
     778    def setup_xi(self):
     779        from xpra.gtk_common.error import xsync
     780        def may_enable_xi2():
     781            if self.client.server_input_devices!="xi":
     782                log.warn("Warning: server does not support xi input devices")
     783                return
     784            try:
     785                with xsync:
     786                    assert X11WindowBindings, "no X11 window bindings"
     787                    assert X11XI2Bindings, "no XI2 window bindings"
     788                    X11XI2Bindings().gdk_inject()
     789                    self.init_x11_filter()
     790                    XI2 = X11XI2Bindings()
     791                    XI2.select_xi2_events()
     792                    devices = XI2.get_devices()
     793                    if devices:
     794                        self.client.send_input_devices(devices)
     795            except Exception as e:
     796                log("enable_xi2()", exc_info=True)
     797                log.error("Error: cannot enable XI2 events")
     798                log.error(" %s", e)
     799            else:
     800                #register our enhanced event handlers:
     801                self.add_xi2_method_overrides()
     802        with xsync:
     803            try:
     804                #this would trigger warnings with our temporary opengl windows:
     805                #only enable it after we have connected:
     806                self.client.after_handshake(may_enable_xi2)
     807            except Exception as e:
     808                log("setup_xi()", exc_info=True)
     809                log.error("Error: failed to load the XI2 bindings")
     810                log.error(" %s", e)
     811
     812    def add_xi2_method_overrides(self):
     813        global WINDOW_ADD_HOOKS
     814        WINDOW_ADD_HOOKS = [XI2_Window]
     815
     816
    650817    def _get_xsettings(self):
    651818        try:
    652819            return self._xsettings_watcher.get_settings()
  • xpra/scripts/config.py

     
    489489                    "dbus-launch"       : str,
    490490                    "webcam"            : str,
    491491                    "mousewheel"        : str,
     492                    "input-devices"     : str,
    492493                    #ssl options:
    493494                    "ssl"               : str,
    494495                    "ssl-key"           : str,
     
    619620                  "quality", "min-quality", "speed", "min-speed",
    620621                  "compression_level",
    621622                  "dpi", "video-scaling", "auto-refresh-delay",
    622                   "webcam", "mousewheel", "pings",
     623                  "webcam", "mousewheel", "input-devices", "pings",
    623624                  "tray", "keyboard-sync", "cursors", "bell", "notifications",
    624625                  "xsettings", "system-tray", "sharing",
    625626                  "delay-tray", "windows", "readonly",
     
    811812                    "dbus-launch"       : "dbus-launch --close-stderr",
    812813                    "webcam"            : ["auto", "no"][OSX],
    813814                    "mousewheel"        : "on",
     815                    "input-devices"     : "auto",
    814816                    #ssl options:
    815817                    "ssl"               : "auto",
    816818                    "ssl-key"           : "",
  • xpra/scripts/main.py

     
    562562    group.add_option("--mousewheel", action="store",
    563563                      dest="mousewheel", default=defaults.mousewheel,
    564564                      help="Mouse wheel forwarding, can be used to disable the device or invert some axes. Default: %s." % defaults.webcam)
     565    from xpra.platform.features import INPUT_DEVICES
     566    if len(INPUT_DEVICES)>1:
     567        group.add_option("--input-devices", action="store", metavar="APINAME",
     568                          dest="input_devices", default=defaults.input_devices,
     569                          help="Which API to use for input devices. Default: %s." % defaults.input_devices)
     570    else:
     571        ignore({"input-devices" : INPUT_DEVICES[0]})
    565572    legacy_bool_parse("global-menus")
    566573    group.add_option("--global-menus", action="store",
    567574                      dest="global_menus", default=defaults.global_menus, metavar="yes|no",
  • xpra/server/gtk_server_base.py

     
    168168        pass
    169169
    170170
    171     def _move_pointer(self, wid, pos):
     171    def _move_pointer(self, wid, pos, *args):
    172172        x, y = pos
    173173        display = gdk.display_get_default()
    174174        display.warp_pointer(display.get_default_screen(), x, y)
  • xpra/server/server_base.py

     
    143143        self.webcam_encodings = []
    144144        self.webcam_forwarding_device = None
    145145        self.virtual_video_devices = 0
     146        self.input_devices = "auto"
     147        self.input_devices_data = None
    146148        self.mem_bytes = 0
    147149
    148150        #sound:
     
    256258        self.notifications = opts.notifications
    257259        self.scaling_control = parse_bool_or_int("video-scaling", opts.video_scaling)
    258260        self.webcam_forwarding = opts.webcam.lower() not in FALSE_OPTIONS
     261        self.input_devices = opts.input_devices or "auto"
    259262
    260263        #sound:
    261264        self.pulseaudio = opts.pulseaudio
     
    752755            "info-request":                         self._process_info_request,
    753756            "start-command":                        self._process_start_command,
    754757            "print":                                self._process_print,
     758            "input-devices":                        self._process_input_devices,
    755759            # Note: "clipboard-*" packets are handled via a special case..
    756760            })
    757761
     
    13761380                #newer flags:
    13771381                "av-sync",
    13781382                "auto-video-encoding",
    1379                 "window-filters"))
     1383                "window-filters",
     1384                ))
    13801385        f["sound"] = {
    13811386                      "ogg-latency-fix" : True,
    13821387                      "eos-sequence"    : True,
     
    14111416                 "webcam"                       : self.webcam_forwarding,
    14121417                 "webcam.encodings"             : self.webcam_encodings,
    14131418                 "virtual-video-devices"        : self.virtual_video_devices,
     1419                 "input-devices"                : self.input_devices,
    14141420                 })
    14151421            capabilities.update(self.file_transfer.get_file_transfer_features())
    14161422            capabilities.update(flatten_dict(self.get_server_features()))
     
    28572863        ss.user_event()
    28582864
    28592865
    2860     def _move_pointer(self, wid, pos):
     2866    def _move_pointer(self, wid, pos, *args):
    28612867        raise NotImplementedError()
    28622868
    28632869    def _adjust_pointer(self, proto, wid, pointer):
     
    28782884                        return px+(wx-cx), py+(wy-cy)
    28792885        return pointer
    28802886
    2881     def _process_mouse_common(self, proto, wid, pointer):
     2887    def _process_mouse_common(self, proto, wid, pointer, *args):
    28822888        pointer = self._adjust_pointer(proto, wid, pointer)
    2883         self.do_process_mouse_common(proto, wid, pointer)
     2889        #TODO: adjust args too
     2890        self.do_process_mouse_common(proto, wid, pointer, *args)
    28842891        return pointer
    28852892
    2886     def do_process_mouse_common(self, proto, wid, pointer):
     2893    def do_process_mouse_common(self, proto, wid, pointer, *args):
    28872894        pass
    28882895
    28892896
     
    29152922        if self.ui_driver and self.ui_driver!=ss.uuid:
    29162923            return
    29172924        self._update_modifiers(proto, wid, modifiers)
    2918         self._process_mouse_common(proto, wid, pointer)
     2925        self._process_mouse_common(proto, wid, pointer, *packet[5:])
    29192926
    29202927
    29212928    def _process_damage_sequence(self, proto, packet):
     
    31763183            ss.send_webcam_stop(device, str(e))
    31773184            self.stop_virtual_webcam()
    31783185
     3186    def _process_input_devices(self, ss, packet):
     3187        self.input_devices_data = packet[1]
     3188        from xpra.util import print_nested_dict
     3189        log.info("client input devices:")
     3190        print_nested_dict(self.input_devices_data, print_fn=log.info)
     3191        self.setup_input_devices()
    31793192
     3193    def setup_input_devices(self):
     3194        pass
     3195
    31803196    def process_clipboard_packet(self, ss, packet):
    31813197        if self.readonly:
    31823198            return
  • xpra/x11/bindings/core_bindings.pxd

     
    1515    cdef Display * display
    1616    cdef char * display_name
    1717    cdef Atom xatom(self, str_or_int)
     18
     19    cdef munge_packed_ints_to_longs(self, data)
     20    cdef munge_packed_longs_to_ints(self, data)
    1821#    def get_error_text(self, code)
  • xpra/x11/bindings/core_bindings.pyx

     
    11# This file is part of Xpra.
    22# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
    3 # Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk>
     3# Copyright (C) 2010-2017 Antoine Martin <antoine@devloop.org.uk>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
    77import os
    88import time
     9import struct
    910
    1011from xpra.util import dump_exc, envbool
    1112from xpra.os_util import strtobytes
     
    3334    ctypedef int Bool
    3435
    3536    Atom XInternAtom(Display * display, char * atom_name, Bool only_if_exists)
     37    char *XGetAtomName(Display *display, Atom atom)
    3638
    3739    int XFree(void * data)
    3840
     
    4749from display_source cimport get_display
    4850from display_source import get_display_name
    4951
     52
     53cdef _munge_packed_ints_to_longs(data):
     54    assert len(data) % sizeof(int) == 0
     55    n = len(data) / sizeof(int)
     56    format_from = "@" + "i" * n
     57    format_to = "@" + "l" * n
     58    return struct.pack(format_to, *struct.unpack(format_from, data))
     59
     60cdef _munge_packed_longs_to_ints(data):
     61    assert len(data) % sizeof(long) == 0
     62    n = len(data) / sizeof(long)
     63    format_from = "@" + "l" * n
     64    format_to = "@" + "i" * n
     65    #import binascii
     66    #log.info("_munge_packed_longs_to_ints(%s) hex data=%s", data, binascii.hexlify(data))
     67    return struct.pack(format_to, *struct.unpack(format_from, data))
     68
     69
    5070cdef _X11CoreBindings singleton = None
    5171def X11CoreBindings():
    5272    global singleton
     
    83103    def get_xatom(self, str_or_int):
    84104        return self.xatom(str_or_int)
    85105
     106    def XGetAtomName(self, Atom atom):
     107        v = XGetAtomName(self.display, atom)
     108        return v[:]
     109
     110    cdef munge_packed_ints_to_longs(self, data):
     111        return _munge_packed_ints_to_longs(data)
     112
     113    cdef munge_packed_longs_to_ints(self, data):
     114        return _munge_packed_longs_to_ints(data)
     115
     116
    86117    def get_error_text(self, code):
    87118        assert self.display!=NULL, "display is closed"
    88119        if type(code)!=int:
  • xpra/x11/bindings/window_bindings.pyx

     
    8181        pass
    8282
    8383    Atom XInternAtom(Display * display, char * atom_name, Bool only_if_exists)
    84     char *XGetAtomName(Display *display, Atom atom)
    8584
    8685    Window XDefaultRootWindow(Display * display)
    8786
     
    357356    void XDamageSubtract(Display *, Damage, XserverRegion repair, XserverRegion parts)
    358357
    359358
    360 
    361 
    362 
    363 cpdef _munge_packed_ints_to_longs(data):
    364     assert len(data) % sizeof(int) == 0
    365     n = len(data) / sizeof(int)
    366     format_from = "@" + "i" * n
    367     format_to = "@" + "l" * n
    368     return struct.pack(format_to, *struct.unpack(format_from, data))
    369 
    370 cpdef _munge_packed_longs_to_ints(data):
    371     assert len(data) % sizeof(long) == 0
    372     n = len(data) / sizeof(long)
    373     format_from = "@" + "l" * n
    374     format_to = "@" + "i" * n
    375     return struct.pack(format_to, *struct.unpack(format_from, data))
    376 
    377 
    378 
    379 
    380 
    381 
    382359cdef long cast_to_long(i):
    383360    if i < 0:
    384361        return <long>i
     
    461438        return XDefaultRootWindow(self.display)
    462439
    463440
    464     cpdef XGetAtomName(self, Atom atom):
    465         v = XGetAtomName(self.display, atom)
    466         return v[:]
    467 
    468441    def MapWindow(self, Window xwindow):
    469442        XMapWindow(self.display, xwindow)
    470443
     
    905878                                     0,
    906879                                     # This argument has to be divided by 4.  Thus
    907880                                     # speaks the spec.
    908                                      buffer_size / 4,
     881                                     buffer_size // 4,
    909882                                     False,
    910883                                     xreq_type, &xactual_type,
    911884                                     &actual_format, &nitems, &bytes_after, &prop)
     
    937910        data = (<char *> prop)[:nbytes]
    938911        XFree(prop)
    939912        if actual_format == 32:
    940             return _munge_packed_longs_to_ints(data)
     913            return self.munge_packed_longs_to_ints(data)
    941914        else:
    942915            return data
    943916
     
    985958        assert (len(data) % (format / 8)) == 0, "size of data is not a multiple of %s" % (format/8)
    986959        cdef int nitems = len(data) / (format / 8)
    987960        if format == 32:
    988             data = _munge_packed_ints_to_longs(data)
     961            data = self.munge_packed_ints_to_longs(data)
    989962        cdef char * data_str
    990963        data_str = data
    991964        #print("XChangeProperty(%#x, %s, %s) data=%s" % (xwindow, property, value, str([hex(x) for x in data_str])))
  • xpra/x11/bindings/xi2_bindings.pyx

     
     1# This file is part of Xpra.
     2# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import os
     7import time
     8import struct
     9import collections
     10
     11from xpra.log import Logger
     12log = Logger("x11", "bindings", "xinput")
     13
     14from xpra.x11.gtk2.common import X11Event
     15
     16from libc.stdint cimport uintptr_t
     17
     18
     19###################################
     20# Headers, python magic
     21###################################
     22cdef extern from "string.h":
     23    void* memset(void * ptr, int value, size_t num)
     24
     25cdef extern from "X11/Xutil.h":
     26    pass
     27
     28######
     29# Xlib primitives and constants
     30######
     31
     32include "constants.pxi"
     33ctypedef unsigned long CARD32
     34
     35cdef extern from "X11/Xlib.h":
     36    ctypedef struct Display:
     37        pass
     38
     39    ctypedef CARD32 XID
     40    ctypedef int Bool
     41    ctypedef int Status
     42    ctypedef CARD32 Atom
     43    ctypedef XID Window
     44    ctypedef CARD32 Time
     45
     46    ctypedef struct XGenericEventCookie:
     47        int            type     # of event. Always GenericEvent
     48        unsigned long  serial
     49        Bool           send_event
     50        Display        *display
     51        int            extension    #major opcode of extension that caused the event
     52        int            evtype       #actual event type
     53        unsigned int   cookie
     54        void           *data
     55
     56    int XIAnyPropertyType
     57
     58    Atom XInternAtom(Display * display, char * atom_name, Bool only_if_exists)
     59    int XFree(void * data)
     60
     61    Bool XQueryExtension(Display * display, char *name,
     62                         int *major_opcode_return, int *first_event_return, int *first_error_return)
     63
     64    Bool XGetEventData(Display *display, XGenericEventCookie *cookie)
     65    void XFreeEventData(Display *display, XGenericEventCookie *cookie)
     66
     67    Window XDefaultRootWindow(Display * display)
     68
     69    Bool XQueryPointer(Display *display, Window w, Window *root_return, Window *child_return, int *root_x_return, int *root_y_return,
     70                       int *win_x_return, int *win_y_return, unsigned int *mask_return)
     71    int XFlush(Display *dpy)
     72
     73cdef extern from "X11/extensions/XInput2.h":
     74    int XI_LASTEVENT
     75    int XI_DeviceChanged
     76    int XI_KeyPress
     77    int XI_KeyRelease
     78    int XI_ButtonPress
     79    int XI_ButtonRelease
     80    int XI_Motion
     81    int XI_Enter
     82    int XI_Leave
     83    int XI_FocusIn
     84    int XI_FocusOut
     85    int XI_HierarchyChanged
     86    int XI_PropertyEvent
     87    int XI_RawKeyPress
     88    int XI_RawKeyRelease
     89    int XI_RawButtonPress
     90    int XI_RawButtonRelease
     91    int XI_RawMotion
     92    int XI_TouchBegin
     93    int XI_TouchUpdate
     94    int XI_TouchEnd
     95    int XI_TouchOwnership
     96    int XI_RawTouchBegin
     97    int XI_RawTouchUpdate
     98    int XI_RawTouchEnd
     99
     100    int XIMasterPointer
     101    int XIMasterKeyboard
     102    int XISlavePointer
     103    int XISlaveKeyboard
     104    int XIFloatingSlave
     105
     106    int XIButtonClass
     107    int XIKeyClass
     108    int XIValuatorClass
     109    int XIScrollClass
     110    int XITouchClass   
     111
     112    int XIAllDevices
     113    int XIAllMasterDevices
     114
     115    ctypedef struct XIValuatorState:
     116        int           mask_len
     117        unsigned char *mask
     118        double        *values
     119
     120    ctypedef struct XIEvent:
     121        int           type
     122        unsigned long serial
     123        Bool          send_event
     124        Display       *display
     125        int           extension
     126        int           evtype
     127        Time          time
     128
     129    ctypedef struct XIRawEvent:
     130        int           type      #GenericEvent
     131        unsigned long serial
     132        Bool          send_event
     133        Display       *display
     134        int           extension #XI extension offset
     135        int           evtype    #XI_RawKeyPress, XI_RawKeyRelease, etc
     136        Time          time
     137        int           deviceid
     138        int           sourceid
     139        int           detail
     140        int           flags
     141        XIValuatorState valuators
     142        double        *raw_values
     143
     144    ctypedef struct XIButtonState:
     145        int           mask_len
     146        unsigned char *mask
     147
     148    ctypedef struct XIModifierState:
     149        int    base
     150        int    latched
     151        int    locked
     152        int    effective
     153
     154    ctypedef XIModifierState XIGroupState
     155
     156    ctypedef struct XIDeviceEvent:
     157        int           type
     158        unsigned long serial
     159        Bool          send_event
     160        Display       *display
     161        int           extension
     162        int           evtype
     163        Time          time
     164        int           deviceid
     165        int           sourceid
     166        int           detail
     167        Window        root
     168        Window        event
     169        Window        child
     170        double        root_x
     171        double        root_y
     172        double        event_x
     173        double        event_y
     174        int           flags
     175        XIButtonState       buttons
     176        XIValuatorState     valuators
     177        XIModifierState     mods
     178        XIGroupState        group
     179
     180    ctypedef struct XIHierarchyInfo:
     181        int           deviceid
     182        int           attachment
     183        int           use
     184        Bool          enabled
     185        int           flags
     186
     187    ctypedef struct XIHierarchyEvent:
     188        int           type
     189        unsigned long serial
     190        Bool          send_event
     191        Display       *display
     192        int           extension
     193        int           evtype            #XI_HierarchyChanged
     194        Time          time
     195        int           flags
     196        int           num_info
     197        XIHierarchyInfo *info
     198
     199    ctypedef struct XIEventMask:
     200        int                 deviceid
     201        int                 mask_len
     202        unsigned char*      mask
     203
     204    ctypedef struct XIAnyClassInfo:
     205        int         type
     206        int         sourceid
     207
     208    ctypedef struct XIDeviceInfo:
     209        int                 deviceid
     210        char                *name
     211        int                 use
     212        int                 attachment
     213        Bool                enabled
     214        int                 num_classes
     215        XIAnyClassInfo      **classes
     216
     217    ctypedef struct XIButtonClassInfo:
     218        int         type
     219        int         sourceid
     220        int         num_buttons
     221        Atom        *labels
     222        XIButtonState state
     223
     224    ctypedef struct XIKeyClassInfo:
     225        int         type
     226        int         sourceid
     227        int         num_keycodes
     228        int         *keycodes
     229
     230    ctypedef struct XIValuatorClassInfo:
     231        int         type
     232        int         sourceid
     233        int         number
     234        Atom        label
     235        double      min
     236        double      max
     237        double      value
     238        int         resolution
     239        int         mode
     240
     241    ctypedef struct XIScrollClassInfo:
     242        int         type
     243        int         sourceid
     244        int         number
     245        int         scroll_type
     246        double      increment
     247        int         flags
     248
     249    ctypedef struct XITouchClassInfo:
     250        int         type
     251        int         sourceid
     252        int         mode
     253        int         num_touches
     254
     255    Status XIQueryVersion(Display *display, int *major_version_inout, int *minor_version_inout)
     256    Status XISelectEvents(Display *display, Window win, XIEventMask *masks, int num_masks)
     257    XIDeviceInfo* XIQueryDevice(Display *display, int deviceid, int *ndevices_return)
     258    void XIFreeDeviceInfo(XIDeviceInfo *info)
     259    Atom *XIListProperties(Display *display, int deviceid, int *num_props_return)
     260    Status XIGetProperty(Display *display, int deviceid, Atom property, long offset, long length,
     261                         Bool delete_property, Atom type, Atom *type_return,
     262                         int *format_return, unsigned long *num_items_return,
     263                         unsigned long *bytes_after_return, unsigned char **data)
     264
     265
     266DEF MAX_XI_EVENTS = 64
     267DEF XI_EVENT_MASK_SIZE = (MAX_XI_EVENTS+7)//8
     268
     269XI_EVENT_NAMES = {
     270    XI_DeviceChanged    : "XI_DeviceChanged",
     271    XI_KeyPress         : "XI_KeyPress",
     272    XI_KeyRelease       : "XI_KeyRelease",
     273    XI_ButtonPress      : "XI_ButtonPress",
     274    XI_ButtonRelease    : "XI_ButtonRelease",
     275    XI_Motion           : "XI_Motion",
     276    XI_Enter            : "XI_Enter",
     277    XI_Leave            : "XI_Leave",
     278    XI_FocusIn          : "XI_FocusIn",
     279    XI_FocusOut         : "XI_FocusOut",
     280    XI_HierarchyChanged : "XI_HierarchyChanged",
     281    XI_PropertyEvent    : "XI_PropertyEvent",
     282    XI_RawKeyPress      : "XI_RawKeyPress",
     283    XI_RawKeyRelease    : "XI_RawKeyRelease",
     284    XI_RawButtonPress   : "XI_RawButtonPress",
     285    XI_RawButtonRelease : "XI_RawButtonRelease",
     286    XI_RawMotion        : "XI_RawMotion",
     287    XI_TouchBegin       : "XI_TouchBegin",
     288    XI_TouchUpdate      : "XI_TouchUpdate",
     289    XI_TouchEnd         : "XI_TouchEnd",
     290    XI_TouchOwnership   : "XI_TouchOwnership",
     291    XI_RawTouchBegin    : "XI_RawTouchBegin",
     292    XI_RawTouchUpdate   : "XI_RawTouchUpdate",
     293    XI_RawTouchEnd      : "XI_RawTouchEnd",
     294    }
     295
     296XI_USE = {
     297    XIMasterPointer     : "master pointer",
     298    XIMasterKeyboard    : "master keyboard",
     299    XISlavePointer      : "slave pointer",
     300    XISlaveKeyboard     : "slave keyboard",
     301    XIFloatingSlave     : "floating slave",
     302    }
     303
     304CLASS_INFO = {
     305    XIButtonClass       : "button",
     306    XIKeyClass          : "key",
     307    XIValuatorClass     : "valuator",
     308    XIScrollClass       : "scroll",
     309    XITouchClass        : "touch",
     310    }
     311
     312
     313from core_bindings cimport _X11CoreBindings
     314
     315cdef _X11XI2Bindings singleton = None
     316def X11XI2Bindings():
     317    global singleton
     318    if singleton is None:
     319        singleton = _X11XI2Bindings()
     320    return singleton
     321
     322cdef class _X11XI2Bindings(_X11CoreBindings):
     323
     324    cdef int opcode
     325    cdef object events
     326    cdef object event_handlers
     327
     328    def __init__(self):
     329        self.opcode = -1
     330        self.event_handlers = {}
     331        self.reset_events()
     332
     333    def __repr__(self):
     334        return "X11XI2Bindings(%s)" % self.display_name
     335
     336    def connect(self, window, event, handler):
     337        self.event_handlers.setdefault(window, {})[event] = handler
     338
     339    def disconnect(self, window):
     340        try:
     341            del self.event_handlers[window]
     342        except:
     343            pass
     344
     345
     346    def reset_events(self):
     347        self.events = collections.deque(maxlen=100)
     348
     349    def find_event(self, event_name, serial):
     350        for x in reversed(self.events):
     351            #log.info("find_event(%s, %#x) checking %s", event_name, serial, x)
     352            if x.name==event_name and x.serial==serial:
     353                #log.info("matched")
     354                return x
     355            if x.serial<serial:
     356                #log.info("serial too old")
     357                return None
     358        return None
     359
     360    def find_events(self, event_name, windows):
     361        cdef Window found = 0
     362        cdef Window window
     363        matches = []
     364        for x in reversed(self.events):
     365            window = x.xid
     366            if x.name==event_name and ((found>0 and found==window) or (found==0 and window in windows)):
     367                matches.append(x)
     368                found = window
     369            elif found:
     370                break
     371        return matches
     372
     373    cdef int get_xi_opcode(self, int major=2, int minor=2):
     374        if self.opcode!=-1:
     375            return self.opcode
     376        cdef int opcode, event, error
     377        if not XQueryExtension(self.display, "XInputExtension", &opcode, &event, &error):
     378            log.warn("Warning: XI2 events are not supported")
     379            self.opcode = 0
     380            return 0
     381        cdef int rmajor = major, rminor = minor
     382        cdef int rc = XIQueryVersion(self.display, &rmajor, &rminor)
     383        if rc == BadRequest:
     384            log.warn("Warning: no XI2 %i.%i support,", major, minor)
     385            log.warn(" server supports version %i.%i only", rmajor, rminor)
     386            self.opcode = 0
     387            return 0
     388        elif rc:
     389            log.warn("Warning: Xlib bug querying XI2, code %i", rc)
     390            self.opcode = 0
     391            return 0
     392        self.opcode = opcode
     393        log("get_xi_opcode%s=%i", (major, minor), opcode)
     394        return opcode
     395
     396    cdef register_parser(self):
     397        log("register_parser()")
     398        if self.opcode>0:
     399            from xpra.x11.gtk2.gdk_bindings import add_x_event_parser
     400            add_x_event_parser(self.opcode, self.parse_xi_event)
     401
     402    cdef register_gdk_events(self):
     403        log("register_gdk_events()")
     404        if self.opcode<=0:
     405            return
     406        global XI_EVENT_NAMES
     407        from xpra.x11.gtk2.gdk_bindings import add_x_event_signal, add_x_event_type_name
     408        for e, xi_event_name in {
     409            XI_DeviceChanged    : "device-changed",
     410            XI_KeyPress         : "key-press",
     411            XI_KeyRelease       : "key-release",
     412            XI_ButtonPress      : "button-press",
     413            XI_ButtonRelease    : "button-release",
     414            XI_Motion           : "motion",
     415            XI_Enter            : "enter",
     416            XI_Leave            : "leave",
     417            XI_FocusIn          : "focus-in",
     418            XI_FocusOut         : "focus-out",
     419            XI_HierarchyChanged : "focus-changed",
     420            XI_PropertyEvent    : "property-event",
     421            XI_RawKeyPress      : "raw-key-press",
     422            XI_RawKeyRelease    : "raw-key-release",
     423            XI_RawButtonPress   : "raw-button-press",
     424            XI_RawButtonRelease : "raw-button-release",
     425            XI_RawMotion        : "raw-motion",
     426            XI_TouchBegin       : "touch-begin",
     427            XI_TouchUpdate      : "touch-update",
     428            XI_TouchEnd         : "touch-end",
     429            XI_TouchOwnership   : "touch-ownership",
     430            XI_RawTouchBegin    : "raw-touch-begin",
     431            XI_RawTouchUpdate   : "raw-touch-update",
     432            XI_RawTouchEnd      : "raw-touch-end",
     433            }.items():
     434            event = self.opcode+e
     435            add_x_event_signal(event, ("xi-%s" % xi_event_name, None))
     436            name = XI_EVENT_NAMES[e]
     437            add_x_event_type_name(event, name)
     438
     439    def select_xi2_events(self):
     440        cdef Window win = XDefaultRootWindow(self.display)
     441        log("select_xi2_events() root window=%#x", win)
     442        assert XI_LASTEVENT<MAX_XI_EVENTS, "bug: source needs to be updated, XI_LASTEVENT=%i" % XI_LASTEVENT
     443        cdef XIEventMask evmasks[1]
     444        cdef unsigned char mask1[XI_EVENT_MASK_SIZE]
     445        memset(mask1, 0, XI_EVENT_MASK_SIZE)
     446        #define XISetMask(ptr, event)   (((unsigned char*)(ptr))[(event)>>3] |=  (1 << ((event) & 7)))
     447        #XISetMask(mask1, XI_RawMotion)
     448        for e in (
     449            XI_KeyPress, XI_KeyRelease,
     450            XI_Motion,
     451            XI_HierarchyChanged,
     452            XI_ButtonPress, XI_ButtonRelease,
     453            XI_RawButtonPress, XI_RawButtonRelease,
     454            XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd,
     455            XI_RawTouchBegin, XI_RawTouchUpdate, XI_RawTouchEnd,
     456            XI_RawMotion,
     457            ):
     458            mask1[e>>3] |= (1<< (e & 0x7))
     459        evmasks[0].deviceid = XIAllDevices  #XIAllMasterDevices    #XIAllDevices
     460        evmasks[0].mask_len = XI_EVENT_MASK_SIZE
     461        evmasks[0].mask = mask1
     462        XISelectEvents(self.display, win, evmasks, 1)
     463        XFlush(self.display)
     464
     465    def parse_xi_event(self, display, uintptr_t _cookie):
     466        log("parse_xi_event(%s)", _cookie)
     467        cdef XGenericEventCookie *cookie = <XGenericEventCookie*> _cookie
     468        cdef XIDeviceEvent *device_e
     469        cdef XIHierarchyEvent * hierarchy_e
     470        cdef XIEvent *xie
     471        cdef XIRawEvent *raw
     472        cdef int i = 0, j = 0
     473        if not XGetEventData(self.display, cookie):
     474            return None
     475        xie = <XIEvent*> cookie.data
     476        device_e = <XIDeviceEvent*> cookie.data
     477        cdef int xi_type = cookie.evtype
     478        etype = self.opcode+xi_type
     479        global XI_EVENT_NAMES
     480        event_name = XI_EVENT_NAMES.get(xi_type)
     481        if not event_name:
     482            log("unknown XI2 event code: %i", xi_type)
     483            return None
     484
     485        #don't parse the same thing again:
     486        if len(self.events)>0:
     487            last_event = self.events[-1]
     488            if last_event.serial==xie.serial and last_event.type==etype:
     489                return last_event
     490
     491        pyev = X11Event(event_name)
     492        pyev.type = etype
     493        pyev.display = display
     494        pyev.send_event = bool(xie.send_event)
     495        pyev.serial = xie.serial
     496        pyev.time = int(xie.time)
     497        pyev.window = int(XDefaultRootWindow(self.display))
     498
     499        if xi_type in (XI_KeyPress, XI_KeyRelease,
     500                       XI_ButtonPress, XI_ButtonRelease,
     501                       XI_Motion,
     502                       XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd):
     503            device = <XIDeviceEvent*> cookie.data
     504            #pyev.source = device.sourceid    #always 0
     505            pyev.device = device.deviceid
     506            pyev.detail = device.detail
     507            pyev.flags = device.flags
     508            pyev.window = int(device.child or device.event or device.root)
     509            pyev.x_root = device.root_x
     510            pyev.y_root = device.root_y
     511            pyev.x = device.event_x
     512            pyev.y = device.event_y
     513            #mask = []
     514            valuators = {}
     515            valuator = 0
     516            for i in range(device.valuators.mask_len*8):
     517                if device.valuators.mask[i>>3] & (1 << (i & 0x7)):
     518                    valuators[i] = device.valuators.values[valuator]
     519                    valuator += 1
     520            pyev.valuators = valuators
     521            buttons = []
     522            for i in range(device.buttons.mask_len):
     523                if device.buttons.mask[i>>3] & (1<< (i & 0x7)):
     524                    buttons.append(i)
     525            pyev.buttons = buttons
     526            state = []
     527            pyev.state = state
     528            pyev.modifiers = {
     529                "base"      : device.mods.base,
     530                "latched"   : device.mods.latched,
     531                "locked"    : device.mods.locked,
     532                "effective" : device.mods.effective,
     533                }
     534            #make it compatible with gdk events:
     535            pyev.state = device.mods.effective
     536        elif xi_type in (XI_RawKeyPress, XI_RawKeyRelease,
     537                         XI_RawButtonPress, XI_RawButtonRelease,
     538                         XI_RawMotion,
     539                         XI_RawTouchBegin, XI_RawTouchUpdate, XI_RawTouchEnd):
     540            raw = <XIRawEvent*> cookie.data
     541            valuators = {}
     542            raw_valuators = {}
     543            valuator = 0
     544            for i in range(raw.valuators.mask_len*8):
     545                if raw.valuators.mask[i>>3] & (1 << (i & 0x7)):
     546                    valuators[i] = raw.valuators.values[valuator]
     547                    raw_valuators[i] = raw.raw_values[valuator]
     548                    valuator += 1
     549            pyev.valuators = valuators
     550            pyev.raw_valuators = raw_valuators
     551        elif xi_type == XI_HierarchyChanged:
     552            pass
     553            #hierarchy_e = <XIHierarchyEvent*> cookie.data
     554            #pyev.flags = hierarchy_e.flags
     555            #info = {}
     556            #for i in range(hierarchy_e.num_info):
     557            #
     558        XFreeEventData(self.display, cookie)
     559        pyev.xid = pyev.window
     560        self.events.append(pyev)
     561
     562        handler = self.event_handlers.get(pyev.window, {}).get(event_name)
     563        log("parse_xi_event: %s, handler=%s", pyev, handler)
     564        if handler:
     565            handler(pyev)
     566        return None
     567
     568    def get_devices(self, show_all=True, show_disabled=False):
     569        global XI_USE
     570        cdef int ndevices, i, j
     571        cdef XIDeviceInfo *devices
     572        cdef XIDeviceInfo *device
     573        cdef XIAnyClassInfo *clazz
     574        if show_all:
     575            device_types = XIAllDevices
     576        else:
     577            device_types = XIAllMasterDevices
     578        devices = XIQueryDevice(self.display, device_types, &ndevices)
     579        dinfo = {}
     580        for i in range(ndevices):
     581            device = &devices[i]
     582            if not device.enabled and not show_disabled:
     583                continue
     584            info = {
     585                "name"          : device.name,
     586                "use"           : XI_USE.get(device.use, "unknown use: %i" % device.use),
     587                "attachment"    : device.attachment,
     588                "enabled"       : device.enabled,
     589                }
     590            classes = {}
     591            for j in range(device.num_classes):
     592                clazz = device.classes[j]
     593                classes[j] = self.get_class_info(clazz)
     594            info["classes"] = classes
     595            properties = self.get_device_properties(device.deviceid)
     596            if properties:
     597                info["properties"] = properties
     598            dinfo[device.deviceid] = info
     599        XIFreeDeviceInfo(devices)
     600        return dinfo
     601
     602    def get_device_properties(self, deviceid):
     603        cdef Atom *atoms
     604        cdef int nprops, i
     605        atoms = XIListProperties(self.display, deviceid, &nprops)
     606        if atoms==NULL or nprops==0:
     607            return None
     608        props = {}
     609        for i in range(nprops):
     610            value = self.get_device_property(deviceid, atoms[i])
     611            if value is not None:
     612                prop_name = self.XGetAtomName(atoms[i])
     613                props[prop_name] = value
     614        return props
     615
     616    cdef get_device_property(self, int deviceid, Atom property, req_type=0):
     617        #code mostly duplicated from window_bindings XGetWindowProperty:
     618        cdef int buffer_size = 64 * 1024
     619        cdef Atom xactual_type = <Atom> 0
     620        cdef int actual_format = 0
     621        cdef long offset = 0
     622        cdef unsigned long nitems = 0, bytes_after = 0
     623        cdef unsigned char *prop = NULL
     624        cdef Status status
     625        cdef Atom xreq_type = XIAnyPropertyType
     626        if req_type:
     627            xreq_type = self.get_xatom(req_type)
     628
     629        status = XIGetProperty(self.display,
     630                               deviceid, property,
     631                               0,
     632                               buffer_size//4,
     633                               False,
     634                               xreq_type, &xactual_type,
     635                               &actual_format, &nitems, &bytes_after, &prop)
     636        if status != Success:
     637            raise Exception("failed to retrieve XI property")
     638        if xactual_type == XNone:
     639            return None
     640        if xreq_type and xreq_type != xactual_type:
     641            raise Exception("expected %s but got %s" % (req_type, self.XGetAtomName(xactual_type)))
     642        # This should only occur for bad property types:
     643        assert not (bytes_after and not nitems)
     644        if bytes_after:
     645            raise Exception("reserved %i bytes for buffer, but data is bigger by %i bytes!" % (buffer_size, bytes_after))
     646        assert actual_format > 0
     647        if actual_format == 8:
     648            bytes_per_item = 1
     649        elif actual_format == 16:
     650            bytes_per_item = sizeof(short)
     651        elif actual_format == 32:
     652            bytes_per_item = 4  #sizeof(long)
     653        else:
     654            assert False
     655        cdef int nbytes = bytes_per_item * nitems
     656        data = (<char *> prop)[:nbytes]
     657        XFree(prop)
     658        prop_type = self.XGetAtomName(xactual_type)
     659        import binascii
     660        log.info("%s : %s value=%s", self.XGetAtomName(property), prop_type, data)
     661        log.info("hex=%s (type=%s, nitems=%i, bytes per item=%i, actual format=%i)", binascii.hexlify(data), prop_type, nitems, bytes_per_item, actual_format)
     662
     663        #FIXME: 32 here.. breaks
     664        if actual_format == 33:
     665            data = self.munge_packed_longs_to_ints(data)
     666
     667        fmt = None
     668        if prop_type=="INTEGER":
     669            fmt = {
     670                8   : "b",
     671                16  : "h",
     672                32  : "i",
     673                }.get(actual_format)
     674        elif prop_type=="CARDINAL":
     675            fmt = {
     676                8   : "B",
     677                16  : "H",
     678                32  : "I",
     679                }.get(actual_format)
     680        elif prop_type=="FLOAT":
     681            fmt = "f"
     682        if fmt:
     683            log.info("parsing using %s", fmt*nitems)
     684            value = struct.unpack(fmt*nitems, data)
     685            if nitems==1:
     686                return value[0]
     687            return value
     688        return data
     689
     690    cdef get_class_info(self, XIAnyClassInfo *class_info):
     691        cdef int i
     692        cdef XIButtonClassInfo *button
     693        cdef XIKeyClassInfo *key
     694        cdef XIValuatorClassInfo *valuator
     695        cdef XIScrollClassInfo *scroll
     696        cdef XITouchClassInfo *touch
     697        info = {
     698            "type"      : CLASS_INFO.get(class_info.type, "unknown type: %i" % class_info.type),
     699            "sourceid"  : class_info.sourceid,
     700            }
     701        if class_info.type==XIButtonClass:
     702            button = <XIButtonClassInfo*> class_info
     703            buttons = []
     704            for i in range(button.num_buttons):
     705                if button.labels[i]>0:
     706                    buttons.append(self.XGetAtomName(button.labels[i]))
     707            info["buttons"] = buttons
     708            #XIButtonState state
     709        elif class_info.type==XIKeyClass:
     710            key = <XIKeyClassInfo*> class_info
     711            keys = []
     712            for i in range(key.num_keycodes):
     713                keys.append(key.keycodes[i])
     714        elif class_info.type==XIValuatorClass:
     715            valuator = <XIValuatorClassInfo*> class_info
     716            info.update({
     717                "number"    : valuator.number,
     718                "min"       : valuator.min,
     719                "max"       : valuator.max,
     720                "value"     : valuator.value,
     721                "resolution": valuator.resolution,
     722                "mode"      : valuator.mode,
     723                })
     724            if valuator.label:
     725                info["label"] = self.XGetAtomName(valuator.label)
     726        elif class_info.type==XIScrollClass:
     727            scroll = <XIScrollClassInfo*> class_info
     728            info.update({
     729                "number"        : scroll.number,
     730                "scroll-type"   : scroll.scroll_type,
     731                "increment"     : scroll.increment,
     732                "flags"         : scroll.flags,
     733                })
     734        elif class_info.type==XITouchClass:
     735            touch = <XITouchClassInfo*> class_info
     736            info.update({
     737                "mode"          : touch.mode,
     738                "num-touches"   : touch.num_touches,
     739                })
     740        return info
     741
     742
     743    def gdk_inject(self):
     744        self.get_xi_opcode()
     745        #log.info("XInput Devices:")
     746        #from xpra.util import print_nested_dict
     747        #print_nested_dict(self.get_devices(), print_fn=log.info)
     748        self.register_parser()
     749        self.register_gdk_events()
     750        #self.select_xi2_events()
  • xpra/x11/desktop_server.py

     
    352352            self._damage(window, 0, 0, w, h)
    353353
    354354
    355     def _move_pointer(self, wid, pos):
     355    def _move_pointer(self, wid, pos, *args):
    356356        if wid>=0:
    357357            window = self._id_to_window.get(wid)
    358358            if not window:
     
    360360            else:
    361361                #TODO: just like shadow server, adjust for window position
    362362                pass
    363         X11ServerBase._move_pointer(self, wid, pos)
     363        X11ServerBase._move_pointer(self, wid, pos, *args)
    364364
    365365
    366366    def _process_close_window(self, proto, packet):
  • xpra/x11/gtk2/gdk_bindings.pyx

     
    10471047            #log("calling %s%s", parser, (d, <uintptr_t> &e.xcookie))
    10481048            pyev = parser(d, <uintptr_t> &e.xcookie)
    10491049            #log("pyev=%s (window=%#x)", pyev, e.xany.window)
    1050             pyev.window = _gw(d, pyev.window)
    1051             pyev.delivered_to = pyev.window
     1050            if pyev and type(pyev.window)==int:
     1051                pyev.window = _gw(d, pyev.window)
     1052                pyev.delivered_to = pyev.window
    10521053            return pyev
    10531054        return None
    10541055
  • xpra/x11/server.py

     
    948948
    949949    """ override so we can raise the window under the cursor
    950950        (gtk raise does not change window stacking, just focus) """
    951     def _move_pointer(self, wid, pos):
     951    def _move_pointer(self, wid, pos, *args):
    952952        if wid>=0:
    953953            window = self._id_to_window.get(wid)
    954954            if not window:
     
    956956            else:
    957957                mouselog("raising %s", window)
    958958                window.raise_window()
    959         X11ServerBase._move_pointer(self, wid, pos)
     959        X11ServerBase._move_pointer(self, wid, pos, *args)
    960960
    961961
    962962    def _process_close_window(self, proto, packet):
  • xpra/x11/x11_server_base.py

     
    4343grablog = Logger("server", "grab")
    4444cursorlog = Logger("server", "cursor")
    4545screenlog = Logger("server", "screen")
     46inputlog = Logger("input")
    4647gllog = Logger("screen", "opengl")
    4748
    4849from xpra.util import iround, envbool, envint
     
    7475        log("found window: %s", window_info(window))
    7576
    7677
     78class XTestPointerDevice(object):
     79
     80    def __repr__(self):
     81        return "XTestPointerDevice"
     82
     83    def move_pointer(self, screen_no, x, y, *args):
     84        mouselog("xtest_fake_motion(%i, %s, %s)", screen_no, x, y)
     85        X11Keyboard.xtest_fake_motion(screen_no, x, y)
     86
     87    def click(self, button, pressed, *args):
     88        mouselog("xtest_fake_button(%i, %s)", button, pressed)
     89        X11Keyboard.xtest_fake_button(button, pressed)
     90
     91    def destroy(self):
     92        pass
     93
     94class UInputPointerDevice(object):
     95
     96    def __init__(self, name, uinput_device):
     97        self.name = name
     98        self.uinput_device = uinput_device
     99
     100    def __repr__(self):
     101        return "UInputPointerDevice(%s)" % self.name
     102
     103    def move_pointer(self, screen_no, x, y, ex=None, ey=None, x_root=None, y_root=None, valuators=None, raw_valuators=None):
     104        import uinput
     105        mouselog.warn("move_pointer(%i, %s, %s, ..) raw_valuators=%s", screen_no, x, y, raw_valuators)
     106        def fv(value):
     107            return float(value[0])/float(value[1])
     108        def iv(value):
     109            f = fv(value)
     110            if f>0:
     111                return max(1, int(f))
     112            else:
     113                return min(-1, int(f))
     114        #if valuators:
     115        #    x = valuators.get(0)
     116        #    y = valuators.get(1)
     117        #    events = []
     118        #    if x:
     119        #        events.append((uinput.ABS_X, iv(x)))
     120        #    if y:
     121        #        events.append((uinput.ABS_Y, iv(y)))
     122        #    self.post_events(events)
     123        #    return
     124        if raw_valuators:
     125            x = raw_valuators.get(0)
     126            y = raw_valuators.get(1)
     127            events = []
     128            if x:
     129                events.append((uinput.REL_X, iv(x)))
     130            if y:
     131                events.append((uinput.REL_Y, iv(y)))
     132            self.post_events(events)
     133
     134    def post_events(self, events):
     135        if events:
     136            while events:
     137                event = events.pop()
     138                syn = len(events)==0
     139                self.uinput_device.emit(*event, syn=syn)
     140
     141    def click(self, button, pressed, *args):
     142        mouselog.warn("click(%i, %s, %s)", button, pressed, args)
     143        #ui.write(e.EV_KEY, e.BTN_LEFT, 1)
     144        import uinput
     145        uinput_button = {
     146            1   : uinput.BTN_LEFT,
     147            2   : uinput.BTN_RIGHT,
     148            3   : uinput.BTN_MIDDLE,
     149            4   : uinput.BTN_SIDE,
     150            5   : uinput.BTN_EXTRA,
     151            6   : uinput.BTN_FORWARD,
     152            7   : uinput.BTN_BACK,
     153            8   : uinput.BTN_TASK,
     154            }.get(button, -1)
     155        if uinput_button==-1:
     156            log.error("Error: unknown button %i", button)
     157            return
     158        self.uinput_device.emit_click(uinput_button)
     159
     160
     161    def destroy(self):
     162        ud = self.uinput_device
     163        if ud:
     164            self.uinput_device = None
     165            ud.destroy()
     166
     167
    77168class X11ServerBase(GTKServerBase):
    78169    """
    79170        Base class for X11 servers,
     
    93184        self.current_xinerama_config = None
    94185        self.x11_init()
    95186        GTKServerBase.init(self, opts)
     187        self.pointer_devices = {}
     188        self.keyboard_devices = {}
     189        if self.input_devices=="xi":
     190            try:
     191                import uinput
     192                self.uinput = uinput
     193            except ImportError as  e:
     194                inputlog.warn("Warning: cannot enable xi input devices via python-uinput:")
     195                inputlog.warn(" %s", e)
     196                self.input_devices = "auto"
    96197
    97198    def x11_init(self):
    98199        if self.fake_xinerama:
     
    618719    def do_cleanup(self, *args):
    619720        GTKServerBase.do_cleanup(self)
    620721        cleanup_fakeXinerama()
     722        self.cleanup_input_devices()
    621723
    622724
    623725    def _process_server_settings(self, proto, packet):
     
    710812        #-1 uses the current screen
    711813        return -1
    712814
    713     def _move_pointer(self, wid, pos):
     815
     816    def cleanup_input_devices(self):
     817        def destroy_devices(devices):
     818            for device in devices:
     819                try:
     820                    device.destroy()
     821                except Exception as e:
     822                    inputlog.error("Error destroying input device %s:", device)
     823                    inputlog.error(" %s", device)
     824        pointer_devices = self.pointer_devices.values()
     825        self.pointer_devices = {}
     826        destroy_devices(pointer_devices)
     827        keyboard_devices = self.keyboard_devices.values()
     828        self.keyboard_devices = {}
     829        destroy_devices(keyboard_devices)
     830
     831
     832    def setup_input_devices(self):
     833        inputlog("setup_input_devices() input_devices=%s, uinput=%s", self.input_devices, self.uinput)
     834        #default is always present:
     835        self.pointer_devices[-1] = XTestPointerDevice()
     836        if self.input_devices!="xi" or not self.uinput or not self.input_devices_data:
     837            return
     838        for deviceid, props in self.input_devices_data.items():
     839            if not props.get("enabled"):
     840                continue
     841            name = props.get("name")
     842            use = props.get("use")
     843            if use in ("master keyboard", "master pointer"):
     844                #should not be needed, we want to use the slave devices
     845                log("ignoring %s: %s", use, name)
     846                continue
     847            if name=="Virtual core XTEST keyboard":
     848                #should always be present already - just needs mapping to it
     849                #this should use the legacy XTEST code path
     850                log("ignoring %s", name)
     851                continue
     852            if name=="Virtual core XTEST pointer":
     853                self.pointer_devices[deviceid] = XTestPointerDevice()
     854                continue
     855            self.pointer_devices[-1] = XTestPointerDevice()
     856            if use=="slave keyboard":
     857                #not implemented yet!
     858                log("TODO: handle slave keyboard %s", name)
     859                continue
     860            if use=="slave pointer":
     861                self.setup_pointer_device(deviceid)
     862
     863    def setup_pointer_device(self, deviceid):
     864        props = self.input_devices_data[deviceid]
     865        inputlog("setup_pointer_device(%s) properties=%s", deviceid, props)
     866        assert props["use"] == "slave pointer"
     867        name = props.get("name", "")
     868        #is_touchpad = name.lower().find("touchpad")>=0
     869        uinput = self.uinput
     870        events = (
     871                uinput.ABS_X,
     872                uinput.ABS_Y,
     873                uinput.REL_X,
     874                uinput.REL_Y,
     875                uinput.BTN_LEFT,
     876                uinput.BTN_RIGHT,
     877                )
     878        device = uinput.Device(events, name="%s - xpra pointer" % name)
     879        self.pointer_devices[deviceid] = UInputPointerDevice(name, device)
     880        log.info("setup_pointer_device(%s) %s=%s", deviceid, name, device)
     881
     882
     883    def _move_pointer(self, wid, pos, deviceid, *args):
     884        #args =
    714885        #(this is called within an xswallow context)
    715886        screen_no = self.get_screen_number(wid)
    716         mouselog("move_pointer(%s, %s) screen_no=%i", wid, pos, screen_no)
     887        device = self.pointer_devices[deviceid]
     888        mouselog.info("move_pointer(%s, %s, %s) screen_no=%i, device=%s", wid, pos, deviceid, screen_no, device)
    717889        x, y = pos
    718         X11Keyboard.xtest_fake_motion(screen_no, x, y)
     890        try:
     891            device.move_pointer(screen_no, x, y, *args)
     892        except Exception as e:
     893            mouselog.error("Error: failed to move the pointer to %sx%s using %s", x, y, device)
     894            mouselog.error(" %s", e)
    719895
    720     def do_process_mouse_common(self, proto, wid, pointer):
     896    def do_process_mouse_common(self, proto, wid, pointer, deviceid=-1, *args):
     897        log.info("do_process_mouse_common%s", tuple([proto, wid, pointer, deviceid]+list(args)))
    721898        if self.readonly:
    722899            return
     900        if self.input_devices_data:
     901            device_data = self.input_devices_data.get(deviceid)
     902            if device_data:
     903                log.warn("process_mouse_common from device=%s", device_data.get("name"))
    723904        pos = self.root_window.get_pointer()[:2]
    724         if pos!=pointer:
     905        if pos!=pointer or self.input_devices=="xi":
    725906            ss = self._server_sources.get(proto)
    726907            assert ss, "source not found for %s" % proto
    727908            self.last_mouse_user = ss.uuid
    728909            with xswallow:
    729                 self._move_pointer(wid, pointer)
     910                self._move_pointer(wid, pointer, deviceid, *args)
    730911
    731912    def _update_modifiers(self, proto, wid, modifiers):
    732913        if self.readonly:
     
    739920            if wid==self.get_focus():
    740921                ss.user_event()
    741922
    742     def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args):
     923    def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, buttons=[], deviceid=-1, *args):
    743924        self._update_modifiers(proto, wid, modifiers)
    744         self._process_mouse_common(proto, wid, pointer)
    745         mouselog("xtest_fake_button(%s, %s) at %s", button, pressed, pointer)
     925        #TODO: pass extra args
     926        self._process_mouse_common(proto, wid, pointer, deviceid)
     927        device = self.pointer_devices.get(deviceid)
     928        assert device, "pointer device %s not found" % deviceid
    746929        try:
    747930            with xsync:
    748                 X11Keyboard.xtest_fake_button(button, pressed)
     931                device.click(button, pressed, *args)
    749932        except XError:
    750933            err = "Failed to pass on (un)press of mouse button %s" % button
    751934            if button>=4: