Skip to content

Commit

Permalink
* move systray functions to common location
Browse files Browse the repository at this point in the history
* move about to common location
* add systray to win32 shadow server

git-svn-id: https://xpra.org/svn/Xpra/trunk@11482 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Dec 26, 2015
1 parent b939e6e commit 5aa90a0
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 86 deletions.
4 changes: 2 additions & 2 deletions src/xpra/client/gtk_base/client_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
from xpra.scripts.config import read_config, make_defaults_struct, validate_config, save_config
from xpra.codecs.loader import PREFERED_ENCODING_ORDER
from xpra.gtk_common.gtk_util import gtk_main, add_close_accel, scaled_image, pixbuf_new_from_file, color_parse, \
OptionMenu, choose_file, \
OptionMenu, choose_file, set_use_tray_workaround, \
WIN_POS_CENTER, STATE_NORMAL, \
DIALOG_DESTROY_WITH_PARENT, MESSAGE_INFO, BUTTONS_CLOSE, \
FILE_CHOOSER_ACTION_SAVE, FILE_CHOOSER_ACTION_OPEN
from xpra.os_util import thread
from xpra.client.gtk_base.gtk_tray_menu_base import make_min_auto_menu, make_encodingsmenu, set_use_tray_workaround, \
from xpra.client.gtk_base.gtk_tray_menu_base import make_min_auto_menu, make_encodingsmenu, \
MIN_QUALITY_OPTIONS, QUALITY_OPTIONS, MIN_SPEED_OPTIONS, SPEED_OPTIONS
from xpra.client.gtk_base.about import about
from xpra.net.crypto import ENCRYPTION_CIPHERS
Expand Down
73 changes: 5 additions & 68 deletions src/xpra/client/gtk_base/gtk_tray_menu_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

import sys
import os
from xpra.gtk_common.gobject_compat import import_gtk, import_glib, is_gtk3
from xpra.gtk_common.gobject_compat import import_gtk, import_glib
gtk = import_gtk()
glib = import_glib()

from xpra.util import CLIENT_EXIT, iround
from xpra.os_util import bytestostr
from xpra.gtk_common.gtk_util import ensure_item_selected, menuitem, BUTTON_PRESS_MASK
from xpra.gtk_common.gtk_util import ensure_item_selected, menuitem, popup_menu_workaround, CheckMenuItem
from xpra.client.client_base import EXIT_OK
from xpra.client.gtk_base.about import about, close_about
from xpra.codecs.loader import PREFERED_ENCODING_ORDER, ENCODINGS_HELP, ENCODINGS_TO_NAME
Expand Down Expand Up @@ -55,41 +55,6 @@
SPEED_OPTIONS[100] = "Lowest Latency"


class TrayCheckMenuItem(gtk.CheckMenuItem):
""" We add a button handler to catch clicks that somehow do not
trigger the "toggled" signal on some platforms (win32?) when we
show the tray menu with a right click and click on the item with the left click.
(or the other way around?)
"""
def __init__(self, label, tooltip=None):
gtk.CheckMenuItem.__init__(self, label)
self.label = label
if tooltip:
self.set_tooltip_text(tooltip)
self.add_events(BUTTON_PRESS_MASK)
self.connect("button-release-event", self.on_button_release_event)

def on_button_release_event(self, *args):
log("TrayCheckMenuItem.on_button_release_event(%s) label=%s", args, self.label)
self.active_state = self.get_active()
def recheck():
log("TrayCheckMenuItem: recheck() active_state=%s, get_active()=%s", self.active_state, self.get_active())
state = self.active_state
self.active_state = None
if state is not None and state==self.get_active():
#toggle did not fire after the button release, so force it:
self.set_active(not state)
glib.idle_add(recheck)

def set_use_tray_workaround(enabled):
global CheckMenuItem
if enabled and sys.platform.startswith("win"):
CheckMenuItem = TrayCheckMenuItem
else:
CheckMenuItem = gtk.CheckMenuItem
set_use_tray_workaround(True)


def set_sensitive(widget, sensitive):
if sys.platform.startswith("darwin"):
if sensitive:
Expand Down Expand Up @@ -320,9 +285,11 @@ def menu_deactivated(self, *args):
self.menu_shown = False

def activate(self):
log("activate()")
self.show_menu(1, 0)

def popup(self, button, time):
log("popup(%s, %s)", button, time)
self.show_menu(button, time)

def show_menu(self, button, time):
Expand Down Expand Up @@ -360,7 +327,6 @@ def checkitem(self, title, cb=None, active=False):
return check_item



def make_aboutmenuitem(self):
return self.menuitem("About Xpra", "information.png", None, about)

Expand Down Expand Up @@ -1020,33 +986,4 @@ def make_closemenuitem(self):


def popup_menu_workaround(self, menu):
#win32 workaround:
if sys.platform.startswith("win") and not is_gtk3():
self.add_popup_menu_workaround(menu)

def add_popup_menu_workaround(self, menu):
""" windows does not automatically close the popup menu when we click outside it
so we workaround it by using a timer and closing the menu when the mouse
has stayed outside it for more than 0.5s.
This code must be added to all the sub-menus of the popup menu too!
"""
def enter_menu(*args):
log("mouse_in_tray_menu=%s", self.mouse_in_tray_menu)
self.mouse_in_tray_menu_counter += 1
self.mouse_in_tray_menu = True
def leave_menu(*args):
log("mouse_in_tray_menu=%s", self.mouse_in_tray_menu)
self.mouse_in_tray_menu_counter += 1
self.mouse_in_tray_menu = False
def check_menu_left(expected_counter):
if self.mouse_in_tray_menu:
return False
if expected_counter!=self.mouse_in_tray_menu_counter:
return False #counter has changed
self.close_menu()
glib.timeout_add(500, check_menu_left, self.mouse_in_tray_menu_counter)
self.mouse_in_tray_menu_counter = 0
self.mouse_in_tray_menu = False
log("popup_menu_workaround: adding events callbacks")
menu.connect("enter-notify-event", enter_menu)
menu.connect("leave-notify-event", leave_menu)
popup_menu_workaround(menu, self.close_menu)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
about.py
"""

import sys
import os.path

from xpra.gtk_common.gobject_compat import import_gtk, is_gtk3
Expand Down
94 changes: 80 additions & 14 deletions src/xpra/gtk_common/gtk_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ def get_gtk_version_info():
return GTK_VERSION_INFO.copy()


def popup_menu_workaround(*args):
#only implemented with GTK2 on win32 below
pass


if is_gtk3():
def is_realized(widget):
return widget.get_realized()
Expand Down Expand Up @@ -359,6 +364,81 @@ def gtk_main():
finally:
gdk.threads_leave()

if WIN32:
traylog = Logger("tray", "win32")
mouse_in_tray_menu_counter = 0
mouse_in_tray_menu = False
def popup_menu_workaround(menu, close_cb):
""" MS Windows does not automatically close the popup menu when we click outside it
so we workaround it by using a timer and closing the menu when the mouse
has stayed outside it for more than 0.5s.
This code must be added to all the sub-menus of the popup menu too!
"""
global mouse_in_tray_menu, mouse_in_tray_menu_counter
def enter_menu(*args):
global mouse_in_tray_menu, mouse_in_tray_menu_counter
traylog("mouse_in_tray_menu=%s", mouse_in_tray_menu)
mouse_in_tray_menu_counter += 1
mouse_in_tray_menu = True
def leave_menu(*args):
global mouse_in_tray_menu, mouse_in_tray_menu_counter
traylog("mouse_in_tray_menu=%s", mouse_in_tray_menu)
mouse_in_tray_menu_counter += 1
mouse_in_tray_menu = False
def check_menu_left(expected_counter):
if mouse_in_tray_menu:
return False
if expected_counter!=mouse_in_tray_menu_counter:
return False #counter has changed
close_cb()
gobject.timeout_add(500, check_menu_left, mouse_in_tray_menu_counter)
mouse_in_tray_menu_counter = 0
mouse_in_tray_menu = False
traylog("popup_menu_workaround: adding events callbacks")
menu.connect("enter-notify-event", enter_menu)
menu.connect("leave-notify-event", leave_menu)


class TrayCheckMenuItem(gtk.CheckMenuItem):
""" We add a button handler to catch clicks that somehow do not
trigger the "toggled" signal on some platforms (win32?) when we
show the tray menu with a right click and click on the item with the left click.
(or the other way around?)
"""
def __init__(self, label, tooltip=None):
gtk.CheckMenuItem.__init__(self, label)
self.label = label
if tooltip:
self.set_tooltip_text(tooltip)
self.add_events(BUTTON_PRESS_MASK)
self.connect("button-release-event", self.on_button_release_event)

def on_button_release_event(self, *args):
log("TrayCheckMenuItem.on_button_release_event(%s) label=%s", args, self.label)
self.active_state = self.get_active()
def recheck():
log("TrayCheckMenuItem: recheck() active_state=%s, get_active()=%s", self.active_state, self.get_active())
state = self.active_state
self.active_state = None
if state is not None and state==self.get_active():
#toggle did not fire after the button release, so force it:
self.set_active(not state)
gobject.idle_add(recheck)

CheckMenuItemClass = gtk.CheckMenuItem
def CheckMenuItem(*args, **kwargs):
global CheckMenuItemClass
return CheckMenuItemClass(*args, **kwargs)

def set_use_tray_workaround(enabled):
global CheckMenuItemClass
if enabled and WIN32:
CheckMenuItemClass = TrayCheckMenuItem
else:
CheckMenuItemClass = gtk.CheckMenuItem
set_use_tray_workaround(True)



def get_display_info():
display = display_get_default()
Expand Down Expand Up @@ -669,20 +749,6 @@ def get_active_item(items):
return item


def CheckMenuItem(label, tooltip=None):
""" adds a get_label() method for older versions of gtk which do not have it
beware that this label is not mutable!
"""
cmi = gtk.CheckMenuItem(label)
if not hasattr(cmi, "get_label"):
def get_label():
return label
cmi.get_label = get_label
if tooltip:
cmi.set_tooltip_text(tooltip)
return cmi


class TableBuilder(object):

def __init__(self, rows=1, columns=2, homogeneous=False):
Expand Down
114 changes: 114 additions & 0 deletions src/xpra/platform/win32/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from xpra.log import Logger
from xpra.util import AdHocStruct
log = Logger("shadow", "win32")
traylog = Logger("tray")
shapelog = Logger("shape")

from xpra.os_util import StringIOClass
Expand Down Expand Up @@ -231,6 +232,117 @@ def __init__(self):
ShadowServerBase.__init__(self, gtk.gdk.get_default_root_window())
GTKServerBase.__init__(self)
self.keycodes = {}
self.menu = None
self.menu_shown = False
self.tray_widget = None
self.tray = False
self.delay_tray = False
self.tray_icon = None

def init(self, opts):
GTKServerBase.init(self, opts)
self.tray = opts.tray
self.delay_tray = opts.delay_tray
self.tray_icon = opts.tray_icon or "xpra.ico"
if self.tray:
self.setup_tray()

############################################################################
# system tray methods, mostly copied from the gtk client...
# (most of these should probably be moved to a common location instead)

def setup_tray(self):
try:
from xpra.gtk_common.gobject_compat import import_gtk
gtk = import_gtk()
from xpra.gtk_common.gtk_util import popup_menu_workaround
#menu:
self.menu = gtk.Menu()
self.menu.set_title("Xpra Server")
from xpra.gtk_common.about import about
self.menu.append(self.menuitem("About Xpra", "information.png", None, about))
self.menu.append(self.menuitem("Exit", "quit.png", None, self.quit))
self.menu.append(self.menuitem("Close Menu", "close.png", None, self.close_menu))
#maybe add: session info, clipboard, sharing, etc
#control: disconnect clients
self.menu.connect("deactivate", self.menu_deactivated)
popup_menu_workaround(self.menu, self.close_menu)
#tray:
from xpra.platform.paths import get_icon_dir
icon_filename = os.path.join(get_icon_dir(), self.tray_icon)
from xpra.platform.win32.win32_NotifyIcon import win32NotifyIcon
self.tray_widget = win32NotifyIcon("Xpra Server", None, self.click_callback, self.exit_callback)
self.tray_widget.set_icon(icon_filename)
except ImportError as e:
traylog.warn("Warning: failed to load systemtray:")
traylog.warn(" %s", e)
except Exception as e:
traylog.error("Error setting up system tray", exc_info=True)

def menuitem(self, title, icon_name=None, tooltip=None, cb=None):
""" Utility method for easily creating an ImageMenuItem """
from xpra.gtk_common.gtk_util import menuitem
image = None
if icon_name:
from xpra.platform.gui import get_icon_size
icon_size = get_icon_size()
image = self.get_image(icon_name, icon_size)
return menuitem(title, image, tooltip, cb)

def get_pixbuf(self, icon_name):
from xpra.platform.paths import get_icon_filename
from xpra.gtk_common.gtk_util import pixbuf_new_from_file
try:
if not icon_name:
traylog("get_pixbuf(%s)=None", icon_name)
return None
icon_filename = get_icon_filename(icon_name)
traylog("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename)
if icon_filename:
return pixbuf_new_from_file(icon_filename)
except:
traylog.error("get_pixbuf(%s)", icon_name, exc_info=True)
return None

def get_image(self, icon_name, size=None):
from xpra.gtk_common.gtk_util import scaled_image
try:
pixbuf = self.get_pixbuf(icon_name)
traylog("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf)
if not pixbuf:
return None
return scaled_image(pixbuf, size)
except:
traylog.error("get_image(%s, %s)", icon_name, size, exc_info=True)
return None


def menu_deactivated(self, *args):
self.menu_shown = False

def click_callback(self, button, pressed):
traylog("click_callback(%s, %s)", button, pressed)
if pressed:
self.close_menu()
self.menu.popup(None, None, None, button, 0)
self.menu_shown = True

def exit_callback(self, *args):
self.quit(False)

def close_menu(self, *args):
if self.menu_shown:
self.menu.popdown()
self.menu_shown = False

def cleanup(self):
GTKServerBase.cleanup(self)
if self.tray_widget:
self.tray_widget.close()
self.tray_widget = None

############################################################################


def makeRootWindowModel(self):
return Win32RootWindowModel(self.root)
Expand Down Expand Up @@ -279,6 +391,8 @@ def get_info(self, proto):
info = GTKServerBase.get_info(self, proto)
info["features.shadow"] = True
info["server.type"] = "Python/gtk2/win32-shadow"
info["server.tray"] = self.tray
info["server.tray-icon"] = self.tray_icon or ""
return info


Expand Down
2 changes: 1 addition & 1 deletion src/xpra/server/server_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def log_exit_state(self):
for l in x.splitlines():
log("%s", l)

def quit(self, upgrading):
def quit(self, upgrading=False):
log("quit(%s)", upgrading)
self._upgrading = upgrading
log.info("xpra is terminating.")
Expand Down

0 comments on commit 5aa90a0

Please sign in to comment.