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# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2011-2020 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 gi.repository import Gtk 

8 

9from xpra.util import envbool 

10from xpra.os_util import OSX 

11from xpra.gtk_common.gtk_util import menuitem 

12from xpra.gtk_common.about import about, close_about 

13from xpra.platform.gui import get_icon_size 

14from xpra.log import Logger 

15 

16log = Logger("menu") 

17 

18MENU_ICONS = envbool("XPRA_MENU_ICONS", True) 

19HIDE_DISABLED_MENU_ENTRIES = OSX 

20 

21 

22def ll(m): 

23 try: 

24 return "%s:%s" % (type(m), m.get_label()) 

25 except AttributeError: 

26 return str(m) 

27 

28def set_sensitive(widget, sensitive): 

29 if OSX: 

30 if sensitive: 

31 widget.show() 

32 else: 

33 widget.hide() 

34 widget.set_sensitive(sensitive) 

35 

36 

37#utility method to ensure there is always only one CheckMenuItem 

38#selected in a submenu: 

39def ensure_item_selected(submenu, item, recurse=True): 

40 if not isinstance(item, Gtk.CheckMenuItem): 

41 return None 

42 if item.get_active(): 

43 #deactivate all except this one 

44 def deactivate(items, skip=None): 

45 for x in items: 

46 if x==skip: 

47 continue 

48 if isinstance(x, Gtk.MenuItem): 

49 submenu = x.get_submenu() 

50 if submenu and recurse: 

51 deactivate(submenu.get_children(), skip) 

52 if isinstance(x, Gtk.CheckMenuItem): 

53 if x!=item and x.get_active(): 

54 x.set_active(False) 

55 deactivate(submenu.get_children(), item) 

56 return item 

57 #ensure there is at least one other active item 

58 def get_active_item(items): 

59 for x in items: 

60 if isinstance(x, Gtk.MenuItem): 

61 submenu = x.get_submenu() 

62 if submenu: 

63 a = get_active_item(submenu.get_children()) 

64 if a: 

65 return a 

66 if isinstance(x, Gtk.CheckMenuItem): 

67 if x.get_active(): 

68 return x 

69 return None 

70 active = get_active_item(submenu.get_children()) 

71 if active: 

72 return active 

73 #if not then keep this one active: 

74 item.set_active(True) 

75 return item 

76 

77 

78 

79def make_min_auto_menu(title, min_options, options, 

80 get_current_min_value, 

81 get_current_value, 

82 set_min_value_cb, 

83 set_value_cb): 

84 #note: we must keep references to the parameters on the submenu 

85 #(closures and gtk callbacks don't mix so well!) 

86 submenu = Gtk.Menu() 

87 submenu.get_current_min_value = get_current_min_value 

88 submenu.get_current_value = get_current_value 

89 submenu.set_min_value_cb = set_min_value_cb 

90 submenu.set_value_cb = set_value_cb 

91 fstitle = Gtk.MenuItem() 

92 fstitle.set_label("Fixed %s:" % title) 

93 set_sensitive(fstitle, False) 

94 submenu.append(fstitle) 

95 submenu.menu_items = {} 

96 submenu.min_menu_items = {} 

97 def populate_menu(options, value, set_fn): 

98 found_match = False 

99 items = {} 

100 if value and value>0 and value not in options: 

101 options[value] = "%s%%" % value 

102 for s in sorted(options.keys()): 

103 t = options.get(s) 

104 qi = Gtk.CheckMenuItem(label=t) 

105 qi.set_draw_as_radio(True) 

106 candidate_match = s>=max(0, value) 

107 qi.set_active(not found_match and candidate_match) 

108 found_match |= candidate_match 

109 qi.connect('activate', set_fn, submenu) 

110 if s>0: 

111 qi.set_tooltip_text("%s%%" % s) 

112 submenu.append(qi) 

113 items[s] = qi 

114 return items 

115 def set_value(item, ss): 

116 if not item.get_active(): 

117 return 

118 #user select a new value from the menu: 

119 s = -1 

120 for ts,tl in options.items(): 

121 if tl==item.get_label(): 

122 s = ts 

123 break 

124 if s>=0 and s!=ss.get_current_value(): 

125 log("setting %s to %s", title, s) 

126 ss.set_value_cb(s) 

127 #deselect other items: 

128 for x in ss.menu_items.values(): 

129 if x!=item: 

130 x.set_active(False) 

131 #min is only relevant in auto-mode: 

132 if s!=0: 

133 for v,x in ss.min_menu_items.items(): 

134 x.set_active(v==0) 

135 submenu.menu_items.update(populate_menu(options, get_current_value(), set_value)) 

136 submenu.append(Gtk.SeparatorMenuItem()) 

137 mstitle = Gtk.MenuItem() 

138 mstitle.set_label("Minimum %s:" % title) 

139 set_sensitive(mstitle, False) 

140 submenu.append(mstitle) 

141 def set_min_value(item, ss): 

142 if not item.get_active(): 

143 return 

144 #user selected a new min-value from the menu: 

145 s = -1 

146 for ts,tl in min_options.items(): 

147 if tl==item.get_label(): 

148 s = ts 

149 break 

150 if s>=0 and s!=ss.get_current_min_value(): 

151 log("setting min-%s to %s", title, s) 

152 ss.set_min_value_cb(s) 

153 #deselect other min items: 

154 for x in ss.min_menu_items.values(): 

155 if x!=item: 

156 x.set_active(False) 

157 #min requires auto-mode: 

158 for x in ss.menu_items.values(): 

159 if x.get_label()=="Auto": 

160 if not x.get_active(): 

161 x.activate() 

162 else: 

163 x.set_active(False) 

164 mv = -1 

165 if get_current_value()<=0: 

166 mv = get_current_min_value() 

167 submenu.min_menu_items.update(populate_menu(min_options, mv, set_min_value)) 

168 submenu.show_all() 

169 return submenu 

170 

171def make_encodingsmenu(get_current_encoding, set_encoding, encodings, server_encodings): 

172 encodings_submenu = Gtk.Menu() 

173 populate_encodingsmenu(encodings_submenu, get_current_encoding, set_encoding, encodings, server_encodings) 

174 return encodings_submenu 

175 

176def populate_encodingsmenu(encodings_submenu, get_current_encoding, set_encoding, encodings, server_encodings): 

177 from xpra.codecs.loader import get_encoding_help, get_encoding_name 

178 encodings_submenu.get_current_encoding = get_current_encoding 

179 encodings_submenu.set_encoding = set_encoding 

180 encodings_submenu.encodings = encodings 

181 encodings_submenu.server_encodings = server_encodings 

182 encodings_submenu.index_to_encoding = {} 

183 encodings_submenu.encoding_to_index = {} 

184 NAME_TO_ENCODING = {} 

185 for i, encoding in enumerate(encodings): 

186 name = get_encoding_name(encoding) 

187 descr = get_encoding_help(encoding) 

188 NAME_TO_ENCODING[name] = encoding 

189 encoding_item = Gtk.CheckMenuItem(label=name) 

190 if descr: 

191 if encoding not in server_encodings: 

192 descr += "\n(not available on this server)" 

193 encoding_item.set_tooltip_text(descr) 

194 def encoding_changed(item): 

195 ensure_item_selected(encodings_submenu, item) 

196 enc = NAME_TO_ENCODING.get(item.get_label()) 

197 log("encoding_changed(%s) enc=%s, current=%s", item, enc, encodings_submenu.get_current_encoding()) 

198 if enc is not None and encodings_submenu.get_current_encoding()!=enc: 

199 encodings_submenu.set_encoding(enc) 

200 log("populate_encodingsmenu(..) encoding=%s, current=%s, active=%s", 

201 encoding, get_current_encoding(), encoding==get_current_encoding()) 

202 encoding_item.set_active(encoding==get_current_encoding()) 

203 sensitive = encoding in server_encodings 

204 if not sensitive and HIDE_DISABLED_MENU_ENTRIES: 

205 continue 

206 set_sensitive(encoding_item, encoding in server_encodings) 

207 encoding_item.set_draw_as_radio(True) 

208 encoding_item.connect("toggled", encoding_changed) 

209 encodings_submenu.append(encoding_item) 

210 encodings_submenu.index_to_encoding[i] = encoding 

211 encodings_submenu.encoding_to_index[encoding] = i 

212 encodings_submenu.show_all() 

213 

214 

215class MenuHelper: 

216 

217 def __init__(self, client): 

218 self.client = client 

219 self.menu = None 

220 self.menu_shown = False 

221 self.menu_icon_size = get_icon_size() 

222 

223 def build(self): 

224 if self.menu is None: 

225 try: 

226 self.menu = self.setup_menu() 

227 except Exception as e: 

228 log("build()", exc_info=True) 

229 log.error("Error: failed to setup menu") 

230 log.error(" %s", e) 

231 return self.menu 

232 

233 def show_shortcuts(self, *args): 

234 self.client.show_shorcuts(*args) 

235 

236 def show_session_info(self, *args): 

237 self.client.show_session_info(*args) 

238 

239 def show_bug_report(self, *args): 

240 self.client.show_bug_report(*args) 

241 

242 

243 def get_image(self, icon_name, size=None): 

244 return self.client.get_image(icon_name, size) 

245 

246 def setup_menu(self): 

247 raise NotImplementedError() 

248 

249 def cleanup(self): 

250 self.close_menu() 

251 close_about() 

252 

253 def close_menu(self, *_args): 

254 if self.menu_shown: 

255 self.menu.popdown() 

256 self.menu_shown = False 

257 

258 def menu_deactivated(self, *_args): 

259 self.menu_shown = False 

260 

261 def activate(self, button=1, time=0): 

262 log("activate(%s, %s)", button, time) 

263 self.show_menu(button, time) 

264 

265 def popup(self, button, time): 

266 log("popup(%s, %s)", button, time) 

267 self.show_menu(button, time) 

268 

269 def show_menu(self, button, time): 

270 self.close_menu() 

271 self.menu.popup(None, None, None, None, button, time) 

272 self.menu_shown = True 

273 

274 

275 def handshake_menuitem(self, *args, **kwargs): 

276 """ Same as menuitem() but this one will be disabled until we complete the server handshake """ 

277 mi = self.menuitem(*args, **kwargs) 

278 set_sensitive(mi, False) 

279 def enable_menuitem(*_args): 

280 set_sensitive(mi, True) 

281 self.client.after_handshake(enable_menuitem) 

282 return mi 

283 

284 

285 def make_menu(self): 

286 return Gtk.Menu() 

287 

288 def menuitem(self, title, icon_name=None, tooltip=None, cb=None, **kwargs): 

289 """ Utility method for easily creating an ImageMenuItem """ 

290 image = None 

291 if MENU_ICONS: 

292 image = kwargs.get("image") 

293 if icon_name and not image: 

294 icon_size = self.menu_icon_size or get_icon_size() 

295 image = self.get_image(icon_name, icon_size) 

296 return menuitem(title, image, tooltip, cb) 

297 

298 def checkitem(self, title, cb=None, active=False): 

299 """ Utility method for easily creating a CheckMenuItem """ 

300 check_item = Gtk.CheckMenuItem(label=title) 

301 check_item.set_active(active) 

302 if cb: 

303 check_item.connect("toggled", cb) 

304 check_item.show() 

305 return check_item 

306 

307 

308 def make_aboutmenuitem(self): 

309 return self.menuitem("About Xpra", "xpra.png", None, about) 

310 

311 def make_updatecheckmenuitem(self): 

312 def show_update_window(*_args): 

313 from xpra.client.gtk_base.update_status import getUpdateStatusWindow 

314 w = getUpdateStatusWindow() 

315 w.show() 

316 w.check() 

317 return self.menuitem("Check for updates", "update.png", None, show_update_window) 

318 

319 

320 def make_qrmenuitem(self): 

321 from xpra.net.qrcode import show_qr, get_qrencode_fn 

322 def show(*_args): 

323 uri = self.client.display_desc.get("display_name") 

324 show_qr(uri) 

325 self.qr_menuitem = self.menuitem("Show QR connection string", "qr.png", None, show) 

326 qrencode_fn = get_qrencode_fn() 

327 log("make_qrmenuitem() qrencode_fn=%s", qrencode_fn) 

328 if qrencode_fn: 

329 def with_connection(*_args): 

330 uri = self.client.display_desc.get("display_name") 

331 if not uri or not any(uri.startswith(proto) for proto in ("tcp:", "ws:", "wss:")): 

332 set_sensitive(self.qr_menuitem, False) 

333 self.qr_menuitem.set_tooltip_text("server uri is not shareable") 

334 self.client.after_handshake(with_connection) 

335 else: 

336 set_sensitive(self.qr_menuitem, False) 

337 self.qr_menuitem.set_tooltip_text("qrencode library is missing") 

338 return self.qr_menuitem 

339 

340 def make_sessioninfomenuitem(self): 

341 def show_session_info_cb(*_args): 

342 #we define a generic callback to remove the arguments 

343 #(which contain the menu widget and are of no interest to the 'show_session_info' function) 

344 self.show_session_info() 

345 sessioninfomenuitem = self.handshake_menuitem("Session Info", "statistics.png", None, show_session_info_cb) 

346 return sessioninfomenuitem 

347 

348 def make_bugreportmenuitem(self): 

349 def show_bug_report_cb(*_args): 

350 self.show_bug_report() 

351 return self.menuitem("Bug Report", "bugs.png", None, show_bug_report_cb) 

352 

353 def make_closemenuitem(self): 

354 return self.menuitem("Close Menu", "close.png", None, self.close_menu)