Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/platform/xposix/gui.py : 44%
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# This file is part of Xpra.
2# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2011-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7import os
8import sys
9import struct
11from xpra.os_util import bytestostr, hexstr
12from xpra.util import iround, envbool, envint, csv, ellipsizer
13from xpra.os_util import is_unity, is_gnome, is_kde, is_Ubuntu, is_Fedora, is_X11, is_Wayland, saved_env
14from xpra.log import Logger
16log = Logger("posix")
17eventlog = Logger("posix", "events")
18screenlog = Logger("posix", "screen")
19dbuslog = Logger("posix", "dbus")
20traylog = Logger("posix", "tray")
21mouselog = Logger("posix", "mouse")
22xinputlog = Logger("posix", "xinput")
24_X11Window = False
25def X11WindowBindings():
26 global _X11Window
27 if _X11Window is False:
28 _X11Window = None
29 if is_X11():
30 try:
31 from xpra.x11.bindings.window_bindings import X11WindowBindings as _X11WindowBindings #@UnresolvedImport
32 _X11Window = _X11WindowBindings()
33 except Exception as e:
34 log("X11WindowBindings()", exc_info=True)
35 log.error("Error: no X11 bindings")
36 log.error(" %s", e)
37 return _X11Window
39def X11RandRBindings():
40 if is_X11():
41 try:
42 from xpra.x11.bindings.randr_bindings import RandRBindings #@UnresolvedImport
43 return RandRBindings()
44 except Exception as e:
45 log("RandRBindings()", exc_info=True)
46 log.error("Error: no X11 RandR bindings")
47 log.error(" %s", e)
48 return None
50X11XI2 = False
51def X11XI2Bindings():
52 global X11XI2
53 if X11XI2 is False:
54 X11XI2 = None
55 if is_X11():
56 try:
57 from xpra.x11.bindings.xi2_bindings import X11XI2Bindings as _X11XI2Bindings #@UnresolvedImport
58 X11XI2 = _X11XI2Bindings()
59 except Exception:
60 log.error("no XI2 bindings", exc_info=True)
61 return X11XI2
64device_bell = None
65GTK_MENUS = envbool("XPRA_GTK_MENUS", False)
66RANDR_DPI = envbool("XPRA_RANDR_DPI", True)
67XSETTINGS_DPI = envbool("XPRA_XSETTINGS_DPI", True)
68USE_NATIVE_TRAY = envbool("XPRA_USE_NATIVE_TRAY", is_unity() or (is_Ubuntu() and is_gnome()) or (is_gnome() and not is_Fedora()) or is_kde())
69XINPUT_WHEEL_DIV = envint("XPRA_XINPUT_WHEEL_DIV", 15)
70DBUS_SCREENSAVER = envbool("XPRA_DBUS_SCREENSAVER", False)
73def gl_check():
74 if not is_X11() and is_Wayland():
75 return "disabled under wayland with GTK3 (buggy)"
76 return None
79def get_native_system_tray_classes():
80 return []
82def get_wm_name():
83 wm_name = os.environ.get("XDG_CURRENT_DESKTOP", "") or os.environ.get("XDG_SESSION_DESKTOP") or os.environ.get("DESKTOP_SESSION")
84 if os.environ.get("XDG_SESSION_TYPE")=="wayland" or os.environ.get("GDK_BACKEND")=="wayland":
85 if wm_name:
86 wm_name += " on wayland"
87 else:
88 wm_name = "wayland"
89 elif is_X11():
90 try:
91 wm_check = _get_X11_root_property("_NET_SUPPORTING_WM_CHECK", "WINDOW")
92 if wm_check:
93 xid = struct.unpack(b"@L", wm_check)[0]
94 traylog("_NET_SUPPORTING_WM_CHECK window=%#x", xid)
95 wm_name = _get_X11_window_property(xid, "_NET_WM_NAME", "UTF8_STRING")
96 traylog("_NET_WM_NAME=%s", wm_name)
97 if wm_name:
98 return wm_name.decode("utf-8")
99 except Exception as e:
100 traylog("get_wm_name()", exc_info=True)
101 traylog.error("Error accessing window manager information:")
102 traylog.error(" %s", e)
103 return wm_name
105def get_clipboard_native_class():
106 if is_Wayland():
107 return "xpra.gtk_common.gtk_clipboard.GTK_Clipboard"
108 return "xpra.x11.gtk_x11.clipboard.X11Clipboard"
110def get_native_tray_classes():
111 #could restrict to only DEs that have a broken system tray like "GNOME Shell"?
112 c = []
113 if USE_NATIVE_TRAY:
114 try:
115 from xpra.platform.xposix.appindicator_tray import AppindicatorTray
116 c.append(AppindicatorTray)
117 except (ImportError, ValueError):
118 traylog("cannot load appindicator tray", exc_info=True)
119 traylog.warn("Warning: appindicator library not found")
120 traylog.warn(" you may want to install libappindicator")
121 traylog.warn(" to enable the system tray.")
122 if saved_env.get("XDG_CURRENT_DESKTOP")=="GNOME":
123 traylog.warn(" With gnome-shell, you may also need some extensions:")
124 traylog.warn(" 'top icons plus' and / or 'appindicator'")
125 return c
128def get_native_notifier_classes():
129 ncs = []
130 try:
131 from xpra.notifications.dbus_notifier import DBUS_Notifier_factory
132 ncs.append(DBUS_Notifier_factory)
133 except Exception as e:
134 dbuslog("cannot load dbus notifier: %s", e)
135 try:
136 from xpra.notifications.pynotify_notifier import PyNotify_Notifier
137 ncs.append(PyNotify_Notifier)
138 except Exception as e:
139 log("cannot load pynotify notifier: %s", e)
140 return ncs
143def get_session_type():
144 return os.environ.get("XDG_SESSION_TYPE", "")
147#we duplicate some of the code found in gtk_x11.prop ...
148#which is still better than having dependencies on that GTK2 code
149def _get_X11_window_property(xid, name, req_type):
150 try:
151 from xpra.gtk_common.error import xsync
152 from xpra.x11.bindings.window_bindings import PropertyError #@UnresolvedImport
153 try:
154 with xsync:
155 prop = X11WindowBindings().XGetWindowProperty(xid, name, req_type)
156 log("_get_X11_window_property(%#x, %s, %s)=%s, len=%s", xid, name, req_type, type(prop), len(prop or []))
157 return prop
158 except PropertyError as e:
159 log("_get_X11_window_property(%#x, %s, %s): %s", xid, name, req_type, e)
160 except Exception as e:
161 log.warn("Warning: failed to get X11 window property '%s' on window %#x: %s", name, xid, e)
162 log("get_X11_window_property%s", (xid, name, req_type), exc_info=True)
163 return None
164def _get_X11_root_property(name, req_type):
165 try:
166 root_xid = X11WindowBindings().getDefaultRootWindow()
167 return _get_X11_window_property(root_xid, name, req_type)
168 except Exception as e:
169 log("_get_X11_root_property(%s, %s)", name, req_type, exc_info=True)
170 log.warn("Warning: failed to get X11 root property '%s'", name)
171 log.warn(" %s", e)
172 return None
175def _get_xsettings():
176 from xpra.gtk_common.error import xlog
177 X11Window = X11WindowBindings()
178 if not X11Window:
179 return None
180 with xlog:
181 selection = "_XSETTINGS_S0"
182 owner = X11Window.XGetSelectionOwner(selection)
183 if not owner:
184 return None
185 XSETTINGS = "_XSETTINGS_SETTINGS"
186 data = X11Window.XGetWindowProperty(owner, XSETTINGS, XSETTINGS)
187 if not data:
188 return None
189 from xpra.x11.xsettings_prop import get_settings
190 return get_settings(X11Window.get_display_name(), data)
191 return None
193def _get_xsettings_dict():
194 d = {}
195 if is_Wayland():
196 return d
197 v = _get_xsettings()
198 if v:
199 _, values = v
200 for setting_type, prop_name, value, _ in values:
201 d[bytestostr(prop_name)] = (setting_type, value)
202 return d
205def _get_xsettings_dpi():
206 if XSETTINGS_DPI and is_X11():
207 from xpra.x11.xsettings_prop import XSettingsTypeInteger
208 d = _get_xsettings_dict()
209 for k,div in {
210 "Xft.dpi" : 1,
211 "Xft/DPI" : 1024,
212 "gnome.Xft/DPI" : 1024,
213 #"Gdk/UnscaledDPI" : 1024, ??
214 }.items():
215 if k in d:
216 value_type, value = d.get(k)
217 if value_type==XSettingsTypeInteger:
218 actual_value = max(10, min(1000, value//div))
219 screenlog("_get_xsettings_dpi() found %s=%s, div=%i, actual value=%i", k, value, div, actual_value)
220 return actual_value
221 return -1
223def _get_randr_dpi():
224 if RANDR_DPI and not is_Wayland():
225 from xpra.gtk_common.error import xlog
226 with xlog:
227 randr_bindings = X11RandRBindings()
228 if randr_bindings and randr_bindings.has_randr():
229 wmm, hmm = randr_bindings.get_screen_size_mm()
230 if wmm>0 and hmm>0:
231 w, h = randr_bindings.get_screen_size()
232 dpix = iround(w * 25.4 / wmm)
233 dpiy = iround(h * 25.4 / hmm)
234 screenlog("xdpi=%s, ydpi=%s - size-mm=%ix%i, size=%ix%i", dpix, dpiy, wmm, hmm, w, h)
235 return dpix, dpiy
236 return -1, -1
238def get_xdpi():
239 dpi = _get_xsettings_dpi()
240 if dpi>0:
241 return dpi
242 return _get_randr_dpi()[0]
244def get_ydpi():
245 dpi = _get_xsettings_dpi()
246 if dpi>0:
247 return dpi
248 return _get_randr_dpi()[1]
251def get_icc_info():
252 if not is_Wayland():
253 try:
254 data = _get_X11_root_property("_ICC_PROFILE", "CARDINAL")
255 if data:
256 screenlog("_ICC_PROFILE=%s (%s)", type(data), len(data))
257 version = _get_X11_root_property("_ICC_PROFILE_IN_X_VERSION", "CARDINAL")
258 screenlog("get_icc_info() found _ICC_PROFILE_IN_X_VERSION=%s, _ICC_PROFILE=%s",
259 hexstr(version or ""), hexstr(data))
260 icc = {
261 "source" : "_ICC_PROFILE",
262 "data" : data,
263 }
264 if version:
265 try:
266 version = ord(version)
267 except TypeError:
268 pass
269 icc["version"] = version
270 screenlog("get_icc_info()=%s", icc)
271 return icc
272 except Exception as e:
273 screenlog.error("Error: cannot access _ICC_PROFILE X11 window property")
274 screenlog.error(" %s", e)
275 screenlog("get_icc_info()", exc_info=True)
276 from xpra.platform.gui import default_get_icc_info
277 return default_get_icc_info()
280def get_antialias_info():
281 info = {}
282 try:
283 from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString
284 d = _get_xsettings_dict()
285 for prop_name, name in {"Xft/Antialias" : "enabled",
286 "Xft/Hinting" : "hinting"}.items():
287 if prop_name in d:
288 value_type, value = d.get(prop_name)
289 if value_type==XSettingsTypeInteger and value>0:
290 info[name] = bool(value)
291 def get_contrast(value):
292 #win32 API uses numerical values:
293 #(this is my best guess at translating the X11 names)
294 return {"hintnone" : 0,
295 "hintslight" : 1000,
296 "hintmedium" : 1600,
297 "hintfull" : 2200}.get(bytestostr(value))
298 for prop_name, name, convert in (
299 ("Xft/HintStyle", "hintstyle", bytestostr),
300 ("Xft/HintStyle", "contrast", get_contrast),
301 ("Xft/RGBA", "orientation", lambda x : bytestostr(x).upper())
302 ):
303 if prop_name in d:
304 value_type, value = d.get(prop_name)
305 if value_type==XSettingsTypeString:
306 cval = convert(value)
307 if cval is not None:
308 info[name] = cval
309 except Exception as e:
310 screenlog.warn("failed to get antialias info from xsettings: %s", e)
311 screenlog("get_antialias_info()=%s", info)
312 return info
315def get_current_desktop():
316 v = -1
317 if not is_Wayland():
318 d = None
319 try:
320 d = _get_X11_root_property("_NET_CURRENT_DESKTOP", "CARDINAL")
321 if d:
322 v = struct.unpack(b"@L", d)[0]
323 except Exception as e:
324 log.warn("failed to get current desktop: %s", e)
325 log("get_current_desktop() %s=%s", hexstr(d or ""), v)
326 return v
328def get_workarea():
329 if not is_Wayland():
330 try:
331 d = get_current_desktop()
332 if d<0:
333 return None
334 workarea = _get_X11_root_property("_NET_WORKAREA", "CARDINAL")
335 if not workarea:
336 return None
337 screenlog("get_workarea() _NET_WORKAREA=%s (%s), len=%s",
338 ellipsizer(workarea), type(workarea), len(workarea))
339 #workarea comes as a list of 4 CARDINAL dimensions (x,y,w,h), one for each desktop
340 sizeof_long = struct.calcsize(b"@L")
341 if len(workarea)<(d+1)*4*sizeof_long:
342 screenlog.warn("get_workarea() invalid _NET_WORKAREA value")
343 else:
344 cur_workarea = workarea[d*4*sizeof_long:(d+1)*4*sizeof_long]
345 v = struct.unpack(b"@LLLL", cur_workarea)
346 screenlog("get_workarea() %s=%s", hexstr(cur_workarea), v)
347 return v
348 except Exception as e:
349 screenlog("get_workarea()", exc_info=True)
350 screenlog.warn("Warning: failed to query workarea: %s", e)
351 return None
354def get_number_of_desktops():
355 v = 0
356 if not is_Wayland():
357 d = None
358 try:
359 d = _get_X11_root_property("_NET_NUMBER_OF_DESKTOPS", "CARDINAL")
360 if d:
361 v = struct.unpack(b"@L", d)[0]
362 except Exception as e:
363 screenlog.warn("failed to get number of desktop: %s", e)
364 v = max(1, v)
365 screenlog("get_number_of_desktops() %s=%s", hexstr(d or ""), v)
366 return v
368def get_desktop_names():
369 v = []
370 if not is_Wayland():
371 v = ["Main"]
372 d = None
373 try:
374 d = _get_X11_root_property("_NET_DESKTOP_NAMES", "UTF8_STRING")
375 if d:
376 v = d.split(b"\0")
377 if len(v)>1 and v[-1]==b"":
378 v = v[:-1]
379 return [x.decode("utf8") for x in v]
380 except Exception as e:
381 screenlog.warn("failed to get desktop names: %s", e)
382 screenlog("get_desktop_names() %s=%s", hexstr(d or ""), v)
383 return v
386def get_vrefresh():
387 v = -1
388 if not is_Wayland():
389 try:
390 from xpra.x11.bindings.randr_bindings import RandRBindings #@UnresolvedImport
391 randr = RandRBindings()
392 if randr.has_randr():
393 v = randr.get_vrefresh()
394 except Exception as e:
395 log("get_vrefresh()", exc_info=True)
396 log.warn("Warning: failed to query the display vertical refresh rate:")
397 log.warn(" %s", e)
398 screenlog("get_vrefresh()=%s", v)
399 return v
402def _get_xresources():
403 if not is_Wayland():
404 try:
405 from xpra.x11.gtk_x11.prop import prop_get
406 from xpra.gtk_common.gtk_util import get_default_root_window
407 root = get_default_root_window()
408 value = prop_get(root, "RESOURCE_MANAGER", "latin1", ignore_errors=True)
409 log("RESOURCE_MANAGER=%s", value)
410 if value is None:
411 return None
412 #parse the resources into a dict:
413 values={}
414 options = value.split("\n")
415 for option in options:
416 if not option:
417 continue
418 parts = option.split(":\t", 1)
419 if len(parts)!=2:
420 log("skipped invalid option: '%s'", option)
421 continue
422 values[parts[0]] = parts[1]
423 return values
424 except Exception as e:
425 log("_get_xresources error: %s", e)
426 return None
428def get_cursor_size():
429 d = _get_xresources() or {}
430 try:
431 return int(d.get("Xcursor.size", 0))
432 except ValueError:
433 return -1
436def _get_xsettings_int(name, default_value):
437 d = _get_xsettings_dict()
438 if name not in d:
439 return default_value
440 value_type, value = d.get(name)
441 from xpra.x11.xsettings_prop import XSettingsTypeInteger
442 if value_type!=XSettingsTypeInteger:
443 return default_value
444 return value
446def get_double_click_time():
447 return _get_xsettings_int("Net/DoubleClickTime", -1)
449def get_double_click_distance():
450 v = _get_xsettings_int("Net/DoubleClickDistance", -1)
451 return v, v
453def get_window_frame_sizes():
454 #for X11, have to create a window and then check the
455 #_NET_FRAME_EXTENTS value after sending a _NET_REQUEST_FRAME_EXTENTS message,
456 #so this is done in the gtk client instead of here...
457 return {}
460def system_bell(window, device, percent, _pitch, _duration, bell_class, bell_id, bell_name):
461 if not is_X11():
462 return False
463 global device_bell
464 if device_bell is False:
465 #failed already
466 return False
467 from xpra.gtk_common.error import XError
468 def x11_bell():
469 global device_bell
470 if device_bell is None:
471 #try to load it:
472 from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
473 device_bell = X11KeyboardBindings().device_bell
474 device_bell(window.get_xid(), device, bell_class, bell_id, percent, bell_name)
475 try:
476 from xpra.gtk_common.error import xlog
477 with xlog:
478 x11_bell()
479 return True
480 except XError as e:
481 log("x11_bell()", exc_info=True)
482 log.error("Error using device_bell: %s", e)
483 log.error(" switching native X11 bell support off")
484 device_bell = False
485 return False
488def _send_client_message(window, message_type, *values):
489 try:
490 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source
491 init_gdk_display_source()
492 from xpra.x11.bindings.window_bindings import constants #@UnresolvedImport
493 X11Window = X11WindowBindings()
494 root_xid = X11Window.getDefaultRootWindow()
495 if window:
496 xid = window.get_xid()
497 else:
498 xid = root_xid
499 SubstructureNotifyMask = constants["SubstructureNotifyMask"]
500 SubstructureRedirectMask = constants["SubstructureRedirectMask"]
501 event_mask = SubstructureNotifyMask | SubstructureRedirectMask
502 from xpra.gtk_common.error import xsync
503 with xsync:
504 X11Window.sendClientMessage(root_xid, xid, False, event_mask, message_type, *values)
505 except Exception as e:
506 log.warn("failed to send client message '%s' with values=%s: %s", message_type, values, e)
508def show_desktop(b):
509 _send_client_message(None, "_NET_SHOWING_DESKTOP", int(bool(b)))
511def set_fullscreen_monitors(window, fsm, source_indication=0):
512 if not isinstance(fsm, (tuple, list)):
513 log.warn("invalid type for fullscreen-monitors: %s", type(fsm))
514 return
515 if len(fsm)!=4:
516 log.warn("invalid number of fullscreen-monitors: %s", len(fsm))
517 return
518 values = list(fsm)+[source_indication]
519 _send_client_message(window, "_NET_WM_FULLSCREEN_MONITORS", *values)
521def _toggle_wm_state(window, state, enabled):
522 if enabled:
523 action = 1 #"_NET_WM_STATE_ADD"
524 else:
525 action = 0 #"_NET_WM_STATE_REMOVE"
526 _send_client_message(window, "_NET_WM_STATE", action, state)
528def set_shaded(window, shaded):
529 _toggle_wm_state(window, "_NET_WM_STATE_SHADED", shaded)
533WINDOW_ADD_HOOKS = []
534def add_window_hooks(window):
535 global WINDOW_ADD_HOOKS
536 for x in WINDOW_ADD_HOOKS:
537 x(window)
538 log("add_window_hooks(%s) added %s", window, WINDOW_ADD_HOOKS)
540WINDOW_REMOVE_HOOKS = []
541def remove_window_hooks(window):
542 global WINDOW_REMOVE_HOOKS
543 for x in WINDOW_REMOVE_HOOKS:
544 x(window)
545 log("remove_window_hooks(%s) added %s", window, WINDOW_REMOVE_HOOKS)
548def get_info():
549 from xpra.platform.gui import get_info_base
550 i = get_info_base()
551 s = _get_xsettings()
552 if s:
553 serial, values = s
554 xi = {"serial" : serial}
555 for _,name,value,_ in values:
556 xi[bytestostr(name)] = value
557 i["xsettings"] = xi
558 i.setdefault("dpi", {
559 "xsettings" : _get_xsettings_dpi(),
560 "randr" : _get_randr_dpi()
561 })
562 return i
565class XI2_Window:
566 def __init__(self, window):
567 log("XI2_Window(%s)", window)
568 self.XI2 = X11XI2Bindings()
569 self.X11Window = X11WindowBindings()
570 self.window = window
571 self.xid = window.get_window().get_xid()
572 self.windows = ()
573 self.motion_valuators = {}
574 window.connect("configure-event", self.configured)
575 self.configured()
576 #replace event handlers with XI2 version:
577 self._do_motion_notify_event = window._do_motion_notify_event
578 window._do_motion_notify_event = self.noop
579 window._do_button_press_event = self.noop
580 window._do_button_release_event = self.noop
581 window._do_scroll_event = self.noop
582 window.connect("destroy", self.cleanup)
584 def noop(self, *args):
585 pass
587 def cleanup(self, *_args):
588 for window in self.windows:
589 self.XI2.disconnect(window)
590 self.windows = []
591 self.window = None
593 def configured(self, *_args):
594 from xpra.gtk_common.error import xlog
595 with xlog:
596 self.windows = self.get_parent_windows(self.xid)
597 for window in (self.windows or ()):
598 self.XI2.connect(window, "XI_Motion", self.do_xi_motion)
599 self.XI2.connect(window, "XI_ButtonPress", self.do_xi_button)
600 self.XI2.connect(window, "XI_ButtonRelease", self.do_xi_button)
601 self.XI2.connect(window, "XI_DeviceChanged", self.do_xi_device_changed)
602 self.XI2.connect(window, "XI_HierarchyChanged", self.do_xi_hierarchy_changed)
604 def do_xi_device_changed(self, *_args):
605 self.motion_valuators = {}
607 def do_xi_hierarchy_changed(self, *_args):
608 self.motion_valuators = {}
611 def get_parent_windows(self, oxid):
612 windows = [oxid]
613 root = self.X11Window.getDefaultRootWindow()
614 xid = oxid
615 while True:
616 xid = self.X11Window.getParent(xid)
617 if xid==0 or xid==root:
618 break
619 windows.append(xid)
620 xinputlog("get_parent_windows(%#x)=%s", oxid, csv(hex(x) for x in windows))
621 return windows
624 def do_xi_button(self, event, device):
625 window = self.window
626 client = window._client
627 if client.readonly:
628 return
629 xinputlog("do_xi_button(%s, %s) server_input_devices=%s", event, device, client.server_input_devices)
630 if client.server_input_devices=="xi" or (client.server_input_devices=="uinput" and client.server_precise_wheel):
631 #skip synthetic scroll events,
632 #as the server should synthesize them from the motion events
633 #those have the same serial:
634 matching_motion = self.XI2.find_event("XI_Motion", event.serial)
635 #maybe we need more to distinguish?
636 if matching_motion:
637 return
638 button = event.detail
639 depressed = (event.name == "XI_ButtonPress")
640 args = self.get_pointer_extra_args(event)
641 window._button_action(button, event, depressed, *args)
643 def do_xi_motion(self, event, device):
644 window = self.window
645 if window.moveresize_event:
646 xinputlog("do_xi_motion(%s, %s) handling as a moveresize event on window %s", event, device, window)
647 window.motion_moveresize(event)
648 self._do_motion_notify_event(event)
649 return
650 client = window._client
651 if client.readonly:
652 return
653 pointer, relative_pointer, modifiers, buttons = window._pointer_modifiers(event)
654 wid = self.window.get_mouse_event_wid(*pointer)
655 #log("server_input_devices=%s, server_precise_wheel=%s",
656 # client.server_input_devices, client.server_precise_wheel)
657 valuators = event.valuators
658 unused_valuators = valuators.copy()
659 dx, dy = 0, 0
660 if (valuators and device and device.get("enabled") and
661 client.server_input_devices=="uinput" and client.server_precise_wheel):
662 XIModeRelative = 0
663 classes = device.get("classes")
664 val_classes = {}
665 for c in classes.values():
666 number = c.get("number")
667 if number is not None and c.get("type")=="valuator" and c.get("mode")==XIModeRelative:
668 val_classes[number] = c
669 #previous values:
670 mv = self.motion_valuators.setdefault(event.device, {})
671 last_x, last_y = 0, 0
672 wheel_x, wheel_y = 0, 0
673 unused_valuators = {}
674 for number, value in valuators.items():
675 valuator = val_classes.get(number)
676 if valuator:
677 label = valuator.get("label")
678 if label:
679 mouselog("%s: %s", label, value)
680 if label.lower().find("horiz")>=0:
681 wheel_x = value
682 last_x = mv.get(number)
683 continue
684 elif label.lower().find("vert")>=0:
685 wheel_y = value
686 last_y = mv.get(number)
687 continue
688 unused_valuators[number] = value
689 #new absolute motion values:
690 #calculate delta if we have both old and new values:
691 if last_x is not None and wheel_x is not None:
692 dx = last_x-wheel_x
693 if last_y is not None and wheel_y is not None:
694 dy = last_y-wheel_y
695 #whatever happens, update our motion cached values:
696 mv.update(event.valuators)
697 #send plain motion first, if any:
698 if unused_valuators:
699 xinputlog("do_xi_motion(%s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s",
700 event, device, wid, window._client._focused, window._id, event.device, pointer, modifiers, buttons)
701 pdata = pointer
702 if client.server_pointer_relative:
703 pdata = list(pointer)+list(relative_pointer)
704 packet = ["pointer-position", wid, pdata, modifiers, buttons] + self.get_pointer_extra_args(event)
705 client.send_mouse_position(packet)
706 #now see if we have anything to send as a wheel event:
707 if dx!=0 or dy!=0:
708 xinputlog("do_xi_motion(%s, %s) wheel deltas: dx=%i, dy=%i", event, device, dx, dy)
709 #normalize (xinput is always using 15 degrees?)
710 client.wheel_event(wid, dx/XINPUT_WHEEL_DIV, dy/XINPUT_WHEEL_DIV, event.device)
712 def get_pointer_extra_args(self, event):
713 def intscaled(f):
714 return int(f*1000000), 1000000
715 def dictscaled(d):
716 return dict((k,intscaled(v)) for k,v in d.items())
717 raw_valuators = {}
718 raw_event_name = event.name.replace("XI_", "XI_Raw") #ie: XI_Motion -> XI_RawMotion
719 raw = self.XI2.find_event(raw_event_name, event.serial)
720 #mouselog("raw(%s)=%s", raw_event_name, raw)
721 if raw:
722 raw_valuators = raw.raw_valuators
723 args = [event.device]
724 for x in ("x", "y", "x_root", "y_root"):
725 args.append(intscaled(getattr(event, x)))
726 for v in (event.valuators, raw_valuators):
727 args.append(dictscaled(v))
728 return args
731class ClientExtras:
732 def __init__(self, client, _opts):
733 self.client = client
734 self._xsettings_watcher = None
735 self._root_props_watcher = None
736 self.system_bus = None
737 self.session_bus = None
738 self.upower_resuming_match = None
739 self.upower_sleeping_match = None
740 self.login1_match = None
741 self.screensaver_match = None
742 self.x11_filter = None
743 if client.xsettings_enabled:
744 self.setup_xprops()
745 self.xi_setup_failures = 0
746 input_devices = getattr(client, "input_devices", None)
747 if input_devices in ("xi", "auto"):
748 #this would trigger warnings with our temporary opengl windows:
749 #only enable it after we have connected:
750 self.client.after_handshake(self.setup_xi)
751 self.setup_dbus_signals()
753 def ready(self):
754 pass
756 def init_x11_filter(self):
757 if self.x11_filter:
758 return
759 try:
760 from xpra.x11.gtk_x11.gdk_bindings import init_x11_filter #@UnresolvedImport, @UnusedImport
761 self.x11_filter = init_x11_filter()
762 log("x11_filter=%s", self.x11_filter)
763 except Exception as e:
764 log("init_x11_filter()", exc_info=True)
765 log.error("Error: failed to initialize X11 GDK filter:")
766 log.error(" %s", e)
767 self.x11_filter = None
769 def cleanup(self):
770 log("cleanup() xsettings_watcher=%s, root_props_watcher=%s", self._xsettings_watcher, self._root_props_watcher)
771 if self.x11_filter:
772 self.x11_filter = None
773 from xpra.x11.gtk_x11.gdk_bindings import cleanup_x11_filter #@UnresolvedImport, @UnusedImport
774 cleanup_x11_filter()
775 if self._xsettings_watcher:
776 self._xsettings_watcher.cleanup()
777 self._xsettings_watcher = None
778 if self._root_props_watcher:
779 self._root_props_watcher.cleanup()
780 self._root_props_watcher = None
781 if self.system_bus:
782 bus = self.system_bus
783 log("cleanup() system bus=%s, matches: %s",
784 bus, (self.upower_resuming_match, self.upower_sleeping_match, self.login1_match))
785 self.system_bus = None
786 if self.upower_resuming_match:
787 bus._clean_up_signal_match(self.upower_resuming_match)
788 self.upower_resuming_match = None
789 if self.upower_sleeping_match:
790 bus._clean_up_signal_match(self.upower_sleeping_match)
791 self.upower_sleeping_match = None
792 if self.login1_match:
793 bus._clean_up_signal_match(self.login1_match)
794 self.login1_match = None
795 if self.session_bus:
796 if self.screensaver_match:
797 self.session_bus._clean_up_signal_match(self.screensaver_match)
798 self.screensaver_match = None
799 global WINDOW_METHOD_OVERRIDES
800 WINDOW_METHOD_OVERRIDES = {}
802 def resuming_callback(self, *args):
803 eventlog("resuming_callback%s", args)
804 self.client.resume()
806 def sleeping_callback(self, *args):
807 eventlog("sleeping_callback%s", args)
808 self.client.suspend()
811 def setup_dbus_signals(self):
812 try:
813 import xpra.dbus
814 assert xpra.dbus
815 except ImportError as e:
816 dbuslog("setup_dbus_signals()", exc_info=True)
817 dbuslog.info("dbus support is not installed")
818 dbuslog.info(" no support for power events")
819 return
820 try:
821 from xpra.dbus.common import init_system_bus, init_session_bus
822 except ImportError as e:
823 dbuslog("setup_dbus_signals()", exc_info=True)
824 dbuslog.error("Error: dbus bindings are missing,")
825 dbuslog.error(" cannot setup event listeners:")
826 dbuslog.error(" %s", e)
827 return
829 try:
830 bus = init_system_bus()
831 self.system_bus = bus
832 dbuslog("setup_dbus_signals() system bus=%s", bus)
833 except Exception as e:
834 dbuslog("setup_dbus_signals()", exc_info=True)
835 dbuslog.error("Error setting up dbus signals:")
836 dbuslog.error(" %s", e)
837 else:
838 #the UPower signals:
839 try:
840 bus_name = 'org.freedesktop.UPower'
841 dbuslog("bus has owner(%s)=%s", bus_name, bus.name_has_owner(bus_name))
842 iface_name = 'org.freedesktop.UPower'
843 self.upower_resuming_match = bus.add_signal_receiver(self.resuming_callback, 'Resuming', iface_name, bus_name)
844 self.upower_sleeping_match = bus.add_signal_receiver(self.sleeping_callback, 'Sleeping', iface_name, bus_name)
845 dbuslog("listening for 'Resuming' and 'Sleeping' signals on %s", iface_name)
846 except Exception as e:
847 dbuslog("failed to setup UPower event listener: %s", e)
849 #the "logind" signals:
850 try:
851 bus_name = 'org.freedesktop.login1'
852 dbuslog("bus has owner(%s)=%s", bus_name, bus.name_has_owner(bus_name))
853 def sleep_event_handler(suspend):
854 if suspend:
855 self.sleeping_callback()
856 else:
857 self.resuming_callback()
858 iface_name = 'org.freedesktop.login1.Manager'
859 self.login1_match = bus.add_signal_receiver(sleep_event_handler, 'PrepareForSleep', iface_name, bus_name)
860 dbuslog("listening for 'PrepareForSleep' signal on %s", iface_name)
861 except Exception as e:
862 dbuslog("failed to setup login1 event listener: %s", e)
864 if DBUS_SCREENSAVER:
865 try:
866 session_bus = init_session_bus()
867 self.session_bus = session_bus
868 dbuslog("setup_dbus_signals() session bus=%s", session_bus)
869 except Exception as e:
870 dbuslog("setup_dbus_signals()", exc_info=True)
871 dbuslog.error("Error setting up dbus signals:")
872 dbuslog.error(" %s", e)
873 else:
874 #screensaver signals:
875 try:
876 bus_name = "org.gnome.ScreenSaver"
877 iface_name = bus_name
878 self.screensaver_match = bus.add_signal_receiver(self.ActiveChanged, "ActiveChanged", iface_name, bus_name)
879 dbuslog("listening for 'ActiveChanged' signal on %s", iface_name)
880 except Exception as e:
881 dbuslog.warn("Warning: failed to setup screensaver event listener: %s", e)
883 def ActiveChanged(self, active):
884 log("ActiveChanged(%s)", active)
885 if active:
886 self.client.suspend()
887 else:
888 self.client.resume()
891 def setup_xprops(self):
892 #wait for handshake to complete:
893 if not is_Wayland():
894 self.client.after_handshake(self.do_setup_xprops)
896 def do_setup_xprops(self, *args):
897 log("do_setup_xprops(%s)", args)
898 ROOT_PROPS = ["RESOURCE_MANAGER", "_NET_WORKAREA", "_NET_CURRENT_DESKTOP"]
899 try:
900 self.init_x11_filter()
901 from xpra.gtk_common.gtk_util import get_default_root_window
902 from xpra.x11.xsettings import XSettingsWatcher
903 from xpra.x11.xroot_props import XRootPropWatcher
904 root = get_default_root_window()
905 if self._xsettings_watcher is None:
906 self._xsettings_watcher = XSettingsWatcher()
907 self._xsettings_watcher.connect("xsettings-changed", self._handle_xsettings_changed)
908 self._handle_xsettings_changed()
909 if self._root_props_watcher is None:
910 self._root_props_watcher = XRootPropWatcher(ROOT_PROPS, root)
911 self._root_props_watcher.connect("root-prop-changed", self._handle_root_prop_changed)
912 #ensure we get the initial value:
913 self._root_props_watcher.do_notify("RESOURCE_MANAGER")
914 except ImportError as e:
915 log("do_setup_xprops%s", args, exc_info=True)
916 log.error("Error: failed to load X11 properties/settings bindings:")
917 log.error(" %s", e)
918 log.error(" root window properties will not be propagated")
921 def do_xi_devices_changed(self, event):
922 log("do_xi_devices_changed(%s)", event)
923 XI2 = X11XI2Bindings()
924 devices = XI2.get_devices()
925 if devices:
926 self.client.send_input_devices("xi", devices)
928 def setup_xi(self):
929 self.client.timeout_add(100, self.do_setup_xi)
931 def do_setup_xi(self):
932 if self.client.server_input_devices not in ("xi", "uinput"):
933 xinputlog("server does not support xi input devices")
934 if self.client.server_input_devices:
935 log(" server uses: %s", self.client.server_input_devices)
936 return False
937 try:
938 from xpra.gtk_common.error import xsync, XError
939 assert X11WindowBindings, "no X11 window bindings"
940 assert X11XI2Bindings, "no XI2 window bindings"
941 XI2 = X11XI2Bindings()
942 #this may fail when windows are being destroyed,
943 #ie: when another client disconnects because we are stealing the session
944 try:
945 with xsync:
946 XI2.select_xi2_events()
947 except XError:
948 self.xi_setup_failures += 1
949 xinputlog("select_xi2_events() failed, attempt %i",
950 self.xi_setup_failures, exc_info=True)
951 return self.xi_setup_failures<10 #try again
952 with xsync:
953 XI2.gdk_inject()
954 self.init_x11_filter()
955 if self.client.server_input_devices:
956 XI2.connect(0, "XI_HierarchyChanged", self.do_xi_devices_changed)
957 devices = XI2.get_devices()
958 if devices:
959 self.client.send_input_devices("xi", devices)
960 except Exception as e:
961 xinputlog("enable_xi2()", exc_info=True)
962 xinputlog.error("Error: cannot enable XI2 events")
963 xinputlog.error(" %s", e)
964 else:
965 #register our enhanced event handlers:
966 self.add_xi2_method_overrides()
967 return False
969 def add_xi2_method_overrides(self):
970 global WINDOW_ADD_HOOKS
971 WINDOW_ADD_HOOKS = [XI2_Window]
974 def _get_xsettings(self):
975 try:
976 return self._xsettings_watcher.get_settings()
977 except:
978 log.error("failed to get XSETTINGS", exc_info=True)
979 return None
981 def _handle_xsettings_changed(self, *_args):
982 settings = self._get_xsettings()
983 log("xsettings_changed new value=%s", settings)
984 if settings is not None:
985 self.client.send("server-settings", {"xsettings-blob": settings})
987 def get_resource_manager(self):
988 try:
989 from xpra.gtk_common.gtk_util import get_default_root_window
990 from xpra.x11.gtk_x11.prop import prop_get
991 root = get_default_root_window()
992 value = prop_get(root, "RESOURCE_MANAGER", "latin1", ignore_errors=True)
993 if value is not None:
994 return value.encode("utf-8")
995 except:
996 log.error("failed to get RESOURCE_MANAGER", exc_info=True)
997 return None
999 def _handle_root_prop_changed(self, obj, prop):
1000 log("root_prop_changed(%s, %s)", obj, prop)
1001 if prop=="RESOURCE_MANAGER":
1002 rm = self.get_resource_manager()
1003 if rm is not None:
1004 self.client.send("server-settings", {"resource-manager" : rm})
1005 elif prop=="_NET_WORKAREA":
1006 self.client.screen_size_changed("from %s event" % self._root_props_watcher)
1007 elif prop=="_NET_CURRENT_DESKTOP":
1008 self.client.workspace_changed("from %s event" % self._root_props_watcher)
1009 elif prop in ("_NET_DESKTOP_NAMES", "_NET_NUMBER_OF_DESKTOPS"):
1010 self.client.desktops_changed("from %s event" % self._root_props_watcher)
1011 else:
1012 log.error("unknown property %s", prop)
1015def main():
1016 try:
1017 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source
1018 init_gdk_display_source()
1019 except ImportError:
1020 pass
1021 from xpra.platform.gui import main as gui_main
1022 gui_main()
1025if __name__ == "__main__":
1026 sys.exit(main())