Exemple #1
0
 def init_virtual_devices(self, devices):
     #(this runs in the main thread - before the main loop starts)
     #for the time being, we only use the pointer if there is one:
     pointer = devices.get("pointer")
     touchpad = devices.get("touchpad")
     mouselog("init_virtual_devices(%s) got pointer=%s, touchpad=%s",
              devices, pointer, touchpad)
     self.input_devices = "xtest"
     if pointer:
         uinput_device = pointer.get("uinput")
         device_path = pointer.get("device")
         if uinput_device:
             from xpra.x11.uinput_device import UInputPointerDevice
             self.input_devices = "uinput"
             self.pointer_device = UInputPointerDevice(
                 uinput_device, device_path)
             self.verify_uinput_pointer_device()
     if self.input_devices == "uinput" and touchpad:
         uinput_device = touchpad.get("uinput")
         device_path = touchpad.get("device")
         if uinput_device:
             from xpra.x11.uinput_device import UInputTouchpadDevice
             root_w, root_h = self.get_root_window_size()
             self.touchpad_device = UInputTouchpadDevice(
                 uinput_device, device_path, root_w, root_h)
     try:
         mouselog.info(
             "pointer device emulation using %s",
             str(self.pointer_device).replace("PointerDevice", ""))
     except Exception as e:
         mouselog("cannot get pointer device class from %s: %s",
                  self.pointer_device, e)
Exemple #2
0
class X11ServerBase(X11ServerCore):
    """
        Base class for X11 servers,
        adds uinput, icc and xsettings synchronization to the X11ServerCore class
        (see XpraServer or DesktopServer for actual implementations)
    """
    def __init__(self, clobber):
        self.clobber = clobber
        X11ServerCore.__init__(self)
        self._default_xsettings = {}
        self._settings = {}
        self.double_click_time = 0
        self.double_click_distance = 0
        self.dpi = 0
        self.default_dpi = 0
        self._xsettings_manager = None
        self._xsettings_enabled = False
        self.display_pid = 0
        self.icc_profile = b""

    def do_init(self, opts):
        super().do_init(opts)
        self._xsettings_enabled = opts.xsettings
        if self._xsettings_enabled:
            from xpra.x11.xsettings import XSettingsHelper
            self._default_xsettings = XSettingsHelper().get_settings()
            log("_default_xsettings=%s", self._default_xsettings)
            self.init_all_server_settings()

    def init_display_pid(self, pid):
        if pid:
            from xpra.scripts.server import _save_int
            _save_int(b"_XPRA_SERVER_PID", pid)
        elif self.clobber:
            from xpra.scripts.server import _get_int
            pid = _get_int(b"_XPRA_SERVER_PID")
            if not pid:
                log.info("xvfb pid not found")
            else:
                log.info("xvfb pid=%i", pid)
        self.display_pid = pid

    def kill_display(self):
        if self.display_pid:
            from xpra.server import EXITING_CODE
            if self._upgrading == EXITING_CODE:
                log.info("exiting: not cleaning up Xvfb")
            elif self._upgrading:
                log.info("upgrading: not cleaning up Xvfb")
            else:
                from xpra.x11.vfb_util import kill_xvfb
                kill_xvfb(self.display_pid)

    def do_cleanup(self):
        X11ServerCore.do_cleanup(self)
        self.kill_display()

    def configure_best_screen_size(self):
        root_w, root_h = X11ServerCore.configure_best_screen_size(self)
        if self.touchpad_device:
            self.touchpad_device.root_w = root_w
            self.touchpad_device.root_h = root_h
        return root_w, root_h

    def init_dbus(self, dbus_pid, dbus_env):
        try:
            from xpra.server.dbus import dbus_start
            assert dbus_start
        except ImportError as e:
            log("init_dbus(%s, %s)", dbus_pid, dbus_env, exc_info=True)
            log.warn("Warning: cannot initialize dbus")
            log.warn(" %s", e)
            return
        from xpra.server.dbus.dbus_start import (
            get_saved_dbus_pid,
            get_saved_dbus_env,
            save_dbus_pid,
            save_dbus_env,
        )
        super().init_dbus(dbus_pid, dbus_env)
        if self.clobber:
            #get the saved pids and env
            self.dbus_pid = get_saved_dbus_pid()
            self.dbus_env = get_saved_dbus_env()
            dbuslog("retrieved existing dbus attributes: %s, %s",
                    self.dbus_pid, self.dbus_env)
            if self.dbus_env:
                os.environ.update(self.dbus_env)
        else:
            #now we can save values on the display
            #(we cannot access gtk3 until dbus has started up)
            if self.dbus_pid:
                save_dbus_pid(self.dbus_pid)
            if self.dbus_env:
                save_dbus_env(self.dbus_env)

    def last_client_exited(self):
        self.reset_settings()
        X11ServerCore.last_client_exited(self)

    def init_virtual_devices(self, devices):
        #(this runs in the main thread - before the main loop starts)
        #for the time being, we only use the pointer if there is one:
        pointer = devices.get("pointer")
        touchpad = devices.get("touchpad")
        mouselog("init_virtual_devices(%s) got pointer=%s, touchpad=%s",
                 devices, pointer, touchpad)
        self.input_devices = "xtest"
        if pointer:
            uinput_device = pointer.get("uinput")
            device_path = pointer.get("device")
            if uinput_device:
                from xpra.x11.uinput_device import UInputPointerDevice
                self.input_devices = "uinput"
                self.pointer_device = UInputPointerDevice(
                    uinput_device, device_path)
                self.verify_uinput_pointer_device()
        if self.input_devices == "uinput" and touchpad:
            uinput_device = touchpad.get("uinput")
            device_path = touchpad.get("device")
            if uinput_device:
                from xpra.x11.uinput_device import UInputTouchpadDevice
                root_w, root_h = self.get_root_window_size()
                self.touchpad_device = UInputTouchpadDevice(
                    uinput_device, device_path, root_w, root_h)
        try:
            mouselog.info(
                "pointer device emulation using %s",
                str(self.pointer_device).replace("PointerDevice", ""))
        except Exception as e:
            mouselog("cannot get pointer device class from %s: %s",
                     self.pointer_device, e)

    def verify_uinput_pointer_device(self):
        xtest = XTestPointerDevice()
        ox, oy = 100, 100
        with xlog:
            xtest.move_pointer(0, ox, oy)
        nx, ny = 200, 200
        self.pointer_device.move_pointer(0, nx, ny)

        def verify_uinput_moved():
            pos = None  #@UnusedVariable
            with xswallow:
                pos = X11Keyboard.query_pointer()
                mouselog("X11Keyboard.query_pointer=%s", pos)
            if pos == (ox, oy):
                mouselog.warn("Warning: %s failed verification",
                              self.pointer_device)
                mouselog.warn(" expected pointer at %s, now at %s", (nx, ny),
                              pos)
                mouselog.warn(" usign XTest fallback")
                self.pointer_device = xtest
                self.input_devices = "xtest"

        self.timeout_add(1000, verify_uinput_moved)

    def dpi_changed(self):
        #re-apply the same settings, which will apply the new dpi override to it:
        self.update_server_settings()

    def get_info(self, proto=None, client_uuids=None):
        info = super().get_info(proto=proto, client_uuids=client_uuids)
        info.setdefault("display", {})["icc"] = self.get_icc_info()
        return info

    def get_icc_info(self) -> dict:
        icc_info = {
            "sync": SYNC_ICC,
        }
        if SYNC_ICC:
            icc_info["profile"] = hexstr(self.icc_profile)
        return icc_info

    def set_icc_profile(self):
        if not SYNC_ICC:
            return
        ui_clients = [s for s in self._server_sources.values() if s.ui_client]
        if len(ui_clients) != 1:
            screenlog("%i UI clients, resetting ICC profile to default",
                      len(ui_clients))
            self.reset_icc_profile()
            return
        icc = typedict(ui_clients[0].icc)
        data = None
        for x in ("data", "icc-data", "icc-profile"):
            data = icc.strget(x)
            if data:
                break
        if not data:
            screenlog("no icc data found in %s", icc)
            self.reset_icc_profile()
            return
        screenlog("set_icc_profile() icc data for %s: %s (%i bytes)",
                  ui_clients[0], hexstr(data or ""), len(data or ""))
        self.icc_profile = data
        from xpra.x11.gtk_x11.prop import prop_set
        prop_set(self.root_window, "_ICC_PROFILE", ["u32"],
                 [ord(x) for x in data])
        prop_set(self.root_window, "_ICC_PROFILE_IN_X_VERSION", "u32",
                 0 * 100 + 4)  #0.4 -> 0*100+4*1

    def reset_icc_profile(self):
        screenlog("reset_icc_profile()")
        from xpra.x11.gtk_x11.prop import prop_del
        prop_del(self.root_window, "_ICC_PROFILE")
        prop_del(self.root_window, "_ICC_PROFILE_IN_X_VERSION")
        self.icc_profile = b""

    def reset_settings(self):
        if not self._xsettings_enabled:
            return
        log("resetting xsettings to: %s", self._default_xsettings)
        self.set_xsettings(self._default_xsettings or (0, ()))

    def set_xsettings(self, v):
        if not self._xsettings_enabled:
            return
        log("set_xsettings(%s)", v)
        with xsync:
            if self._xsettings_manager is None:
                from xpra.x11.xsettings import XSettingsManager
                self._xsettings_manager = XSettingsManager()
            self._xsettings_manager.set_settings(v)

    def init_all_server_settings(self):
        log("init_all_server_settings() dpi=%i, default_dpi=%i", self.dpi,
            self.default_dpi)
        #almost like update_all, except we use the default_dpi,
        #since this is called before the first client connects
        self.do_update_server_settings(
            {
                b"resource-manager": b"",
                b"xsettings-blob": (0, [])
            },
            reset=True,
            dpi=self.default_dpi,
            cursor_size=24)

    def update_all_server_settings(self, reset=False):
        self.update_server_settings(
            {
                b"resource-manager": b"",
                b"xsettings-blob": (0, []),
            },
            reset=reset)

    def update_server_settings(self, settings=None, reset=False):
        self.do_update_server_settings(settings or self._settings, reset,
                                       self.dpi, self.double_click_time,
                                       self.double_click_distance,
                                       self.antialias, self.cursor_size)

    def do_update_server_settings(self,
                                  settings,
                                  reset=False,
                                  dpi=0,
                                  double_click_time=0,
                                  double_click_distance=(-1, -1),
                                  antialias={},
                                  cursor_size=-1):
        if not self._xsettings_enabled:
            log("ignoring xsettings update: %s", settings)
            return
        if reset:
            #FIXME: preserve serial? (what happens when we change values which had the same serial?)
            self.reset_settings()
            self._settings = {}
            if self._default_xsettings:
                #try to parse default xsettings into a dict:
                try:
                    for _, prop_name, value, _ in self._default_xsettings[1]:
                        self._settings[prop_name] = value
                except Exception as e:
                    log("failed to parse %s", self._default_xsettings)
                    log.warn("Warning: failed to parse default XSettings:")
                    log.warn(" %s", e)
        old_settings = dict(self._settings)
        log("server_settings: old=%s, updating with=%s", nonl(old_settings),
            nonl(settings))
        log(
            "overrides: dpi=%s, double click time=%s, double click distance=%s",
            dpi, double_click_time, double_click_distance)
        log("overrides: antialias=%s", antialias)
        self._settings.update(settings)
        for k, v in settings.items():
            #cook the "resource-manager" value to add the DPI and/or antialias values:
            if k == b"resource-manager" and (dpi > 0 or antialias
                                             or cursor_size > 0):
                value = bytestostr(v)
                #parse the resources into a dict:
                values = {}
                options = value.split("\n")
                for option in options:
                    if not option:
                        continue
                    parts = option.split(":\t", 1)
                    if len(parts) != 2:
                        log("skipped invalid option: '%s'", option)
                        continue
                    if parts[0] in BLACKLISTED_XSETTINGS:
                        log("skipped blacklisted option: '%s'", option)
                        continue
                    values[parts[0]] = parts[1]
                if cursor_size > 0:
                    values["Xcursor.size"] = cursor_size
                if dpi > 0:
                    values["Xft.dpi"] = dpi
                    values["Xft/DPI"] = dpi * 1024
                    values["gnome.Xft/DPI"] = dpi * 1024
                if antialias:
                    ad = typedict(antialias)
                    subpixel_order = "none"
                    sss = tuple(self._server_sources.values())
                    if len(sss) == 1:
                        #only honour sub-pixel hinting if a single client is connected
                        #and only when it is not using any scaling (or overriden with SCALED_FONT_ANTIALIAS):
                        ss = sss[0]
                        ds_unscaled = getattr(ss, "desktop_size_unscaled",
                                              None)
                        ds_scaled = getattr(ss, "desktop_size", None)
                        if SCALED_FONT_ANTIALIAS or (not ds_unscaled or
                                                     ds_unscaled == ds_scaled):
                            subpixel_order = ad.strget("orientation",
                                                       "none").lower()
                    values.update({
                        "Xft.antialias": ad.intget("enabled", -1),
                        "Xft.hinting": ad.intget("hinting", -1),
                        "Xft.rgba": subpixel_order,
                        "Xft.hintstyle": _get_antialias_hintstyle(ad)
                    })
                log("server_settings: resource-manager values=%s",
                    nonl(values))
                #convert the dict back into a resource string:
                value = ''
                for vk, vv in values.items():
                    value += "%s:\t%s\n" % (vk, vv)
                #record the actual value used
                self._settings[b"resource-manager"] = value
                v = value.encode("utf-8")

            #cook xsettings to add various settings:
            #(as those may not be present in xsettings on some platforms.. like win32 and osx)
            if k==b"xsettings-blob" and \
            (self.double_click_time>0 or self.double_click_distance!=(-1, -1) or antialias or dpi>0):
                #start by removing blacklisted options:
                def filter_blacklisted():
                    serial, values = v
                    new_values = []
                    for _t, _n, _v, _s in values:
                        if bytestostr(_n) in BLACKLISTED_XSETTINGS:
                            log("skipped blacklisted option %s",
                                (_t, _n, _v, _s))
                        else:
                            new_values.append((_t, _n, _v, _s))
                    return serial, new_values

                v = filter_blacklisted()

                def set_xsettings_value(name, value_type, value):
                    #remove existing one, if any:
                    serial, values = v
                    new_values = [(_t, _n, _v, _s)
                                  for (_t, _n, _v, _s) in values if _n != name]
                    new_values.append((value_type, name, value, 0))
                    return serial, new_values

                def set_xsettings_int(name, value):
                    if value < 0:  #not set, return v unchanged
                        return v
                    return set_xsettings_value(name, XSettingsTypeInteger,
                                               value)

                if dpi > 0:
                    v = set_xsettings_int(b"Xft/DPI", dpi * 1024)
                if double_click_time > 0:
                    v = set_xsettings_int(b"Net/DoubleClickTime",
                                          self.double_click_time)
                if antialias:
                    ad = typedict(antialias)
                    v = set_xsettings_int(b"Xft/Antialias",
                                          ad.intget("enabled", -1))
                    v = set_xsettings_int(b"Xft/Hinting",
                                          ad.intget("hinting", -1))
                    v = set_xsettings_value(
                        b"Xft/RGBA", XSettingsTypeString,
                        ad.strget("orientation", "none").lower())
                    v = set_xsettings_value(b"Xft/HintStyle",
                                            XSettingsTypeString,
                                            _get_antialias_hintstyle(ad))
                if double_click_distance != (-1, -1):
                    #some platforms give us a value for each axis,
                    #but X11 only has one, so take the average
                    try:
                        x, y = double_click_distance
                        if x > 0 and y > 0:
                            d = iround((x + y) / 2.0)
                            d = max(1, min(128, d))  #sanitize it a bit
                            v = set_xsettings_int(b"Net/DoubleClickDistance",
                                                  d)
                    except Exception as e:
                        log.warn(
                            "error setting double click distance from %s: %s",
                            double_click_distance, e)

            if k not in old_settings or v != old_settings[k]:
                if k == b"xsettings-blob":
                    self.set_xsettings(v)
                elif k == b"resource-manager":
                    from xpra.x11.gtk_x11.prop import prop_set
                    p = "RESOURCE_MANAGER"
                    log("server_settings: setting %s to %s", nonl(p), nonl(v))
                    prop_set(self.root_window, p, "latin1",
                             strtobytes(v).decode("latin1"))
                else:
                    log.warn("Warning: unexpected setting '%s'", bytestostr(k))