From 8723ca2b235ff48126fc6f35040c2d6a650ecd4e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 6 Oct 2015 16:02:28 +0000 Subject: [PATCH] #489: * add generic window filter code * add x11 specific filters via subclassing * add dbus methods for setting and clearing the filters git-svn-id: https://xpra.org/svn/Xpra/trunk@10751 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/server/dbus/dbus_source.py | 8 ++++ src/xpra/server/server_base.py | 9 +++- src/xpra/server/source.py | 58 ++++++++++++++++++++++- src/xpra/x11/bindings/constants.txt | 1 + src/xpra/x11/bindings/window_bindings.pyx | 40 +++++++++++++++- src/xpra/x11/gtk_x11/prop.py | 11 +++++ src/xpra/x11/x11_server_base.py | 5 ++ src/xpra/x11/x11_source.py | 44 +++++++++++++++++ 8 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 src/xpra/x11/x11_source.py diff --git a/src/xpra/server/dbus/dbus_source.py b/src/xpra/server/dbus/dbus_source.py index b1399bff68..3da700a076 100755 --- a/src/xpra/server/dbus/dbus_source.py +++ b/src/xpra/server/dbus/dbus_source.py @@ -101,6 +101,14 @@ def ShowDesktop(self, show): def RaiseWindow(self, wid): self.source.raise_window(ni(wid)) + @dbus.service.method(INTERFACE, in_signature='') + def ResetWindowFilters(self): + self.source.reset_window_filters() + + @dbus.service.method(INTERFACE, in_signature='sssv') + def AddWindowFilter(self, object_name, property_name, operator, value): + self.source.add_window_filter(ns(object_name), ns(property_name), ns(operator), n(value)) + @dbus.service.method(INTERFACE, in_signature='') def SetDefaultKeymap(self): diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index 88888d9ec8..41274808f6 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -859,8 +859,8 @@ def drop_client(reason="unknown", *args): self.disconnect_client(proto, reason, *args) def get_window_id(wid): return self._window_to_id.get(wid) - from xpra.server.source import ServerSource - ss = ServerSource(proto, drop_client, + ServerSourceClass = self.get_server_source_class() + ss = ServerSourceClass(proto, drop_client, self.idle_add, self.timeout_add, self.source_remove, self.idle_timeout, self.idle_timeout_cb, self.idle_grace_timeout_cb, self._socket_dir, self.main_socket_path, self.dbus_control, @@ -886,6 +886,11 @@ def get_window_id(wid): send_ui = ui_client and not is_request self.idle_add(self.parse_hello_ui, ss, c, auth_caps, send_ui, share_count) + def get_server_source_class(self): + from xpra.server.source import ServerSource + return ServerSource + + def parse_hello_ui(self, ss, c, auth_caps, send_ui, share_count): #adds try:except around parse hello ui code: try: diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index c6cb978184..bebd8a7807 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -148,6 +148,40 @@ def raw(): raise Exception("unhandled property name: %s" % propname) +class WindowPropertyFilter(object): + def __init__(self, property_name, value): + self.property_name = property_name + self.value = value + + def get_window_value(self, window): + return window.get_property(self.property_name) + + def show(self, window): + try: + v = self.get_window_value(window) + log("%s.show(%s) %s(..)=%s", type(self).__name__, window, self.get_window_value, v) + except Exception: + log("%s.show(%s) %s(..) error:", type(self).__name__, window, self.get_window_value, exc_info=True) + v = None + e = self.evaluate(v) + return e + + def evaluate(self, window_value): + raise NotImplementedError() + + +class WindowPropertyIn(WindowPropertyFilter): + + def evaluate(self, window_value): + return window_value in self.value + + +class WindowPropertyNotIn(WindowPropertyIn): + + def evaluate(self, window_value): + return not(WindowPropertyIn.evaluate(window_value)) + + class ServerSource(object): """ A ServerSource represents a client connection. @@ -296,7 +330,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove dbuslog.error(" %s", e) def __str__(self): - return "ServerSource(%s)" % self.protocol + return "%s(%s)" % (type(self).__name__, self.protocol) def init_vars(self): self.encoding = None #the default encoding for all windows @@ -309,6 +343,7 @@ def init_vars(self): self.window_sources = {} #WindowSource for each Window ID self.suspended = False + self.window_filters = [] self.uuid = "" self.machine_id = "" @@ -1693,7 +1728,28 @@ def window_metadata(self, wid, window, prop): if len(metadata)>0: self.send("window-metadata", wid, metadata) + def reset_window_filters(self): + self.window_filters = [] + + def get_window_filter(self, object_name, property_name, operator, value): + if object_name!="window": + raise ValueError("invalid object name") + if operator=="=": + return WindowPropertyIn(property_name, [value]) + elif operator=="!=": + return WindowPropertyNotIn(property_name, [value]) + raise ValueError("unknown filter operator: %s" % operator) + + def add_window_filter(self, object_name, property_name, operator, value): + window_filter = self.get_window_filter(object_name, property_name, operator, value) + assert window_filter + self.window_filters.append(window_filter.show) + def can_send_window(self, window): + for x in self.window_filters: + v = x(window) + if v is True or v is False: + return v if self.send_windows and self.system_tray: #common case shortcut return True diff --git a/src/xpra/x11/bindings/constants.txt b/src/xpra/x11/bindings/constants.txt index 52056aac13..9c8ba3475b 100644 --- a/src/xpra/x11/bindings/constants.txt +++ b/src/xpra/x11/bindings/constants.txt @@ -16,6 +16,7 @@ PointerWindow InputFocus PointerRoot CurrentTime +AnyPropertyType # Map states IsUnmapped diff --git a/src/xpra/x11/bindings/window_bindings.pyx b/src/xpra/x11/bindings/window_bindings.pyx index a0f03aae7e..996427c9e0 100644 --- a/src/xpra/x11/bindings/window_bindings.pyx +++ b/src/xpra/x11/bindings/window_bindings.pyx @@ -864,7 +864,7 @@ cdef class X11WindowBindings(X11CoreBindings): self.addXSelectInput(xwindow, FocusChangeMask) - def XGetWindowProperty(self, Window xwindow, property, req_type, etype=None): + def XGetWindowProperty(self, Window xwindow, property, req_type=0, etype=None): # NB: Accepts req_type == 0 for AnyPropertyType # "64k is enough for anybody" # (Except, I've found window icons that are strictly larger) @@ -876,7 +876,9 @@ cdef class X11WindowBindings(X11CoreBindings): cdef unsigned long nitems = 0, bytes_after = 0 cdef unsigned char * prop = 0 cdef Status status - xreq_type = self.get_xatom(req_type) + cdef Atom xreq_type = AnyPropertyType + if req_type: + xreq_type = self.get_xatom(req_type) # This is the most bloody awful API I have ever seen. You will probably # not be able to understand this code fully without reading # XGetWindowProperty's man page at least 3 times, slowly. @@ -922,6 +924,40 @@ cdef class X11WindowBindings(X11CoreBindings): else: return data + + def GetWindowPropertyType(self, Window xwindow, property): + #as above, but for any property type + #and returns the type found + cdef int buffer_size = 64 * 1024 + cdef Atom xactual_type = 0 + cdef int actual_format = 0 + cdef unsigned long nitems = 0, bytes_after = 0 + cdef unsigned char * prop = 0 + cdef Status status + cdef Atom xreq_type = AnyPropertyType + status = XGetWindowProperty(self.display, + xwindow, + self.get_xatom(property), + 0, + # This argument has to be divided by 4. Thus + # speaks the spec. + buffer_size / 4, + False, + xreq_type, &xactual_type, + &actual_format, &nitems, &bytes_after, &prop) + if status != Success: + raise None + if xactual_type == XNone: + raise None + # This should only occur for bad property types: + assert not (bytes_after and not nitems) + if bytes_after: + raise None + assert actual_format in (8, 16, 32) + XFree(prop) + return self.XGetAtomName(xactual_type) + + def XDeleteProperty(self, Window xwindow, property): XDeleteProperty(self.display, xwindow, self.get_xatom(property)) diff --git a/src/xpra/x11/gtk_x11/prop.py b/src/xpra/x11/gtk_x11/prop.py index 16c3fcbac8..c33012912a 100644 --- a/src/xpra/x11/gtk_x11/prop.py +++ b/src/xpra/x11/gtk_x11/prop.py @@ -257,6 +257,17 @@ def get_xsettings(disp, v): return get_settings(_get_display_name(disp), v) +def get_python_type(scalar_type): + #ie: get_python_type("STRING") = "latin1" + return {"UTF8_STRING" : "utf8", + "STRING" : "latin1", + "ATOM" : "atom", + "CARDINAL" : "u32", + "INTEGER" : "integer", + "VISUALID" : "visual", + "WINDOW" : "window", + }.get(scalar_type) + _prop_types = { # Python type, X type Atom, formatbits, serializer, deserializer, list # terminator diff --git a/src/xpra/x11/x11_server_base.py b/src/xpra/x11/x11_server_base.py index ce131ff2ed..5c2f6742f9 100644 --- a/src/xpra/x11/x11_server_base.py +++ b/src/xpra/x11/x11_server_base.py @@ -133,6 +133,11 @@ def init_packet_handlers(self): self._authenticated_ui_packet_handlers["force-ungrab"] = self._process_force_ungrab + def get_server_source_class(self): + from xpra.x11.x11_source import X11ServerSource + return X11ServerSource + + def get_child_env(self): #adds fakeXinerama: env = GTKServerBase.get_child_env(self) diff --git a/src/xpra/x11/x11_source.py b/src/xpra/x11/x11_source.py new file mode 100644 index 0000000000..a416f5a689 --- /dev/null +++ b/src/xpra/x11/x11_source.py @@ -0,0 +1,44 @@ +# coding=utf8 +# This file is part of Xpra. +# Copyright (C) 2015 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from xpra.server.source import ServerSource +from xpra.gtk_common.gobject_compat import get_xid +from xpra.gtk_common.error import xsync +from xpra.x11.gtk_x11.prop import prop_get, get_python_type +from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport +window_bindings = X11WindowBindings() + +from xpra.log import Logger +log = Logger("x11", "server") + + +def get_x11_window_value(filter_object, window): + xid = get_xid(window) + #log("get_x11_window_value(%s, %s) xid=%#x", filter_object, window, xid) + with xsync: + x11type = window_bindings.GetWindowPropertyType(xid, filter_object.property_name) + ptype = get_python_type(x11type) + #log("%s: %s (%s)", filter_object.property_name, x11type, ptype) + assert ptype, "type '%s' is not handled!" % x11type + v = prop_get(window, filter_object.property_name, ptype) + log("%s=%s", filter_object.property_name, v) + return v + + +class X11ServerSource(ServerSource): + """ Adds the ability to filter windows using X11 properties """ + + def get_window_filter(self, object_name, property_name, operator, value): + if object_name.lower() not in ("x11window", "window"): + raise ValueError("invalid object name") + wf = ServerSource.get_window_filter(self, "window", property_name, operator, value) + if object_name.lower()=="x11window": + #same filter but use X11 properties: + def get_window_value(window): + return get_x11_window_value(wf, window) + wf.get_window_value = get_window_value + log("get_window_filter%s=%s", (object_name, property_name, operator, value), wf) + return wf