Skip to content

Commit

Permalink
#476:
Browse files Browse the repository at this point in the history
* provide a default menu for applications that do not forward one
* when we are running under gnome, don't try to setup a tray icon (as this is broken), use the menu instead

git-svn-id: https://xpra.org/svn/Xpra/trunk@10702 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Sep 29, 2015
1 parent 19442e9 commit e878ec7
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 48 deletions.
16 changes: 14 additions & 2 deletions src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ def run_command_cb(command):
return self.start_new_command


def show_about(self, *args):
from xpra.client.gtk_base.about import about
about()

def show_session_info(self, *args):
if self.session_info and not self.session_info.is_closed:
#exists already: just raise its window:
Expand Down Expand Up @@ -273,13 +277,21 @@ def supports_system_tray(self):
return True


def set_window_menu(self, wid, menus, application_action_callback=None, window_action_callback=None):
def cook_metadata(self, new_window, metadata):
metadata = UIXpraClient.cook_metadata(self, new_window, metadata)
#ensures we will call set_window_menu for this window when we create it:
if new_window and b"menu" not in metadata and self._set_window_menu:
metadata[b"menu"] = {}
return metadata


def set_window_menu(self, add, wid, menus, application_action_callback=None, window_action_callback=None):
assert self._set_window_menu
model = self._id_to_window.get(wid)
window = None
if model:
window = model.get_window()
self._set_window_menu(wid, window, menus, application_action_callback, window_action_callback)
self._set_window_menu(add, wid, window, menus, application_action_callback, window_action_callback)


def get_root_window(self):
Expand Down
4 changes: 2 additions & 2 deletions src/xpra/client/gtk_base/gtk_client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ def set_workspace(self, workspace):
def set_menu(self, menu):
menulog("set_menu(%s)", menu)
def do_set_menu():
self._client.set_window_menu(self._id, menu, self.application_action_callback, self.window_action_callback)
self._client.set_window_menu(True, self._id, menu, self.application_action_callback, self.window_action_callback)
self.when_realized("menu", do_set_menu)

def application_action_callback(self, action_service, action, state, pdata):
Expand Down Expand Up @@ -977,7 +977,7 @@ def move_resize(self, x, y, w, h, resize_counter=0):

def destroy(self):
if self._client._set_window_menu:
self._client.set_window_menu(self._id, {})
self._client.set_window_menu(False, self._id, {})
self.on_realize_cb = {}
ClientWindowBase.destroy(self)
gtk.Window.destroy(self)
Expand Down
4 changes: 3 additions & 1 deletion src/xpra/client/tray_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class TrayBase(object):
Utility superclass for all tray implementations
"""

def __init__(self, menu, tooltip, icon_filename, size_changed_cb, click_cb, mouseover_cb, exit_cb):
def __init__(self, client, menu, tooltip, icon_filename, size_changed_cb, click_cb, mouseover_cb, exit_cb):
#we don't keep a reference to client,
#because calling functions on the client directly should be discouraged
self.menu = menu
self.tooltip = tooltip
self.size_changed_cb = size_changed_cb
Expand Down
15 changes: 9 additions & 6 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,9 @@ def send_refresh_all(self):
self.send_refresh(-1)


def show_about(self, *args):
log.warn("show_about() is not implemented in %s", self)

def show_session_info(self, *args):
log.warn("show_session_info() is not implemented in %s", self)

Expand Down Expand Up @@ -639,7 +642,7 @@ def make_system_tray(self, *args):
""" tray used for application systray forwarding """
tc = self.get_system_tray_classes()
traylog("make_system_tray%s system tray classes=%s", args, tc)
return self.make_instance(tc, *args)
return self.make_instance(tc, self, *args)

def get_system_tray_classes(self):
#subclasses may add their toolkit specific variants, if any
Expand All @@ -652,7 +655,7 @@ def make_tray(self, *args):
""" tray used by our own application """
tc = self.get_tray_classes()
traylog("make_tray%s tray classes=%s", args, tc)
return self.make_instance(tc, *args)
return self.make_instance(tc, self, *args)

def get_tray_classes(self):
#subclasses may add their toolkit specific variants, if any
Expand Down Expand Up @@ -2135,7 +2138,7 @@ def cp(self, x, y):
def _process_new_common(self, packet, override_redirect):
self._ui_event()
wid, x, y, w, h = packet[1:6]
metadata = self.cook_metadata(packet[6])
metadata = self.cook_metadata(True, packet[6])
windowlog("process_new_common: %s, metadata=%s, OR=%s", packet[1:7], metadata, override_redirect)
assert wid not in self._id_to_window, "we already have a window %s" % wid
if w<=1 or h<=1:
Expand All @@ -2151,7 +2154,7 @@ def _process_new_common(self, packet, override_redirect):
client_properties = packet[7]
self.make_new_window(wid, x, y, ww, wh, bw, bh, metadata, override_redirect, client_properties)

def cook_metadata(self, metadata):
def cook_metadata(self, new_window, metadata):
#convert to a typedict and apply client-side overrides:
metadata = typedict(metadata)
if self.server_is_shadow and self.shadow_fullscreen:
Expand Down Expand Up @@ -2291,7 +2294,7 @@ def _process_new_tray(self, packet):
h = max(1, self.sy(h))
metadata = typedict()
if len(packet)>=5:
metadata = self.cook_metadata(packet[4])
metadata = self.cook_metadata(True, packet[4])
assert wid not in self._id_to_window, "we already have a window %s" % wid
tray = self.setup_system_tray(self, wid, w, h, metadata.get("title", ""))
traylog("process_new_tray(%s) tray=%s", packet, tray)
Expand Down Expand Up @@ -2463,7 +2466,7 @@ def _process_window_metadata(self, packet):
wid, metadata = packet[1:3]
window = self._id_to_window.get(wid)
if window:
metadata = self.cook_metadata(metadata)
metadata = self.cook_metadata(False, metadata)
window.update_metadata(metadata)

def _process_window_icon(self, packet):
Expand Down
90 changes: 65 additions & 25 deletions src/xpra/platform/xposix/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,54 @@
# 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 os
import struct
import binascii
from xpra.log import Logger
log = Logger("posix")
eventlog = Logger("posix", "events")
screenlog = Logger("posix", "screen")
dbuslog = Logger("posix", "dbus")
traylog = Logger("posix", "menu")
menulog = Logger("posix", "menu")

from xpra.gtk_common.gobject_compat import get_xid, is_gtk3

device_bell = None


def get_native_system_tray_classes():
try:
from xpra.platform.xposix.appindicator_tray import AppindicatorTray, can_use_appindicator
if can_use_appindicator():
return [AppindicatorTray]
except Exception as e:
traylog("cannot load appindicator tray: %s", e)
return []

def get_native_tray_classes():
wm_name = os.environ.get("XDG_CURRENT_DESKTOP", "")
try:
wm_check = _get_X11_root_property("_NET_SUPPORTING_WM_CHECK", "WINDOW")
if wm_check:
xid = struct.unpack("=I", wm_check)[0]
traylog("_NET_SUPPORTING_WM_CHECK window=%#x", xid)
wm_name = _get_X11_window_property(xid, "_NET_WM_NAME", "UTF8_STRING")
traylog("_NET_WM_NAME=%s", wm_name)
except Exception as e:
traylog.error("Error accessing window manager information:")
traylog.error(" %s", e)
#could restrict to only DEs that have a broken system tray like "GNOME Shell"?
if has_gtk_menu_support(): #and wm_name=="GNOME Shell":
try:
from xpra.platform.xposix.gtkmenu_tray import GTKMenuTray
traylog("using GTKMenuTray for %s", wm_name)
return [GTKMenuTray]
except Exception as e:
traylog("cannot load gtk menu tray: %s", e)
return get_native_system_tray_classes()


def get_native_notifier_classes():
ncs = []
try:
Expand All @@ -31,51 +66,47 @@ def get_native_notifier_classes():
log("cannot load pynotify notifier: %s", e)
return ncs

def get_native_tray_classes():
try:
from xpra.platform.xposix.appindicator_tray import AppindicatorTray, can_use_appindicator
if can_use_appindicator():
return [AppindicatorTray]
except Exception as e:
log("cannot load appindicator tray: %s", e)
return []

def get_native_system_tray_classes():
#appindicator can be used for both
return get_native_tray_classes()


#we duplicate some of the code found in gtk_x11.prop ...
#which is still better than having dependencies on that GTK2 code
def _get_X11_root_property(name, req_type):
def _get_X11_window_property(xid, name, req_type):
try:
from xpra.gtk_common.error import xsync
from xpra.x11.bindings.window_bindings import X11WindowBindings, PropertyError #@UnresolvedImport
window_bindings = X11WindowBindings()
root = window_bindings.getDefaultRootWindow()
try:
with xsync:
prop = window_bindings.XGetWindowProperty(root, name, req_type)
log("_get_X11_root_property(%s, %s)=%s, len=%s", name, req_type, type(prop), len(prop or []))
prop = window_bindings.XGetWindowProperty(xid, name, req_type)
log("_get_X11_window_property(%#x, %s, %s)=%s, len=%s", xid, name, req_type, type(prop), len(prop or []))
return prop
except PropertyError as e:
log("_get_X11_root_property(%s, %s): %s", name, req_type, e)
log("_get_X11_window_property(%#x, %s, %s): %s", xid, name, req_type, e)
except Exception as e:
log.warn("failed to get X11 window property %s on window %#x: %s", name, xid, e)
return None
def _get_X11_root_property(name, req_type):
try:
from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport
window_bindings = X11WindowBindings()
root_xid = window_bindings.getDefaultRootWindow()
return _get_X11_window_property(root_xid, name, req_type)
except Exception as e:
log.warn("failed to get X11 root property %s: %s", name, e)
return None


def _set_gtk_x11_window_menu(wid, window, menus, application_action_callback=None, window_action_callback=None):
def _set_gtk_x11_window_menu(add, wid, window, menus, application_action_callback=None, window_action_callback=None):
from xpra.x11.gtk_x11.menu import setup_dbus_window_menu
from xpra.x11.gtk_x11.prop import prop_set, prop_del
window_props = setup_dbus_window_menu(wid, menus, application_action_callback, window_action_callback)
window_props = setup_dbus_window_menu(add, wid, menus, application_action_callback, window_action_callback)
#window_props may contains X11 window properties we have to clear or set
if not window_props:
return
if not window:
#window has already been closed
#(but we still want to call setup_dbus_window_menu above to ensure we clear things up!)
return
menulog("will set/remove the following window properties for wid=%i: %s", wid, window_props)
try:
from xpra.gtk_common.error import xsync
with xsync:
Expand All @@ -89,17 +120,26 @@ def _set_gtk_x11_window_menu(wid, window, menus, application_action_callback=Non
menulog.error("Error setting menu window properties:")
menulog.error(" %s", e)

def get_menu_support_function():

_has_gtk_menu_support = None
def has_gtk_menu_support():
global _has_gtk_menu_support
if _has_gtk_menu_support is not None:
return _has_gtk_menu_support
try:
from xpra.gtk_common.gtk_util import get_default_root_window
from xpra.x11.gtk_x11.menu import has_gtk_menu_support
root = get_default_root_window()
has_gtk_menu = has_gtk_menu_support(root)
menulog("has_gtk_menu_support(%s)=%s", root, has_gtk_menu)
if has_gtk_menu:
return _set_gtk_x11_window_menu
_has_gtk_menu_support = has_gtk_menu_support(root)
menulog("has_gtk_menu_support(%s)=%s", root, _has_gtk_menu_support)
except Exception as e:
menulog("cannot enable gtk-x11 menu support: %s", e)
_has_gtk_menu_support = False
return _has_gtk_menu_support

def get_menu_support_function():
if has_gtk_menu_support():
return _set_gtk_x11_window_menu
return None


Expand Down
30 changes: 18 additions & 12 deletions src/xpra/x11/gtk_x11/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,23 @@ def has_gtk_menu_support(root_window):
window_menus = {}
window_menu_services = weakref.WeakValueDictionary()

def setup_dbus_window_menu(wid, menus, application_action_callback=None, window_action_callback=None):
global window_menu_services, window_menus
fallback_menus = {}

def setup_dbus_window_menu(add, wid, menus, application_action_callback=None, window_action_callback=None):
def nomenu():
#tell caller to clear all properties if they exist:
return {
"_GTK_APP_MENU_OBJECT_PATH" : None,
"_GTK_WINDOW_OBJECT_PATH" : None,
"_GTK_APPLICATION_OBJECT_PATH" : None,
"_GTK_UNIQUE_BUS_NAME" : None,
"_GTK_APPLICATION_ID" : None
}
if add is False:
return nomenu()
global window_menu_services, window_menus, fallback_menus
if len(menus)==0 and fallback_menus:
menus = fallback_menus
#ie: menu = {
# 'enabled': True,
# 'application-id': 'org.xpra.ExampleMenu',
Expand All @@ -61,7 +76,7 @@ def remove_services(*args):
pass
if enabled:
m = typedict(menus)
app_id = bytestostr(m.strget("application-id")).decode()
app_id = bytestostr(m.strget("application-id", b"org.xpra.Window%i" % wid)).decode()
app_actions = m.dictget("application-actions")
window_actions = m.dictget("window-actions")
window_menu = m.dictget("window-menu")
Expand All @@ -77,15 +92,6 @@ def remove_services(*args):
window_actions_service.set_actions(window_actions)
window_menu_service.set_menus(window_menu)
return
def nomenu():
#tell caller to clear all properties if they exist:
return {
"_GTK_APP_MENU_OBJECT_PATH" : None,
"_GTK_WINDOW_OBJECT_PATH" : None,
"_GTK_APPLICATION_OBJECT_PATH" : None,
"_GTK_UNIQUE_BUS_NAME" : None,
"_GTK_APPLICATION_ID" : None
}
if not enabled:
#tell caller to clear all properties if they exist:
return nomenu()
Expand Down

0 comments on commit e878ec7

Please sign in to comment.