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