Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/x11_server_base.py : 56%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
4# Copyright (C) 2010-2018 Antoine Martin <antoine@xpra.org>
5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
7# later version. See the file COPYING for details.
9import os
11from xpra.os_util import bytestostr, strtobytes, hexstr
12from xpra.util import typedict, envbool, iround
13from xpra.gtk_common.error import xswallow, xsync, xlog
14from xpra.x11.x11_server_core import X11ServerCore, XTestPointerDevice
15from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
16from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString, BLACKLISTED_XSETTINGS
17from xpra.log import Logger
19log = Logger("x11", "server")
20mouselog = Logger("x11", "server", "mouse")
21screenlog = Logger("server", "screen")
22dbuslog = Logger("dbus")
24X11Keyboard = X11KeyboardBindings()
26SCALED_FONT_ANTIALIAS = envbool("XPRA_SCALED_FONT_ANTIALIAS", False)
27SYNC_ICC = envbool("XPRA_SYNC_ICC", True)
30def _get_antialias_hintstyle(antialias):
31 hintstyle = antialias.strget("hintstyle", "").lower()
32 if hintstyle in ("hintnone", "hintslight", "hintmedium", "hintfull"):
33 #X11 clients can give us what we need directly:
34 return hintstyle
35 #win32 style contrast value:
36 contrast = antialias.intget("contrast", -1)
37 if contrast>1600:
38 return "hintfull"
39 if contrast>1000:
40 return "hintmedium"
41 if contrast>0:
42 return "hintslight"
43 return "hintnone"
46class X11ServerBase(X11ServerCore):
47 """
48 Base class for X11 servers,
49 adds uinput, icc and xsettings synchronization to the X11ServerCore class
50 (see XpraServer or DesktopServer for actual implementations)
51 """
53 def __init__(self, clobber):
54 self.clobber = clobber
55 X11ServerCore.__init__(self)
56 self._default_xsettings = {}
57 self._settings = {}
58 self.double_click_time = 0
59 self.double_click_distance = 0
60 self.dpi = 0
61 self.default_dpi = 0
62 self._xsettings_manager = None
63 self._xsettings_enabled = False
64 self.display_pid = 0
65 self.icc_profile = b""
67 def do_init(self, opts):
68 super().do_init(opts)
69 self._xsettings_enabled = opts.xsettings
70 if self._xsettings_enabled:
71 from xpra.x11.xsettings import XSettingsHelper
72 self._default_xsettings = XSettingsHelper().get_settings()
73 log("_default_xsettings=%s", self._default_xsettings)
74 self.init_all_server_settings()
77 def init_display_pid(self, pid):
78 if pid:
79 from xpra.scripts.server import _save_int
80 _save_int(b"_XPRA_SERVER_PID", pid)
81 else:
82 from xpra.scripts.server import _get_int
83 pid = _get_int(b"_XPRA_SERVER_PID")
84 if not pid:
85 log.info("xvfb pid not found")
86 else:
87 log.info("xvfb pid=%i", pid)
88 self.display_pid = pid
90 def kill_display(self):
91 if self.display_pid:
92 from xpra.server import EXITING_CODE
93 if self._upgrading==EXITING_CODE:
94 log.info("exiting: not cleaning up Xvfb")
95 elif self._upgrading:
96 log.info("upgrading: not cleaning up Xvfb")
97 else:
98 from xpra.x11.vfb_util import kill_xvfb
99 kill_xvfb(self.display_pid)
102 def do_cleanup(self):
103 X11ServerCore.do_cleanup(self)
104 self.kill_display()
107 def configure_best_screen_size(self):
108 root_w, root_h = X11ServerCore.configure_best_screen_size(self)
109 if self.touchpad_device:
110 self.touchpad_device.root_w = root_w
111 self.touchpad_device.root_h = root_h
112 return root_w, root_h
115 def init_dbus(self, dbus_pid, dbus_env):
116 try:
117 from xpra.server.dbus import dbus_start
118 assert dbus_start
119 except ImportError as e:
120 log("init_dbus(%s, %s)", dbus_pid, dbus_env, exc_info=True)
121 log.warn("Warning: cannot initialize dbus")
122 log.warn(" %s", e)
123 return
124 from xpra.server.dbus.dbus_start import (
125 get_saved_dbus_pid, get_saved_dbus_env,
126 save_dbus_pid, save_dbus_env,
127 )
128 super().init_dbus(dbus_pid, dbus_env)
129 if self.clobber:
130 #get the saved pids and env
131 self.dbus_pid = get_saved_dbus_pid()
132 self.dbus_env = get_saved_dbus_env()
133 dbuslog("retrieved existing dbus attributes: %s, %s", self.dbus_pid, self.dbus_env)
134 if self.dbus_env:
135 os.environ.update(self.dbus_env)
136 else:
137 #now we can save values on the display
138 #(we cannot access gtk3 until dbus has started up)
139 if self.dbus_pid:
140 save_dbus_pid(self.dbus_pid)
141 if self.dbus_env:
142 save_dbus_env(self.dbus_env)
145 def last_client_exited(self):
146 self.reset_settings()
147 X11ServerCore.last_client_exited(self)
149 def init_virtual_devices(self, devices):
150 #(this runs in the main thread - before the main loop starts)
151 #for the time being, we only use the pointer if there is one:
152 pointer = devices.get("pointer")
153 touchpad = devices.get("touchpad")
154 mouselog("init_virtual_devices(%s) got pointer=%s, touchpad=%s", devices, pointer, touchpad)
155 self.input_devices = "xtest"
156 if pointer:
157 uinput_device = pointer.get("uinput")
158 device_path = pointer.get("device")
159 if uinput_device:
160 from xpra.x11.uinput_device import UInputPointerDevice
161 self.input_devices = "uinput"
162 self.pointer_device = UInputPointerDevice(uinput_device, device_path)
163 self.verify_uinput_pointer_device()
164 if self.input_devices=="uinput" and touchpad:
165 uinput_device = touchpad.get("uinput")
166 device_path = touchpad.get("device")
167 if uinput_device:
168 from xpra.x11.uinput_device import UInputTouchpadDevice
169 root_w, root_h = self.get_root_window_size()
170 self.touchpad_device = UInputTouchpadDevice(uinput_device, device_path, root_w, root_h)
171 try:
172 mouselog.info("pointer device emulation using %s", str(self.pointer_device).replace("PointerDevice", ""))
173 except Exception as e:
174 mouselog("cannot get pointer device class from %s: %s", self.pointer_device, e)
176 def verify_uinput_pointer_device(self):
177 xtest = XTestPointerDevice()
178 ox, oy = 100, 100
179 with xlog:
180 xtest.move_pointer(0, ox, oy)
181 nx, ny = 200, 200
182 self.pointer_device.move_pointer(0, nx, ny)
183 def verify_uinput_moved():
184 pos = None #@UnusedVariable
185 with xswallow:
186 pos = X11Keyboard.query_pointer()
187 mouselog("X11Keyboard.query_pointer=%s", pos)
188 if pos==(ox, oy):
189 mouselog.warn("Warning: %s failed verification", self.pointer_device)
190 mouselog.warn(" expected pointer at %s, now at %s", (nx, ny), pos)
191 mouselog.warn(" usign XTest fallback")
192 self.pointer_device = xtest
193 self.input_devices = "xtest"
194 self.timeout_add(1000, verify_uinput_moved)
197 def dpi_changed(self):
198 #re-apply the same settings, which will apply the new dpi override to it:
199 self.update_server_settings()
202 def get_info(self, proto=None, client_uuids=None):
203 info = super().get_info(proto=proto, client_uuids=client_uuids)
204 display_info = info.setdefault("display", {})
205 if self.display_pid:
206 display_info["pid"] = self.display_pid
207 display_info["icc"] = self.get_icc_info()
208 return info
210 def get_icc_info(self) -> dict:
211 icc_info = {
212 "sync" : SYNC_ICC,
213 }
214 if SYNC_ICC:
215 icc_info["profile"] = hexstr(self.icc_profile)
216 return icc_info
218 def set_icc_profile(self):
219 if not SYNC_ICC:
220 return
221 ui_clients = [s for s in self._server_sources.values() if s.ui_client]
222 if len(ui_clients)!=1:
223 screenlog("%i UI clients, resetting ICC profile to default", len(ui_clients))
224 self.reset_icc_profile()
225 return
226 icc = typedict(ui_clients[0].icc)
227 data = None
228 for x in ("data", "icc-data", "icc-profile"):
229 data = icc.strget(x)
230 if data:
231 break
232 if not data:
233 screenlog("no icc data found in %s", icc)
234 self.reset_icc_profile()
235 return
236 screenlog("set_icc_profile() icc data for %s: %s (%i bytes)",
237 ui_clients[0], hexstr(data or ""), len(data or ""))
238 self.icc_profile = data
239 from xpra.x11.gtk_x11.prop import prop_set
240 prop_set(self.root_window, "_ICC_PROFILE", ["u32"], [ord(x) for x in data])
241 prop_set(self.root_window, "_ICC_PROFILE_IN_X_VERSION", "u32", 0*100+4) #0.4 -> 0*100+4*1
243 def reset_icc_profile(self):
244 screenlog("reset_icc_profile()")
245 from xpra.x11.gtk_x11.prop import prop_del
246 prop_del(self.root_window, "_ICC_PROFILE")
247 prop_del(self.root_window, "_ICC_PROFILE_IN_X_VERSION")
248 self.icc_profile = b""
251 def reset_settings(self):
252 if not self._xsettings_enabled:
253 return
254 log("resetting xsettings to: %s", self._default_xsettings)
255 self.set_xsettings(self._default_xsettings or (0, ()))
257 def set_xsettings(self, v):
258 if not self._xsettings_enabled:
259 return
260 log("set_xsettings(%s)", v)
261 with xsync:
262 if self._xsettings_manager is None:
263 from xpra.x11.xsettings import XSettingsManager
264 self._xsettings_manager = XSettingsManager()
265 self._xsettings_manager.set_settings(v)
267 def init_all_server_settings(self):
268 log("init_all_server_settings() dpi=%i, default_dpi=%i", self.dpi, self.default_dpi)
269 #almost like update_all, except we use the default_dpi,
270 #since this is called before the first client connects
271 self.do_update_server_settings({
272 b"resource-manager" : b"",
273 b"xsettings-blob" : (0, [])
274 }, reset = True, dpi = self.default_dpi, cursor_size=24)
276 def update_all_server_settings(self, reset=False):
277 self.update_server_settings({
278 b"resource-manager" : b"",
279 b"xsettings-blob" : (0, []),
280 }, reset=reset)
282 def update_server_settings(self, settings=None, reset=False):
283 self.do_update_server_settings(settings or self._settings, reset,
284 self.dpi, self.double_click_time, self.double_click_distance, self.antialias, self.cursor_size)
286 def do_update_server_settings(self, settings, reset=False,
287 dpi=0, double_click_time=0, double_click_distance=(-1, -1), antialias={}, cursor_size=-1):
288 if not self._xsettings_enabled:
289 log("ignoring xsettings update: %s", settings)
290 return
291 if reset:
292 #FIXME: preserve serial? (what happens when we change values which had the same serial?)
293 self.reset_settings()
294 self._settings = {}
295 if self._default_xsettings:
296 #try to parse default xsettings into a dict:
297 try:
298 for _, prop_name, value, _ in self._default_xsettings[1]:
299 self._settings[prop_name] = value
300 except Exception as e:
301 log("failed to parse %s", self._default_xsettings)
302 log.warn("Warning: failed to parse default XSettings:")
303 log.warn(" %s", e)
304 old_settings = dict(self._settings)
305 log("server_settings: old=%r, updating with=%r", old_settings, settings)
306 log("overrides: dpi=%s, double click time=%s, double click distance=%s",
307 dpi, double_click_time, double_click_distance)
308 log("overrides: antialias=%s", antialias)
309 self._settings.update(settings)
310 for k, v in settings.items():
311 #cook the "resource-manager" value to add the DPI and/or antialias values:
312 if k==b"resource-manager" and (dpi>0 or antialias or cursor_size>0):
313 value = bytestostr(v)
314 #parse the resources into a dict:
315 values={}
316 options = value.split("\n")
317 for option in options:
318 if not option:
319 continue
320 parts = option.split(":\t", 1)
321 if len(parts)!=2:
322 log("skipped invalid option: '%s'", option)
323 continue
324 if parts[0] in BLACKLISTED_XSETTINGS:
325 log("skipped blacklisted option: '%s'", option)
326 continue
327 values[parts[0]] = parts[1]
328 if cursor_size>0:
329 values["Xcursor.size"] = cursor_size
330 if dpi>0:
331 values["Xft.dpi"] = dpi
332 values["Xft/DPI"] = dpi*1024
333 values["gnome.Xft/DPI"] = dpi*1024
334 if antialias:
335 ad = typedict(antialias)
336 subpixel_order = "none"
337 sss = tuple(self._server_sources.values())
338 if len(sss)==1:
339 #only honour sub-pixel hinting if a single client is connected
340 #and only when it is not using any scaling (or overriden with SCALED_FONT_ANTIALIAS):
341 ss = sss[0]
342 ds_unscaled = getattr(ss, "desktop_size_unscaled", None)
343 ds_scaled = getattr(ss, "desktop_size", None)
344 if SCALED_FONT_ANTIALIAS or (not ds_unscaled or ds_unscaled==ds_scaled):
345 subpixel_order = ad.strget("orientation", "none").lower()
346 values.update({
347 "Xft.antialias" : ad.intget("enabled", -1),
348 "Xft.hinting" : ad.intget("hinting", -1),
349 "Xft.rgba" : subpixel_order,
350 "Xft.hintstyle" : _get_antialias_hintstyle(ad)})
351 log("server_settings: resource-manager values=%r", values)
352 #convert the dict back into a resource string:
353 value = ''
354 for vk, vv in values.items():
355 value += "%s:\t%s\n" % (vk, vv)
356 #record the actual value used
357 self._settings[b"resource-manager"] = value
358 v = value.encode("utf-8")
360 #cook xsettings to add various settings:
361 #(as those may not be present in xsettings on some platforms.. like win32 and osx)
362 if k==b"xsettings-blob" and \
363 (self.double_click_time>0 or self.double_click_distance!=(-1, -1) or antialias or dpi>0):
364 #start by removing blacklisted options:
365 def filter_blacklisted():
366 serial, values = v
367 new_values = []
368 for _t,_n,_v,_s in values:
369 if bytestostr(_n) in BLACKLISTED_XSETTINGS:
370 log("skipped blacklisted option %s", (_t, _n, _v, _s))
371 else:
372 new_values.append((_t, _n, _v, _s))
373 return serial, new_values
374 v = filter_blacklisted()
375 def set_xsettings_value(name, value_type, value):
376 #remove existing one, if any:
377 serial, values = v
378 new_values = [(_t,_n,_v,_s) for (_t,_n,_v,_s) in values if _n!=name]
379 new_values.append((value_type, name, value, 0))
380 return serial, new_values
381 def set_xsettings_int(name, value):
382 if value<0: #not set, return v unchanged
383 return v
384 return set_xsettings_value(name, XSettingsTypeInteger, value)
385 if dpi>0:
386 v = set_xsettings_int(b"Xft/DPI", dpi*1024)
387 if double_click_time>0:
388 v = set_xsettings_int(b"Net/DoubleClickTime", self.double_click_time)
389 if antialias:
390 ad = typedict(antialias)
391 v = set_xsettings_int(b"Xft/Antialias", ad.intget("enabled", -1))
392 v = set_xsettings_int(b"Xft/Hinting", ad.intget("hinting", -1))
393 v = set_xsettings_value(b"Xft/RGBA", XSettingsTypeString, ad.strget("orientation", "none").lower())
394 v = set_xsettings_value(b"Xft/HintStyle", XSettingsTypeString, _get_antialias_hintstyle(ad))
395 if double_click_distance!=(-1, -1):
396 #some platforms give us a value for each axis,
397 #but X11 only has one, so take the average
398 try:
399 x,y = double_click_distance
400 if x>0 and y>0:
401 d = iround((x+y)/2.0)
402 d = max(1, min(128, d)) #sanitize it a bit
403 v = set_xsettings_int(b"Net/DoubleClickDistance", d)
404 except Exception as e:
405 log.warn("error setting double click distance from %s: %s", double_click_distance, e)
407 if k not in old_settings or v != old_settings[k]:
408 if k == b"xsettings-blob":
409 self.set_xsettings(v)
410 elif k == b"resource-manager":
411 from xpra.x11.gtk_x11.prop import prop_set
412 p = "RESOURCE_MANAGER"
413 log("server_settings: setting %s to %r", p, v)
414 prop_set(self.root_window, p, "latin1", strtobytes(v).decode("latin1"))
415 else:
416 log.warn("Warning: unexpected setting '%s'", bytestostr(k))