xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #228: gtk-menu-forwarding.patch

File gtk-menu-forwarding.patch, 25.9 KB (added by Antoine Martin, 6 years ago)

menu forwarding for gtk clients - still needs rpc code

  • tests/xpra/x11/set_gtk_menu.py

     
    5656                    'paste'         : [True, 's', []],
    5757                    'find'          : [True, 's', []],
    5858                    'help'          : [True, '', []]}
    59     menuactions_service = Actions(app_id, window_path, session_bus, window_actions)
     59    Actions(app_id, window_path, session_bus, window_actions)
    6060    app_actions = {
    6161                    'quit'          : [True, '', []],
    6262                    'about'         : [True, '', []],
     
    6565                    'help'          : [True, '', []],
    6666                    'custom'        : [True, '', []],
    6767                  }
    68     appactions_service = Actions(app_id, app_path, session_bus, app_actions)
     68    Actions(app_id, app_path, session_bus, app_actions)
    6969    menus = {0:
    7070             {
    7171                0: [{':section': (0, 1)}, {':section': (0, 2)}, {':section': (0, 3)}],
     
    8585            }
    8686    menus_service = Menus(app_id, menu_path, session_bus, menus)
    8787
    88     def pset(key, etype, value, ignore_errors=True):
    89         return prop_set(w, key, etype, value)
    90     pset("_GTK_APP_MENU_OBJECT_PATH", "utf8", menu_path)
    91     pset("_GTK_WINDOW_OBJECT_PATH", "utf8", window_path)
    92     pset("_GTK_APPLICATION_OBJECT_PATH", "utf8", app_path)
    93     pset("_GTK_UNIQUE_BUS_NAME", "utf8", bus_name)
    94     pset("_GTK_APPLICATION_ID", "utf8", app_id)
     88    def pset(key, value):
     89        return prop_set(w, key, "utf8", value)
     90    pset("_GTK_APP_MENU_OBJECT_PATH",       menu_path)
     91    pset("_GTK_WINDOW_OBJECT_PATH",         window_path)
     92    pset("_GTK_APPLICATION_OBJECT_PATH",   app_path)
     93    pset("_GTK_UNIQUE_BUS_NAME",           bus_name)
     94    pset("_GTK_APPLICATION_ID",             app_id)
    9595    print("gtk menu properties for window %#x on display %s" % (w.xid, display.get_name()))
    9696
    9797    import copy
  • xpra/client/client_window_base.py

     
    320320        if b"command" in metadata:
    321321            self.set_command(metadata.strget("command"))
    322322
     323        if b"menu" in metadata:
     324            self.set_menu(metadata.dictget("menu"))
    323325
     326
     327    def set_menu(self, menu):
     328        pass
     329
    324330    def set_command(self, command):
    325331        pass
    326332
  • xpra/client/gtk_base/gtk_client_base.py

     
    2020cursorlog = Logger("gtk", "client", "cursor")
    2121screenlog = Logger("gtk", "client", "screen")
    2222framelog = Logger("gtk", "client", "frame")
     23menulog = Logger("gtk", "client", "menu")
    2324
    2425from xpra.gtk_common.quit import (gtk_main_quit_really,
    2526                           gtk_main_quit_on_fatal_exceptions_enable)
    26 from xpra.util import bytestostr, updict, pver, DEFAULT_METADATA_SUPPORTED
     27from xpra.util import typedict, bytestostr, updict, pver, DEFAULT_METADATA_SUPPORTED
    2728from xpra.gtk_common.cursor_names import cursor_names
    2829from xpra.gtk_common.gtk_util import get_gtk_version_info, scaled_image, get_default_cursor, \
    2930            new_Cursor_for_display, new_Cursor_from_pixbuf, icon_theme_get_default, \
     
    6667        #group leader bits:
    6768        self._ref_to_group_leader = {}
    6869        self._group_leader_wids = {}
     70        try:
     71            from xpra.x11.gtk_x11.menu import has_gtk_menu_support
     72            self._has_menu_support = has_gtk_menu_support(self.get_root_window())
     73        except ImportError as e:
     74            self._has_menu_support = False
     75            menulog("no gtk menu support: %s", e)
     76        self._window_menus = {}
     77       
    6978
    7079    def init(self, opts):
    7180        GObjectXpraClient.init(self, opts)
     
    270279        return True
    271280
    272281
     282    def set_window_menu(self, wid, menus):
     283        assert self._has_menu_support
     284        #ie: menu = {
     285        #         'enabled': True,
     286        #         'application-id':         'org.xpra.ExampleMenu',
     287        #         'application-actions':    {'quit': (True, '', ()), 'about': (True, '', ()), 'help': (True, '', ()), 'custom': (True, '', ()), 'activate-tab': (True, 's', ()), 'preferences': (True, '', ())},
     288        #         'window-actions':         {'edit-profile': (True, 's', ()), 'reset': (True, 'b', ()), 'about': (True, '', ()), 'help': (True, '', ()), 'fullscreen': (True, '', (0,)), 'detach-tab': (True, '', ()), 'save-contents': (True, '', ()), 'zoom': (True, 'i', ()), 'move-tab': (True, 'i', ()), 'new-terminal': (True, '(ss)', ()), 'switch-tab': (True, 'i', ()), 'new-profile': (True, '', ()), 'close': (True, 's', ()), 'show-menubar': (True, '', (1,)), 'select-all': (True, '', ()), 'copy': (True, '', ()), 'paste': (True, 's', ()), 'find': (True, 's', ()), 'preferences': (True, '', ())},
     289        #         'window-menu':            {0: {0: ({':section': (0, 1)}, {':section': (0, 2)}, {':section': (0, 3)}), 1: ({'action': 'win.new-terminal', 'target': ('default', 'default'), 'label': '_New Terminal'},), 2: ({'action': 'app.preferences', 'label': '_Preferences'},), 3: ({'action': 'app.help', 'label': '_Help'}, {'action': 'app.about', 'label': '_About'}, {'action': 'app.quit', 'label': '_Quit'})}}
     290        #           }
     291        enabled = menus.get("enabled", False)
     292        app_actions_service, window_actions_service, window_menu_service = None, None, None
     293        def remove_services(*args):
     294            for x in (app_actions_service, window_actions_service, window_menu_service):
     295                if x:
     296                    try:
     297                        x.remove_from_connection()
     298                    except Exception as e:
     299                        menulog.warn("Error removing %s: %s", x, e)
     300                try:
     301                    del self._window_menus[wid]
     302                except:
     303                    pass
     304           
     305        if enabled:
     306            m = typedict(menus)
     307            app_id          = bytestostr(m.strget("application-id")).decode()
     308            app_actions     = m.dictget("application-actions")
     309            window_actions  = m.dictget("window-actions")
     310            window_menu     = m.dictget("window-menu")
     311        if wid in self._window_menus:
     312            #update, destroy or re-create
     313            app_actions_service, window_actions_service, window_menu_service, cur_app_id = self._window_menus[wid]
     314            if not enabled or cur_app_id!=app_id:
     315                remove_services()
     316                if not enabled:
     317                    return
     318                app_actions_service, window_actions_service, window_menu_service = None, None, None
     319            else:
     320                app_actions_service.set_actions(app_actions)
     321                window_actions_service.set_actions(window_actions)
     322                window_menu_service.set_menus(window_menu)
     323                return
     324        if not enabled:
     325            return
     326        #make new services:
     327        try:
     328            NAME_PREFIX = "org.xpra."
     329            from xpra.util import strtobytes
     330            from xpra.x11.gtk_x11.prop import prop_set
     331            from xpra.dbus.common import init_session_bus
     332            from xpra.dbus.gtk_menuactions import Menus, Actions
     333            session_bus = init_session_bus()
     334            bus_name = session_bus.get_unique_name().decode()           
     335            name = NAME_PREFIX + app_id.lstrip("org.").lstrip("gtk.")
     336            app_path = strtobytes("/"+name.replace(".", "/")).decode()
     337            app_actions_service = Actions(name, app_path, session_bus, app_actions)
     338            window_path = u"%s/window/%s" % (app_path, wid)
     339            window_actions_service = Actions(name, window_path, session_bus, window_actions)
     340            menu_path = u"%s/menus/appmenu" % app_path
     341            window_menu_service = Menus(app_id, menu_path, session_bus, window_menu)
     342            self._window_menus[wid] = app_actions_service, window_actions_service, window_menu_service, app_id
     343
     344            window = self._id_to_window[wid].get_window()
     345            def pset(key, value):
     346                return prop_set(window, key, "utf8", value)
     347            pset("_GTK_APP_MENU_OBJECT_PATH",       menu_path)
     348            pset("_GTK_WINDOW_OBJECT_PATH",         window_path)
     349            pset("_GTK_APPLICATION_OBJECT_PATH",    app_path)
     350            pset("_GTK_UNIQUE_BUS_NAME",            bus_name)
     351            pset("_GTK_APPLICATION_ID",             app_id)
     352        except Exception:
     353            remove_services()
     354            raise
     355
     356
    273357    def get_root_window(self):
    274358        return get_default_root_window()
    275359
     
    322406            ms += ["shaded", "bypass-compositor", "strut", "fullscreen-monitors"]
    323407        if HAS_X11_BINDINGS:
    324408            ms += ["shape"]
     409        if self._has_menu_support:
     410            ms += ["menu"]
     411        #figure out if we can handle the "global menu" stuff:
     412        if os.name=="posix" and not sys.platform.startswith("darwin"):
     413            try:
     414                from xpra.dbus.helper import DBusHelper
     415                assert DBusHelper
     416            except:
     417                pass
    325418        log("metadata.supported: %s", ms)
    326419        capabilities["metadata.supported"] = ms
    327420        #we need the bindings to support initiate-moveresize (posix only for now):
  • xpra/client/gtk_base/gtk_client_window_base.py

     
    2121shapelog = Logger("shape")
    2222mouselog = Logger("mouse")
    2323geomlog = Logger("geometry")
     24menulog = Logger("menu")
    2425
    2526
    2627from xpra.os_util import memoryview_to_bytes
     
    742743        return self._window_workspace
    743744
    744745
     746    def set_menu(self, menu):
     747        assert self._client._has_menu_support
     748        menulog("set_menu(%s)", menu)
     749        def do_set_menu():
     750            try:
     751                self._client.set_window_menu(self._id, menu)
     752            except Exception as e:
     753                menulog.error("Error parsing or applying menu:", exc_info=True)
     754        self.when_realized("menu", do_set_menu)
     755
     756
    745757    def initiate_moveresize(self, x_root, y_root, direction, button, source_indication):
    746758        statelog("initiate_moveresize%s", (x_root, y_root, direction, button, source_indication))
    747759        assert HAS_X11_BINDINGS, "cannot handle initiate-moveresize without X11 bindings"
  • xpra/dbus/gtk_menuactions.py

     
    99import dbus.service
    1010
    1111from xpra.log import Logger
    12 log = Logger("dbus")
     12log = Logger("dbus", "menu")
    1313
    1414ACTIONS = "org.gtk.Actions"
    1515MENUS = "org.gtk.Menus"
     
    2525def ds(v):
    2626    return dbus.String(v)
    2727
     28def busnamestr(o):
     29    try:
     30        return o._name._name
     31    except:
     32        return str(o)
    2833
     34def ordered_ints(*lists):
     35    """ merge all the lists and returned a sorted list with all the ints
     36        errors out if the values are not ints
     37    """
     38    import itertools
     39    return sorted(int(x) for x in set(itertools.chain(*lists)))
     40
    2941class Actions(dbus.service.Object):
    3042    SUPPORTS_MULTIPLE_OBJECT_PATHS = True
    3143 
    3244    def __init__(self, name, path, session_bus, actions={}):
     45        log("Actions%s", (name, path, session_bus, actions))
    3346        self.actions = actions
    3447        bus_name = dbus.service.BusName(name, session_bus)
    3548        dbus.service.Object.__init__(self, bus_name, path)
     
    4760        enabled_changed = []
    4861        state_changed = []
    4962        added = []
    50         allactions = list(set(list(oldactions.keys()) + list(actions.keys())))
     63        allactions = list(set(oldactions.keys() + actions.keys()))
    5164        for action in allactions:
    5265            if action not in actions:
    5366                removed.append(ds(action))
     
    6679
    6780
    6881    def log(self, fmt, *args):
    69         log("%s(%s:%s)"+fmt, ACTIONS, self._name, self._object_path, *args)
     82        log("%s(%s:%s)"+fmt, ACTIONS,  busnamestr(self), self._object_path, *args)
    7083
    7184
    7285    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='v')
     
    128141        paction = str(action)
    129142        pstate = n(state)
    130143        ppdata = n(pdata)
    131         self.log("Activate(%s, %s, %s) calling %s%s", action, state, pdata, cb, (paction, pstate, ppdata))
     144        self.log(".Activate(%s, %s, %s) calling %s%s", action, state, pdata, cb, (paction, pstate, ppdata))
    132145        cb(paction, pstate, ppdata)
    133146
    134147
     
    144157class Menus(dbus.service.Object):
    145158 
    146159    def __init__(self, name, path, session_bus, menus={}):
     160        log("Menus%s", (name, path, session_bus, menus))
    147161        self.menus = menus
    148162        self.subscribed = {}
    149163        bus_name = dbus.service.BusName(name, session_bus)
    150164        dbus.service.Object.__init__(self, bus_name, path)
    151         self.set_menus(menus)
    152165
    153166    def set_menus(self, menus):
    154167        oldmenus = self.menus
     
    155168        self.menus = menus
    156169        #build the change list for emitting the signal:
    157170        changed = []
    158         groups_ids = sorted(list(set(list(oldmenus.keys())+list(menus.keys()))))
     171        self.log(".set_menus(%s) old menus=%s", menus, oldmenus)
     172        groups_ids = ordered_ints(oldmenus.keys(), menus.keys())
     173        self.log(".set_menus(..) group_ids=%s", groups_ids)
    159174        for group_id in groups_ids:
    160175            oldgroup = oldmenus.get(group_id, {})
    161176            group = menus.get(group_id, {})
    162             menu_ids = sorted(list(set(list(oldgroup.keys()) + list(group.keys()))))
     177            menu_ids = ordered_ints(oldgroup.keys(), group.keys())
     178            self.log(".set_menus(..) menu_ids(%s)=%s", group_id, menu_ids)
    163179            for menu_id in menu_ids:
    164180                oldmenu = oldgroup.get(menu_id, [])
    165181                menu = group.get(menu_id, [])
    166182                if menu==oldmenu:
    167183                    continue
    168                 self.log("set_menus(..) found change at group=%i, menu_id=%i : from %s to %s", group_id, menu_id, oldmenu, menu)
     184                self.log(".set_menus(..) found change at group=%i, menu_id=%i : from %s to %s", group_id, menu_id, oldmenu, menu)
    169185                delcount = len(oldmenu)     #remove all
    170186                insert = [self._make_menu_item(menu[i]) for i in range(len(menu))]
    171187                changed.append((di(group_id), di(menu_id), di(0), di(delcount), dbus.Array(insert)))
    172         self.log("menus changed: %s", changed)
     188        self.log(".set_menus(..) changed: %s", changed)
    173189        if changed:
    174190            self.Changed(dbus.Array(changed))
    175191
    176192    def log(self, fmt, *args):
    177         log("%s(%s:%s)"+fmt, MENUS, self._name, self._object_path, *args)
     193        log("%s(%s:%s)"+fmt, MENUS,  busnamestr(self), self._object_path, *args)
    178194
    179195
    180196    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
     
    241257                        menu_items.append(menu_item)
    242258                if menu_items:
    243259                    menus.append((group, dbus.UInt32(n), menu_items))
    244         self.log("Start(%s)=%s", ids, menus)
     260        self.log(".Start(%s)=%s", ids, menus)
    245261        return menus
    246262 
    247263    @dbus.service.method(MENUS, in_signature="au")
  • xpra/server/source.py

     
    100100        if wid:
    101101            return {"transient-for" : wid}
    102102        return {}
    103     elif propname in ("window-type", "shape"):
     103    elif propname in ("window-type", "shape", "menu"):
    104104        #always send unchanged:
    105105        return {propname : raw()}
    106106    elif propname in ("iconic", "fullscreen", "maximized", "decorations", "above", "below", "shaded", "sticky", "skip-taskbar", "skip-pager", "modal", "focused"):
     
    343343        self.vrefresh = -1
    344344        self.double_click_time  = -1
    345345        self.double_click_distance = -1, -1
     346        self.application_groups = False
    346347        #what we send back in hello packet:
    347348        self.ui_client = True
    348349        self.wants_aliases = True
     
    635636        self.desktop_size_unscaled = c.intpair("desktop_size.unscaled")
    636637        self.set_screen_sizes(c.listget("screen_sizes"))
    637638        self.set_desktops(c.intget("desktops", 1), c.strlistget("desktop.names"))
     639        self.application_groups = c.boolget("application-groups")
    638640
    639641        #sound stuff:
    640642        self.pulseaudio_id = c.strget("sound.pulseaudio.id")
  • xpra/x11/gtk2/models/base.py

     
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     7import os
    78
    89from xpra.util import WORKSPACE_UNSET, WORKSPACE_ALL
    910from xpra.x11.gtk_x11.prop import prop_set, prop_get
     
    1718workspacelog = Logger("x11", "window", "workspace")
    1819metalog = Logger("x11", "window", "metadata")
    1920geomlog = Logger("x11", "window", "geometry")
     21menulog = Logger("x11", "window", "menu")
    2022
    2123
     24dbus_helper = None
     25MENU_FORWARDING = os.environ.get("XPRA_MENU_FORWARDING", "1")=="1"
     26if MENU_FORWARDING:
     27    try:
     28        from xpra.dbus.helper import DBusHelper
     29        dbus_helper = DBusHelper()
     30        from xpra.dbus.gtk_menuactions import query_actions, query_menu, ACTIONS, MENUS
     31    except Exception as e:
     32        log.warn("Warning: menu forwarding is disabled:")
     33        log.warn(" cannot load dbus helper: %s", e)
     34        MENU_FORWARDING = False
     35
     36
    2237X11Window = X11WindowBindings()
    2338
    2439#_NET_WM_STATE:
     
    7186                      "Does this window ever accept keyboard input?", "",
    7287                      True,
    7388                      gobject.PARAM_READWRITE),
    74         #
     89        #from WM_NORMAL_HINTS
    7590        "size-hints": (gobject.TYPE_PYOBJECT,
    7691                       "Client hints on constraining its size", "",
    7792                       gobject.PARAM_READABLE),
     
    88103        "strut": (gobject.TYPE_PYOBJECT,
    89104                  "Struts requested by window, or None", "",
    90105                  gobject.PARAM_READABLE),
     106        #from _GTK_*MENU*
     107        "menu": (gobject.TYPE_PYOBJECT,
     108                  "Application menu, or None", "",
     109                  gobject.PARAM_READABLE),
    91110        #from _NET_WM_DESKTOP
    92111        "workspace": (gobject.TYPE_UINT,
    93112                "The workspace this window is on", "",
     
    151170        })
    152171    _property_names = CoreX11WindowModel._property_names + [
    153172                      "size-hints", "transient-for", "fullscreen-monitors", "bypass-compositor", "group-leader", "window-type", "workspace", "strut",
     173                      "menu",
    154174                      #virtual attributes:
    155175                      "fullscreen", "focused", "maximized", "above", "below", "shaded", "skip-taskbar", "skip-pager", "sticky"]
    156176    _dynamic_property_names = CoreX11WindowModel._dynamic_property_names + [
    157177                              "size-hints", "attention-requested",
     178                              "menu",
    158179                              "fullscreen", "focused", "maximized", "above", "below", "shaded", "skip-taskbar", "skip-pager", "sticky"]
    159180    _internal_property_names = CoreX11WindowModel._internal_property_names+["state"]
    160181    _initial_x11_properties = CoreX11WindowModel._initial_x11_properties + [
     
    164185                              "_NET_WM_FULLSCREEN_MONITORS",
    165186                              "_NET_WM_BYPASS_COMPOSITOR",
    166187                              "_NET_WM_STRUT",
    167                               "_NET_WM_STRUT_PARTIAL",      #redundant as it uses the same function as _NET_WM_STRUT
     188                              #redundant as it uses the same function as _NET_WM_STRUT:
     189                              #"_NET_WM_STRUT_PARTIAL",
    168190                              "_NET_WM_WINDOW_OPACITY",
    169                               "WM_HINTS"]
     191                              "WM_HINTS",
     192                              "_GTK_APP_MENU_OBJECT_PATH",
     193                              #redundant as they use the same function as _GTK_APP_MENU_OBJECT_PATH:
     194                              #"_GTK_APPLICATION_ID",
     195                              #"_GTK_UNIQUE_BUS_NAME",
     196                              #"_GTK_APPLICATION_OBJECT_PATH",
     197                              #"_GTK_APP_MENU_OBJECT_PATH",
     198                              #"_GTK_WINDOW_OBJECT_PATH",
     199                              ]
    170200    _DEFAULT_NET_WM_ALLOWED_ACTIONS = ["_NET_WM_ACTION_%s" % x for x in (
    171201        "CLOSE", "MOVE", "RESIZE", "FULLSCREEN",
    172202        "MINIMIZE", "SHADE", "STICK",
     
    351381        # gets no-op updated -- some apps like FSF Emacs 21 like to update
    352382        # their properties every time they see a ConfigureNotify, and this
    353383        # reduces the chance for us to get caught in loops:
    354         old_hints = self.get_property("size-hints")
    355         if hints and hints!=old_hints:
    356             self._internal_set_property("size-hints", hints)
     384        if self._updateprop("size-hints", hints):
    357385            if self._setup_done:
    358386                self._update_client_geometry()
    359387
     388    def _handle_gtk_app_menu_change(self):
     389        def nomenu(*args):
     390            self._updateprop("menu", {})
     391        if not MENU_FORWARDING:
     392            nomenu()
     393        props = {}
     394        for k,x11name in {
     395            "application-id"    : "_GTK_APPLICATION_ID",            #ie: org.gnome.baobab
     396            "bus-name"          : "_GTK_UNIQUE_BUS_NAME",           #ie: :1.745
     397            "application-path"  : "_GTK_APPLICATION_OBJECT_PATH",   #ie: /org/gnome/baobab
     398            "app-menu-path"     : "_GTK_APP_MENU_OBJECT_PATH",      #ie: /org/gnome/baobab/menus/appmenu
     399            "window-path"       : "_GTK_WINDOW_OBJECT_PATH",        #ie: /org/gnome/baobab/window/1
     400            }.items():
     401            v = self.prop_get(x11name, "utf8", ignore_errors=True, raise_xerrors=False)
     402            menulog("%s=%s", x11name, v)
     403            if v:
     404                props[k] = v
     405            else:
     406                nomenu()
     407                return
     408        menulog("gtk menu properties: %s", props)
     409        app_id      = props["application-id"]
     410        bus_name    = props["bus-name"]
     411        app_path    = props["application-path"]
     412        menu_path   = props["app-menu-path"]
     413        window_path = props["window-path"]
     414        def updatemenu(k, v):
     415            """ Updates the 'menu' property if the value has changed.
     416                Sets the 'enabled' flag if all the required properties are present.
     417            """
     418            menu = self._gproperties.get("menu") or {}
     419            cv = menu.get(k)
     420            if cv==v:
     421                return
     422            if v is None:
     423                try:
     424                    del menu[k]
     425                except:
     426                    pass
     427            else:
     428                menu[k] = v
     429            enabled = all(menu.get(x) for x in ("application-id", "application-actions", "window-actions", "window-menu"))
     430            menu["enabled"] = enabled
     431            menulog("menu(%s)=%s", self, menu)
     432            self._internal_set_property("menu", menu)
     433        updatemenu("application-id", app_id)
     434        def app_actions_cb(actions):
     435            menulog("application-actions=%s", actions)
     436            updatemenu("application-actions", actions)
     437        def app_actions_err(err):
     438            menulog.error("Error: failed to query %s at %s:", ACTIONS, app_path)
     439            menulog.error(" %s", err)
     440            updatemenu("application-actions", None)
     441        query_actions(bus_name, app_path, app_actions_cb, app_actions_err)
     442        def window_actions_cb(actions):
     443            menulog("window-actions", actions)
     444            updatemenu("window-actions", actions)
     445        def window_actions_err(err):
     446            menulog.error("Error: failed to query %s at %s:", ACTIONS, window_path)
     447            menulog.error(" %s", err)
     448            updatemenu("window-actions", None)
     449        query_actions(bus_name, window_path, window_actions_cb, window_actions_err)
     450        def menu_cb(menu):
     451            menulog("window-menu", menu)
     452            updatemenu("window-menu", menu)
     453        def menu_err(err):
     454            menulog.error("Error: failed to query %s at %s:", MENUS, menu_path)
     455            menulog.error(" %s", err)
     456            updatemenu("window-menu", None)
     457        query_menu(bus_name, menu_path, menu_cb, menu_err)
    360458
     459
    361460    _x11_property_handlers = CoreX11WindowModel._x11_property_handlers.copy()
    362461    _x11_property_handlers.update({
    363462        "WM_TRANSIENT_FOR"              : _handle_transient_for_change,
     
    370469        "_NET_WM_WINDOW_OPACITY"        : _handle_opacity_change,
    371470        "WM_HINTS"                      : _handle_wm_hints_change,
    372471        "WM_NORMAL_HINTS"               : _handle_wm_normal_hints_change,
     472        "_GTK_APPLICATION_ID"           : _handle_gtk_app_menu_change,
     473        "_GTK_UNIQUE_BUS_NAME"          : _handle_gtk_app_menu_change,
     474        "_GTK_APPLICATION_OBJECT_PATH"  : _handle_gtk_app_menu_change,
     475        "_GTK_APP_MENU_OBJECT_PATH"     : _handle_gtk_app_menu_change,
     476        "_GTK_WINDOW_OBJECT_PATH"       : _handle_gtk_app_menu_change,
    373477        })
    374478
    375479
  • xpra/x11/gtk2/models/core.py

     
    488488                self.notify(name)
    489489            else:
    490490                metalog("not sending notify(%s) (setup done=%s, managed=%s)", name, self._setup_done, self._managed)
    491         else:
    492             metalog("updateprop(%s, %s) unchanged", name, value)
     491            return True
     492        metalog("updateprop(%s, %s) unchanged", name, value)
     493        return False
    493494
    494495    def get(self, name, default_value=None):
    495496        """ Allows us the avoid defining all the attributes we may ever query,