Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/gtk_common/gtk_util.py : 46%
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.
7import os.path
8import cairo
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
17from xpra.util import iround, first_time, envint, envbool
18from xpra.os_util import strtobytes, WIN32, OSX
19from xpra.log import Logger
21log = Logger("gtk", "util")
22screenlog = Logger("gtk", "screen")
23alphalog = Logger("gtk", "alpha")
25SHOW_ALL_VISUALS = False
26#try to get workarea from GTK:
27GTK_WORKAREA = envbool("XPRA_GTK_WORKAREA", True)
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
43 if not GTK_VERSION_INFO:
44 V("gobject", GObject, "pygobject_version")
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")
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)
64 av("cairo", cairo.version_info) #pylint: disable=no-member
65 av("pango", Pango.version_string())
66 return GTK_VERSION_INFO.copy()
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)
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)
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
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)
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
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()
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
182def get_default_cursor() -> Gdk.Cursor:
183 display = Gdk.Display.get_default()
184 return Gdk.Cursor.new_from_name(display, "default")
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 }
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
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
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 }
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 }
223BYTE_ORDER_NAMES = {
224 Gdk.ByteOrder.LSB_FIRST : "LSB",
225 Gdk.ByteOrder.MSB_FIRST : "MSB",
226 }
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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
673class TableBuilder:
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
682 def get_table(self):
683 return self.table
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()
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)
706 def inc(self):
707 self.row += 1
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)
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
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
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()
750if __name__ == "__main__":
751 main()