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) 2008, 2009 Nathaniel Smith <njs@pobox.com> 

3# Copyright (C) 2012-2019 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 

7""" 

8Functions for converting to and from X11 properties. 

9 prop_encode 

10 prop_decode 

11""" 

12 

13import struct 

14from io import BytesIO 

15 

16from xpra.os_util import hexstr 

17from xpra.x11.bindings.window_bindings import constants #@UnresolvedImport 

18from xpra.log import Logger 

19 

20log = Logger("x11", "window") 

21 

22 

23USPosition = constants["USPosition"] 

24PPosition = constants["PPosition"] 

25PMaxSize = constants["PMaxSize"] 

26PMinSize = constants["PMinSize"] 

27PBaseSize = constants["PBaseSize"] 

28PResizeInc = constants["PResizeInc"] 

29PAspect = constants["PAspect"] 

30PWinGravity = constants["PWinGravity"] 

31XUrgencyHint = constants["XUrgencyHint"] 

32WindowGroupHint = constants["WindowGroupHint"] 

33StateHint = constants["StateHint"] 

34IconicState = constants["IconicState"] 

35InputHint = constants["InputHint"] 

36 

37 

38def unsupported(*_args): 

39 raise Exception("unsupported") 

40 

41def _force_length(name, data, length, noerror_length=None): 

42 if len(data)==length: 

43 return data 

44 if len(data)!=noerror_length: 

45 log.warn("Odd-lengthed property %s: wanted %s bytes, got %s: %r" 

46 % (name, length, len(data), data)) 

47 # Zero-pad data 

48 data += b"\0" * length 

49 return data[:length] 

50 

51 

52class NetWMStrut: 

53 def __init__(self, _disp, data): 

54 # This eats both _NET_WM_STRUT and _NET_WM_STRUT_PARTIAL. If we are 

55 # given a _NET_WM_STRUT instead of a _NET_WM_STRUT_PARTIAL, then it 

56 # will be only length 4 instead of 12, we just don't define the other values 

57 # and let the client deal with it appropriately 

58 if len(data)==16: 

59 self.left, self.right, self.top, self.bottom = struct.unpack(b"@LLLL", data) 

60 else: 

61 data = _force_length("_NET_WM_STRUT or _NET_WM_STRUT_PARTIAL", data, 4 * 12) 

62 ( 

63 self.left, self.right, self.top, self.bottom, 

64 self.left_start_y, self.left_end_y, 

65 self.right_start_y, self.right_end_y, 

66 self.top_start_x, self.top_end_x, 

67 self.bottom_start_x, self.bottom_stop_x, 

68 ) = struct.unpack(b"@" + b"L" * 12, data) 

69 

70 def todict(self): 

71 return self.__dict__ 

72 

73 def __str__(self): 

74 return "NetWMStrut(%s)" % self.todict() 

75 

76 

77class MotifWMHints: 

78 def __init__(self, _disp, data): 

79 #some applications use the wrong size (ie: blender uses 16) so pad it: 

80 sizeof_long = struct.calcsize(b"@L") 

81 pdata = _force_length("_MOTIF_WM_HINTS", data, sizeof_long*5, sizeof_long*4) 

82 self.flags, self.functions, self.decorations, self.input_mode, self.status = \ 

83 struct.unpack(b"@LLLlL", pdata) 

84 log("MotifWMHints(%s)=%s", hexstr(data), self) 

85 

86 #found in mwmh.h: 

87 # "flags": 

88 FUNCTIONS_BIT = 0 

89 DECORATIONS_BIT = 1 

90 INPUT_MODE_BIT = 2 

91 STATUS_BIT = 3 

92 # "functions": 

93 ALL_BIT = 0 

94 RESIZE_BIT = 1 

95 MOVE_BIT = 2 # like _NET_WM_ACTION_MOVE 

96 MINIMIZE_BIT = 3 # like _NET_WM_ACTION_MINIMIZE 

97 MAXIMIZE_BIT = 4 # like _NET_WM_ACTION_(FULLSCREEN|MAXIMIZE_(HORZ|VERT)) 

98 CLOSE_BIT = 5 # like _NET_WM_ACTION_CLOSE 

99 SHADE_BIT = 6 # like _NET_WM_ACTION_SHADE 

100 STICK_BIT = 7 # like _NET_WM_ACTION_STICK 

101 FULLSCREEN_BIT = 8 # like _NET_WM_ACTION_FULLSCREEN 

102 ABOVE_BIT = 9 # like _NET_WM_ACTION_ABOVE 

103 BELOW_BIT = 10 # like _NET_WM_ACTION_BELOW 

104 MAXIMUS_BIT = 11 # like _NET_WM_ACTION_MAXIMUS_(LEFT|RIGHT|TOP|BOTTOM) 

105 # "decorations": 

106 ALL_BIT = 0 

107 BORDER_BIT = 1 

108 RESIZEH_BIT = 2 

109 TITLE_BIT = 3 

110 MENU_BIT = 4 

111 MINIMIZE_BIT = 5 

112 MAXIMIZE_BIT = 6 

113 #CLOSE_BIT # non-standard close button 

114 #RESIZE_BIT # non-standard resize button 

115 #SHADE_BIT, # non-standard shade button 

116 #STICK_BIT, # non-standard stick button 

117 #MAXIMUS_BIT # non-standard maxim 

118 # "input": 

119 MODELESS = 0 

120 PRIMARY_APPLICATION_MODAL = 1 

121 SYSTEM_MODAL = 2 

122 FULL_APPLICATION_MODAL = 3 

123 # "status": 

124 TEAROFF_WINDOW = 0 

125 

126 FLAGS_STR = { 

127 FUNCTIONS_BIT : "functions", 

128 DECORATIONS_BIT : "decorations", 

129 INPUT_MODE_BIT : "input", 

130 STATUS_BIT : "status", 

131 } 

132 FUNCTIONS_STR = { 

133 ALL_BIT : "all", 

134 RESIZE_BIT : "resize", 

135 MOVE_BIT : "move", 

136 MINIMIZE_BIT : "minimize", 

137 MAXIMIZE_BIT : "maximize", 

138 CLOSE_BIT : "close", 

139 SHADE_BIT : "shade", 

140 STICK_BIT : "stick", 

141 FULLSCREEN_BIT : "fullscreen", 

142 ABOVE_BIT : "above", 

143 BELOW_BIT : "below", 

144 MAXIMUS_BIT : "maximus", 

145 } 

146 DECORATIONS_STR = { 

147 ALL_BIT : "all", 

148 BORDER_BIT : "border", 

149 RESIZEH_BIT : "resizeh", 

150 TITLE_BIT : "title", 

151 MENU_BIT : "menu", 

152 MINIMIZE_BIT : "minimize", 

153 MAXIMIZE_BIT : "maximize", 

154 } 

155 INPUT_STR = { 

156 MODELESS : "modeless", 

157 PRIMARY_APPLICATION_MODAL : "primary-application-modal", 

158 SYSTEM_MODAL : "system-modal", 

159 FULL_APPLICATION_MODAL : "full-application-modal", 

160 } 

161 

162 STATUS_STR = { 

163 TEAROFF_WINDOW : "tearoff", 

164 } 

165 

166 def bits_to_strs(self, int_val, flag_bit, dict_str): 

167 if flag_bit and not self.flags & (2**flag_bit): 

168 #the bit is not set, ignore this attribute 

169 return () 

170 return tuple(v for k,v in dict_str.items() if int_val & (2**k)) 

171 def flags_strs(self): 

172 return self.bits_to_strs(self.flags, 

173 0, 

174 MotifWMHints.FLAGS_STR) 

175 def functions_strs(self): 

176 return self.bits_to_strs(self.functions, 

177 MotifWMHints.FUNCTIONS_BIT, 

178 MotifWMHints.FUNCTIONS_STR) 

179 def decorations_strs(self): 

180 return self.bits_to_strs(self.decorations, 

181 MotifWMHints.DECORATIONS_BIT, 

182 MotifWMHints.DECORATIONS_STR) 

183 def input_strs(self): 

184 if self.flags & (2**MotifWMHints.INPUT_MODE_BIT): 

185 return MotifWMHints.INPUT_STR.get(self.input_mode, "unknown mode: %i" % self.input_mode) 

186 return "modeless" 

187 def status_strs(self): 

188 return self.bits_to_strs(self.input_mode, 

189 MotifWMHints.STATUS_BIT, 

190 MotifWMHints.STATUS_STR) 

191 

192 def __str__(self): 

193 return "MotifWMHints(%s)" % { 

194 "flags" : self.flags_strs(), 

195 "functions" : self.functions_strs(), 

196 "decorations" : self.decorations_strs(), 

197 "input_mode" : self.input_strs(), 

198 "status" : self.status_strs(), 

199 } 

200 

201 

202def _read_image(_disp, stream): 

203 try: 

204 int_size = struct.calcsize(b"@I") 

205 long_size = struct.calcsize(b"@L") 

206 header = stream.read(long_size*2) 

207 if not header: 

208 return None 

209 width, height = struct.unpack(b"@LL", header) 

210 data = stream.read(width * height * long_size) 

211 expected = width * height * long_size 

212 if len(data) < expected: 

213 log.warn("Warning: corrupt _NET_WM_ICON, execpted %i bytes but got %i", 

214 expected, len(data)) 

215 return None 

216 if int_size!=long_size: 

217 #long to ints (CARD32): 

218 longs = struct.unpack(b"@"+b"l"*(width*height), data) 

219 data = struct.pack(b"@"+b"i"*(width*height), *longs) 

220 except Exception: 

221 log.warn("Weird corruption in _NET_WM_ICON", exc_info=True) 

222 return None 

223 return width, height, "BGRA", data 

224 

225# This returns a list of icons from a _NET_WM_ICON property. 

226# each icon is a tuple: 

227# (width, height, fmt, data) 

228def NetWMIcons(disp, data): 

229 icons = [] 

230 stream = BytesIO(data) 

231 while True: 

232 icon = _read_image(disp, stream) 

233 if icon is None: 

234 break 

235 icons.append(icon) 

236 if not icons: 

237 return None 

238 return icons 

239 

240 

241def _to_latin1(_disp, v): 

242 return v.encode("latin1") 

243 

244def _from_latin1(_disp, v): 

245 return v.decode("latin1") 

246 

247def _to_utf8(_disp, v): 

248 return v.encode("UTF-8") 

249 

250def _from_utf8(_disp, v): 

251 return v.decode("UTF-8") 

252 

253 

254def _from_long(_disp, v): 

255 return struct.unpack(b"@L", v)[0] 

256 

257def _to_long(_disp, v): 

258 return struct.pack(b"@L", v) 

259 

260 

261PROP_TYPES = { 

262 # Python type, X type Atom, formatbits, serializer, deserializer, list 

263 # terminator 

264 "utf8": (str, "UTF8_STRING", 8, _to_utf8, _from_utf8, b"\0"), 

265 # In theory, there should be something clever about COMPOUND_TEXT here. I 

266 # am not sufficiently clever to deal with COMPOUNT_TEXT. Even knowing 

267 # that Xutf8TextPropertyToTextList exists. 

268 "latin1": (str, "STRING", 8, _to_latin1, _from_latin1, b"\0"), 

269 "state": (int, "WM_STATE", 32, _to_long, _from_long, b""), 

270 "u32": (int, "CARDINAL", 32, _to_long, _from_long, b""), 

271 "integer": (int, "INTEGER", 32, _to_long, _from_long, b""), 

272 "strut": (NetWMStrut, "CARDINAL", 32, 

273 unsupported, NetWMStrut, None), 

274 "strut-partial": (NetWMStrut, "CARDINAL", 32, 

275 unsupported, NetWMStrut, None), 

276 "motif-hints": (MotifWMHints, "_MOTIF_WM_HINTS", 32, 

277 unsupported, MotifWMHints, None), 

278 "icons": (list, "CARDINAL", 32, 

279 unsupported, NetWMIcons, None), 

280 } 

281 

282PROP_SIZES = { 

283 "icons" : 4*1024*1024, 

284 } 

285 

286 

287def prop_encode(disp, etype, value): 

288 if isinstance(etype, (list, tuple)): 

289 return _prop_encode_list(disp, etype[0], value) 

290 return _prop_encode_scalar(disp, etype, value) 

291 

292def _prop_encode_scalar(disp, etype, value): 

293 pytype, atom, formatbits, serialize = PROP_TYPES[etype][:4] 

294 assert isinstance(value, pytype), "value for atom %s is not a %s: %s" % (atom, pytype, type(value)) 

295 return (atom, formatbits, serialize(disp, value)) 

296 

297def _prop_encode_list(disp, etype, value): 

298 (_, atom, formatbits, _, _, terminator) = PROP_TYPES[etype] 

299 value = tuple(value) 

300 serialized = tuple(_prop_encode_scalar(disp, etype, v)[2] for v in value) 

301 # Strings in X really are null-separated, not null-terminated (ICCCM 

302 # 2.7.1, see also note in 4.1.2.5) 

303 return (atom, formatbits, terminator.join(x for x in serialized if x is not None)) 

304 

305 

306def prop_decode(disp, etype, data): 

307 if isinstance(etype, (list, tuple)): 

308 return _prop_decode_list(disp, etype[0], data) 

309 return _prop_decode_scalar(disp, etype, data) 

310 

311def _prop_decode_scalar(disp, etype, data): 

312 (pytype, _, _, _, deserialize, _) = PROP_TYPES[etype] 

313 value = deserialize(disp, data) 

314 assert value is None or isinstance(value, pytype), "expected a %s but value is a %s" % (pytype, type(value)) 

315 return value 

316 

317def _prop_decode_list(disp, etype, data): 

318 (_, _, formatbits, _, _, terminator) = PROP_TYPES[etype] 

319 if terminator: 

320 datums = data.split(terminator) 

321 else: 

322 datums = [] 

323 if formatbits==32: 

324 nbytes = struct.calcsize("@L") 

325 else: 

326 nbytes = formatbits // 8 

327 while data: 

328 datums.append(data[:nbytes]) 

329 data = data[nbytes:] 

330 props = (_prop_decode_scalar(disp, etype, datum) for datum in datums) 

331 return [x for x in props if x is not None]