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 

7import os.path 

8import cairo 

9 

10import gi 

11gi.require_version("Gdk", "3.0") 

12gi.require_version("Gtk", "3.0") 

13gi.require_version("Pango", "1.0") 

14gi.require_version("GdkPixbuf", "2.0") 

15from gi.repository import GLib, GdkPixbuf, Pango, GObject, Gtk, Gdk #@UnresolvedImport 

16 

17from xpra.util import iround, first_time, envint, envbool 

18from xpra.os_util import strtobytes, WIN32, OSX 

19from xpra.log import Logger 

20 

21log = Logger("gtk", "util") 

22screenlog = Logger("gtk", "screen") 

23alphalog = Logger("gtk", "alpha") 

24 

25SHOW_ALL_VISUALS = False 

26#try to get workarea from GTK: 

27GTK_WORKAREA = envbool("XPRA_GTK_WORKAREA", True) 

28 

29GTK_VERSION_INFO = {} 

30def get_gtk_version_info() -> dict: 

31 #update props given: 

32 global GTK_VERSION_INFO 

33 def av(k, v): 

34 GTK_VERSION_INFO.setdefault(k, {})["version"] = v 

35 def V(k, module, *fields): 

36 for field in fields: 

37 v = getattr(module, field, None) 

38 if v is not None: 

39 av(k, v) 

40 return True 

41 return False 

42 

43 if not GTK_VERSION_INFO: 

44 V("gobject", GObject, "pygobject_version") 

45 

46 #this isn't the actual version, (only shows as "3.0") 

47 #but still better than nothing: 

48 V("gi", gi, "__version__") 

49 V("gtk", Gtk, "_version") 

50 V("gdk", Gdk, "_version") 

51 V("gobject", GObject, "_version") 

52 V("pixbuf", GdkPixbuf, "_version") 

53 

54 V("pixbuf", GdkPixbuf, "PIXBUF_VERSION") 

55 def MAJORMICROMINOR(name, module): 

56 try: 

57 v = tuple(getattr(module, x) for x in ("MAJOR_VERSION", "MICRO_VERSION", "MINOR_VERSION")) 

58 av(name, ".".join(str(x) for x in v)) 

59 except Exception: 

60 pass 

61 MAJORMICROMINOR("gtk", Gtk) 

62 MAJORMICROMINOR("glib", GLib) 

63 

64 av("cairo", cairo.version_info) #pylint: disable=no-member 

65 av("pango", Pango.version_string()) 

66 return GTK_VERSION_INFO.copy() 

67 

68 

69def pixbuf_save_to_memory(pixbuf, fmt="png") -> bytes: 

70 buf = [] 

71 def save_to_memory(data, *_args, **_kwargs): 

72 buf.append(strtobytes(data)) 

73 return True 

74 pixbuf.save_to_callbackv(save_to_memory, None, fmt, [], []) 

75 return b"".join(buf) 

76 

77 

78def GDKWindow(parent=None, width=1, height=1, window_type=Gdk.WindowType.TOPLEVEL, 

79 event_mask=0, wclass=Gdk.WindowWindowClass.INPUT_OUTPUT, title=None, 

80 x=None, y=None, override_redirect=False, visual=None) -> Gdk.Window: 

81 attributes_mask = 0 

82 attributes = Gdk.WindowAttr() 

83 if x is not None: 

84 attributes.x = x 

85 attributes_mask |= Gdk.WindowAttributesType.X 

86 if y is not None: 

87 attributes.y = y 

88 attributes_mask |= Gdk.WindowAttributesType.Y 

89 #attributes.type_hint = Gdk.WindowTypeHint.NORMAL 

90 #attributes_mask |= Gdk.WindowAttributesType.TYPE_HINT 

91 attributes.width = width 

92 attributes.height = height 

93 attributes.window_type = window_type 

94 if title: 

95 attributes.title = title 

96 attributes_mask |= Gdk.WindowAttributesType.TITLE 

97 if visual: 

98 attributes.visual = visual 

99 attributes_mask |= Gdk.WindowAttributesType.VISUAL 

100 #OR: 

101 attributes.override_redirect = override_redirect 

102 attributes_mask |= Gdk.WindowAttributesType.NOREDIR 

103 #events: 

104 attributes.event_mask = event_mask 

105 #wclass: 

106 attributes.wclass = wclass 

107 mask = Gdk.WindowAttributesType(attributes_mask) 

108 return Gdk.Window(parent, attributes, mask) 

109 

110def set_visual(window, alpha : bool=True) -> bool: 

111 screen = window.get_screen() 

112 if alpha: 

113 visual = screen.get_rgba_visual() 

114 else: 

115 visual = screen.get_system_visual() 

116 alphalog("set_visual(%s, %s) screen=%s, visual=%s", window, alpha, screen, visual) 

117 #we can't do alpha on win32 with plain GTK, 

118 #(though we handle it in the opengl backend) 

119 if WIN32 or not first_time("no-rgba"): 

120 l = alphalog 

121 else: 

122 l = alphalog.warn 

123 if alpha and visual is None or (not WIN32 and not screen.is_composited()): 

124 l("Warning: cannot handle window transparency") 

125 if visual is None: 

126 l(" no RGBA visual") 

127 else: 

128 assert not screen.is_composited() 

129 l(" screen is not composited") 

130 return None 

131 alphalog("set_visual(%s, %s) using visual %s", window, alpha, visual) 

132 if visual: 

133 window.set_visual(visual) 

134 return visual 

135 

136 

137def get_pixbuf_from_data(rgb_data, has_alpha : bool, w : int, h : int, rowstride : int) -> GdkPixbuf.Pixbuf: 

138 data = GLib.Bytes(rgb_data) 

139 return GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, 

140 has_alpha, 8, w, h, rowstride) 

141 

142def color_parse(*args) -> Gdk.Color: 

143 v = Gdk.RGBA() 

144 ok = v.parse(*args) 

145 if ok: 

146 return v.to_color() 

147 ok, v = Gdk.Color.parse(*args) 

148 if ok: 

149 return v 

150 return None 

151 

152def get_default_root_window() -> Gdk.Window: 

153 screen = Gdk.Screen.get_default() 

154 if screen is None: 

155 return None 

156 return screen.get_root_window() 

157 

158def get_root_size(): 

159 if OSX: 

160 #the easy way: 

161 root = get_default_root_window() 

162 w, h = root.get_geometry()[2:4] 

163 else: 

164 #GTK3 on win32 triggers this warning: 

165 #"GetClientRect failed: Invalid window handle." 

166 #if we try to use the root window, 

167 #and on Linux with Wayland, we get bogus values... 

168 screen = Gdk.Screen.get_default() 

169 if screen is None: 

170 return 1920, 1024 

171 w = screen.get_width() 

172 h = screen.get_height() 

173 if w<=0 or h<=0 or w>32768 or h>32768: 

174 if first_time("Gtk root window dimensions"): 

175 log.warn("Warning: Gdk returned invalid root window dimensions: %ix%i", w, h) 

176 w, h = 1920, 1080 

177 log.warn(" using %ix%i instead", w, h) 

178 if WIN32: 

179 log.warn(" no access to the display?") 

180 return w, h 

181 

182def get_default_cursor() -> Gdk.Cursor: 

183 display = Gdk.Display.get_default() 

184 return Gdk.Cursor.new_from_name(display, "default") 

185 

186BUTTON_MASK = { 

187 Gdk.ModifierType.BUTTON1_MASK : 1, 

188 Gdk.ModifierType.BUTTON2_MASK : 2, 

189 Gdk.ModifierType.BUTTON3_MASK : 3, 

190 Gdk.ModifierType.BUTTON4_MASK : 4, 

191 Gdk.ModifierType.BUTTON5_MASK : 5, 

192 } 

193 

194em = Gdk.EventMask 

195WINDOW_EVENT_MASK = em.STRUCTURE_MASK | em.KEY_PRESS_MASK | em.KEY_RELEASE_MASK \ 

196 | em.POINTER_MOTION_MASK | em.BUTTON_PRESS_MASK | em.BUTTON_RELEASE_MASK \ 

197 | em.PROPERTY_CHANGE_MASK | em.SCROLL_MASK 

198del em 

199 

200 

201orig_pack_start = Gtk.Box.pack_start 

202def pack_start(self, child, expand=True, fill=True, padding=0): 

203 orig_pack_start(self, child, expand, fill, padding) 

204Gtk.Box.pack_start = pack_start 

205 

206GRAB_STATUS_STRING = { 

207 Gdk.GrabStatus.SUCCESS : "SUCCESS", 

208 Gdk.GrabStatus.ALREADY_GRABBED : "ALREADY_GRABBED", 

209 Gdk.GrabStatus.INVALID_TIME : "INVALID_TIME", 

210 Gdk.GrabStatus.NOT_VIEWABLE : "NOT_VIEWABLE", 

211 Gdk.GrabStatus.FROZEN : "FROZEN", 

212 } 

213 

214VISUAL_NAMES = { 

215 Gdk.VisualType.STATIC_GRAY : "STATIC_GRAY", 

216 Gdk.VisualType.GRAYSCALE : "GRAYSCALE", 

217 Gdk.VisualType.STATIC_COLOR : "STATIC_COLOR", 

218 Gdk.VisualType.PSEUDO_COLOR : "PSEUDO_COLOR", 

219 Gdk.VisualType.TRUE_COLOR : "TRUE_COLOR", 

220 Gdk.VisualType.DIRECT_COLOR : "DIRECT_COLOR", 

221 } 

222 

223BYTE_ORDER_NAMES = { 

224 Gdk.ByteOrder.LSB_FIRST : "LSB", 

225 Gdk.ByteOrder.MSB_FIRST : "MSB", 

226 } 

227 

228 

229def get_screens_info() -> dict: 

230 display = Gdk.Display.get_default() 

231 info = {} 

232 for i in range(display.get_n_screens()): 

233 screen = display.get_screen(i) 

234 info[i] = get_screen_info(display, screen) 

235 return info 

236 

237def get_screen_sizes(xscale=1, yscale=1): 

238 from xpra.platform.gui import get_workarea, get_workareas 

239 def xs(v): 

240 return iround(v/xscale) 

241 def ys(v): 

242 return iround(v/yscale) 

243 def swork(*workarea): 

244 return xs(workarea[0]), ys(workarea[1]), xs(workarea[2]), ys(workarea[3]) 

245 display = Gdk.Display.get_default() 

246 if not display: 

247 return () 

248 MIN_DPI = envint("XPRA_MIN_DPI", 10) 

249 MAX_DPI = envint("XPRA_MIN_DPI", 500) 

250 def dpi(size_pixels, size_mm): 

251 if size_mm==0: 

252 return 0 

253 return iround(size_pixels * 254 / size_mm / 10) 

254 n_screens = display.get_n_screens() 

255 get_n_monitors = getattr(display, "get_n_monitors", None) 

256 if get_n_monitors: 

257 #GTK 3.22: always just one screen 

258 n_monitors = get_n_monitors() 

259 workareas = get_workareas() 

260 if workareas and len(workareas)!=n_monitors: 

261 screenlog(" workareas: %s", workareas) 

262 screenlog(" number of monitors does not match number of workareas!") 

263 workareas = [] 

264 monitors = [] 

265 for j in range(n_monitors): 

266 monitor = display.get_monitor(j) 

267 geom = monitor.get_geometry() 

268 manufacturer, model = monitor.get_manufacturer(), monitor.get_model() 

269 if manufacturer and model: 

270 plug_name = "%s %s" % (manufacturer, model) 

271 elif manufacturer: 

272 plug_name = manufacturer 

273 elif model: 

274 plug_name = model 

275 else: 

276 plug_name = "%i" % j 

277 wmm, hmm = monitor.get_width_mm(), monitor.get_height_mm() 

278 monitor_info = [plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm] 

279 screenlog(" monitor %s: %s", j, monitor) 

280 if GTK_WORKAREA and hasattr(monitor, "get_workarea"): 

281 rect = monitor.get_workarea() 

282 monitor_info += list(swork(rect.x, rect.y, rect.width, rect.height)) 

283 elif workareas: 

284 w = workareas[j] 

285 monitor_info += list(swork(*w)) 

286 monitors.append(tuple(monitor_info)) 

287 screen = display.get_default_screen() 

288 sw, sh = screen.get_width(), screen.get_height() 

289 work_x, work_y, work_width, work_height = swork(0, 0, sw, sh) 

290 workarea = get_workarea() #pylint: disable=assignment-from-none 

291 if workarea: 

292 work_x, work_y, work_width, work_height = swork(*workarea) #pylint: disable=not-an-iterable 

293 screenlog(" workarea=%s", workarea) 

294 wmm = screen.get_width_mm() 

295 hmm = screen.get_height_mm() 

296 xdpi = dpi(sw, wmm) 

297 ydpi = dpi(sh, hmm) 

298 if xdpi<MIN_DPI or xdpi>MAX_DPI or ydpi<MIN_DPI or ydpi>MAX_DPI: 

299 log("ignoring invalid screen size %ix%imm", wmm, hmm) 

300 if os.environ.get("WAYLAND_DISPLAY"): 

301 log(" (wayland display?)") 

302 if n_monitors>0: 

303 wmm = sum(display.get_monitor(i).get_width_mm() for i in range(n_monitors)) 

304 hmm = sum(display.get_monitor(i).get_height_mm() for i in range(n_monitors)) 

305 xdpi = dpi(sw, wmm) 

306 ydpi = dpi(sh, hmm) 

307 if xdpi<MIN_DPI or xdpi>MAX_DPI or ydpi<MIN_DPI or ydpi>MAX_DPI: 

308 #still invalid, generate one from DPI=96 

309 wmm = iround(sw*25.4/96) 

310 hmm = iround(sh*25.4/96) 

311 log(" using %ix%i mm", wmm, hmm) 

312 item = (screen.make_display_name(), xs(sw), ys(sh), 

313 wmm, hmm, 

314 monitors, 

315 work_x, work_y, work_width, work_height) 

316 screenlog(" screen: %s", item) 

317 screen_sizes = [item] 

318 else: 

319 i=0 

320 screen_sizes = [] 

321 #GTK2 or GTK3<3.22: 

322 screenlog("get_screen_sizes(%f, %f) found %s screens", xscale, yscale, n_screens) 

323 while i<n_screens: 

324 screen = display.get_screen(i) 

325 j = 0 

326 monitors = [] 

327 workareas = [] 

328 #native "get_workareas()" is only valid for a single screen (but describes all the monitors) 

329 #and it is only implemented on win32 right now 

330 #other platforms only implement "get_workarea()" instead, which is reported against the screen 

331 n_monitors = screen.get_n_monitors() 

332 screenlog(" screen %s has %s monitors", i, n_monitors) 

333 if n_screens==1: 

334 workareas = get_workareas() 

335 if workareas and len(workareas)!=n_monitors: 

336 screenlog(" workareas: %s", workareas) 

337 screenlog(" number of monitors does not match number of workareas!") 

338 workareas = [] 

339 while j<screen.get_n_monitors(): 

340 geom = screen.get_monitor_geometry(j) 

341 plug_name = "" 

342 if hasattr(screen, "get_monitor_plug_name"): 

343 plug_name = screen.get_monitor_plug_name(j) or "" 

344 wmm = -1 

345 if hasattr(screen, "get_monitor_width_mm"): 

346 wmm = screen.get_monitor_width_mm(j) 

347 hmm = -1 

348 if hasattr(screen, "get_monitor_height_mm"): 

349 hmm = screen.get_monitor_height_mm(j) 

350 monitor = [plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm] 

351 screenlog(" monitor %s: %s", j, monitor) 

352 if workareas: 

353 w = workareas[j] 

354 monitor += list(swork(*w)) 

355 monitors.append(tuple(monitor)) 

356 j += 1 

357 work_x, work_y, work_width, work_height = swork(0, 0, screen.get_width(), screen.get_height()) 

358 workarea = get_workarea() #pylint: disable=assignment-from-none 

359 if workarea: 

360 work_x, work_y, work_width, work_height = swork(*workarea) #pylint: disable=not-an-iterable 

361 screenlog(" workarea=%s", workarea) 

362 item = (screen.make_display_name(), xs(screen.get_width()), ys(screen.get_height()), 

363 screen.get_width_mm(), screen.get_height_mm(), 

364 monitors, 

365 work_x, work_y, work_width, work_height) 

366 screenlog(" screen %s: %s", i, item) 

367 screen_sizes.append(item) 

368 i += 1 

369 return screen_sizes 

370 

371def get_screen_info(display, screen) -> dict: 

372 info = {} 

373 if not WIN32: 

374 try: 

375 w = screen.get_root_window() 

376 if w: 

377 info["root"] = w.get_geometry() 

378 except Exception: 

379 pass 

380 info["name"] = screen.make_display_name() 

381 for x in ("width", "height", "width_mm", "height_mm", "resolution", "primary_monitor"): 

382 fn = getattr(screen, "get_"+x) 

383 try: 

384 info[x] = int(fn()) 

385 except Exception: 

386 pass 

387 info["monitors"] = screen.get_n_monitors() 

388 m_info = info.setdefault("monitor", {}) 

389 for i in range(screen.get_n_monitors()): 

390 m_info[i] = get_monitor_info(display, screen, i) 

391 fo = screen.get_font_options() 

392 #win32 and osx return nothing here... 

393 if fo: 

394 fontoptions = info.setdefault("fontoptions", {}) 

395 fontoptions.update(get_font_info(fo)) 

396 vinfo = info.setdefault("visual", {}) 

397 def visual(name, v): 

398 i = get_visual_info(v) 

399 if i: 

400 vinfo[name] = i 

401 visual("rgba", screen.get_rgba_visual()) 

402 visual("system_visual", screen.get_system_visual()) 

403 if SHOW_ALL_VISUALS: 

404 for i, v in enumerate(screen.list_visuals()): 

405 visual(i, v) 

406 #Gtk.settings 

407 def get_setting(key, gtype): 

408 v = GObject.Value() 

409 v.init(gtype) 

410 if screen.get_setting(key, v): 

411 return v.get_value() 

412 return None 

413 sinfo = info.setdefault("settings", {}) 

414 for x, gtype in { 

415 #NET: 

416 "enable-event-sounds" : GObject.TYPE_INT, 

417 "icon-theme-name" : GObject.TYPE_STRING, 

418 "sound-theme-name" : GObject.TYPE_STRING, 

419 "theme-name" : GObject.TYPE_STRING, 

420 #Xft: 

421 "xft-antialias" : GObject.TYPE_INT, 

422 "xft-dpi" : GObject.TYPE_INT, 

423 "xft-hinting" : GObject.TYPE_INT, 

424 "xft-hintstyle" : GObject.TYPE_STRING, 

425 "xft-rgba" : GObject.TYPE_STRING, 

426 }.items(): 

427 try: 

428 v = get_setting("gtk-"+x, gtype) 

429 except Exception: 

430 log("failed to query screen '%s'", x, exc_info=True) 

431 continue 

432 if v is None: 

433 v = "" 

434 if x.startswith("xft-"): 

435 x = x[4:] 

436 sinfo[x] = v 

437 return info 

438 

439def get_font_info(font_options): 

440 #pylint: disable=no-member 

441 font_info = {} 

442 for x,vdict in { 

443 "antialias" : { 

444 cairo.ANTIALIAS_DEFAULT : "default", 

445 cairo.ANTIALIAS_NONE : "none", 

446 cairo.ANTIALIAS_GRAY : "gray", 

447 cairo.ANTIALIAS_SUBPIXEL : "subpixel", 

448 }, 

449 "hint_metrics" : { 

450 cairo.HINT_METRICS_DEFAULT : "default", 

451 cairo.HINT_METRICS_OFF : "off", 

452 cairo.HINT_METRICS_ON : "on", 

453 }, 

454 "hint_style" : { 

455 cairo.HINT_STYLE_DEFAULT : "default", 

456 cairo.HINT_STYLE_NONE : "none", 

457 cairo.HINT_STYLE_SLIGHT : "slight", 

458 cairo.HINT_STYLE_MEDIUM : "medium", 

459 cairo.HINT_STYLE_FULL : "full", 

460 }, 

461 "subpixel_order": { 

462 cairo.SUBPIXEL_ORDER_DEFAULT : "default", 

463 cairo.SUBPIXEL_ORDER_RGB : "RGB", 

464 cairo.SUBPIXEL_ORDER_BGR : "BGR", 

465 cairo.SUBPIXEL_ORDER_VRGB : "VRGB", 

466 cairo.SUBPIXEL_ORDER_VBGR : "VBGR", 

467 }, 

468 }.items(): 

469 fn = getattr(font_options, "get_"+x) 

470 val = fn() 

471 font_info[x] = vdict.get(val, val) 

472 return font_info 

473 

474def get_visual_info(v): 

475 if not v: 

476 return {} 

477 vinfo = {} 

478 for x, vdict in { 

479 "bits_per_rgb" : {}, 

480 "byte_order" : BYTE_ORDER_NAMES, 

481 "colormap_size" : {}, 

482 "depth" : {}, 

483 "red_pixel_details" : {}, 

484 "green_pixel_details" : {}, 

485 "blue_pixel_details" : {}, 

486 "visual_type" : VISUAL_NAMES, 

487 }.items(): 

488 val = None 

489 try: 

490 #ugly workaround for "visual_type" -> "type" for GTK2... 

491 val = getattr(v, x.replace("visual_", "")) 

492 except AttributeError: 

493 try: 

494 fn = getattr(v, "get_"+x) 

495 except AttributeError: 

496 pass 

497 else: 

498 val = fn() 

499 if val is not None: 

500 vinfo[x] = vdict.get(val, val) 

501 return vinfo 

502 

503def get_monitor_info(_display, screen, i) -> dict: 

504 info = {} 

505 geom = screen.get_monitor_geometry(i) 

506 for x in ("x", "y", "width", "height"): 

507 info[x] = getattr(geom, x) 

508 if hasattr(screen, "get_monitor_plug_name"): 

509 info["plug_name"] = screen.get_monitor_plug_name(i) or "" 

510 for x in ("scale_factor", "width_mm", "height_mm"): 

511 fn = getattr(screen, "get_monitor_"+x, None) 

512 if fn: 

513 info[x] = int(fn(i)) 

514 rectangle = screen.get_monitor_workarea(i) 

515 workarea_info = info.setdefault("workarea", {}) 

516 for x in ("x", "y", "width", "height"): 

517 workarea_info[x] = getattr(rectangle, x) 

518 return info 

519 

520 

521def get_display_info() -> dict: 

522 display = Gdk.Display.get_default() 

523 info = { 

524 "root-size" : get_root_size(), 

525 "screens" : display.get_n_screens(), 

526 "name" : display.get_name(), 

527 "pointer" : display.get_pointer()[-3:-1], 

528 "devices" : len(display.list_devices()), 

529 "default_cursor_size" : display.get_default_cursor_size(), 

530 "maximal_cursor_size" : display.get_maximal_cursor_size(), 

531 "pointer_is_grabbed" : display.pointer_is_grabbed(), 

532 } 

533 if not WIN32: 

534 info["root"] = get_default_root_window().get_geometry() 

535 sinfo = info.setdefault("supports", {}) 

536 for x in ("composite", "cursor_alpha", "cursor_color", "selection_notification", "clipboard_persistence", "shapes"): 

537 f = "supports_"+x 

538 if hasattr(display, f): 

539 fn = getattr(display, f) 

540 sinfo[x] = fn() 

541 info["screens"] = get_screens_info() 

542 dm = display.get_device_manager() 

543 for dt, name in {Gdk.DeviceType.MASTER : "master", 

544 Gdk.DeviceType.SLAVE : "slave", 

545 Gdk.DeviceType.FLOATING: "floating"}.items(): 

546 dinfo = info.setdefault("device", {}) 

547 dtinfo = dinfo.setdefault(name, {}) 

548 devices = dm.list_devices(dt) 

549 for i, d in enumerate(devices): 

550 dtinfo[i] = d.get_name() 

551 return info 

552 

553 

554def scaled_image(pixbuf, icon_size=None) -> Gtk.Image: 

555 if not pixbuf: 

556 return None 

557 if icon_size: 

558 pixbuf = pixbuf.scale_simple(icon_size, icon_size, GdkPixbuf.InterpType.BILINEAR) 

559 return Gtk.Image.new_from_pixbuf(pixbuf) 

560 

561 

562def get_icon_from_file(filename): 

563 if not filename: 

564 log("get_icon_from_file(%s)=None", filename) 

565 return None 

566 try: 

567 if not os.path.exists(filename): 

568 log.warn("Warning: cannot load icon, '%s' does not exist", filename) 

569 return None 

570 with open(filename, mode='rb') as f: 

571 data = f.read() 

572 loader = GdkPixbuf.PixbufLoader() 

573 loader.write(data) 

574 loader.close() 

575 except Exception as e: 

576 log("get_icon_from_file(%s)", filename, exc_info=True) 

577 log.error("Error: failed to load '%s'", filename) 

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

579 return None 

580 pixbuf = loader.get_pixbuf() 

581 return pixbuf 

582 

583 

584def imagebutton(title, icon, tooltip=None, clicked_callback=None, icon_size=32, 

585 default=False, min_size=None, label_color=None, label_font=None) -> Gtk.Button: 

586 button = Gtk.Button(title) 

587 settings = button.get_settings() 

588 settings.set_property('gtk-button-images', True) 

589 if icon: 

590 if icon_size: 

591 icon = scaled_image(icon, icon_size) 

592 button.set_image(icon) 

593 if tooltip: 

594 button.set_tooltip_text(tooltip) 

595 if min_size: 

596 button.set_size_request(min_size, min_size) 

597 if clicked_callback: 

598 button.connect("clicked", clicked_callback) 

599 if default: 

600 button.set_can_default(True) 

601 if label_color or label_font: 

602 try: 

603 alignment = button.get_children()[0] 

604 b_hbox = alignment.get_children()[0] 

605 l = b_hbox.get_children()[1] 

606 except IndexError: 

607 pass 

608 else: 

609 if label_color: 

610 l.modify_fg(Gtk.StateType.NORMAL, label_color) 

611 if label_font: 

612 l.modify_font(label_font) 

613 return button 

614 

615def menuitem(title, image=None, tooltip=None, cb=None) -> Gtk.ImageMenuItem: 

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

617 menu_item = Gtk.ImageMenuItem() 

618 menu_item.set_label(title) 

619 if image: 

620 menu_item.set_image(image) 

621 #override gtk defaults: we *want* icons: 

622 settings = menu_item.get_settings() 

623 settings.set_property('gtk-menu-images', True) 

624 if hasattr(menu_item, "set_always_show_image"): 

625 menu_item.set_always_show_image(True) 

626 if tooltip: 

627 menu_item.set_tooltip_text(tooltip) 

628 if cb: 

629 menu_item.connect('activate', cb) 

630 menu_item.show() 

631 return menu_item 

632 

633 

634def get_icon_pixbuf(icon_name): 

635 if not icon_name: 

636 return None 

637 from xpra.platform.paths import get_icon_filename 

638 icon_filename = get_icon_filename(icon_name) 

639 if icon_filename and os.path.exists(icon_filename): 

640 return GdkPixbuf.Pixbuf.new_from_file(icon_filename) 

641 return None 

642 

643 

644def add_close_accel(window, callback): 

645 accel_groups = [] 

646 def wa(s, cb): 

647 accel_groups.append(add_window_accel(window, s, cb)) 

648 wa('<control>F4', callback) 

649 wa('<Alt>F4', callback) 

650 wa('Escape', callback) 

651 return accel_groups 

652 

653def add_window_accel(window, accel, callback) -> Gtk.AccelGroup: 

654 def connect(ag, *args): 

655 ag.connect(*args) 

656 accel_group = Gtk.AccelGroup() 

657 key, mod = Gtk.accelerator_parse(accel) 

658 connect(accel_group, key, mod, Gtk.AccelFlags.LOCKED, callback) 

659 window.add_accel_group(accel_group) 

660 return accel_group 

661 

662 

663def label(text="", tooltip=None, font=None) -> Gtk.Label: 

664 l = Gtk.Label(text) 

665 if font: 

666 fontdesc = Pango.FontDescription(font) 

667 l.modify_font(fontdesc) 

668 if tooltip: 

669 l.set_tooltip_text(tooltip) 

670 return l 

671 

672 

673class TableBuilder: 

674 

675 def __init__(self, rows=1, columns=2, homogeneous=False, col_spacings=0, row_spacings=0): 

676 self.table = Gtk.Table(rows, columns, homogeneous) 

677 self.table.set_col_spacings(col_spacings) 

678 self.table.set_row_spacings(row_spacings) 

679 self.row = 0 

680 self.widget_xalign = 0.0 

681 

682 def get_table(self): 

683 return self.table 

684 

685 def add_row(self, widget, *widgets, **kwargs): 

686 if widget: 

687 l_al = Gtk.Alignment(xalign=1.0, yalign=0.5, xscale=0.0, yscale=0.0) 

688 l_al.add(widget) 

689 self.attach(l_al, 0) 

690 if widgets: 

691 i = 1 

692 for w in widgets: 

693 if w: 

694 w_al = Gtk.Alignment(xalign=self.widget_xalign, yalign=0.5, xscale=0.0, yscale=0.0) 

695 w_al.add(w) 

696 self.attach(w_al, i, **kwargs) 

697 i += 1 

698 self.inc() 

699 

700 def attach(self, widget, i, count=1, 

701 xoptions=Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.FILL, 

702 xpadding=10, ypadding=0): 

703 self.table.attach(widget, i, i+count, self.row, self.row+1, 

704 xoptions=xoptions, yoptions=yoptions, xpadding=xpadding, ypadding=ypadding) 

705 

706 def inc(self): 

707 self.row += 1 

708 

709 def new_row(self, row_label_str="", value1=None, value2=None, label_tooltip=None, **kwargs): 

710 row_label = label(row_label_str, label_tooltip) 

711 self.add_row(row_label, value1, value2, **kwargs) 

712 

713 

714def choose_files(parent_window, title, action=Gtk.FileChooserAction.OPEN, action_button=Gtk.STOCK_OPEN, callback=None, file_filter=None, multiple=True): 

715 log("choose_files%s", (parent_window, title, action, action_button, callback, file_filter)) 

716 chooser = Gtk.FileChooserDialog(title, 

717 parent=parent_window, action=action, 

718 buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, action_button, Gtk.ResponseType.OK)) 

719 chooser.set_select_multiple(multiple) 

720 chooser.set_default_response(Gtk.ResponseType.OK) 

721 if file_filter: 

722 chooser.add_filter(file_filter) 

723 response = chooser.run() 

724 filenames = chooser.get_filenames() 

725 chooser.hide() 

726 chooser.destroy() 

727 if response!=Gtk.ResponseType.OK: 

728 return None 

729 return filenames 

730 

731def choose_file(parent_window, title, action=Gtk.FileChooserAction.OPEN, action_button=Gtk.STOCK_OPEN, callback=None, file_filter=None): 

732 filenames = choose_files(parent_window, title, action, action_button, callback, file_filter, False) 

733 if not filenames or len(filenames)!=1: 

734 return None 

735 filename = filenames[0] 

736 if callback: 

737 callback(filename) 

738 return filename 

739 

740 

741def main(): 

742 from xpra.platform import program_context 

743 from xpra.log import enable_color 

744 with program_context("GTK-Version-Info", "GTK Version Info"): 

745 enable_color() 

746 print("%s" % get_gtk_version_info()) 

747 get_screen_sizes() 

748 

749 

750if __name__ == "__main__": 

751 main()