Skip to content

Commit

Permalink
#173 / #1611: add uinput server support:
Browse files Browse the repository at this point in the history
* ship "/etc/X11/xorg.conf.d/90-xpra-virtual.conf" so that our virtual devices are ignored by other Xorg servers
* change our xorg.conf to "AutoEnableDevices" and "AutoAddDevices" but ignore all devices that don't match the product string "Xpra"
* use "${XDG_RUNTIME_DIR}/.xpra/xorg.conf.d/$PID" as xorg configdir, create it if needed and destroy it on exit
* if "input-devices=uinput" and starting as root, create a uinput "pointer" device
* when starting the Xdummy vfb, create a virtual device file for each uinput device
* X11 servers (seamless and desktop) can then use the uinput handle so send device events

git-svn-id: https://xpra.org/svn/Xpra/trunk@16572 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jul 31, 2017
1 parent ddcb4f8 commit 77c8221
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 24 deletions.
7 changes: 7 additions & 0 deletions src/etc/X11/xorg.conf.d/90-xpra-virtual.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Ignore all xpra virtual devices by default,
# these will be enabled explicitly when needed.
Section "InputClass"
Identifier "xpra-virtual-device"
MatchProduct "Xpra"
Option "Ignore" "true"
EndSection
2 changes: 1 addition & 1 deletion src/etc/xpra/conf.d/55_server_x11.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ sync-xvfb = 0
# +extension GLX +extension RANDR +extension RENDER \
# -auth $XAUTHORITY \
# -logfile %(log_dir)s/Xorg.${DISPLAY}.log \
# -configdir ${HOME}/.xpra/xorg.conf.d \
# -configdir ${XDG_RUNTIME_DIR}/.xpra/xorg.conf.d/$PID \
# -config %(conf_dir)s/xorg.conf
#
# Selecting virtual X server:
Expand Down
12 changes: 10 additions & 2 deletions src/etc/xpra/xorg.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ Section "ServerFlags"
Option "DontVTSwitch" "true"
Option "AllowMouseOpenFail" "true"
Option "PciForceNone" "true"
Option "AutoEnableDevices" "false"
Option "AutoAddDevices" "false"
Option "AllowEmptyInput" "true"
Option "AutoEnableDevices" "true"
Option "AutoAddDevices" "true"
EndSection

Section "InputClass"
Identifier "ignore-non-xpra-devices"
NoMatchProduct "Xpra"
Option "Ignore" "true"
EndSection


Section "Device"
Identifier "dummy_videocard"
Driver "dummy"
Expand Down
1 change: 1 addition & 0 deletions src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,7 @@ def copytodir(src, dst_dir, chmod=0o644):
etc_xpra_files += ["cuda.conf", "nvenc.keys"]
for x in etc_xpra_files:
copytodir("etc/xpra/%s" % x, "/etc/xpra")
copytodir("etc/X11/xorg.conf.d/90-xpra-virtual.conf", "/etc/X11/xorg.conf.d/")

if pam_ENABLED:
copytodir("etc/pam.d/xpra", "/etc/pam.d")
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/platform/xposix/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
DEFAULT_SSH_CMD = "ssh"
CLIPBOARDS=["CLIPBOARD", "PRIMARY", "SECONDARY"]

INPUT_DEVICES = ["auto", "xi"]
INPUT_DEVICES = ["auto", "xi", "uinput"]
11 changes: 10 additions & 1 deletion src/xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ def get_xorg_bin():
return None


def get_Xdummy_confdir():
from xpra.platform.xposix.paths import _get_runtime_dir
xrd = _get_runtime_dir()
if xrd:
base = "${XDG_RUNTIME_DIR}/xpra"
else:
base = "${HOME}/.xpra"
return base+"/xorg.conf.d/$PID"

def get_Xdummy_command(xorg_cmd="Xorg", log_dir="${XPRA_LOG_DIR}", xorg_conf="/etc/xpra/xorg.conf"):
cmd = [xorg_cmd] #ie: ["Xorg"] or ["xpra_Xdummy"] or ["./install/bin/xpra_Xdummy"]
cmd += [
Expand All @@ -82,7 +91,7 @@ def get_Xdummy_command(xorg_cmd="Xorg", log_dir="${XPRA_LOG_DIR}", xorg_conf="/e
"-logfile", "%s/Xorg.${DISPLAY}.log" % log_dir,
#must be specified with some Xorg versions (ie: arch linux)
#this directory can store xorg config files, it does not need to be created:
"-configdir", "${HOME}/.xpra/xorg.conf.d",
"-configdir", get_Xdummy_confdir(),
"-config", xorg_conf
]
return cmd
Expand Down
12 changes: 10 additions & 2 deletions src/xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None

# Generate the script text now, because os.getcwd() will
# change if/when we daemonize:
from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir
from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, write_pidfile, find_log_dir, create_input_devices
script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir)

uid = int(opts.uid)
Expand Down Expand Up @@ -632,6 +632,11 @@ def add_tcp_mdns_rec(host, iport):
os.environ.update(protected_env)
log("env=%s", os.environ)

#create devices for vfb if needed:
devices = {}
if start_vfb and ROOT and opts.input_devices.lower()=="uinput":
devices = create_input_devices(uid)

# Start the Xvfb server first to get the display_name if needed
from xpra.server.vfb_util import start_Xvfb, check_xvfb_process, verify_display_ready, verify_gdk_display, set_initial_resolution
odisplay_name = display_name
Expand All @@ -640,7 +645,9 @@ def add_tcp_mdns_rec(host, iport):
if start_vfb:
assert not proxying and xauth_data
pixel_depth = validate_pixel_depth(opts.pixel_depth)
xvfb, display_name = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, xauth_data)
xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices)
for f in cleanups:
add_cleanup(f)
xvfb_pid = xvfb.pid
#always update as we may now have the "real" display name:
os.environ["DISPLAY"] = display_name
Expand Down Expand Up @@ -827,6 +834,7 @@ def kill_dbus():
from xpra.x11.desktop_server import XpraDesktopServer
app = XpraDesktopServer()
server_type_info = "xpra desktop"
app.init_virtual_devices(devices)

#publish mdns records:
if opts.mdns:
Expand Down
102 changes: 102 additions & 0 deletions src/xpra/server/server_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,105 @@ def cleanuppidfile():
except Exception as e:
log.error("Error: failed to write pid %i to pidfile '%s':", os.getpid(), pidfile)
log.error(" %s", e)


def get_uinput_device_path(device):
from xpra.log import Logger
log = Logger("server", "mouse")
try:
log("get_uinput_device_path(%s)", device)
fd = device._Device__uinput_fd
log("fd(%s)=%s", device, fd)
import fcntl #@UnresolvedImport
import ctypes
l = 16
buf = ctypes.create_string_buffer(l)
_IOC_READ = 2
#this magic value was calculated using the C macros:
l = fcntl.ioctl(fd, 2148554028, buf)
if l>0 and l<16:
virt_dev_path = buf.raw[:l].rstrip("\0")
log("UI_GET_SYSNAME(%s)=%s", fd, virt_dev_path)
uevent_path = b"/sys/devices/virtual/input/%s" % virt_dev_path
event_dirs = [x for x in os.listdir(uevent_path) if x.startswith("event")]
log("event dirs(%s)=%s", uevent_path, event_dirs)
for d in event_dirs:
uevent_filename = os.path.join(uevent_path, d, "uevent")
uevent_conf = open(uevent_filename, "rb").read()
for line in uevent_conf.splitlines():
if line.find(b"=")>0:
k,v = line.split(b"=", 1)
log("%s=%s", k, v)
if k==b"DEVNAME":
dev_path = b"/dev/%s" % v
log("found device path: %s" % dev_path)
return dev_path
except Exception as e:
log.error("Error: cannot query uinput device path:")
log.error(" %", e)
return None

def create_uinput_pointer_device(uid, gid):
from xpra.log import Logger
log = Logger("server")
try:
import uinput
except ImportError as e:
log.error("Error: cannot create uinput devices:")
log.error(" %s", e)
return None
events = (
uinput.REL_X,
uinput.REL_Y,
uinput.REL_WHEEL,
#REL_HIRES_WHEEL = 0x10
#uinput.REL_HWHEEL,
uinput.BTN_LEFT,
uinput.BTN_RIGHT,
uinput.BTN_MIDDLE,
uinput.BTN_SIDE,
uinput.BTN_EXTRA,
uinput.BTN_FORWARD,
uinput.BTN_BACK,
)
BUS_USB = 0x03
#BUS_VIRTUAL = 0x06
name = "Xpra Virtual Pointer"
try:
uinput_pointer = uinput.Device(events, name=name, bustype=BUS_USB, vendor=0, product=0, version=0)
except OSError as e:
log.error("Error: cannot open uinput,")
log.error(" make sure that the kernel module is loaded")
log.error(" and that the /dev/uinput device exists:")
log.error(" %s", e)
return None
dev_path = get_uinput_device_path(uinput_pointer)
if not dev_path:
uinput_pointer.destroy()
return None
#log("chown%s", (dev_path, uid, gid))
#os.lchown(dev_path, uid, gid)
#udev rules change the device ownership
#FIXME: fix udev or use inotify? (racy)
import time
time.sleep(1)
log("chown%s", (dev_path, uid, gid))
os.lchown(dev_path, uid, gid)
os.lchown("/dev/input/mouse2", uid, gid)
return name, uinput_pointer, dev_path

def create_uinput_devices(uid, gid):
d = create_uinput_pointer_device(uid, gid)
if not d:
return {}
name, uinput_pointer, dev_path = d
return {
"pointer" : {
"name" : name,
"uinput" : uinput_pointer,
"device" : dev_path,
}
}

def create_input_devices(uid, gid=-1):
return create_uinput_devices(uid, gid)
114 changes: 98 additions & 16 deletions src/xpra/server/vfb_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
import os.path

from xpra.scripts.main import no_gtk
from xpra.scripts.config import InitException
from xpra.scripts.config import InitException, get_Xdummy_confdir
from xpra.os_util import setsid, shellsub, monotonic_time, close_fds, setuidgid, getuid, getgid, strtobytes, POSIX
from xpra.platform.dotxpra import osexpand


DEFAULT_VFB_RESOLUTION = tuple(int(x) for x in os.environ.get("XPRA_DEFAULT_VFB_RESOLUTION", "8192x4096").replace(",", "x").split("x", 1))
Expand All @@ -24,12 +23,84 @@
assert len(DEFAULT_DESKTOP_VFB_RESOLUTION)==2


def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data):
XORG_MATCH_OPTIONS = {
"pointer" : """
MatchIsPointer "True"
Driver "libinput"
Option "AccelProfile" "flat"
""",
"keyboard" : 'MatchIsKeyboard "True"',
}


def create_xorg_device_configs(xorg_conf_dir, devices, uid, gid):
from xpra.log import Logger
log = Logger("server", "x11")
cleanups = []
if not devices:
return cleanups

def makedir(dirname):
log("makedir(%s)", dirname)
os.mkdir(dirname)
os.lchown(dirname, uid, gid)
def cleanup_dir():
try:
log("cleanup_dir() %s", dirname)
os.rmdir(dirname)
except Exception as e:
log("failed to cleanup %s: %s", dirname, e)
cleanups.insert(0, cleanup_dir)

#create conf dir if needed:
d = xorg_conf_dir
dirs = []
while d and not os.path.exists(d):
log("create_device_configs: dir does not exist: %s", d)
dirs.insert(0, d)
d = os.path.dirname(d)
for d in dirs:
makedir(d)

#create individual device files:
i = 0
for dev_type, devdef in devices.items():
#ie:
#name = "pointer"
#devdef = {"uinput" : uninput.Device, "device" : "/dev/input20" }
match_type = XORG_MATCH_OPTIONS.get(dev_type)
uinput = devdef.get("uinput")
device = devdef.get("device")
name = devdef.get("name")
if match_type and uinput and device and name:
conf_file = os.path.join(xorg_conf_dir, "%02i-%s.conf" % (i, dev_type))
with open(conf_file, "wb") as f:
f.write("""
Section "InputClass"
Identifier "xpra-virtual-%s"
MatchProduct "%s"
Option "Ignore" "False"
%s
EndSection
""" % (dev_type, name, match_type))
os.fchown(f.fileno(), uid, gid)
#Option "AccelerationProfile" "-1"
#Option "AccelerationScheme" "none"
#Option "AccelSpeed" "-1"
def cleanup_conf_file():
log("cleanup_conf_file: %s", conf_file)
os.unlink(conf_file)
cleanups.insert(0, cleanup_conf_file)
return cleanups


def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, devices={}):
if not POSIX:
raise InitException("starting an Xvfb is not supported on %s" % os.name)
if not xvfb_str:
raise InitException("the 'xvfb' command is not defined")

from xpra.platform.xposix.paths import _get_runtime_dir
from xpra.log import Logger
log = Logger("server", "x11")

Expand All @@ -47,6 +118,26 @@ def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data):
log.error(" %s", e)
use_display_fd = display_name[0]=='S'

HOME = os.path.expanduser("~%s" % username)
subs = {
"XAUTHORITY" : xauthority,
"USER" : username or os.environ.get("USER", "unknown-user"),
"UID" : uid,
"GID" : gid,
"PID" : os.getpid(),
"HOME" : HOME,
"DISPLAY" : display_name,
"XDG_RUNTIME_DIR" : os.environ.get("XDG_RUNTIME_DIR", _get_runtime_dir()),
"XPRA_LOG_DIR" : os.environ.get("XPRA_LOG_DIR"),
}
def pathexpand(s):
return shellsub(s, subs)

#create uinput device definition files:
#(we are assuming that Xorg is configured to use this path..)
xorg_conf_dir = pathexpand(get_Xdummy_confdir())
cleanups = create_xorg_device_configs(xorg_conf_dir, devices, uid, gid)

#identify logfile argument if it exists,
#as we may have to rename it, or create the directory for it:
import shlex
Expand All @@ -62,7 +153,8 @@ def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data):
#keep track of it so we can rename it later:
tmp_xorg_log_file = xvfb_cmd[logfile_argindex+1]
#make sure the Xorg log directory exists:
xorg_log_dir = osexpand(os.path.dirname(xvfb_cmd[logfile_argindex+1]))
xorg_log_dir = os.path.dirname(pathexpand(xvfb_cmd[logfile_argindex+1]))
log.info("xorg_log_dir=%s - exists=%s", xorg_log_dir, os.path.exists(xorg_log_dir))
if not os.path.exists(xorg_log_dir):
try:
log("creating Xorg log dir '%s'", xorg_log_dir)
Expand All @@ -76,17 +168,7 @@ def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data):
raise InitException("failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e))

#apply string substitutions:
subs = {
"XAUTHORITY" : xauthority,
"USER" : os.environ.get("USER", "unknown-user"),
"UID" : os.getuid(),
"GID" : os.getgid(),
"HOME" : os.environ.get("HOME", cwd),
"DISPLAY" : display_name,
"XPRA_LOG_DIR" : os.environ.get("XPRA_LOG_DIR"),
}
xvfb_cmd = shellsub(xvfb_str, subs).split()
log("shellsub(%s, %s)=%s", xvfb_str, subs, xvfb_cmd)
xvfb_cmd = pathexpand(xvfb_str).split()

if not xvfb_cmd:
raise InitException("cannot start Xvfb, the command definition is missing!")
Expand Down Expand Up @@ -160,7 +242,7 @@ def preexec():
xauth_add(display_name, xauth_data)
log("xvfb process=%s", xvfb)
log("display_name=%s", display_name)
return xvfb, display_name
return xvfb, display_name, cleanups


def set_initial_resolution(desktop=False):
Expand Down
Loading

0 comments on commit 77c8221

Please sign in to comment.