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

6 

7from gi.repository import GObject 

8 

9from xpra.util import WORKSPACE_UNSET, WORKSPACE_ALL 

10from xpra.x11.models.core import CoreX11WindowModel, xswallow, Above, RESTACKING_STR 

11from xpra.x11.bindings.window_bindings import X11WindowBindings, constants #@UnresolvedImport 

12from xpra.server.window.content_guesser import guess_content_type, get_content_type_properties 

13from xpra.x11.gtk_x11.gdk_bindings import get_pywindow, get_pyatom #@UnresolvedImport 

14from xpra.x11.gtk_x11.prop import prop_set, prop_get, prop_del 

15from xpra.log import Logger 

16 

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

18workspacelog = Logger("x11", "window", "workspace") 

19metalog = Logger("x11", "window", "metadata") 

20 

21 

22dbus_helper = None 

23query_actions = None 

24 

25 

26X11Window = X11WindowBindings() 

27 

28#_NET_WM_STATE: 

29_NET_WM_STATE_REMOVE = 0 

30_NET_WM_STATE_ADD = 1 

31_NET_WM_STATE_TOGGLE = 2 

32STATE_STRING = { 

33 _NET_WM_STATE_REMOVE : "REMOVE", 

34 _NET_WM_STATE_ADD : "ADD", 

35 _NET_WM_STATE_TOGGLE : "TOGGLE", 

36 } 

37IconicState = constants["IconicState"] 

38NormalState = constants["NormalState"] 

39ICONIC_STATE_STRING = { 

40 IconicState : "Iconic", 

41 NormalState : "Normal", 

42 } 

43 

44#add user friendly workspace logging: 

45WORKSPACE_STR = { 

46 WORKSPACE_UNSET : "UNSET", 

47 WORKSPACE_ALL : "ALL", 

48 } 

49def workspacestr(w): 

50 return WORKSPACE_STR.get(w, w) 

51 

52 

53class BaseWindowModel(CoreX11WindowModel): 

54 """ 

55 Extends CoreX11WindowModel to add properties 

56 that we find on real X11 windows (as opposed to trays). 

57 Also wraps access to _NET_WM_STATE using simpler gobject booleans. 

58 """ 

59 __common_properties__ = CoreX11WindowModel.__common_properties__.copy() 

60 __common_properties__.update({ 

61 #from WM_TRANSIENT_FOR 

62 "transient-for": (GObject.TYPE_PYOBJECT, 

63 "Transient for (or None)", "", 

64 GObject.ParamFlags.READABLE), 

65 #from _NET_WM_WINDOW_OPACITY 

66 "opacity": (GObject.TYPE_INT64, 

67 "Opacity", "", 

68 -1, 0xffffffff, -1, 

69 GObject.ParamFlags.READABLE), 

70 #from WM_HINTS.window_group 

71 "group-leader": (GObject.TYPE_PYOBJECT, 

72 "Window group leader as a pair: (xid, gdk window)", "", 

73 GObject.ParamFlags.READABLE), 

74 #from WM_HINTS.urgency or _NET_WM_STATE 

75 "attention-requested": (GObject.TYPE_BOOLEAN, 

76 "Urgency hint from client, or us", "", 

77 False, 

78 GObject.ParamFlags.READWRITE), 

79 #from WM_HINTS.input or WM_TAKE_FOCUS 

80 "can-focus": (GObject.TYPE_BOOLEAN, 

81 "Does this window ever accept keyboard input?", "", 

82 True, 

83 GObject.ParamFlags.READWRITE), 

84 #from _NET_WM_BYPASS_COMPOSITOR 

85 "bypass-compositor": (GObject.TYPE_INT, 

86 "hint that the window would benefit from running uncomposited ", "", 

87 0, 2, 0, 

88 GObject.ParamFlags.READABLE), 

89 #from _NET_WM_FULLSCREEN_MONITORS 

90 "fullscreen-monitors": (GObject.TYPE_PYOBJECT, 

91 "List of 4 monitor indices indicating the top, bottom, left, and right edges"+ 

92 " of the window when the fullscreen state is enabled", "", 

93 GObject.ParamFlags.READABLE), 

94 #from _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT 

95 "strut": (GObject.TYPE_PYOBJECT, 

96 "Struts requested by window, or None", "", 

97 GObject.ParamFlags.READABLE), 

98 #for our own use: 

99 "content-type": (GObject.TYPE_PYOBJECT, 

100 "What type of content is shown in this window", "", 

101 GObject.ParamFlags.READABLE), 

102 #from _XPRA_QUALITY 

103 "quality": (GObject.TYPE_INT, 

104 "Quality", "", 

105 -1, 100, -1, 

106 GObject.ParamFlags.READABLE), 

107 #from _XPRA_SPEED 

108 "speed": (GObject.TYPE_INT, 

109 "Speed", "", 

110 -1, 100, -1, 

111 GObject.ParamFlags.READABLE), 

112 #from _XPRA_ENCODING 

113 "encoding": (GObject.TYPE_PYOBJECT, 

114 "Encoding", "", 

115 GObject.ParamFlags.READABLE), 

116 #from _NET_WM_DESKTOP 

117 "workspace": (GObject.TYPE_UINT, 

118 "The workspace this window is on", "", 

119 0, 2**32-1, WORKSPACE_UNSET, 

120 GObject.ParamFlags.READWRITE), 

121 #set initially only by the window model class 

122 #(derived from XGetWindowAttributes.override_redirect) 

123 "override-redirect": (GObject.TYPE_BOOLEAN, 

124 "Is the window of type override-redirect", "", 

125 False, 

126 GObject.ParamFlags.READABLE), 

127 #from _NET_WM_WINDOW_TYPE 

128 "window-type": (GObject.TYPE_PYOBJECT, 

129 "Window type", 

130 "NB, most preferred comes first, then fallbacks", 

131 GObject.ParamFlags.READABLE), 

132 #this value is synced to "_NET_WM_STATE": 

133 "state": (GObject.TYPE_PYOBJECT, 

134 "State, as per _NET_WM_STATE", "", 

135 GObject.ParamFlags.READABLE), 

136 #all the attributes below are virtual attributes from WM_STATE: 

137 "modal": (GObject.TYPE_PYOBJECT, 

138 "Modal", "", 

139 GObject.ParamFlags.READWRITE), 

140 "fullscreen": (GObject.TYPE_BOOLEAN, 

141 "Fullscreen-ness of window", "", 

142 False, 

143 GObject.ParamFlags.READWRITE), 

144 "focused": (GObject.TYPE_BOOLEAN, 

145 "Is the window focused", "", 

146 False, 

147 GObject.ParamFlags.READWRITE), 

148 "maximized": (GObject.TYPE_BOOLEAN, 

149 "Is the window maximized", "", 

150 False, 

151 GObject.ParamFlags.READWRITE), 

152 "above": (GObject.TYPE_BOOLEAN, 

153 "Is the window on top of most windows", "", 

154 False, 

155 GObject.ParamFlags.READWRITE), 

156 "below": (GObject.TYPE_BOOLEAN, 

157 "Is the window below most windows", "", 

158 False, 

159 GObject.ParamFlags.READWRITE), 

160 "shaded": (GObject.TYPE_BOOLEAN, 

161 "Is the window shaded", "", 

162 False, 

163 GObject.ParamFlags.READWRITE), 

164 "skip-taskbar": (GObject.TYPE_BOOLEAN, 

165 "Should the window be included on a taskbar", "", 

166 False, 

167 GObject.ParamFlags.READWRITE), 

168 "skip-pager": (GObject.TYPE_BOOLEAN, 

169 "Should the window be included on a pager", "", 

170 False, 

171 GObject.ParamFlags.READWRITE), 

172 "sticky": (GObject.TYPE_BOOLEAN, 

173 "Is the window's position fixed on the screen", "", 

174 False, 

175 GObject.ParamFlags.READWRITE), 

176 }) 

177 _property_names = CoreX11WindowModel._property_names + [ 

178 "transient-for", "fullscreen-monitors", "bypass-compositor", 

179 "group-leader", "window-type", "workspace", "strut", "opacity", 

180 "content-type", 

181 #virtual attributes: 

182 "fullscreen", "focused", "maximized", "above", "below", "shaded", 

183 "skip-taskbar", "skip-pager", "sticky", 

184 ] 

185 _dynamic_property_names = CoreX11WindowModel._dynamic_property_names + [ 

186 "attention-requested", "content-type", 

187 "workspace", "opacity", 

188 "fullscreen", "focused", "maximized", "above", "below", "shaded", 

189 "skip-taskbar", "skip-pager", "sticky", 

190 "quality", "speed", "encoding", 

191 ] 

192 _internal_property_names = CoreX11WindowModel._internal_property_names+["state"] 

193 _initial_x11_properties = CoreX11WindowModel._initial_x11_properties + [ 

194 "WM_TRANSIENT_FOR", 

195 "_NET_WM_WINDOW_TYPE", 

196 "_NET_WM_DESKTOP", 

197 "_NET_WM_FULLSCREEN_MONITORS", 

198 "_NET_WM_BYPASS_COMPOSITOR", 

199 "_NET_WM_STRUT", 

200 #redundant as it uses the same handler as _NET_WM_STRUT: 

201 "_NET_WM_STRUT_PARTIAL", 

202 "_NET_WM_WINDOW_OPACITY", 

203 "WM_HINTS", 

204 "_XPRA_CONTENT_TYPE", 

205 "_XPRA_QUALITY", 

206 "_XPRA_SPEED", 

207 "_XPRA_ENCODING", 

208 ] 

209 _DEFAULT_NET_WM_ALLOWED_ACTIONS = ["_NET_WM_ACTION_%s" % x for x in ( 

210 "CLOSE", "MOVE", "RESIZE", "FULLSCREEN", 

211 "MINIMIZE", "SHADE", "STICK", 

212 "MAXIMIZE_HORZ", "MAXIMIZE_VERT", 

213 "CHANGE_DESKTOP", "ABOVE", "BELOW")] 

214 _MODELTYPE = "Base" 

215 

216 def __init__(self, client_window): 

217 super().__init__(client_window) 

218 self.last_unmap_serial = 0 

219 self._input_field = True # The WM_HINTS input field 

220 #watch for changes to properties that are used to derive the content-type: 

221 for x in get_content_type_properties(): 

222 if x in self.get_dynamic_property_names(): 

223 self.connect("notify::%s" % x, self._content_type_related_property_change) 

224 

225 def serial_after_last_unmap(self, serial) -> bool: 

226 #"The serial member is set from the serial number reported in the protocol 

227 # but expanded from the 16-bit least-significant bits to a full 32-bit value" 

228 if serial>self.last_unmap_serial: 

229 return True 

230 #the serial can wrap around: 

231 if self.last_unmap_serial-serial>=2**15: 

232 return True 

233 return False 

234 

235 

236 def _read_initial_X11_properties(self): 

237 metalog("%s.read_initial_X11_properties()", self._MODELTYPE) 

238 self._updateprop("state", frozenset(self._read_wm_state())) 

239 super()._read_initial_X11_properties() 

240 

241 def _guess_window_type(self) -> str: 

242 #query the X11 property directly, 

243 #in case the python property isn't set yet 

244 if not self.is_OR(): 

245 transient_for = self.prop_get("WM_TRANSIENT_FOR", "window") 

246 if transient_for is not None: 

247 # EWMH says that even if it's transient-for, we MUST check to 

248 # see if it's override-redirect (and if so treat as NORMAL). 

249 # But we wouldn't be here if this was override-redirect. 

250 # (OverrideRedirectWindowModel overrides this method) 

251 return "_NET_WM_WINDOW_TYPE_DIALOG" 

252 return "_NET_WM_WINDOW_TYPE_NORMAL" 

253 

254 

255 ################################ 

256 # Actions 

257 ################################ 

258 

259 def move_to_workspace(self, workspace : int): 

260 #we send a message to ourselves, we could also just update the property 

261 current = self.get_property("workspace") 

262 if current==workspace: 

263 workspacelog("move_to_workspace(%s) unchanged", workspacestr(workspace)) 

264 return 

265 workspacelog("move_to_workspace(%s) current=%s", workspacestr(workspace), workspacestr(current)) 

266 with xswallow: 

267 if workspace==WORKSPACE_UNSET: 

268 workspacelog("removing _NET_WM_DESKTOP property from window %#x", self.xid) 

269 prop_del(self.client_window, "_NET_WM_DESKTOP") 

270 else: 

271 workspacelog("setting _NET_WM_DESKTOP=%s on window %#x", workspacestr(workspace), self.xid) 

272 prop_set(self.client_window, "_NET_WM_DESKTOP", "u32", workspace) 

273 

274 

275 ######################################### 

276 # Python objects synced to X11 properties 

277 ######################################### 

278 

279 def _sync_state(self, *_args): 

280 state = self.get_property("state") 

281 metalog("sync_state: setting _NET_WM_STATE=%s on %#x", state, self.xid) 

282 with xswallow: 

283 prop_set(self.client_window, "_NET_WM_STATE", ["atom"], state) 

284 

285 def _sync_iconic(self, *_args): 

286 def set_state(state): 

287 log("_handle_iconic_update: set_state(%s)", state) 

288 with xswallow: 

289 prop_set(self.client_window, "WM_STATE", "state", state) 

290 

291 if self.get("iconic"): 

292 set_state(IconicState) 

293 self._state_add("_NET_WM_STATE_HIDDEN") 

294 else: 

295 set_state(NormalState) 

296 self._state_remove("_NET_WM_STATE_HIDDEN") 

297 

298 

299 _py_property_handlers = dict(CoreX11WindowModel._py_property_handlers) 

300 _py_property_handlers.update({ 

301 "state" : _sync_state, 

302 "iconic" : _sync_iconic, 

303 }) 

304 

305 

306 ######################################### 

307 # X11 properties synced to Python objects 

308 ######################################### 

309 

310 def _handle_transient_for_change(self): 

311 transient_for = self.prop_get("WM_TRANSIENT_FOR", "window") 

312 metalog("WM_TRANSIENT_FOR=%s", transient_for) 

313 # May be None 

314 self._updateprop("transient-for", transient_for) 

315 

316 def _handle_window_type_change(self): 

317 window_types = self.prop_get("_NET_WM_WINDOW_TYPE", ["atom"]) 

318 metalog("_NET_WM_WINDOW_TYPE=%s", window_types) 

319 if not window_types: 

320 window_type = self._guess_window_type() 

321 metalog("guessed window type=%s", window_type) 

322 #atom = Gdk.Atom.intern(window_type, False) 

323 window_types = [window_type] 

324 #normalize them (hide _NET_WM_WINDOW_TYPE prefix): 

325 window_types = [str(wt).replace("_NET_WM_WINDOW_TYPE_", "").replace("_NET_WM_TYPE_", "") for wt in window_types] 

326 self._updateprop("window-type", window_types) 

327 

328 def _handle_workspace_change(self): 

329 workspace = self.prop_get("_NET_WM_DESKTOP", "u32", True) 

330 if workspace is None: 

331 workspace = WORKSPACE_UNSET 

332 workspacelog("_NET_WM_DESKTOP=%s for window %#x", workspacestr(workspace), self.xid) 

333 self._updateprop("workspace", workspace) 

334 

335 def _handle_fullscreen_monitors_change(self): 

336 fsm = self.prop_get("_NET_WM_FULLSCREEN_MONITORS", ["u32"], True) 

337 metalog("_NET_WM_FULLSCREEN_MONITORS=%s", fsm) 

338 self._updateprop("fullscreen-monitors", fsm) 

339 

340 def _handle_bypass_compositor_change(self): 

341 bypass = self.prop_get("_NET_WM_BYPASS_COMPOSITOR", "u32", True) or 0 

342 metalog("_NET_WM_BYPASS_COMPOSITOR=%s", bypass) 

343 self._updateprop("bypass-compositor", bypass) 

344 

345 def _handle_wm_strut_change(self): 

346 strut = self.prop_get("_NET_WM_STRUT_PARTIAL", "strut-partial") 

347 metalog("_NET_WM_STRUT_PARTIAL=%s", strut) 

348 if strut is None: 

349 strut = self.prop_get("_NET_WM_STRUT", "strut") 

350 metalog("_NET_WM_STRUT=%s", strut) 

351 # Might be None: 

352 self._updateprop("strut", strut) 

353 

354 def _handle_opacity_change(self): 

355 opacity = self.prop_get("_NET_WM_WINDOW_OPACITY", "u32", True) or -1 

356 metalog("_NET_WM_WINDOW_OPACITY=%s", opacity) 

357 self._updateprop("opacity", opacity) 

358 

359 def _handle_wm_hints_change(self): 

360 with xswallow: 

361 wm_hints = X11Window.getWMHints(self.xid) 

362 metalog("getWMHints(%#x)=%s", self.xid, wm_hints) 

363 if wm_hints is None: 

364 return 

365 # GdkWindow or None 

366 group_leader = None 

367 if "window_group" in wm_hints: 

368 xid = wm_hints.get("window_group") 

369 try: 

370 group_leader = xid, get_pywindow(xid) 

371 except Exception: 

372 group_leader = xid, None 

373 self._updateprop("group-leader", group_leader) 

374 self._updateprop("attention-requested", wm_hints.get("urgency", False)) 

375 _input = wm_hints.get("input") 

376 metalog("wm_hints.input = %s", _input) 

377 #we only set this value once: 

378 #(input_field always starts as True, and we then set it to an int) 

379 if self._input_field is True and _input is not None: 

380 #keep the value as an int to differentiate from the start value: 

381 self._input_field = int(_input) 

382 self._update_can_focus() 

383 

384 def _update_can_focus(self, *_args): 

385 can_focus = bool(self._input_field) or "WM_TAKE_FOCUS" in self.get_property("protocols") 

386 self._updateprop("can-focus", can_focus) 

387 

388 

389 def _content_type_related_property_change(self, *_args): 

390 self._update_content_type() 

391 

392 def _handle_xpra_content_type_change(self): 

393 self._update_content_type() 

394 

395 def _update_content_type(self): 

396 #watch for changes to properties that are used to derive the content-type: 

397 content_type = self.prop_get("_XPRA_CONTENT_TYPE", "latin1", True) 

398 #the _XPRA_CONTENT_TYPE property takes precedence 

399 if not content_type: 

400 content_type = guess_content_type(self) 

401 if not content_type and self.is_tray(): 

402 content_type = "picture" 

403 metalog("_update_content_type() %s", content_type) 

404 self._updateprop("content-type", content_type) 

405 

406 

407 def _handle_xpra_quality_change(self): 

408 quality = self.prop_get("_XPRA_QUALITY", "u32", True) or -1 

409 metalog("quality=%s", quality) 

410 self._updateprop("quality", max(-1, min(100, quality))) 

411 

412 def _handle_xpra_speed_change(self): 

413 speed = self.prop_get("_XPRA_SPEED", "u32", True) or -1 

414 metalog("speed=%s", speed) 

415 self._updateprop("speed", max(-1, min(100, speed))) 

416 

417 def _handle_xpra_encoding_change(self): 

418 encoding = self.prop_get("_XPRA_ENCODING", "latin1", True) or "" 

419 metalog("encoding=%s", encoding) 

420 self._updateprop("encoding", encoding) 

421 

422 

423 _x11_property_handlers = CoreX11WindowModel._x11_property_handlers.copy() 

424 _x11_property_handlers.update({ 

425 "WM_TRANSIENT_FOR" : _handle_transient_for_change, 

426 "_NET_WM_WINDOW_TYPE" : _handle_window_type_change, 

427 "_NET_WM_DESKTOP" : _handle_workspace_change, 

428 "_NET_WM_FULLSCREEN_MONITORS" : _handle_fullscreen_monitors_change, 

429 "_NET_WM_BYPASS_COMPOSITOR" : _handle_bypass_compositor_change, 

430 "_NET_WM_STRUT" : _handle_wm_strut_change, 

431 "_NET_WM_STRUT_PARTIAL" : _handle_wm_strut_change, 

432 "_NET_WM_WINDOW_OPACITY" : _handle_opacity_change, 

433 "WM_HINTS" : _handle_wm_hints_change, 

434 "_XPRA_CONTENT_TYPE" : _handle_xpra_content_type_change, 

435 "_XPRA_QUALITY" : _handle_xpra_quality_change, 

436 "_XPRA_SPEED" : _handle_xpra_speed_change, 

437 "_XPRA_ENCODING" : _handle_xpra_encoding_change, 

438 }) 

439 

440 

441 ######################################### 

442 # _NET_WM_STATE 

443 ######################################### 

444 # A few words about _NET_WM_STATE are in order. Basically, it is a set of 

445 # flags. Clients are allowed to set the initial value of this X property 

446 # to anything they like, when their window is first mapped; after that, 

447 # though, only the window manager is allowed to touch this property. So 

448 # we store its value (or at least, our idea as to its value, the X server 

449 # in principle could disagree) as the "state" property. There are 

450 # basically two things we need to accomplish: 

451 # 1) Whenever our property is modified, we mirror that modification into 

452 # the X server. This is done by connecting to our own notify::state 

453 # signal. 

454 # 2) As a more user-friendly interface to these state flags, we provide 

455 # several boolean properties like "attention-requested". 

456 # These are virtual boolean variables; they are actually backed 

457 # directly by the "state" property, and reading/writing them in fact 

458 # accesses the "state" set directly. This is done by overriding 

459 # do_set_property and do_get_property. 

460 _state_properties = { 

461 "attention-requested" : ("_NET_WM_STATE_DEMANDS_ATTENTION", ), 

462 "fullscreen" : ("_NET_WM_STATE_FULLSCREEN", ), 

463 "maximized" : ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"), 

464 "shaded" : ("_NET_WM_STATE_SHADED", ), 

465 "above" : ("_NET_WM_STATE_ABOVE", ), 

466 "below" : ("_NET_WM_STATE_BELOW", ), 

467 "sticky" : ("_NET_WM_STATE_STICKY", ), 

468 "skip-taskbar" : ("_NET_WM_STATE_SKIP_TASKBAR", ), 

469 "skip-pager" : ("_NET_WM_STATE_SKIP_PAGER", ), 

470 "modal" : ("_NET_WM_STATE_MODAL", ), 

471 "focused" : ("_NET_WM_STATE_FOCUSED", ), 

472 } 

473 _state_properties_reversed = {} 

474 for k, states in tuple(_state_properties.items()): 

475 for x in states: 

476 _state_properties_reversed[x] = k 

477 

478 def _state_add(self, *state_names): 

479 curr = set(self.get_property("state")) 

480 add = [s for s in state_names if s not in curr] 

481 if add: 

482 for x in add: 

483 curr.add(x) 

484 #note: _sync_state will update _NET_WM_STATE here: 

485 self._internal_set_property("state", frozenset(curr)) 

486 self._state_notify(add) 

487 

488 def _state_remove(self, *state_names): 

489 curr = set(self.get_property("state")) 

490 discard = [s for s in state_names if s in curr] 

491 if discard: 

492 for x in discard: 

493 curr.discard(x) 

494 #note: _sync_state will update _NET_WM_STATE here: 

495 self._internal_set_property("state", frozenset(curr)) 

496 self._state_notify(discard) 

497 

498 def _state_notify(self, state_names): 

499 notify_props = set() 

500 for x in state_names: 

501 if x in self._state_properties_reversed: 

502 notify_props.add(self._state_properties_reversed[x]) 

503 for x in tuple(notify_props): 

504 self.notify(x) 

505 

506 def _state_isset(self, state_name): 

507 return state_name in self.get_property("state") 

508 

509 def _read_wm_state(self): 

510 wm_state = self.prop_get("_NET_WM_STATE", ["atom"]) 

511 metalog("read _NET_WM_STATE=%s", wm_state) 

512 return wm_state or [] 

513 

514 

515 def do_set_property(self, pspec, value): 

516 #intercept state properties to route via update_state() 

517 if pspec.name in self._state_properties: 

518 #virtual property for WM_STATE: 

519 self.update_wm_state(pspec.name, value) 

520 return 

521 super().do_set_property(pspec, value) 

522 

523 def do_get_property(self, pspec): 

524 #intercept state properties to route via get_wm_state() 

525 if pspec.name in self._state_properties: 

526 #virtual property for WM_STATE: 

527 return self.get_wm_state(pspec.name) 

528 return super().do_get_property(pspec) 

529 

530 

531 def update_wm_state(self, prop, b): 

532 state_names = self._state_properties.get(prop) 

533 assert state_names, "invalid window state %s" % prop 

534 if b: 

535 self._state_add(*state_names) 

536 else: 

537 self._state_remove(*state_names) 

538 

539 def get_wm_state(self, prop): 

540 state_names = self._state_properties.get(prop) 

541 assert state_names, "invalid window state %s" % prop 

542 #this is a virtual property for WM_STATE: 

543 #return True if any is set (only relevant for maximized) 

544 for x in state_names: 

545 if self._state_isset(x): 

546 return True 

547 return False 

548 

549 

550 ######################################### 

551 # X11 Events 

552 ######################################### 

553 

554 def process_client_message_event(self, event): 

555 # FIXME 

556 # Need to listen for: 

557 # _NET_CURRENT_DESKTOP 

558 # _NET_WM_PING responses 

559 # and maybe: 

560 # _NET_WM_STATE (more fully) 

561 

562 if event.message_type=="_NET_WM_STATE": 

563 def update_wm_state(prop): 

564 current = self.get_property(prop) 

565 mode = event.data[0] 

566 if mode==_NET_WM_STATE_ADD: 

567 v = True 

568 elif mode==_NET_WM_STATE_REMOVE: 

569 v = False 

570 elif mode==_NET_WM_STATE_TOGGLE: 

571 v = not bool(current) 

572 else: 

573 log.warn("Warning: invalid mode for _NET_WM_STATE: %s", mode) 

574 return 

575 log("process_client_message_event(%s) window %s=%s after %s (current state=%s)", 

576 event, prop, v, STATE_STRING.get(mode, mode), current) 

577 if v!=current: 

578 self.update_wm_state(prop, v) 

579 atom1 = get_pyatom(event.window, event.data[1]) 

580 log("_NET_WM_STATE: %s", atom1) 

581 if atom1=="_NET_WM_STATE_FULLSCREEN": 

582 update_wm_state("fullscreen") 

583 elif atom1=="_NET_WM_STATE_ABOVE": 

584 update_wm_state("above") 

585 elif atom1=="_NET_WM_STATE_BELOW": 

586 update_wm_state("below") 

587 elif atom1=="_NET_WM_STATE_SHADED": 

588 update_wm_state("shaded") 

589 elif atom1=="_NET_WM_STATE_STICKY": 

590 update_wm_state("sticky") 

591 elif atom1=="_NET_WM_STATE_SKIP_TASKBAR": 

592 update_wm_state("skip-taskbar") 

593 elif atom1=="_NET_WM_STATE_SKIP_PAGER": 

594 update_wm_state("skip-pager") 

595 get_pyatom(event.window, event.data[2]) 

596 elif atom1 in ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"): 

597 atom2 = get_pyatom(event.window, event.data[2]) 

598 #we only have one state for both, so we require both to be set: 

599 if atom1!=atom2 and atom2 in ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"): 

600 update_wm_state("maximized") 

601 elif atom1=="_NET_WM_STATE_HIDDEN": 

602 log("ignoring 'HIDDEN' _NET_WM_STATE: %s", event) 

603 #we don't honour those because they make little sense, see: 

604 #https://mail.gnome.org/archives/wm-spec-list/2005-May/msg00004.html 

605 elif atom1=="_NET_WM_STATE_MODAL": 

606 update_wm_state("modal") 

607 elif atom1=="_NET_WM_STATE_DEMANDS_ATTENTION": 

608 update_wm_state("attention-requested") 

609 else: 

610 log.info("Unhandled _NET_WM_STATE request: '%s'", event, atom1) 

611 log.info(" event%s", event) 

612 return True 

613 if event.message_type=="WM_CHANGE_STATE": 

614 iconic = event.data[0] 

615 log("WM_CHANGE_STATE: %s, serial=%s, last unmap serial=%#x", 

616 ICONIC_STATE_STRING.get(iconic, iconic), event.serial, self.last_unmap_serial) 

617 if ( 

618 iconic in (IconicState, NormalState) and 

619 self.serial_after_last_unmap(event.serial) and 

620 not self.is_OR() and not self.is_tray() 

621 ): 

622 self._updateprop("iconic", iconic==IconicState) 

623 return True 

624 if event.message_type=="_NET_WM_MOVERESIZE": 

625 log("_NET_WM_MOVERESIZE: %s", event) 

626 self.emit("initiate-moveresize", event) 

627 return True 

628 if event.message_type=="_NET_ACTIVE_WINDOW": 

629 #to filter based on the source indication: 

630 #ACTIVE_WINDOW_SOURCE = tuple(int(x) for x in os.environ.get("XPRA_ACTIVE_WINDOW_SOURCE", "0,1").split(",")) 

631 #if event.data[0] in ACTIVE_WINDOW_SOURCE: 

632 log("_NET_ACTIVE_WINDOW: %s", event) 

633 self.set_active() 

634 self.emit("restack", Above, None) 

635 return True 

636 if event.message_type=="_NET_WM_DESKTOP": 

637 workspace = int(event.data[0]) 

638 #query the workspace count on the root window 

639 #since we cannot access Wm from here.. 

640 root = self.client_window.get_screen().get_root_window() 

641 ndesktops = prop_get(root, "_NET_NUMBER_OF_DESKTOPS", "u32", ignore_errors=True) 

642 workspacelog("received _NET_WM_DESKTOP: workspace=%s, number of desktops=%s", 

643 workspacestr(workspace), ndesktops) 

644 if ndesktops>0 and ( 

645 workspace in (WORKSPACE_UNSET, WORKSPACE_ALL) or 

646 0<=workspace<ndesktops 

647 ): 

648 self.move_to_workspace(workspace) 

649 else: 

650 workspacelog.warn("invalid _NET_WM_DESKTOP request: workspace=%s, number of desktops=%s", 

651 workspacestr(workspace), ndesktops) 

652 return True 

653 if event.message_type=="_NET_WM_FULLSCREEN_MONITORS": 

654 log("_NET_WM_FULLSCREEN_MONITORS: %s", event) 

655 #TODO: we should validate the indexes instead of copying them blindly! 

656 #TODO: keep track of source indication so we can forward that to the client 

657 m1, m2, m3, m4 = event.data[0], event.data[1], event.data[2], event.data[3] 

658 N = 16 #FIXME: arbitrary limit 

659 if m1<0 or m1>=N or m2<0 or m2>=N or m3<0 or m3>=N or m4<0 or m4>=N: 

660 log.warn("invalid list of _NET_WM_FULLSCREEN_MONITORS - ignored") 

661 return False 

662 monitors = [m1, m2, m3, m4] 

663 log("_NET_WM_FULLSCREEN_MONITORS: monitors=%s", monitors) 

664 prop_set(self.client_window, "_NET_WM_FULLSCREEN_MONITORS", ["u32"], monitors) 

665 return True 

666 if event.message_type=="_NET_RESTACK_WINDOW": 

667 source = {1 : "application", 2 : "pager"}.get(event.data[0], "default (%s)" % event.data[0]) 

668 sibling_window = event.data[1] 

669 log("%s sent to window %#x for sibling %#x from %s with detail=%s", 

670 event.message_type, event.window, sibling_window, source, RESTACKING_STR.get(event.detail, event.detail)) 

671 self.emit("restack", event.detail, sibling_window) 

672 return True 

673 #TODO: maybe we should process _NET_MOVERESIZE_WINDOW here? 

674 # it may make sense to apply it to the client_window 

675 # whereas the code in WindowModel assumes there is a corral window 

676 #not handled: 

677 return super().process_client_message_event(event)