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-v9.patch

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

updated patch: server know knows details of the device used for some events

  • 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,
     621                  event.device] + self.get_pointer_extra_args(event)
     622        mouselog.info("motion packet: %s", packet)
     623        window._client.send_mouse_position(packet)
     624
     625    def get_pointer_extra_args(self, event):
     626        args = []
     627        def intscaled(f):
     628            return int(f*1000000), 1000000
     629        def dictscaled(v):
     630            return dict((k,intscaled(v)) for k,v in v.items())
     631        raw_valuators = {}
     632        raw_event_name = event.name.replace("XI_", "XI_Raw")    #ie: XI_Motion -> XI_RawMotion
     633        raw = self.XI2.find_event(raw_event_name, event.serial)
     634        #log.info("raw(%s)=%s", raw_event_name, raw)
     635        if raw:
     636            raw_valuators = raw.raw_valuators
     637        for x in ("x", "y", "x_root", "y_root"):
     638            args.append(intscaled(getattr(event, x)))
     639        for v in [event.valuators, raw_valuators]:
     640            args.append(dictscaled(v))
     641        return args
     642
     643
    519644class ClientExtras(object):
    520645    def __init__(self, client, opts):
    521646        self.client = client
     
    528653        self.x11_filter = None
    529654        if client.xsettings_enabled:
    530655            self.setup_xprops()
     656        if client.input_devices=="xi":
     657            self.setup_xi()
    531658        self.setup_dbus_signals()
    532659
    533660    def ready(self):
     
    565692                bus._clean_up_signal_match(self.upower_sleeping_match)
    566693            if self.login1_match:
    567694                bus._clean_up_signal_match(self.login1_match)
     695        global WINDOW_METHOD_OVERRIDES
     696        WINDOW_METHOD_OVERRIDES = {}
    568697
    569698    def resuming_callback(self, *args):
    570699        eventlog("resuming_callback%s", args)
     
    647776        except ImportError as e:
    648777            log.error("failed to load X11 properties/settings bindings: %s - root window properties will not be propagated", e)
    649778
     779    def setup_xi(self):
     780        from xpra.gtk_common.error import xsync
     781        def may_enable_xi2():
     782            if self.client.server_input_devices!="xi":
     783                log.warn("Warning: server does not support xi input devices")
     784                return
     785            try:
     786                with xsync:
     787                    assert X11WindowBindings, "no X11 window bindings"
     788                    assert X11XI2Bindings, "no XI2 window bindings"
     789                    X11XI2Bindings().gdk_inject()
     790                    self.init_x11_filter()
     791                    XI2 = X11XI2Bindings()
     792                    XI2.select_xi2_events()
     793                    devices = XI2.get_devices()
     794                    if devices:
     795                        self.client.send_input_devices(devices)
     796            except Exception as e:
     797                log("enable_xi2()", exc_info=True)
     798                log.error("Error: cannot enable XI2 events")
     799                log.error(" %s", e)
     800            else:
     801                #register our enhanced event handlers:
     802                self.add_xi2_method_overrides()
     803        with xsync:
     804            try:
     805                #this would trigger warnings with our temporary opengl windows:
     806                #only enable it after we have connected:
     807                self.client.after_handshake(may_enable_xi2)
     808            except Exception as e:
     809                log("setup_xi()", exc_info=True)
     810                log.error("Error: failed to load the XI2 bindings")
     811                log.error(" %s", e)
     812
     813    def add_xi2_method_overrides(self):
     814        global WINDOW_ADD_HOOKS
     815        WINDOW_ADD_HOOKS = [XI2_Window]
     816
     817
    650818    def _get_xsettings(self):
    651819        try:
    652820            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/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()))
     
    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, device=-1, *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, device, *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, device=-1, *args):
    28872894        pass
    28882895
    28892896
     
    29092916        if self.readonly:
    29102917            return
    29112918        wid, pointer, modifiers = packet[1:4]
     2919        device = -1
     2920        if len(packet)>=6:
     2921            #buttons = packet[4]
     2922            device = packet[5]
     2923            if self.input_devices_data:
     2924                device_data = self.input_devices_data.get(device)
     2925                if device_data:
     2926                    log.warn("pointer position from device=%s - %s", device_data.get("name"), device_data)
     2927                    #log.warn("pointer position with device=%s", device_data.get("name"))
    29122928        ss = self._server_sources.get(proto)
    29132929        if ss is not None:
    29142930            ss.mouse_last_position = pointer
     
    29152931        if self.ui_driver and self.ui_driver!=ss.uuid:
    29162932            return
    29172933        self._update_modifiers(proto, wid, modifiers)
    2918         self._process_mouse_common(proto, wid, pointer)
     2934        self._process_mouse_common(proto, wid, pointer, device)
    29192935
    29202936
    29212937    def _process_damage_sequence(self, proto, packet):
     
    31763192            ss.send_webcam_stop(device, str(e))
    31773193            self.stop_virtual_webcam()
    31783194
     3195    def _process_input_devices(self, ss, packet):
     3196        self.input_devices_data = packet[1]
     3197        from xpra.util import print_nested_dict
     3198        log.info("client input devices:")
     3199        print_nested_dict(self.input_devices_data, print_fn=log.info)
    31793200
    31803201    def process_clipboard_packet(self, ss, packet):
    31813202        if self.readonly:
  • 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/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/x11_server_base.py

     
    717717        x, y = pos
    718718        X11Keyboard.xtest_fake_motion(screen_no, x, y)
    719719
    720     def do_process_mouse_common(self, proto, wid, pointer):
     720    def do_process_mouse_common(self, proto, wid, pointer, device, *args):
    721721        if self.readonly:
    722722            return
    723723        pos = self.root_window.get_pointer()[:2]
     
    741741
    742742    def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args):
    743743        self._update_modifiers(proto, wid, modifiers)
     744        #TODO: pass extra args
    744745        self._process_mouse_common(proto, wid, pointer)
    745746        mouselog("xtest_fake_button(%s, %s) at %s", button, pressed, pointer)
    746747        try: