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) 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. 

7 

8from gi.repository import Gdk, Gtk, Gio, GdkPixbuf 

9 

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 

17 

18paintlog = Logger("paint") 

19metalog = Logger("metadata") 

20geomlog = Logger("geometry") 

21 

22 

23WINDOW_ICON = envbool("XPRA_WINDOW_ICON", True) 

24WINDOW_XPRA_MENU = envbool("XPRA_WINDOW_XPRA_MENU", is_gnome()) 

25WINDOW_MENU = envbool("XPRA_WINDOW_MENU", True) 

26 

27 

28""" 

29GTK3 version of the ClientWindow class 

30""" 

31class GTK3ClientWindow(GTKClientWindowBase): 

32 

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() 

38 

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)) 

46 

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) 

54 

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 

78 

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) 

107 

108 

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) 

115 

116 def show_window_menu(self, *_args): 

117 self.menu_helper.build() 

118 self.menu_helper.popup(0, 0) 

119 

120 def get_backing_class(self): 

121 raise NotImplementedError() 

122 

123 

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) 

138 

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) 

147 

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) 

179 

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) 

194 

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 

206 

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