Skip to content

Commit

Permalink
#349: event based handling of screen and workspace size changes:
Browse files Browse the repository at this point in the history
* move screen size change function to ui_client_base so we can rely on it existing
* force XRootPropWatcher to initialize the x11 event filter code (if not already enabled)
* use a timer and a flag to avoid firing screen_size_changed too many times

git-svn-id: https://xpra.org/svn/Xpra/trunk@4527 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Oct 17, 2013
1 parent 5c9e79e commit a162c0a
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 51 deletions.
42 changes: 42 additions & 0 deletions src/tests/xpra/x11/test_x11_clientextras.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2013 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import logging
import gobject
gobject.threads_init()
import gtk


from xpra.platform.xposix.gui import ClientExtras
from xpra.x11.gtk_x11 import gdk_display_source
assert gdk_display_source


class FakeClient(object):
def __init__(self):
self.xsettings_tuple = True
self.xsettings_enabled = True
def connect(self, *args):
print("connect(%s)" % str(args))
def send(self, *args):
print("send(%s)" % str(args))
def screen_size_changed(self, *args):
print("screen_size_changed(%s)" % str(args))

def main():
logging.basicConfig(format="%(asctime)s %(message)s")
logging.root.setLevel(logging.INFO)
fc = FakeClient()
ce = ClientExtras(fc)
gobject.timeout_add(1000, ce.do_setup_xprops)
try:
gtk.main()
finally:
ce.cleanup()


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions src/tests/xpra/x11/test_xroot_props.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2013 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import logging
import gobject
gobject.threads_init()
import gtk

from xpra.x11.gtk_x11 import gdk_display_source
assert gdk_display_source
from xpra.x11.xroot_props import XRootPropWatcher


def main():
logging.basicConfig(format="%(asctime)s %(message)s")
logging.root.setLevel(logging.INFO)

ROOT_PROPS = ["RESOURCE_MANAGER", "_NET_WORKAREA"]
xrpw = XRootPropWatcher(ROOT_PROPS)
gobject.timeout_add(1000, xrpw.notify_all)
try:
gtk.main()
finally:
xrpw.cleanup()


if __name__ == "__main__":
main()
19 changes: 1 addition & 18 deletions src/xpra/client/gtk2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def process_ui_capabilities(self, capabilities):
i=0
while i<display.get_n_screens():
screen = display.get_screen(i)
screen.connect("size-changed", self._screen_size_changed)
screen.connect("size-changed", self.screen_size_changed)
i += 1
#if server supports it, enable UI thread monitoring workaround when needed:
if self.suspend_resume:
Expand All @@ -232,23 +232,6 @@ def UI_failed():
w.add_fail_callback(UI_failed)


def _screen_size_changed(self, *args):
def update_size(current=None):
root_w, root_h = self.get_root_size()
ss = self.get_screen_sizes()
log("update_size(%s) sizes=%s", current, ss)
if current is not None and current==ss:
#unchanged
return
log.info("sending updated screen size to server: %sx%s, screen sizes: %s", root_w, root_h, ss)
self.send("desktop_size", root_w, root_h, ss)
#update the max packet size (may have gone up):
self.set_max_packet_size()
#check again soon:
gobject.timeout_add(1000, update_size, ss)
#update via idle_add so the data is actually up to date when we query it!
self.idle_add(update_size)

def get_screen_sizes(self):
display = gdk.display_get_default()
i=0
Expand Down
21 changes: 21 additions & 0 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(self):
self.server_info_request = False
self.server_last_info = None
self.info_request_pending = False
self.screen_size_change_pending = False
self.encoding = self.get_encodings()[0]

#sound:
Expand Down Expand Up @@ -452,6 +453,26 @@ def tray_exit(*args):
tray_widget.show()
return ClientTray(client, wid, w, h, tray_widget, self.mmap_enabled, self.mmap)

def screen_size_changed(self, *args):
log("screen_size_changed(%s) pending=%s", args, self.screen_size_change_pending)
if self.screen_size_change_pending:
return
def update_screen_size():
self.screen_size_change_pending = False
root_w, root_h = self.get_root_size()
ss = self.get_screen_sizes()
log("update_screen_size() sizes=%s", ss)
log.info("sending updated screen size to server: %sx%s, screen sizes: %s", root_w, root_h, ss)
self.send("desktop_size", root_w, root_h, ss)
#update the max packet size (may have gone up):
self.set_max_packet_size()
#update via timer so the data is more likely to be final (up to date) when we query it,
#some properties (like _NET_WORKAREA for X11 clients via xposix "ClientExtras") may
#trigger multiple calls to screen_size_changed, delayed by some amount
#(sometimes up to 1s..)
self.screen_size_change_pending = True
self.timeout_add(1000, update_screen_size)

def get_screen_sizes(self):
raise Exception("override me!")

Expand Down
59 changes: 36 additions & 23 deletions src/xpra/platform/xposix/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from xpra.log import Logger
log = Logger()

import gtk.gdk
try:
from xpra.x11.gtk_x11.error import trap, XError
from xpra.x11.gtk_x11.gdk_bindings import get_xwindow #@UnresolvedImport
from xpra.x11.bindings import X11KeyboardBindings #@UnresolvedImport
from xpra.x11.bindings import X11KeyboardBindings #@UnresolvedImport
device_bell = X11KeyboardBindings().device_bell
except:
device_bell = None
Expand Down Expand Up @@ -66,6 +67,7 @@ def __init__(self, client):
self.setup_xprops()

def cleanup(self):
log("cleanup() xsettings_watcher=%s, root_props_watcher=%s", self._xsettings_watcher, self._root_props_watcher)
if self._xsettings_watcher:
self._xsettings_watcher.cleanup()
self._xsettings_watcher = None
Expand All @@ -74,23 +76,24 @@ def cleanup(self):
self._root_props_watcher = None

def setup_xprops(self):
self.ROOT_PROPS = {
"RESOURCE_MANAGER": "resource-manager"
}
def setup_xprop_xsettings(client):
log.debug("setup_xprop_xsettings(%s)", client)
try:
from xpra.x11.xsettings import XSettingsWatcher
from xpra.x11.xroot_props import XRootPropWatcher
self._xsettings_watcher = XSettingsWatcher()
self._xsettings_watcher.connect("xsettings-changed", self._handle_xsettings_changed)
self._handle_xsettings_changed()
self._root_props_watcher = XRootPropWatcher(self.ROOT_PROPS.keys())
self._root_props_watcher.connect("root-prop-changed", self._handle_root_prop_changed)
self._root_props_watcher.notify_all()
except ImportError, e:
log.error("failed to load X11 properties/settings bindings: %s - root window properties will not be propagated", e)
self.client.connect("handshake-complete", setup_xprop_xsettings)
#wait for handshake to complete:
self.client.connect("handshake-complete", self.do_setup_xprops)

def do_setup_xprops(self, *args):
log.debug("do_setup_xprops(%s)", args)
ROOT_PROPS = ["RESOURCE_MANAGER", "_NET_WORKAREA"]
try:
from xpra.x11.xsettings import XSettingsWatcher
from xpra.x11.xroot_props import XRootPropWatcher
self._xsettings_watcher = XSettingsWatcher()
self._xsettings_watcher.connect("xsettings-changed", self._handle_xsettings_changed)
self._handle_xsettings_changed()
self._root_props_watcher = XRootPropWatcher(ROOT_PROPS)
self._root_props_watcher.connect("root-prop-changed", self._handle_root_prop_changed)
#ensure we get the initial value:
self._root_props_watcher.do_notify("RESOURCE_MANAGER")
except ImportError, e:
log.error("failed to load X11 properties/settings bindings: %s - root window properties will not be propagated", e)

def _handle_xsettings_changed(self, *args):
try:
Expand All @@ -102,8 +105,18 @@ def _handle_xsettings_changed(self, *args):
if blob is not None:
self.client.send("server-settings", {"xsettings-blob": blob})

def _handle_root_prop_changed(self, obj, prop, value):
log("root_prop_changed: %s=%s", prop, value)
assert prop in self.ROOT_PROPS
if value is not None and self.client.xsettings_tuple:
self.client.send("server-settings", {self.ROOT_PROPS[prop]: value.encode("utf-8")})
def _handle_root_prop_changed(self, obj, prop):
log("root_prop_changed(%s, %s)", obj, prop)
if prop=="RESOURCE_MANAGER":
if not self.client.xsettings_tuple:
log.warn("xsettings tuple format not supported, update ignored")
return
root = gtk.gdk.get_default_root_window()
from xpra.x11.gtk_x11.prop import prop_get
value = prop_get(root, "RESOURCE_MANAGER", "latin1", ignore_errors=True)
if value is not None:
self.client.send("server-settings", {"resource-manager" : value.encode("utf-8")})
elif prop=="_NET_WORKAREA":
self.client.screen_size_changed("from %s event" % self._root_props_watcher)
else:
log.error("unknown property %s", prop)
5 changes: 5 additions & 0 deletions src/xpra/x11/gtk_x11/gdk_bindings.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,11 @@ cdef GdkFilterReturn x_event_filter(GdkXEvent * e_gdk,
return GDK_FILTER_CONTINUE


_INIT_X11_FILTER_DONE = False
def init_x11_filter():
global _INIT_X11_FILTER_DONE
if _INIT_X11_FILTER_DONE:
return
init_x11_events()
gdk_window_add_filter(<cGdkWindow*>0, x_event_filter, <void*>0)
_INIT_X11_FILTER_DONE = True
19 changes: 9 additions & 10 deletions src/xpra/x11/xroot_props.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
import gobject
from xpra.gtk_common.gobject_util import n_arg_signal
from xpra.x11.gtk_x11.gdk_bindings import add_event_receiver, remove_event_receiver #@UnresolvedImport
from xpra.x11.gtk_x11.prop import prop_get
from xpra.x11.gtk_x11.gdk_bindings import init_x11_filter #@UnresolvedImport

from xpra.log import Logger
log = Logger()


class XRootPropWatcher(gobject.GObject):
__gsignals__ = {
"root-prop-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING,)),
"root-prop-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )),
"xpra-property-notify-event": n_arg_signal(1),
}

Expand All @@ -26,26 +26,25 @@ def __init__(self, props):
self._root = gtk.gdk.get_default_root_window()
self._saved_event_mask = self._root.get_events()
self._root.set_events(self._saved_event_mask | gtk.gdk.PROPERTY_CHANGE_MASK)
init_x11_filter()
add_event_receiver(self._root, self)

def cleanup(self):
remove_event_receiver(self._root, self)
self._root.set_events(self._saved_event_mask)

def do_xpra_property_notify_event(self, event):
log("XRootPropWatcher.do_xpra_property_notify_event(%s) props=%s", event, self._props)
log("XRootPropWatcher.do_xpra_property_notify_event(%s)", event)
if event.atom in self._props:
self._notify(event.atom)
self.do_notify(event.atom)

def _notify(self, prop):
ptype = "latin1"
v = prop_get(self._root, prop, ptype, ignore_errors=True)
log("XRootPropWatcher._notify(%s) value(%s)=%s", prop, ptype, v)
self.emit("root-prop-changed", prop, str(v))
def do_notify(self, prop):
log("XRootPropWatcher.do_notify(%s)", prop)
self.emit("root-prop-changed", prop)

def notify_all(self):
for prop in self._props:
self._notify(prop)
self.do_notify(prop)


gobject.type_register(XRootPropWatcher)

0 comments on commit a162c0a

Please sign in to comment.