1# This file is part of Xpra. 2# Copyright (C) 2011-2019 Antoine Martin <antoine@xpra.org> 3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4# later version. See the file COPYING for details. 5 6# Ubuntu's "tray" is not very useful: 7# - we can't know its position 8# - we can't get pointer motion events 9# - we can't set the icon using a path (not easily anyway) 10# - we can only show a menu and nothing else 11# - that menu looks bloody awful 12# etc 13 14import os 15import sys 16import tempfile 17import gi 18from time import monotonic 19 20from xpra.util import envbool 21from xpra.os_util import osexpand 22from xpra.client.tray_base import TrayBase 23from xpra.platform.paths import get_icon_dir, get_icon_filename, get_xpra_tmp_dir 24from xpra.log import Logger 25 26log = Logger("tray", "posix") 27 28DELETE_TEMP_FILE = envbool("XPRA_APPINDICATOR_DELETE_TEMP_FILE", True) 29 30gi.require_version('AppIndicator3', '0.1') 31from gi.repository import AppIndicator3 #pylint: disable=wrong-import-order, wrong-import-position, ungrouped-imports 32 33PASSIVE = AppIndicator3.IndicatorStatus.PASSIVE 34ACTIVE = AppIndicator3.IndicatorStatus.ACTIVE 35APPLICATION_STATUS = AppIndicator3.IndicatorCategory.APPLICATION_STATUS 36def Indicator(tooltip, filename, status): 37 return AppIndicator3.Indicator.new(tooltip, filename, status) 38 39 40class AppindicatorTray(TrayBase): 41 42 def __init__(self, *args, **kwargs): 43 super().__init__(*args, **kwargs) 44 filename = get_icon_filename(self.default_icon_filename) or "xpra.png" 45 self._has_icon = False 46 self.tmp_filename = None 47 self.tray_widget = Indicator(self.tooltip, filename, APPLICATION_STATUS) 48 if hasattr(self.tray_widget, "set_icon_theme_path"): 49 self.tray_widget.set_icon_theme_path(get_icon_dir()) 50 try: 51 self.tray_widget.set_attention_icon_full("xpra.png", self.tooltip) 52 except AttributeError: 53 self.tray_widget.set_attention_icon("xpra.png") 54 if filename: 55 self.set_icon_from_file(filename) 56 if not self._has_icon: 57 self.tray_widget.set_label("Xpra") 58 if self.menu: 59 self.tray_widget.set_menu(self.menu) 60 61 def get_geometry(self): 62 #no way to tell :( 63 return None 64 65 def hide(self): 66 self.tray_widget.set_status(PASSIVE) 67 68 def show(self): 69 self.tray_widget.set_status(ACTIVE) 70 71 def set_blinking(self, on): 72 #"I'm Afraid I Can't Do That" 73 pass 74 75 def set_tooltip(self, tooltip=None): 76 #we only use this if we haven't got an icon 77 #as with appindicator this creates a large text label 78 #next to where the icon is/should be 79 if not self._has_icon: 80 self.tray_widget.set_label(tooltip or "Xpra") 81 82 def set_icon_from_data(self, pixels, has_alpha, w, h, rowstride, _options=None): 83 self.clean_last_tmp_icon() 84 #use a temporary file (yuk) 85 from xpra.gtk_common.gtk_util import pixbuf_save_to_memory, get_pixbuf_from_data 86 tray_icon = get_pixbuf_from_data(pixels, has_alpha, w, h, rowstride) 87 png_data = pixbuf_save_to_memory(tray_icon) 88 tmp_dir = osexpand(get_xpra_tmp_dir()) 89 if not os.path.exists(tmp_dir): 90 os.mkdir(tmp_dir, 0o755) 91 fd = None 92 try: 93 fd, self.tmp_filename = tempfile.mkstemp(prefix="tray", suffix=".png", dir=tmp_dir) 94 log("set_icon_from_data%s using temporary file %s", 95 ("%s pixels" % len(pixels), has_alpha, w, h, rowstride), self.tmp_filename) 96 os.write(fd, png_data) 97 except OSError as e: 98 log("error saving temporary file", exc_info=True) 99 log.error("Error saving icon data to temporary file") 100 log.error(" %s", e) 101 return 102 finally: 103 if fd: 104 os.fchmod(fd, 0o644) 105 os.close(fd) 106 self.do_set_icon_from_file(self.tmp_filename) 107 108 def do_set_icon_from_file(self, filename): 109 if not hasattr(self.tray_widget, "set_icon_theme_path"): 110 self.tray_widget.set_icon(filename) 111 self._has_icon = True 112 return 113 head, icon_name = os.path.split(filename) 114 if head: 115 log("do_set_icon_from_file(%s) setting icon theme path=%s", filename, head) 116 self.tray_widget.set_icon_theme_path(head) 117 #remove extension (wtf?) 118 noext = os.path.splitext(icon_name)[0] 119 log("do_set_icon_from_file(%s) setting icon=%s", filename, noext) 120 try: 121 self.tray_widget.set_icon_full(noext, self.tooltip) 122 except AttributeError: 123 self.tray_widget.set_icon(noext) 124 self._has_icon = True 125 self.icon_timestamp = monotonic() 126 127 def clean_last_tmp_icon(self): 128 if self.tmp_filename and DELETE_TEMP_FILE: 129 try: 130 os.unlink(self.tmp_filename) 131 except OSError: 132 log("failed to remove tmp icon", exc_info=True) 133 self.tmp_filename = None 134 135 def cleanup(self): 136 self.clean_last_tmp_icon() 137 super().cleanup() 138 139 140def main(): # pragma: no cover 141 from xpra.platform import program_context 142 with program_context("AppIndicator-Test", "AppIndicator Test"): 143 if "-v" in sys.argv: 144 from xpra.log import enable_debug_for 145 enable_debug_for("tray") 146 147 from xpra.gtk_common.gobject_compat import register_os_signals 148 149 from gi.repository import Gtk 150 menu = Gtk.Menu() 151 item = Gtk.MenuItem(label="Top Menu Item 1") 152 submenu = Gtk.Menu() 153 item.set_submenu(submenu) 154 sub = Gtk.MenuItem(label="Sub Menu Item 1") 155 subsubmenu = Gtk.Menu() 156 sub.set_submenu(subsubmenu) 157 subsubmenu.append(Gtk.MenuItem(label="Sub Sub Menu Item 1")) 158 subsubmenu.append(Gtk.MenuItem(label="Sub Sub Menu Item 2")) 159 submenu.append(sub) 160 sub = Gtk.MenuItem(label="Sub Menu Item 2") 161 submenu.append(sub) 162 menu.append(item) 163 item = Gtk.MenuItem(label="Top Menu Item 2") 164 menu.append(item) 165 menu.show_all() 166 a = AppindicatorTray(None, None, menu, "test", "xpra.png", None, None, None, Gtk.main_quit) 167 a.show() 168 register_os_signals(Gtk.main_quit) 169 Gtk.main() 170 171 172if __name__ == "__main__": # pragma: no cover 173 main() 174