1# This file is part of Xpra. 2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com> 3# Copyright (C) 2012-2021 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, 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 __slots__ = ("flags", "functions", "decorations", "input_mode", "status") 79 def __init__(self, data): 80 #some applications use the wrong size (ie: blender uses 16) so pad it: 81 sizeof_long = struct.calcsize(b"@L") 82 pdata = _force_length("_MOTIF_WM_HINTS", data, sizeof_long*5, sizeof_long*4) 83 self.flags, self.functions, self.decorations, self.input_mode, self.status = \ 84 struct.unpack(b"@LLLlL", pdata) 85 log("MotifWMHints(%s)=%s", hexstr(data), self) 86 87 #found in mwmh.h: 88 # "flags": 89 FUNCTIONS_BIT = 0 90 DECORATIONS_BIT = 1 91 INPUT_MODE_BIT = 2 92 STATUS_BIT = 3 93 # "functions": 94 ALL_BIT = 0 95 RESIZE_BIT = 1 96 MOVE_BIT = 2 # like _NET_WM_ACTION_MOVE 97 MINIMIZE_BIT = 3 # like _NET_WM_ACTION_MINIMIZE 98 MAXIMIZE_BIT = 4 # like _NET_WM_ACTION_(FULLSCREEN|MAXIMIZE_(HORZ|VERT)) 99 CLOSE_BIT = 5 # like _NET_WM_ACTION_CLOSE 100 SHADE_BIT = 6 # like _NET_WM_ACTION_SHADE 101 STICK_BIT = 7 # like _NET_WM_ACTION_STICK 102 FULLSCREEN_BIT = 8 # like _NET_WM_ACTION_FULLSCREEN 103 ABOVE_BIT = 9 # like _NET_WM_ACTION_ABOVE 104 BELOW_BIT = 10 # like _NET_WM_ACTION_BELOW 105 MAXIMUS_BIT = 11 # like _NET_WM_ACTION_MAXIMUS_(LEFT|RIGHT|TOP|BOTTOM) 106 # "decorations": 107 ALL_BIT = 0 108 BORDER_BIT = 1 109 RESIZEH_BIT = 2 110 TITLE_BIT = 3 111 MENU_BIT = 4 112 MINIMIZE_BIT = 5 113 MAXIMIZE_BIT = 6 114 #CLOSE_BIT # non-standard close button 115 #RESIZE_BIT # non-standard resize button 116 #SHADE_BIT, # non-standard shade button 117 #STICK_BIT, # non-standard stick button 118 #MAXIMUS_BIT # non-standard maxim 119 # "input": 120 MODELESS = 0 121 PRIMARY_APPLICATION_MODAL = 1 122 SYSTEM_MODAL = 2 123 FULL_APPLICATION_MODAL = 3 124 # "status": 125 TEAROFF_WINDOW = 0 126 127 FLAGS_STR = { 128 FUNCTIONS_BIT : "functions", 129 DECORATIONS_BIT : "decorations", 130 INPUT_MODE_BIT : "input", 131 STATUS_BIT : "status", 132 } 133 FUNCTIONS_STR = { 134 ALL_BIT : "all", 135 RESIZE_BIT : "resize", 136 MOVE_BIT : "move", 137 MINIMIZE_BIT : "minimize", 138 MAXIMIZE_BIT : "maximize", 139 CLOSE_BIT : "close", 140 SHADE_BIT : "shade", 141 STICK_BIT : "stick", 142 FULLSCREEN_BIT : "fullscreen", 143 ABOVE_BIT : "above", 144 BELOW_BIT : "below", 145 MAXIMUS_BIT : "maximus", 146 } 147 DECORATIONS_STR = { 148 ALL_BIT : "all", 149 BORDER_BIT : "border", 150 RESIZEH_BIT : "resizeh", 151 TITLE_BIT : "title", 152 MENU_BIT : "menu", 153 MINIMIZE_BIT : "minimize", 154 MAXIMIZE_BIT : "maximize", 155 } 156 INPUT_STR = { 157 MODELESS : "modeless", 158 PRIMARY_APPLICATION_MODAL : "primary-application-modal", 159 SYSTEM_MODAL : "system-modal", 160 FULL_APPLICATION_MODAL : "full-application-modal", 161 } 162 163 STATUS_STR = { 164 TEAROFF_WINDOW : "tearoff", 165 } 166 167 def bits_to_strs(self, int_val, flag_bit, dict_str): 168 if flag_bit and not self.flags & (2**flag_bit): 169 #the bit is not set, ignore this attribute 170 return () 171 return tuple(v for k,v in dict_str.items() if int_val & (2**k)) 172 def flags_strs(self): 173 return self.bits_to_strs(self.flags, 174 0, 175 MotifWMHints.FLAGS_STR) 176 def functions_strs(self): 177 return self.bits_to_strs(self.functions, 178 MotifWMHints.FUNCTIONS_BIT, 179 MotifWMHints.FUNCTIONS_STR) 180 def decorations_strs(self): 181 return self.bits_to_strs(self.decorations, 182 MotifWMHints.DECORATIONS_BIT, 183 MotifWMHints.DECORATIONS_STR) 184 def input_strs(self): 185 if self.flags & (2**MotifWMHints.INPUT_MODE_BIT): 186 return MotifWMHints.INPUT_STR.get(self.input_mode, "unknown mode: %i" % self.input_mode) 187 return "modeless" 188 def status_strs(self): 189 return self.bits_to_strs(self.input_mode, 190 MotifWMHints.STATUS_BIT, 191 MotifWMHints.STATUS_STR) 192 193 def __str__(self): 194 return "MotifWMHints(%s)" % { 195 "flags" : self.flags_strs(), 196 "functions" : self.functions_strs(), 197 "decorations" : self.decorations_strs(), 198 "input_mode" : self.input_strs(), 199 "status" : self.status_strs(), 200 } 201 202 203def _read_image(stream): 204 try: 205 int_size = struct.calcsize(b"@I") 206 long_size = struct.calcsize(b"@L") 207 header = stream.read(long_size*2) 208 if not header: 209 return None 210 width, height = struct.unpack(b"@LL", header) 211 data = stream.read(width * height * long_size) 212 expected = width * height * long_size 213 if len(data) < expected: 214 log.warn("Warning: corrupt _NET_WM_ICON, execpted %i bytes but got %i", 215 expected, len(data)) 216 return None 217 if int_size!=long_size: 218 #long to ints (CARD32): 219 longs = struct.unpack(b"@"+b"l"*(width*height), data) 220 data = struct.pack(b"@"+b"i"*(width*height), *longs) 221 except Exception: 222 log.warn("Weird corruption in _NET_WM_ICON", exc_info=True) 223 return None 224 return width, height, "BGRA", data 225 226# This returns a list of icons from a _NET_WM_ICON property. 227# each icon is a tuple: 228# (width, height, fmt, data) 229def NetWMIcons(data): 230 icons = [] 231 stream = BytesIO(data) 232 while True: 233 icon = _read_image(stream) 234 if icon is None: 235 break 236 icons.append(icon) 237 if not icons: 238 return None 239 return icons 240 241 242def _to_latin1(v): 243 return v.encode("latin1") 244 245def _from_latin1(v): 246 return v.decode("latin1") 247 248def _to_utf8(v): 249 return v.encode("UTF-8") 250 251def _from_utf8(v): 252 return v.decode("UTF-8") 253 254 255def _from_long(v): 256 return struct.unpack(b"@L", v)[0] 257 258def _to_long(v): 259 return struct.pack(b"@L", v) 260 261 262PROP_TYPES = { 263 # Python type, X type Atom, formatbits, serializer, deserializer, list 264 # terminator 265 "utf8": (str, "UTF8_STRING", 8, _to_utf8, _from_utf8, b"\0"), 266 # In theory, there should be something clever about COMPOUND_TEXT here. I 267 # am not sufficiently clever to deal with COMPOUNT_TEXT. Even knowing 268 # that Xutf8TextPropertyToTextList exists. 269 "latin1": (str, "STRING", 8, _to_latin1, _from_latin1, b"\0"), 270 "state": (int, "WM_STATE", 32, _to_long, _from_long, b""), 271 "u32": (int, "CARDINAL", 32, _to_long, _from_long, b""), 272 "integer": (int, "INTEGER", 32, _to_long, _from_long, b""), 273 "strut": (NetWMStrut, "CARDINAL", 32, 274 unsupported, NetWMStrut, None), 275 "strut-partial": (NetWMStrut, "CARDINAL", 32, 276 unsupported, NetWMStrut, None), 277 "motif-hints": (MotifWMHints, "_MOTIF_WM_HINTS", 32, 278 unsupported, MotifWMHints, None), 279 "icons": (list, "CARDINAL", 32, 280 unsupported, NetWMIcons, None), 281 } 282 283PROP_SIZES = { 284 "icons" : 4*1024*1024, 285 } 286 287 288def prop_encode(etype, value): 289 if isinstance(etype, (list, tuple)): 290 return _prop_encode_list(etype[0], value) 291 return _prop_encode_scalar(etype, value) 292 293def _prop_encode_scalar(etype, value): 294 pytype, atom, formatbits, serialize = PROP_TYPES[etype][:4] 295 assert isinstance(value, pytype), "value for atom %s is not a %s: %s" % (atom, pytype, type(value)) 296 return (atom, formatbits, serialize(value)) 297 298def _prop_encode_list(etype, value): 299 (_, atom, formatbits, _, _, terminator) = PROP_TYPES[etype] 300 value = tuple(value) 301 serialized = tuple(_prop_encode_scalar(etype, v)[2] for v in value) 302 # Strings in X really are null-separated, not null-terminated (ICCCM 303 # 2.7.1, see also note in 4.1.2.5) 304 return (atom, formatbits, terminator.join(x for x in serialized if x is not None)) 305 306 307def prop_decode(etype, data): 308 if isinstance(etype, (list, tuple)): 309 return _prop_decode_list(etype[0], data) 310 return _prop_decode_scalar(etype, data) 311 312def _prop_decode_scalar(etype, data): 313 (pytype, _, _, _, deserialize, _) = PROP_TYPES[etype] 314 value = deserialize(data) 315 assert value is None or isinstance(value, pytype), "expected a %s but value is a %s" % (pytype, type(value)) 316 return value 317 318def _prop_decode_list(etype, data): 319 (_, _, formatbits, _, _, terminator) = PROP_TYPES[etype] 320 if terminator: 321 datums = data.split(terminator) 322 else: 323 datums = [] 324 if formatbits==32: 325 nbytes = struct.calcsize("@L") 326 else: 327 nbytes = formatbits // 8 328 while data: 329 datums.append(data[:nbytes]) 330 data = data[nbytes:] 331 props = (_prop_decode_scalar(etype, datum) for datum in datums) 332 return [x for x in props if x is not None] 333