Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/gtk3/gtk3_client_window.py : 52%
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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
8from gi.repository import Gdk, Gtk, Gio, GdkPixbuf
10from xpra.client.gtk_base.gtk_client_window_base import GTKClientWindowBase, HAS_X11_BINDINGS
11from xpra.client.gtk3.window_menu import WindowMenuHelper
12from xpra.gtk_common.gtk_util import scaled_image
13from xpra.scripts.config import TRUE_OPTIONS, FALSE_OPTIONS
14from xpra.util import envbool, typedict
15from xpra.os_util import bytestostr, is_gnome
16from xpra.log import Logger
18paintlog = Logger("paint")
19metalog = Logger("metadata")
20geomlog = Logger("geometry")
23WINDOW_ICON = envbool("XPRA_WINDOW_ICON", True)
24WINDOW_XPRA_MENU = envbool("XPRA_WINDOW_XPRA_MENU", is_gnome())
25WINDOW_MENU = envbool("XPRA_WINDOW_MENU", True)
28"""
29GTK3 version of the ClientWindow class
30"""
31class GTK3ClientWindow(GTKClientWindowBase):
33 def init_window(self, metadata):
34 super().init_window(metadata)
35 self.header_bar_image = None
36 if self.can_use_header_bar(metadata):
37 self.add_header_bar()
39 def _icon_size(self):
40 tb = self.get_titlebar()
41 try:
42 h = tb.get_preferred_size()[-1]-8
43 except Exception:
44 h = 24
45 return min(128, max(h, 24))
47 def set_icon(self, pixbuf):
48 super().set_icon(pixbuf)
49 hbi = self.header_bar_image
50 if hbi and WINDOW_ICON:
51 h = self._icon_size()
52 pixbuf = pixbuf.scale_simple(h, h, GdkPixbuf.InterpType.HYPER)
53 hbi.set_from_pixbuf(pixbuf)
55 def can_use_header_bar(self, metadata):
56 if self.is_OR() or not self.get_decorated():
57 return False
58 hbl = (self.headerbar or "").lower().strip()
59 if hbl in FALSE_OPTIONS:
60 return False
61 if hbl in TRUE_OPTIONS:
62 sc = metadata.dictget("size-constraints")
63 if sc is None:
64 return True
65 tsc = typedict(sc)
66 maxs = tsc.intpair("maximum-size")
67 if maxs:
68 return False
69 mins = tsc.intpair("minimum-size")
70 if mins and mins!=(0, 0):
71 return False
72 if tsc.intpair("increment", (0, 0))!=(0, 0):
73 return False
74 return True
75 if hbl=="force":
76 return True
77 return False
79 def add_header_bar(self):
80 self.menu_helper = WindowMenuHelper(self._client, self)
81 hb = Gtk.HeaderBar()
82 hb.set_has_subtitle(False)
83 hb.set_show_close_button(True)
84 hb.props.title = self.get_title()
85 if WINDOW_MENU:
86 #the icon 'open-menu-symbolic' will be replaced with the window icon
87 #when we receive it
88 icon = Gio.ThemedIcon(name="preferences-system-windows")
89 self.header_bar_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
90 button = Gtk.Button()
91 button.add(self.header_bar_image)
92 button.connect("clicked", self.show_window_menu)
93 hb.pack_start(button)
94 elif WINDOW_ICON:
95 #just the icon, no menu:
96 pixbuf = self._client.get_pixbuf("transparent.png")
97 self.header_bar_image = scaled_image(pixbuf, self._icon_size())
98 hb.pack_start(self.header_bar_image)
99 if WINDOW_XPRA_MENU:
100 icon = Gio.ThemedIcon(name="open-menu-symbolic")
101 image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
102 button = Gtk.Button()
103 button.add(image)
104 button.connect("clicked", self.show_xpra_menu)
105 hb.pack_end(button)
106 self.set_titlebar(hb)
109 def show_xpra_menu(self, *_args):
110 mh = getattr(self._client, "menu_helper", None)
111 if not mh:
112 from xpra.client.gtk3.tray_menu import GTK3TrayMenu
113 mh = GTK3TrayMenu(self._client)
114 mh.popup(0, 0)
116 def show_window_menu(self, *_args):
117 self.menu_helper.build()
118 self.menu_helper.popup(0, 0)
120 def get_backing_class(self):
121 raise NotImplementedError()
124 def xget_u32_property(self, target, name):
125 if HAS_X11_BINDINGS:
126 return GTKClientWindowBase.xget_u32_property(self, target, name)
127 #pure Gdk lookup:
128 try:
129 name_atom = Gdk.Atom.intern(name, False)
130 type_atom = Gdk.Atom.intern("CARDINAL", False)
131 prop = Gdk.property_get(target, name_atom, type_atom, 0, 9999, False)
132 if not prop or len(prop)!=3 or len(prop[2])!=1:
133 return None
134 metalog("xget_u32_property(%s, %s)=%s", target, name, prop[2][0])
135 return prop[2][0]
136 except Exception as e:
137 metalog.error("xget_u32_property error on %s / %s: %s", target, name, e)
139 def get_drawing_area_geometry(self):
140 gdkwindow = self.drawing_area.get_window()
141 if gdkwindow:
142 x, y = gdkwindow.get_origin()[1:]
143 else:
144 x, y = self.get_position()
145 w, h = self.get_size()
146 return (x, y, w, h)
148 def apply_geometry_hints(self, hints):
149 """ we convert the hints as a dict into a gdk.Geometry + gdk.WindowHints """
150 wh = Gdk.WindowHints
151 name_to_hint = {
152 "max_width" : wh.MAX_SIZE,
153 "max_height" : wh.MAX_SIZE,
154 "min_width" : wh.MIN_SIZE,
155 "min_height" : wh.MIN_SIZE,
156 "base_width" : wh.BASE_SIZE,
157 "base_height" : wh.BASE_SIZE,
158 "width_inc" : wh.RESIZE_INC,
159 "height_inc" : wh.RESIZE_INC,
160 "min_aspect_ratio" : wh.ASPECT,
161 "max_aspect_ratio" : wh.ASPECT,
162 }
163 #these fields can be copied directly to the gdk.Geometry as ints:
164 INT_FIELDS= ["min_width", "min_height",
165 "max_width", "max_height",
166 "base_width", "base_height",
167 "width_inc", "height_inc"]
168 ASPECT_FIELDS = {
169 "min_aspect_ratio" : "min_aspect",
170 "max_aspect_ratio" : "max_aspect",
171 }
172 thints = typedict(hints)
173 if self.drawing_area:
174 #apply min size to the drawing_area:
175 #(for CSD mode, ie: headerbar)
176 minw = thints.intget("min_width", 0)
177 minh = thints.intget("min_width", 0)
178 self.drawing_area.set_size_request(minw, minh)
180 geom = Gdk.Geometry()
181 mask = 0
182 for k,v in hints.items():
183 k = bytestostr(k)
184 if k in INT_FIELDS:
185 setattr(geom, k, v)
186 mask |= int(name_to_hint.get(k, 0))
187 elif k in ASPECT_FIELDS:
188 field = ASPECT_FIELDS.get(k)
189 setattr(geom, field, float(v))
190 mask |= int(name_to_hint.get(k, 0))
191 gdk_hints = Gdk.WindowHints(mask)
192 geomlog("apply_geometry_hints(%s) geometry=%s, hints=%s", hints, geom, gdk_hints)
193 self.set_geometry_hints(self.drawing_area, geom, gdk_hints)
195 def can_maximize(self):
196 hints = self.geometry_hints
197 if not hints:
198 return True
199 maxw = hints.intget(b"max_width", 32768)
200 maxh = hints.intget(b"max_height", 32768)
201 if maxw>32000 and maxh>32000:
202 return True
203 geom = self.get_drawing_area_geometry()
204 dw, dh = geom[2], geom[3]
205 return dw<maxw and dh<maxh
207 def draw_widget(self, widget, context):
208 paintlog("draw_widget(%s, %s)", widget, context)
209 if not self.get_mapped():
210 return False
211 backing = self._backing
212 if not backing:
213 return False
214 self.paint_backing_offset_border(backing, context)
215 self.clip_to_backing(backing, context)
216 backing.cairo_draw(context)
217 self.cairo_paint_border(context, None)
218 if not self._client.server_ok():
219 self.paint_spinner(context)
220 return True