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 

7# A tray implemented using Gtk.StatusIcon 

8 

9import os 

10from time import time 

11from gi.repository import Gtk, Gdk, GdkPixbuf 

12 

13from xpra.os_util import WIN32, OSX, POSIX, monotonic_time 

14from xpra.util import envbool 

15from xpra.client.tray_base import TrayBase, log 

16from xpra.gtk_common.gtk_util import ( 

17 get_icon_from_file, get_pixbuf_from_data, get_default_root_window, 

18 ) 

19 

20ORIENTATION = { 

21 Gtk.Orientation.HORIZONTAL : "HORIZONTAL", 

22 Gtk.Orientation.VERTICAL : "VERTICAL", 

23 } 

24 

25GUESS_GEOMETRY = WIN32 or OSX 

26GUESS_GEOMETRY = envbool("XPRA_GUESS_ICON_GEOMETRY", GUESS_GEOMETRY) 

27log("tray GUESS_GEOMETRY=%s", GUESS_GEOMETRY) 

28SAVE = envbool("XPRA_SAVE_SYSTRAY", False) 

29 

30 

31class GTKStatusIconTray(TrayBase): 

32 

33 def __init__(self, *args, **kwargs): 

34 super().__init__(*args, **kwargs) 

35 self.tray_widget = Gtk.StatusIcon() 

36 self.tray_widget.set_tooltip_text(self.tooltip or "Xpra") 

37 self.tray_widget.connect('activate', self.activate_menu) 

38 self.tray_widget.connect('popup-menu', self.popup_menu) 

39 if self.size_changed_cb: 

40 self.tray_widget.connect('size-changed', self.size_changed_cb) 

41 if self.default_icon_filename: 

42 self.set_icon() 

43 self.tray_widget.set_visible(True) 

44 

45 def may_guess(self): 

46 log("may_guess() GUESS_GEOMETRY=%s, current guess=%s", GUESS_GEOMETRY, self.geometry_guess) 

47 if GUESS_GEOMETRY: 

48 x, y = get_default_root_window().get_pointer()[-3:-1] 

49 w, h = self.get_size() 

50 self.recalculate_geometry(x, y, w, h) 

51 

52 def activate_menu(self, widget): 

53 modifiers_mask = get_default_root_window().get_pointer()[-1] 

54 log("activate_menu(%s) modifiers_mask=%s", widget, modifiers_mask) 

55 if (modifiers_mask & Gdk.ModifierType.SHIFT_MASK) ^ OSX: 

56 self.handle_click(3) 

57 else: 

58 self.handle_click(1) 

59 

60 def popup_menu(self, widget, button, event_time, *args): 

61 modifiers_mask = get_default_root_window().get_pointer()[-1] 

62 log("popup_menu(%s, %s, %s, %s) modifiers_mask=%s", widget, button, event_time, args, modifiers_mask) 

63 if (modifiers_mask & Gdk.ModifierType.SHIFT_MASK) ^ OSX: 

64 self.handle_click(1) 

65 else: 

66 self.handle_click(3) 

67 

68 def handle_click(self, button, event_time=0): 

69 log("handle_click(%i, %i)", button, event_time) 

70 self.may_guess() 

71 if self.click_cb: 

72 self.click_cb(button, 1, event_time) 

73 self.click_cb(button, 0, event_time) 

74 

75 

76 def hide(self): 

77 log("%s.set_visible(False)", self.tray_widget) 

78 if self.tray_widget: 

79 self.tray_widget.set_visible(False) 

80 

81 def show(self): 

82 log("%s.set_visible(True)", self.tray_widget) 

83 if self.tray_widget: 

84 self.tray_widget.set_visible(True) 

85 

86 

87 def get_screen(self): 

88 if not self.tray_widget: 

89 return -1 

90 ag = self.tray_widget.get_geometry() 

91 if ag is None: 

92 return -1 

93 screen = ag[-3] 

94 if not screen: 

95 return -1 

96 return screen.get_number() 

97 

98 def get_orientation(self): 

99 if not self.tray_widget: 

100 return None 

101 ag = self.tray_widget.get_geometry() 

102 if ag is None: 

103 return None 

104 gtk_orientation = ag[-1] 

105 return ORIENTATION.get(gtk_orientation) 

106 

107 def get_geometry(self): 

108 assert self.tray_widget 

109 #on X11, if we don't have an xid, don't bother querying its geometry, 

110 #as this would trigger some ugly GTK warnings we can do nothing about 

111 if POSIX and os.environ.get("DISPLAY") and self.tray_widget.get_x11_window_id()==0: 

112 ag = None 

113 else: 

114 ag = self.tray_widget.get_geometry() 

115 log("GTKStatusIconTray.get_geometry() %s.get_geometry()=%s", self.tray_widget, ag) 

116 if ag is None: 

117 if not self.geometry_guess: 

118 self.may_guess() 

119 #probably win32 or OSX, gnome-shell or KDE5.. 

120 log("GTKStatusIconTray.get_geometry() no geometry value available, returning guess: %s", 

121 self.geometry_guess) 

122 return self.geometry_guess or (0, 0, 0, 0) 

123 #gtk3 adds an extra argument.. at the beginning 

124 #so we index from the end of the array: 

125 geom = ag[-2] 

126 x, y, w, h = geom.x, geom.y, geom.width, geom.height 

127 log("GTKStatusIconTray.get_geometry() geometry area rectangle=%s", (x, y, w, h)) 

128 if x==0 and y==0 and w==0 and h==0 and self.geometry_guess: 

129 return self.geometry_guess 

130 if x==0 and y==0 and w==200 and h==200: 

131 #this isn't right, take a better guess, at least for the size: 

132 w = 48 

133 h = 48 

134 return x, y, w, h 

135 

136 def get_size(self): 

137 s = max(8, min(256, self.tray_widget.get_size())) 

138 return [s, s] 

139 

140 

141 def set_tooltip(self, tooltip=None): 

142 if self.tray_widget: 

143 self.tray_widget.set_tooltip_text(tooltip or "Xpra") 

144 

145 def set_blinking(self, on): 

146 if self.tray_widget and hasattr(self.tray_widget, "set_blinking"): 

147 self.tray_widget.set_blinking(on) 

148 

149 

150 def set_icon_from_data(self, pixels, has_alpha, w, h, rowstride, _options=None): 

151 tray_icon = get_pixbuf_from_data(pixels, has_alpha, w, h, rowstride) 

152 self.set_icon_from_pixbuf(tray_icon) 

153 

154 def do_set_icon_from_file(self, filename): 

155 tray_icon = get_icon_from_file(filename) 

156 self.set_icon_from_pixbuf(tray_icon) 

157 

158 def set_icon_from_pixbuf(self, tray_icon): 

159 if not tray_icon or not self.tray_widget: 

160 return 

161 tw, th = self.get_geometry()[2:4] 

162 if (tw<=2 or th<=2) or (tw==200 and th==200): 

163 log("bogus tray icon size: %ix%i", tw, th) 

164 tw = th = 48 

165 w = tray_icon.get_width() 

166 h = tray_icon.get_height() 

167 log("set_icon_from_pixbuf(%s) geometry=%s, icon size=%s", tray_icon, self.get_geometry(), (w, h)) 

168 if tw!=w or th!=h: 

169 tray_icon = tray_icon.scale_simple(tw, th, GdkPixbuf.InterpType.HYPER) 

170 log("tray icon scaled to %ix%i", tw, th) 

171 if SAVE: 

172 filename = "./statusicon-%s.png" % time() 

173 tray_icon.save(filename, "png") 

174 log.info("statusicon tray saved to %s", filename) 

175 self.tray_widget.set_from_pixbuf(tray_icon) 

176 self.icon_timestamp = monotonic_time() 

177 

178 

179def main(): 

180 log.enable_debug() 

181 from gi.repository import GLib 

182 log.enable_debug() 

183 s = GTKStatusIconTray(None, None, None, "test", "xpra.png", None, None, None, Gtk.main_quit) 

184 GLib.timeout_add(1000*2, s.set_blinking, True) 

185 GLib.timeout_add(1000*5, s.set_blinking, False) 

186 GLib.timeout_add(1000*30, Gtk.main_quit) 

187 Gtk.main() 

188 

189 

190if __name__ == "__main__": 

191 main()