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: uinput-server.patch

File uinput-server.patch, 18.5 KB (added by Antoine Martin, 4 years ago)

implement pointer as a uinput device

  • etc/X11/xorg.conf.d/90-xpra-virtual.conf

     
     1# Ignore all xpra virtual devices by default,
     2# these will be enabled explicitly when needed.
     3Section "InputClass"
     4        Identifier "xpra-virtual-device"
     5        MatchProduct "Xpra"
     6        Option "Ignore" "true"
     7EndSection
  • etc/xpra/conf.d/55_server_x11.conf.in

     
    3535#        +extension GLX +extension RANDR +extension RENDER \
    3636#        -auth $XAUTHORITY \
    3737#        -logfile %(log_dir)s/Xorg.${DISPLAY}.log \
    38 #        -configdir ${HOME}/.xpra/xorg.conf.d \
     38#        -configdir ${HOME}/.xpra/xorg.conf.d/$PID \
    3939#        -config %(conf_dir)s/xorg.conf
    4040#
    4141# Selecting virtual X server:
  • 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 "AllowEmptyInput" "true"
     11  Option "AutoEnableDevices" "true"
     12  Option "AutoAddDevices" "true"
    1213EndSection
    1314
     15Section "InputClass"
     16  Identifier "ignore-non-xpra-devices"
     17  NoMatchProduct "Xpra"
     18  Option "Ignore" "true"
     19EndSection
     20
     21
    1422Section "Device"
    1523  Identifier "dummy_videocard"
    1624  Driver "dummy"
  • setup.py

     
    14871487                    etc_xpra_files += ["cuda.conf", "nvenc.keys"]
    14881488                for x in etc_xpra_files:
    14891489                    copytodir("etc/xpra/%s" % x, "/etc/xpra")
     1490                copytodir("etc/X11/xorg.conf.d/90-xpra-virtual.conf", "/etc/X11/xorg.conf.d/")
    14901491
    14911492            if pam_ENABLED:
    14921493                copytodir("etc/pam.d/xpra", "/etc/pam.d")
  • xpra/scripts/config.py

     
    7070    return None
    7171
    7272
     73def get_Xdummy_confdir():
     74    return "${HOME}/.xpra/xorg.conf.d/$PID"
     75
    7376def get_Xdummy_command(xorg_cmd="Xorg", log_dir="${XPRA_LOG_DIR}", xorg_conf="/etc/xpra/xorg.conf"):
    7477    cmd = [xorg_cmd]    #ie: ["Xorg"] or ["xpra_Xdummy"] or ["./install/bin/xpra_Xdummy"]
    7578    cmd += [
     
    8285          "-logfile", "%s/Xorg.${DISPLAY}.log" % log_dir,
    8386          #must be specified with some Xorg versions (ie: arch linux)
    8487          #this directory can store xorg config files, it does not need to be created:
    85           "-configdir", "${HOME}/.xpra/xorg.conf.d",
     88          "-configdir", get_Xdummy_confdir(),
    8689          "-config", xorg_conf
    8790          ]
    8891    return cmd
  • xpra/scripts/server.py

     
    424424
    425425    # Generate the script text now, because os.getcwd() will
    426426    # change if/when we daemonize:
    427     from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir
     427    from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir, create_input_devices
    428428    script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir)
    429429
    430430    uid = int(opts.uid)
     
    632632    os.environ.update(protected_env)
    633633    log("env=%s", os.environ)
    634634
     635    #create devices for vfb if needed:
     636    devices = {}
     637    if start_vfb and ROOT and opts.input_devices.lower() in ("auto", "uinput"):
     638        devices = create_input_devices(uid)
     639
    635640    # Start the Xvfb server first to get the display_name if needed
    636641    from xpra.server.vfb_util import start_Xvfb, check_xvfb_process, verify_display_ready, verify_gdk_display, set_initial_resolution
    637642    odisplay_name = display_name
     
    640645    if start_vfb:
    641646        assert not proxying and xauth_data
    642647        pixel_depth = validate_pixel_depth(opts.pixel_depth)
    643         xvfb, display_name = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, xauth_data)
     648        xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices)
     649        for f in cleanups:
     650            add_cleanup(f)
    644651        xvfb_pid = xvfb.pid
    645652        #always update as we may now have the "real" display name:
    646653        os.environ["DISPLAY"] = display_name
     
    827834            from xpra.x11.desktop_server import XpraDesktopServer
    828835            app = XpraDesktopServer()
    829836            server_type_info = "xpra desktop"
     837        app.init_virtual_devices(devices)
    830838
    831839    #publish mdns records:
    832840    if opts.mdns:
  • xpra/server/server_util.py

     
    257257    except Exception as e:
    258258        log.error("Error: failed to write pid %i to pidfile '%s':", os.getpid(), pidfile)
    259259        log.error(" %s", e)
     260
     261
     262def get_uinput_device_path(device):
     263    from xpra.log import Logger
     264    log = Logger("server", "mouse")
     265    try:
     266        log("get_uinput_device_path(%s)", device)
     267        fd = device._Device__uinput_fd
     268        log("fd(%s)=%s", device, fd)
     269        import fcntl        #@UnresolvedImport
     270        import ctypes
     271        l = 16
     272        buf = ctypes.create_string_buffer(l)
     273        _IOC_READ = 2
     274        #this magic value was calculated using the C macros:
     275        l = fcntl.ioctl(fd, 2148554028, buf)
     276        if l>0 and l<16:
     277            virt_dev_path = buf.raw[:l].rstrip("\0")
     278            log("UI_GET_SYSNAME(%s)=%s", fd, virt_dev_path)
     279            uevent_path = b"/sys/devices/virtual/input/%s" % virt_dev_path
     280            event_dirs = [x for x in os.listdir(uevent_path) if x.startswith("event")]
     281            log("event dirs(%s)=%s", uevent_path, event_dirs)
     282            for d in event_dirs:
     283                uevent_filename = os.path.join(uevent_path, d, "uevent")
     284                uevent_conf = open(uevent_filename, "rb").read()
     285                for line in uevent_conf.splitlines():
     286                    if line.find(b"=")>0:
     287                        k,v = line.split(b"=", 1)
     288                        log("%s=%s", k, v)
     289                        if k==b"DEVNAME":
     290                            dev_path = b"/dev/%s" % v
     291                            log("found device path: %s" % dev_path)
     292                            return dev_path
     293    except Exception as e:
     294        log.error("Error: cannot query uinput device path:")
     295        log.error(" %", e)
     296    return None
     297
     298def create_uinput_pointer_device(uid, gid):
     299    from xpra.log import Logger
     300    log = Logger("server")
     301    try:
     302        import uinput
     303    except ImportError as e:
     304        log.error("Error: cannot create uinput devices:")
     305        log.error(" %s", e)
     306
     307    events = (
     308        uinput.REL_X,
     309        uinput.REL_Y,
     310        uinput.BTN_LEFT,
     311        uinput.BTN_RIGHT,
     312        uinput.BTN_MIDDLE,
     313        uinput.BTN_SIDE,
     314        uinput.BTN_EXTRA,
     315        uinput.BTN_FORWARD,
     316        uinput.BTN_BACK,
     317        )
     318    BUS_VIRTUAL = 0x06
     319
     320    name = "Xpra Virtual Pointer"
     321    try:
     322        uinput_pointer = uinput.Device(events, name=name, bustype=BUS_VIRTUAL)
     323    except OSError as e:
     324        log.error("Error: cannot open uinput,")
     325        log.error(" make sure that the kernel module is loaded")
     326        log.error(" and that the /dev/uinput device exists:")
     327        log.error(" %s", e)
     328    dev_path = get_uinput_device_path(uinput_pointer)
     329    if not dev_path:
     330        uinput_pointer.destroy()
     331        return None
     332    log("chown%s", (dev_path, uid, gid))
     333    os.lchown(dev_path, uid, gid)
     334    #no idea why, but something (udev?) changes the device ownership
     335    #FIXME: fix udev or use inotify? (racy)
     336    import time
     337    time.sleep(1)
     338    log("chown%s", (dev_path, uid, gid))
     339    os.lchown(dev_path, uid, gid)
     340    os.lchown("/dev/input/mouse2", uid, gid)
     341    return name, uinput_pointer, dev_path
     342
     343def create_uinput_devices(uid, gid):
     344    d = create_uinput_pointer_device(uid, gid)
     345    if not d:
     346        return {}
     347    name, uinput_pointer, dev_path = d
     348    return {
     349        "pointer" : {
     350            "name"      : name,
     351            "uinput"    : uinput_pointer,
     352            "device"    : dev_path,
     353            }
     354        }
     355
     356def create_evdev_devices(uid, gid=-1):
     357    d = create_uinput_pointer_device(uid, gid)
     358    if not d:
     359        return {}
     360    name, _, dev_path = d
     361    from evdev import InputDevice
     362    evdev_pointer = InputDevice(dev_path)
     363    return {
     364        "pointer" : {
     365            "name"      : name,
     366            "evdev"     : evdev_pointer,
     367            "device"    : dev_path,
     368            }
     369        }
     370
     371def create_input_devices(uid, gid=-1):
     372    return create_uinput_devices(uid, gid)
     373    #return create_evdev_devices(uid, gid)
  • xpra/server/vfb_util.py

     
    1313import os.path
    1414
    1515from xpra.scripts.main import no_gtk
    16 from xpra.scripts.config import InitException
     16from xpra.scripts.config import InitException, get_Xdummy_confdir
    1717from xpra.os_util import setsid, shellsub, monotonic_time, close_fds, setuidgid, getuid, getgid, strtobytes, POSIX
    18 from xpra.platform.dotxpra import osexpand
    1918
    2019
    2120DEFAULT_VFB_RESOLUTION = tuple(int(x) for x in os.environ.get("XPRA_DEFAULT_VFB_RESOLUTION", "8192x4096").replace(",", "x").split("x", 1))
     
    2423assert len(DEFAULT_DESKTOP_VFB_RESOLUTION)==2
    2524
    2625
    27 def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data):
     26def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices={}):
    2827    if not POSIX:
    2928        raise InitException("starting an Xvfb is not supported on %s" % os.name)
    3029    if not xvfb_str:
     
    4746            log.error(" %s", e)
    4847    use_display_fd = display_name[0]=='S'
    4948
     49    subs = {
     50            "XAUTHORITY"    : xauthority,
     51            "USER"          : username or os.environ.get("USER", "unknown-user"),
     52            "UID"           : uid,
     53            "GID"           : gid,
     54            "PID"           : os.getpid(),
     55            "HOME"          : os.path.expanduser("~%s" % username),
     56            "DISPLAY"       : display_name,
     57            "XPRA_LOG_DIR"  : os.environ.get("XPRA_LOG_DIR"),
     58            }
     59    def pathexpand(s):
     60        return shellsub(s, subs)
     61
     62    cleanups = []
     63    #create uinput device definition files:
     64    #(we are assuming that Xorg is configured to use this path..)
     65    conf_dir = pathexpand(get_Xdummy_confdir())
     66    if devices:
     67        if not os.path.exists(conf_dir):
     68            os.makedirs(conf_dir, 0o755)
     69            os.lchown(conf_dir, uid, gid)
     70            def cleanup_conf_dir():
     71                os.rmdir(conf_dir)
     72            cleanups.append(cleanup_conf_dir)
     73        i = 0
     74        for dev_type, devdef in devices.items():
     75            #ie:
     76            #name = "pointer"
     77            #devdef = {"uinput" : uninput.Device, "device" : "/dev/input20" }
     78            match_type = {
     79                "pointer"   :
     80"""
     81    MatchIsPointer "True"
     82    Driver "evdev"
     83    Option "AccelProfile" "flat"
     84""",
     85                "keyboard"  : 'MatchIsKeyboard "True"',
     86                }.get(dev_type)
     87            uinput = devdef.get("uinput")
     88            device = devdef.get("device")
     89            name = devdef.get("name")
     90            if match_type and uinput and device and name:
     91                conf_file = os.path.join(conf_dir, "%02i-%s.conf" % (i, dev_type))
     92                with open(conf_file, "wb") as f:
     93                    f.write("""
     94Section "InputClass"
     95    Identifier "xpra-virtual-%s"
     96    MatchProduct "%s"
     97    Option "Ignore" "False"
     98%s
     99EndSection
     100""" % (dev_type, name, match_type))
     101                    os.fchown(f.fileno(), uid, gid)
     102                    #Option "AccelerationProfile" "-1"
     103                    #Option "AccelerationScheme" "none"
     104                    #Option "AccelSpeed" "-1"
     105                def cleanup_conf_file():
     106                    os.unlink(conf_file)
     107                cleanups.append(cleanup_conf_dir)
     108
    50109    #identify logfile argument if it exists,
    51110    #as we may have to rename it, or create the directory for it:
    52111    import shlex
     
    62121            #keep track of it so we can rename it later:
    63122            tmp_xorg_log_file = xvfb_cmd[logfile_argindex+1]
    64123        #make sure the Xorg log directory exists:
    65         xorg_log_dir = osexpand(os.path.dirname(xvfb_cmd[logfile_argindex+1]))
     124        xorg_log_dir = pathexpand(xvfb_cmd[logfile_argindex+1])
    66125        if not os.path.exists(xorg_log_dir):
    67126            try:
    68127                log("creating Xorg log dir '%s'", xorg_log_dir)
     
    76135                raise InitException("failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e))
    77136
    78137    #apply string substitutions:
    79     subs = {
    80             "XAUTHORITY"    : xauthority,
    81             "USER"          : os.environ.get("USER", "unknown-user"),
    82             "UID"           : os.getuid(),
    83             "GID"           : os.getgid(),
    84             "HOME"          : os.environ.get("HOME", cwd),
    85             "DISPLAY"       : display_name,
    86             "XPRA_LOG_DIR"  : os.environ.get("XPRA_LOG_DIR"),
    87             }
    88     xvfb_cmd = shellsub(xvfb_str, subs).split()
    89     log("shellsub(%s, %s)=%s", xvfb_str, subs, xvfb_cmd)
     138    xvfb_cmd = pathexpand(xvfb_str).split()
    90139
    91140    if not xvfb_cmd:
    92141        raise InitException("cannot start Xvfb, the command definition is missing!")
     
    160209    xauth_add(display_name, xauth_data)
    161210    log("xvfb process=%s", xvfb)
    162211    log("display_name=%s", display_name)
    163     return xvfb, display_name
     212    return xvfb, display_name, cleanups
    164213
    165214
    166215def set_initial_resolution(desktop=False):
  • xpra/x11/bindings/keyboard_bindings.pyx

     
    9595    int XQueryKeymap(Display * display, char [32] keys_return)
    9696    int XFlush(Display *dpy)
    9797
     98    Bool XQueryPointer(Display *display, Window w, Window *root_return, Window *child_return, int *root_x_return, int *root_y_return,
     99                       int *win_x_return, int *win_y_return, unsigned int *mask_return)
    98100
     101
    99102cdef extern from "X11/extensions/XKB.h":
    100103    unsigned long XkbUseCoreKbd
    101104    unsigned long XkbDfltXIId
     
    10611064        if not on:
    10621065            bits = 0
    10631066        return XkbSelectEvents(self.display, XkbUseCoreKbd, XkbBellNotifyMask, bits)
     1067
     1068
     1069    def query_pointer(self):
     1070        cdef Window root_window
     1071        root_window = XDefaultRootWindow(self.display)
     1072        cdef Window root, child
     1073        cdef int root_x, root_y
     1074        cdef int win_x, win_y
     1075        cdef unsigned int mask
     1076        XQueryPointer(self.display, root_window, &root, &child,
     1077                      &root_x, &root_y, &win_x, &win_y, &mask)
     1078        return root_x, root_y
  • xpra/x11/x11_server_base.py

     
    7171    def close(self):
    7272        pass
    7373
     74class UInputPointerDevice(object):
    7475
     76    def __init__(self, device):
     77        self.device = device
     78
     79    def __repr__(self):
     80        return "UInputPointerDevice"
     81
     82    def move_pointer(self, screen_no, x, y, *args):
     83        mouselog("UInput.move_pointer(%i, %s, %s)", screen_no, x, y)
     84        #calculate delta:
     85        with xsync:
     86            cx, cy = X11Keyboard.query_pointer()
     87            mouselog("X11Keyboard.query_pointer=%s, %s", cx, cy)
     88            dx = x-cx
     89            dy = y-cy
     90            mouselog("delta(%s, %s)=%s, %s", cx, cy, dx, dy)
     91        import uinput
     92        #self.device.emit(uinput.ABS_X, x, syn=(dy==0))
     93        #self.device.emit(uinput.ABS_Y, y, syn=True)
     94        if dx or dy:
     95            if dx!=0:
     96                self.device.emit(uinput.REL_X, dx, syn=(dy==0))
     97            if dy!=0:
     98                self.device.emit(uinput.REL_Y, dy, syn=True)
     99
     100    def click(self, button, pressed, *args):
     101        mouselog("UInput.click(%i, %s)", button, pressed)
     102        import uinput
     103        ubutton = {
     104            1   : uinput.BTN_LEFT,
     105            2   : uinput.BTN_RIGHT,
     106            3   : uinput.BTN_MIDDLE,
     107            4   : uinput.BTN_SIDE,
     108            5   : uinput.BTN_EXTRA,
     109            6   : uinput.BTN_FORWARD,
     110            7   : uinput.BTN_BACK,
     111            }.get(button, 0)
     112        if ubutton>0:
     113            self.device.emit(ubutton, bool(pressed))
     114
     115    def close(self):
     116        pass
     117
     118
    75119class X11ServerBase(GTKServerBase):
    76120    """
    77121        Base class for X11 servers,
     
    82126    def __init__(self):
    83127        self.screen_number = gdk.display_get_default().get_default_screen().get_number()
    84128        self.root_window = gdk.get_default_root_window()
     129        self.pointer_device = XTestPointerDevice()
    85130        self.last_mouse_user = None
    86131        GTKServerBase.__init__(self)
    87132
     
    94139        self.fake_xinerama = opts.fake_xinerama
    95140        self.current_xinerama_config = None
    96141        self.x11_init()
    97         self.pointer_device = XTestPointerDevice()
    98142
    99143    def x11_init(self):
    100144        if self.fake_xinerama:
     
    219263        self._authenticated_ui_packet_handlers["force-ungrab"] = self._process_force_ungrab
    220264
    221265
     266    def init_virtual_devices(self, devices):
     267        #for the time being, we only use the pointer if there is one:
     268        pointer = devices.get("pointer")
     269        if pointer:
     270            mouselog.info("init_virtual_devices(%s) got pointer=%s", devices, pointer)
     271            uinput_device = pointer.get("uinput")
     272            #name = pointer.get("name")
     273            #device = pointer.get("device")
     274            if uinput_device:
     275                self.pointer_device = UInputPointerDevice(uinput_device)
     276                mouselog.warn("pointer_device=%s", self.pointer_device)
     277
     278
    222279    def get_server_source_class(self):
    223280        from xpra.x11.x11_source import X11ServerSource
    224281        return X11ServerSource