Hide keyboard shortcuts

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. 

6 

7from collections import deque 

8 

9from xpra.platform.paths import get_icon_filename 

10from xpra.log import Logger 

11from xpra.os_util import monotonic_time 

12 

13log = Logger("tray") 

14 

15 

16class TrayBase: 

17 """ 

18 Utility superclass for all tray implementations 

19 """ 

20 

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 

38 

39 def __repr__(self): 

40 return "Tray(%i:%s)" % (self.app_id, self.tooltip) 

41 

42 def cleanup(self): 

43 if self.tray_widget: 

44 self.hide() 

45 self.tray_widget = None 

46 

47 def ready(self): 

48 pass 

49 

50 def show(self): 

51 raise NotImplementedError("override me!") 

52 

53 def hide(self): 

54 raise NotImplementedError("override me!") 

55 

56 def get_screen(self): 

57 return -1 

58 

59 def get_orientation(self): 

60 return None #assume "HORIZONTAL" 

61 

62 def get_geometry(self): 

63 raise NotImplementedError("override me!") 

64 

65 def get_size(self): 

66 g = self.get_geometry() 

67 if g is None: 

68 return None 

69 return g[2:4] 

70 

71 def set_tooltip(self, tooltip=None): 

72 self.tooltip = tooltip 

73 raise NotImplementedError("override me!") 

74 

75 def set_blinking(self, on): 

76 raise NotImplementedError("override me!") 

77 

78 

79 def set_icon_from_data(self, pixels, has_alpha, w, h, rowstride, options=None): 

80 raise NotImplementedError("override me!") 

81 

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 

88 

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) 

95 

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() 

102 

103 def do_set_icon_from_file(self, filename): 

104 raise NotImplementedError("override me!") 

105 

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()