Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/tray_base.py : 49%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2011-2019 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7from collections import deque
9from xpra.platform.paths import get_icon_filename
10from xpra.log import Logger
11from xpra.os_util import monotonic_time
13log = Logger("tray")
16class TrayBase:
17 """
18 Utility superclass for all tray implementations
19 """
21 def __init__(self, _client, app_id, menu, tooltip, icon_filename, size_changed_cb, click_cb, mouseover_cb, exit_cb):
22 #we don't keep a reference to client,
23 #because calling functions on the client directly should be discouraged
24 self.app_id = app_id
25 self.menu = menu
26 self.tooltip = tooltip
27 self.size_changed_cb = size_changed_cb
28 self.click_cb = click_cb
29 self.mouseover_cb = mouseover_cb
30 self.exit_cb = exit_cb
31 self.tray_widget = None
32 self.default_icon_filename = icon_filename #ie: "xpra" or "/path/to/xpra.png"
33 #some implementations need this for guessing the geometry (see recalculate_geometry):
34 self.geometry_guess = None
35 self.tray_event_locations = deque(maxlen=512)
36 self.default_icon_extension = "png"
37 self.icon_timestamp = 0
39 def __repr__(self):
40 return "Tray(%i:%s)" % (self.app_id, self.tooltip)
42 def cleanup(self):
43 if self.tray_widget:
44 self.hide()
45 self.tray_widget = None
47 def ready(self):
48 pass
50 def show(self):
51 raise NotImplementedError("override me!")
53 def hide(self):
54 raise NotImplementedError("override me!")
56 def get_screen(self):
57 return -1
59 def get_orientation(self):
60 return None #assume "HORIZONTAL"
62 def get_geometry(self):
63 raise NotImplementedError("override me!")
65 def get_size(self):
66 g = self.get_geometry()
67 if g is None:
68 return None
69 return g[2:4]
71 def set_tooltip(self, tooltip=None):
72 self.tooltip = tooltip
73 raise NotImplementedError("override me!")
75 def set_blinking(self, on):
76 raise NotImplementedError("override me!")
79 def set_icon_from_data(self, pixels, has_alpha, w, h, rowstride, options=None):
80 raise NotImplementedError("override me!")
82 def get_icon_filename(self, basename=None):
83 name = basename or self.default_icon_filename
84 f = get_icon_filename(name, self.default_icon_extension)
85 if not f:
86 log.error("Error: cannot find icon '%s'", name)
87 return f
89 def set_icon(self, basename=None):
90 filename = self.get_icon_filename(basename)
91 if not filename:
92 return
93 log("set_icon(%s) using filename=%s", basename, filename)
94 self.set_icon_from_file(filename)
96 def set_icon_from_file(self, filename):
97 log("set_icon_from_file(%s) tray_widget=%s", filename, self.tray_widget)
98 if not self.tray_widget:
99 return
100 self.do_set_icon_from_file(filename)
101 self.icon_timestamp = monotonic_time()
103 def do_set_icon_from_file(self, filename):
104 raise NotImplementedError("override me!")
106 def recalculate_geometry(self, x, y, width, height):
107 log("recalculate_geometry%s guess=%s, tray event locations: %s",
108 (x, y, width, height), self.geometry_guess, len(self.tray_event_locations))
109 if x is None or y is None:
110 return
111 if self.geometry_guess is None:
112 #better than nothing!
113 self.geometry_guess = x, y, width, height
114 if self.tray_event_locations and self.tray_event_locations[-1]==(x,y):
115 #unchanged
116 log("tray event location unchanged")
117 return
118 self.tray_event_locations.append((x, y))
119 #sets of locations that can fit together within (size,size) distance of each other:
120 xs, ys = set(), set()
121 xs.add(x)
122 ys.add(y)
123 #walk though all of them in reverse (and stop when one does not fit):
124 for tx, ty in reversed(self.tray_event_locations):
125 minx = min(xs)
126 miny = min(ys)
127 maxx = max(xs)
128 maxy = max(ys)
129 if (tx<minx and tx<(maxx-width)) or (tx>maxx and tx>(minx+width)):
130 break #cannot fit...
131 if (ty<miny and ty<(maxy-height)) or (ty>maxy and ty>(miny+height)):
132 break #cannot fit...
133 xs.add(tx)
134 ys.add(ty)
135 #now add some padding if needed:
136 minx = min(xs)
137 miny = min(ys)
138 maxx = max(xs)
139 maxy = max(ys)
140 padx = width-(maxx-minx)
141 pady = height-(maxy-miny)
142 assert padx>=0 and pady>=0
143 minx -= padx//2
144 miny -= pady//2
145 oldgeom = self.geometry_guess
146 self.geometry_guess = max(0, minx), max(0, miny), width, height
147 log("recalculate_geometry() geometry guess=%s (old guess=%s)", self.geometry_guess, oldgeom)
148 if self.size_changed_cb and self.geometry_guess!=oldgeom:
149 self.size_changed_cb()