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

7import os 

8import signal 

9from gi.repository import GObject, Gdk, GLib 

10 

11from xpra.util import envbool, first_time 

12from xpra.os_util import bytestostr 

13from xpra.x11.common import Unmanageable 

14from xpra.gtk_common.gobject_util import one_arg_signal, two_arg_signal 

15from xpra.gtk_common.error import XError, xsync, xswallow 

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

17from xpra.x11.models.model_stub import WindowModelStub 

18from xpra.x11.gtk_x11.composite import CompositeHelper 

19from xpra.x11.gtk_x11.prop import prop_get, prop_set, prop_type_get, PYTHON_TYPES 

20from xpra.x11.gtk_x11.send_wm import send_wm_delete_window 

21from xpra.x11.gtk_x11.gdk_bindings import add_event_receiver, remove_event_receiver 

22from xpra.log import Logger 

23 

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

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

26shapelog = Logger("x11", "window", "shape") 

27grablog = Logger("x11", "window", "grab") 

28framelog = Logger("x11", "window", "frame") 

29geomlog = Logger("x11", "window", "geometry") 

30 

31 

32X11Window = X11WindowBindings() 

33em = Gdk.EventMask 

34ADDMASK = em.STRUCTURE_MASK | em.PROPERTY_CHANGE_MASK | em.FOCUS_CHANGE_MASK | em.POINTER_MOTION_MASK 

35 

36FORCE_QUIT = envbool("XPRA_FORCE_QUIT", True) 

37XSHAPE = envbool("XPRA_XSHAPE", True) 

38FRAME_EXTENTS = envbool("XPRA_FRAME_EXTENTS", True) 

39 

40# Re-stacking: 

41Above = 0 

42Below = 1 

43TopIf = 2 

44BottomIf = 3 

45Opposite = 4 

46RESTACKING_STR = { 

47 Above : "Above", 

48 Below : "Below", 

49 TopIf : "TopIf", 

50 BottomIf : "BottomIf", 

51 Opposite : "Opposite", 

52 } 

53 

54# grab stuff: 

55NotifyNormal = constants["NotifyNormal"] 

56NotifyGrab = constants["NotifyGrab"] 

57NotifyUngrab = constants["NotifyUngrab"] 

58NotifyWhileGrabbed = constants["NotifyWhileGrabbed"] 

59NotifyNonlinearVirtual = constants["NotifyNonlinearVirtual"] 

60GRAB_CONSTANTS = { 

61 NotifyNormal : "NotifyNormal", 

62 NotifyGrab : "NotifyGrab", 

63 NotifyUngrab : "NotifyUngrab", 

64 NotifyWhileGrabbed : "NotifyWhileGrabbed", 

65 } 

66DETAIL_CONSTANTS = {} 

67for dconst in ( 

68 "NotifyAncestor", "NotifyVirtual", "NotifyInferior", 

69 "NotifyNonlinear", "NotifyNonlinearVirtual", "NotifyPointer", 

70 "NotifyPointerRoot", "NotifyDetailNone", 

71 ): 

72 DETAIL_CONSTANTS[constants[dconst]] = dconst 

73grablog("pointer grab constants: %s", GRAB_CONSTANTS) 

74grablog("detail constants: %s", DETAIL_CONSTANTS) 

75 

76#these properties are not handled, and we don't want to spam the log file 

77#whenever an app decides to change them: 

78PROPERTIES_IGNORED = os.environ.get("XPRA_X11_PROPERTIES_IGNORED", "_NET_WM_OPAQUE_REGION").split(",") 

79#make it easier to debug property changes, just add them here: 

80#ie: {"WM_PROTOCOLS" : ["atom"]} 

81X11_PROPERTIES_DEBUG = {} 

82PROPERTIES_DEBUG = [prop_debug.strip() 

83 for prop_debug in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")] 

84X11PROPERTY_SYNC = envbool("XPRA_X11PROPERTY_SYNC", True) 

85X11PROPERTY_SYNC_BLACKLIST = os.environ.get("XPRA_X11PROPERTY_SYNC_BLACKLIST", 

86 "_GTK,WM_,_NET,Xdnd").split(",") 

87 

88 

89def sanestr(s): 

90 return (s or "").strip("\0").replace("\0", " ") 

91 

92 

93class CoreX11WindowModel(WindowModelStub): 

94 """ 

95 The utility superclass for all GTK2 / X11 window models, 

96 it wraps an X11 window (the "client-window"). 

97 Defines the common properties and signals, 

98 sets up the composite helper so we get the damage events. 

99 The x11_property_handlers sync X11 window properties into Python objects, 

100 the py_property_handlers do it in the other direction. 

101 """ 

102 __common_properties__ = { 

103 #the actual X11 client window 

104 "client-window": (GObject.TYPE_PYOBJECT, 

105 "gtk.gdk.Window representing the client toplevel", "", 

106 GObject.ParamFlags.READABLE), 

107 #the X11 window id 

108 "xid": (GObject.TYPE_INT, 

109 "X11 window id", "", 

110 -1, 65535, -1, 

111 GObject.ParamFlags.READABLE), 

112 #FIXME: this is an ugly virtual property 

113 "geometry": (GObject.TYPE_PYOBJECT, 

114 "current coordinates (x, y, w, h, border) for the window", "", 

115 GObject.ParamFlags.READABLE), 

116 #bits per pixel 

117 "depth": (GObject.TYPE_INT, 

118 "window bit depth", "", 

119 -1, 64, -1, 

120 GObject.ParamFlags.READABLE), 

121 #if the window depth is 32 bit 

122 "has-alpha": (GObject.TYPE_BOOLEAN, 

123 "Does the window use transparency", "", 

124 False, 

125 GObject.ParamFlags.READABLE), 

126 #from WM_CLIENT_MACHINE 

127 "client-machine": (GObject.TYPE_PYOBJECT, 

128 "Host where client process is running", "", 

129 GObject.ParamFlags.READABLE), 

130 #from _NET_WM_PID 

131 "pid": (GObject.TYPE_INT, 

132 "PID of owning process", "", 

133 -1, 65535, -1, 

134 GObject.ParamFlags.READABLE), 

135 #from _NET_WM_NAME or WM_NAME 

136 "title": (GObject.TYPE_PYOBJECT, 

137 "Window title (unicode or None)", "", 

138 GObject.ParamFlags.READABLE), 

139 #from WM_WINDOW_ROLE 

140 "role" : (GObject.TYPE_PYOBJECT, 

141 "The window's role (ICCCM session management)", "", 

142 GObject.ParamFlags.READABLE), 

143 #from WM_PROTOCOLS via XGetWMProtocols 

144 "protocols": (GObject.TYPE_PYOBJECT, 

145 "Supported WM protocols", "", 

146 GObject.ParamFlags.READABLE), 

147 #from WM_COMMAND 

148 "command": (GObject.TYPE_PYOBJECT, 

149 "Command used to start or restart the client", "", 

150 GObject.ParamFlags.READABLE), 

151 #from WM_CLASS via getClassHint 

152 "class-instance": (GObject.TYPE_PYOBJECT, 

153 "Classic X 'class' and 'instance'", "", 

154 GObject.ParamFlags.READABLE), 

155 #ShapeNotify events will populate this using XShapeQueryExtents 

156 "shape": (GObject.TYPE_PYOBJECT, 

157 "Window XShape data", "", 

158 GObject.ParamFlags.READABLE), 

159 #synced to "_NET_FRAME_EXTENTS" 

160 "frame": (GObject.TYPE_PYOBJECT, 

161 "Size of the window frame, as per _NET_FRAME_EXTENTS", "", 

162 GObject.ParamFlags.READWRITE), 

163 #synced to "_NET_WM_ALLOWED_ACTIONS" 

164 "allowed-actions": (GObject.TYPE_PYOBJECT, 

165 "Supported WM actions", "", 

166 GObject.ParamFlags.READWRITE), 

167 } 

168 

169 __common_signals__ = { 

170 #signals we emit: 

171 "unmanaged" : one_arg_signal, 

172 "restack" : two_arg_signal, 

173 "initiate-moveresize" : one_arg_signal, 

174 "grab" : one_arg_signal, 

175 "ungrab" : one_arg_signal, 

176 "bell" : one_arg_signal, 

177 "client-contents-changed" : one_arg_signal, 

178 "motion" : one_arg_signal, 

179 #x11 events we catch (and often re-emit as something else): 

180 "xpra-property-notify-event" : one_arg_signal, 

181 "xpra-xkb-event" : one_arg_signal, 

182 "xpra-shape-event" : one_arg_signal, 

183 "xpra-configure-event" : one_arg_signal, 

184 "xpra-unmap-event" : one_arg_signal, 

185 "xpra-client-message-event" : one_arg_signal, 

186 "xpra-focus-in-event" : one_arg_signal, 

187 "xpra-focus-out-event" : one_arg_signal, 

188 "xpra-motion-event" : one_arg_signal, 

189 "x11-property-changed" : one_arg_signal, 

190 } 

191 

192 #things that we expose: 

193 _property_names = [ 

194 "xid", "depth", "has-alpha", 

195 "client-machine", "pid", 

196 "title", "role", 

197 "command", "shape", 

198 "class-instance", "protocols", 

199 ] 

200 #exposed and changing (should be watched for notify signals): 

201 _dynamic_property_names = ["title", "command", "shape", "class-instance", "protocols"] 

202 #should not be exported to the clients: 

203 _internal_property_names = ["frame", "allowed-actions"] 

204 _initial_x11_properties = ["_NET_WM_PID", "WM_CLIENT_MACHINE", 

205 #_NET_WM_NAME is redundant, as it calls the same handler as "WM_NAME" 

206 "WM_NAME", "_NET_WM_NAME", 

207 "WM_PROTOCOLS", "WM_CLASS", "WM_WINDOW_ROLE"] 

208 _DEFAULT_NET_WM_ALLOWED_ACTIONS = [] 

209 _MODELTYPE = "Core" 

210 _scrub_x11_properties = [ 

211 "WM_STATE", 

212 #"_NET_WM_STATE", # "..it should leave the property in place when it is shutting down" 

213 "_NET_FRAME_EXTENTS", "_NET_WM_ALLOWED_ACTIONS"] 

214 

215 def __init__(self, client_window): 

216 super().__init__() 

217 self.xid = client_window.get_xid() 

218 log("new window %#x", self.xid) 

219 self.client_window = client_window 

220 self.client_window_saved_events = self.client_window.get_events() 

221 self._composite = None 

222 self._damage_forward_handle = None 

223 self._setup_done = False 

224 self._kill_count = 0 

225 self._internal_set_property("client-window", client_window) 

226 

227 

228 def __repr__(self): 

229 try: 

230 return "%s(%#x)" % (type(self).__name__, self.xid) 

231 except AttributeError: 

232 return repr(self) 

233 

234 

235 ######################################### 

236 # Setup and teardown 

237 ######################################### 

238 

239 def call_setup(self): 

240 """ 

241 Call this method to prepare the window: 

242 * makes sure it still exists 

243 (by querying its geometry which may raise an XError) 

244 * setup composite redirection 

245 * calls setup 

246 The difficulty comes from X11 errors and synchronization: 

247 we want to catch errors and undo what we've done. 

248 The mix of GTK and pure-X11 calls is not helping. 

249 """ 

250 try: 

251 with xsync: 

252 geom = X11Window.geometry_with_border(self.xid) 

253 if geom is None: 

254 raise Unmanageable("window %#x disappeared already" % self.xid) 

255 self._internal_set_property("geometry", geom[:4]) 

256 self._read_initial_X11_properties() 

257 except XError as e: 

258 log("failed to manage %#x", self.xid, exc_info=True) 

259 raise Unmanageable(e) 

260 add_event_receiver(self.client_window, self) 

261 # Keith Packard says that composite state is undefined following a 

262 # reparent, so I'm not sure doing this here in the superclass, 

263 # before we reparent, actually works... let's wait and see. 

264 try: 

265 self._composite = CompositeHelper(self.client_window) 

266 with xsync: 

267 self._composite.setup() 

268 if X11Window.displayHasXShape(): 

269 X11Window.XShapeSelectInput(self.xid) 

270 except Exception as e: 

271 remove_event_receiver(self.client_window, self) 

272 log("%s %#x does not support compositing: %s", self._MODELTYPE, self.xid, e) 

273 with xswallow: 

274 self._composite.destroy() 

275 self._composite = None 

276 if isinstance(e, Unmanageable): 

277 raise 

278 raise Unmanageable(e) 

279 #compositing is now enabled, 

280 #from now on we must call setup_failed to clean things up 

281 self._managed = True 

282 try: 

283 with xsync: 

284 self.setup() 

285 except XError as e: 

286 log("failed to setup %#x", self.xid, exc_info=True) 

287 try: 

288 with xsync: 

289 self.setup_failed(e) 

290 except Exception as ex: 

291 log.error("error in cleanup handler: %s", ex) 

292 raise Unmanageable(e) 

293 self._setup_done = True 

294 

295 def setup_failed(self, e): 

296 log("cannot manage %s %#x: %s", self._MODELTYPE, self.xid, e) 

297 self.do_unmanaged(False) 

298 

299 def setup(self): 

300 # Start listening for important events. 

301 X11Window.addDefaultEvents(self.xid) 

302 self._damage_forward_handle = self._composite.connect("contents-changed", self._forward_contents_changed) 

303 self._setup_property_sync() 

304 

305 

306 def unmanage(self, exiting=False): 

307 if self._managed: 

308 self.emit("unmanaged", exiting) 

309 

310 def do_unmanaged(self, wm_exiting): 

311 if not self._managed: 

312 return 

313 self._managed = False 

314 log("%s.do_unmanaged(%s) damage_forward_handle=%s, composite=%s", 

315 self._MODELTYPE, wm_exiting, self._damage_forward_handle, self._composite) 

316 remove_event_receiver(self.client_window, self) 

317 GLib.idle_add(self.managed_disconnect) 

318 if self._composite: 

319 if self._damage_forward_handle: 

320 self._composite.disconnect(self._damage_forward_handle) 

321 self._damage_forward_handle = None 

322 self._composite.destroy() 

323 self._composite = None 

324 self._scrub_x11() 

325 

326 

327 ######################################### 

328 # Damage / Composite 

329 ######################################### 

330 

331 def acknowledge_changes(self): 

332 c = self._composite 

333 assert c, "composite window destroyed outside the UI thread?" 

334 c.acknowledge_changes() 

335 

336 def _forward_contents_changed(self, _obj, event): 

337 if self._managed: 

338 self.emit("client-contents-changed", event) 

339 

340 def uses_XShm(self) -> bool: 

341 c = self._composite 

342 return c and c.has_xshm() 

343 

344 def get_image(self, x, y, width, height): 

345 return self._composite.get_image(x, y, width, height) 

346 

347 

348 def _setup_property_sync(self): 

349 metalog("setup_property_sync()") 

350 #python properties which trigger an X11 property to be updated: 

351 for prop, cb in self._py_property_handlers.items(): 

352 self.connect("notify::%s" % prop, cb) 

353 #initial sync: 

354 for cb in self._py_property_handlers.values(): 

355 cb(self) 

356 #this one is special, and overriden in BaseWindow too: 

357 self.managed_connect("notify::protocols", self._update_can_focus) 

358 

359 def _update_can_focus(self, *_args): 

360 can_focus = "WM_TAKE_FOCUS" in self.get_property("protocols") 

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

362 

363 def _read_initial_X11_properties(self): 

364 """ This is called within an XSync context, 

365 so that X11 calls can raise XErrors, 

366 pure GTK calls are not allowed. (they would trap the X11 error and crash!) 

367 Calling _updateprop is safe, because setup has not completed yet, 

368 so the property update will not fire notify() 

369 """ 

370 metalog("read_initial_X11_properties() core") 

371 #immutable ones: 

372 depth = X11Window.get_depth(self.xid) 

373 metalog("initial X11 properties: xid=%#x, depth=%i", self.xid, depth) 

374 self._updateprop("depth", depth) 

375 self._updateprop("xid", self.xid) 

376 self._updateprop("has-alpha", depth==32) 

377 self._updateprop("allowed-actions", self._DEFAULT_NET_WM_ALLOWED_ACTIONS) 

378 self._updateprop("shape", self._read_xshape()) 

379 #note: some of those are technically mutable, 

380 #but we don't export them as "dynamic" properties, so this won't be propagated 

381 #maybe we want to catch errors parsing _NET_WM_ICON ? 

382 metalog("initial X11_properties: querying %s", self._initial_x11_properties) 

383 #to make sure we don't call the same handler twice which is pointless 

384 #(the same handler may handle more than one X11 property) 

385 handlers = set() 

386 for mutable in self._initial_x11_properties: 

387 handler = self._x11_property_handlers.get(mutable) 

388 if not handler: 

389 log.error("BUG: unknown initial X11 property: %s", mutable) 

390 elif handler not in handlers: 

391 handlers.add(handler) 

392 try: 

393 handler(self) 

394 except XError: 

395 log("handler %s failed", handler, exc_info=True) 

396 #these will be caught in call_setup() 

397 raise 

398 except Exception: 

399 #try to continue: 

400 log.error("Error parsing initial property '%s':", mutable, exc_info=True) 

401 

402 def _scrub_x11(self): 

403 metalog("scrub_x11() x11 properties=%s", self._scrub_x11_properties) 

404 if not self._scrub_x11_properties: 

405 return 

406 with xswallow: 

407 for prop in self._scrub_x11_properties: 

408 X11Window.XDeleteProperty(self.xid, prop) 

409 

410 

411 ######################################### 

412 # XShape 

413 ######################################### 

414 

415 def _read_xshape(self, x=0, y=0): 

416 if not X11Window.displayHasXShape() or not XSHAPE: 

417 return {} 

418 extents = X11Window.XShapeQueryExtents(self.xid) 

419 if not extents: 

420 shapelog("read_shape for window %#x: no extents", self.xid) 

421 return {} 

422 #w,h = X11Window.getGeometry(xid)[2:4] 

423 shapelog("read_shape for window %#x: extents=%s", self.xid, extents) 

424 bextents = extents[0] 

425 cextents = extents[1] 

426 if bextents[0]==0 and cextents[0]==0: 

427 shapelog("read_shape for window %#x: none enabled", self.xid) 

428 return {} 

429 v = { 

430 "x" : x, 

431 "y" : y, 

432 "Bounding.extents" : bextents, 

433 "Clip.extents" : cextents, 

434 } 

435 for kind, kind_name in SHAPE_KIND.items(): # @UndefinedVariable 

436 rectangles = X11Window.XShapeGetRectangles(self.xid, kind) 

437 v[kind_name+".rectangles"] = rectangles 

438 shapelog("_read_shape()=%s", v) 

439 return v 

440 

441 

442 ################################ 

443 # Property reading 

444 ################################ 

445 

446 def get_dimensions(self): 

447 #just extracts the size from the geometry: 

448 return self.get_property("geometry")[2:4] 

449 

450 def get_geometry(self): 

451 return self.get_property("geometry")[:4] 

452 

453 

454 ######################################### 

455 # Python objects synced to X11 properties 

456 ######################################### 

457 

458 def prop_set(self, key, ptype, value): 

459 prop_set(self.client_window, key, ptype, value) 

460 

461 

462 def _sync_allowed_actions(self, *_args): 

463 actions = self.get_property("allowed-actions") or [] 

464 metalog("sync_allowed_actions: setting _NET_WM_ALLOWED_ACTIONS=%s on %#x", actions, self.xid) 

465 with xswallow: 

466 prop_set(self.client_window, "_NET_WM_ALLOWED_ACTIONS", ["atom"], actions) 

467 def _handle_frame_changed(self, *_args): 

468 #legacy name for _sync_frame() called from Wm 

469 self._sync_frame() 

470 def _sync_frame(self, *_args): 

471 if not FRAME_EXTENTS: 

472 return 

473 v = self.get_property("frame") 

474 framelog("sync_frame: frame(%#x)=%s", self.xid, v) 

475 if not v and (not self.is_OR() and not self.is_tray()): 

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

477 v = prop_get(root, "DEFAULT_NET_FRAME_EXTENTS", ["u32"], ignore_errors=True) 

478 if not v: 

479 #default for OR, or if we don't have any other value: 

480 v = (0, 0, 0, 0) 

481 framelog("sync_frame: setting _NET_FRAME_EXTENTS=%s on %#x", v, self.xid) 

482 with xswallow: 

483 prop_set(self.client_window, "_NET_FRAME_EXTENTS", ["u32"], v) 

484 

485 _py_property_handlers = { 

486 "allowed-actions" : _sync_allowed_actions, 

487 "frame" : _sync_frame, 

488 } 

489 

490 

491 ######################################### 

492 # X11 properties synced to Python objects 

493 ######################################### 

494 

495 def prop_get(self, key, ptype, ignore_errors=None, raise_xerrors=False): 

496 """ 

497 Get an X11 property from the client window, 

498 using the automatic type conversion code from prop.py 

499 Ignores property errors during setup_client. 

500 """ 

501 if ignore_errors is None and (not self._setup_done or not self._managed): 

502 ignore_errors = True 

503 return prop_get(self.client_window, key, ptype, ignore_errors=bool(ignore_errors), raise_xerrors=raise_xerrors) 

504 

505 

506 def do_xpra_property_notify_event(self, event): 

507 #X11: PropertyNotify 

508 assert event.window is self.client_window 

509 self._handle_property_change(str(event.atom)) 

510 

511 def _handle_property_change(self, name): 

512 #ie: _handle_property_change("_NET_WM_NAME") 

513 metalog("Property changed on %#x: %s", self.xid, name) 

514 x11proptype = X11_PROPERTIES_DEBUG.get(name) 

515 if x11proptype is not None: 

516 metalog.info("%s=%s", name, self.prop_get(name, x11proptype, True, False)) 

517 if name in PROPERTIES_IGNORED: 

518 return 

519 if X11PROPERTY_SYNC and not any (name.startswith(x) for x in X11PROPERTY_SYNC_BLACKLIST): 

520 try: 

521 with xsync: 

522 prop_type = prop_type_get(self.client_window, name) 

523 metalog("_handle_property_change(%s) property type=%s", name, prop_type) 

524 if prop_type: 

525 dtype, dformat = prop_type 

526 ptype = PYTHON_TYPES.get(bytestostr(dtype)) 

527 if ptype: 

528 value = self.prop_get(name, ptype, ignore_errors=True) 

529 if value is None: 

530 #retry using scalar type: 

531 value = self.prop_get(name, (ptype,), ignore_errors=True) 

532 metalog("_handle_property_change(%s) value=%s", name, value) 

533 if value: 

534 self.emit("x11-property-changed", (name, ptype, dformat, value)) 

535 return 

536 except Exception: 

537 metalog("_handle_property_change(%s)", name, exc_info=True) 

538 self.emit("x11-property-changed", (name, "", 0, "")) 

539 handler = self._x11_property_handlers.get(name) 

540 if handler: 

541 try: 

542 with xsync: 

543 handler(self) 

544 except XError as e: 

545 log("_handle_property_change", exc_info=True) 

546 log.error("Error processing property change for '%s'", name) 

547 log.error(" on window %#x", self.xid) 

548 log.error(" %s", e) 

549 

550 #specific properties: 

551 def _handle_pid_change(self): 

552 pid = self.prop_get("_NET_WM_PID", "u32") or -1 

553 metalog("_NET_WM_PID=%s", pid) 

554 self._updateprop("pid", pid) 

555 

556 def _handle_client_machine_change(self): 

557 client_machine = self.prop_get("WM_CLIENT_MACHINE", "latin1") 

558 metalog("WM_CLIENT_MACHINE=%s", client_machine) 

559 self._updateprop("client-machine", client_machine) 

560 

561 def _handle_wm_name_change(self): 

562 name = self.prop_get("_NET_WM_NAME", "utf8", True) 

563 metalog("_NET_WM_NAME=%s", name) 

564 if name is None: 

565 name = self.prop_get("WM_NAME", "latin1", True) 

566 metalog("WM_NAME=%s", name) 

567 if self._updateprop("title", sanestr(name)): 

568 metalog("wm_name changed") 

569 

570 def _handle_role_change(self): 

571 role = self.prop_get("WM_WINDOW_ROLE", "latin1") 

572 metalog("WM_WINDOW_ROLE=%s", role) 

573 self._updateprop("role", role) 

574 

575 def _handle_protocols_change(self): 

576 with xsync: 

577 protocols = X11Window.XGetWMProtocols(self.xid) 

578 metalog("WM_PROTOCOLS=%s", protocols) 

579 self._updateprop("protocols", protocols) 

580 

581 def _handle_command_change(self): 

582 command = self.prop_get("WM_COMMAND", "latin1") 

583 metalog("WM_COMMAND=%s", command) 

584 if command: 

585 command = command.strip("\0") 

586 self._updateprop("command", command) 

587 

588 def _handle_class_change(self): 

589 class_instance = X11Window.getClassHint(self.xid) 

590 if class_instance: 

591 class_instance = tuple(v.decode("latin1") for v in class_instance) 

592 metalog("WM_CLASS=%s", class_instance) 

593 self._updateprop("class-instance", class_instance) 

594 

595 #these handlers must not generate X11 errors (must use XSync) 

596 _x11_property_handlers = { 

597 "_NET_WM_PID" : _handle_pid_change, 

598 "WM_CLIENT_MACHINE" : _handle_client_machine_change, 

599 "WM_NAME" : _handle_wm_name_change, 

600 "_NET_WM_NAME" : _handle_wm_name_change, 

601 "WM_WINDOW_ROLE" : _handle_role_change, 

602 "WM_PROTOCOLS" : _handle_protocols_change, 

603 "WM_COMMAND" : _handle_command_change, 

604 "WM_CLASS" : _handle_class_change, 

605 } 

606 

607 

608 ######################################### 

609 # X11 Events 

610 ######################################### 

611 

612 def do_xpra_unmap_event(self, _event): 

613 self.unmanage() 

614 

615 def do_xpra_destroy_event(self, event): 

616 if event.delivered_to is self.client_window: 

617 # This is somewhat redundant with the unmap signal, because if you 

618 # destroy a mapped window, then a UnmapNotify is always generated. 

619 # However, this allows us to catch the destruction of unmapped 

620 # ("iconified") windows, and also catch any mistakes we might have 

621 # made with unmap heuristics. I love the smell of XDestroyWindow in 

622 # the morning. It makes for simple code: 

623 self.unmanage() 

624 

625 

626 def process_client_message_event(self, event): 

627 # FIXME 

628 # Need to listen for: 

629 # _NET_CURRENT_DESKTOP 

630 # _NET_WM_PING responses 

631 # and maybe: 

632 # _NET_RESTACK_WINDOW 

633 # _NET_WM_STATE (more fully) 

634 if event.message_type=="_NET_CLOSE_WINDOW": 

635 log.info("_NET_CLOSE_WINDOW received by %s", self) 

636 self.request_close() 

637 return True 

638 if event.message_type=="_NET_REQUEST_FRAME_EXTENTS": 

639 framelog("_NET_REQUEST_FRAME_EXTENTS") 

640 self._handle_frame_changed() 

641 return True 

642 if event.message_type=="_NET_MOVERESIZE_WINDOW": 

643 #this is overriden in WindowModel, skipped everywhere else: 

644 geomlog("_NET_MOVERESIZE_WINDOW skipped on %s (data=%s)", self, event.data) 

645 return True 

646 if event.message_type=="": 

647 log("empty message type: %s", event) 

648 if first_time("empty-x11-window-message-type-%#x" % event.window.get_xid()): 

649 log.warn("Warning: empty message type received for window %#x:", event.window.get_xid()) 

650 log.warn(" %s", event) 

651 log.warn(" further messages will be silently ignored") 

652 return True 

653 #not handled: 

654 return False 

655 

656 def do_xpra_configure_event(self, event): 

657 if self.client_window is None or not self._managed: 

658 return 

659 #shouldn't the border width always be 0? 

660 geom = (event.x, event.y, event.width, event.height) 

661 geomlog("CoreX11WindowModel.do_xpra_configure_event(%s) client_window=%#x, new geometry=%s", 

662 event, self.xid, geom) 

663 self._updateprop("geometry", geom) 

664 

665 

666 def do_xpra_shape_event(self, event): 

667 shapelog("shape event: %s, kind=%s", event, SHAPE_KIND.get(event.kind, event.kind)) # @UndefinedVariable 

668 cur_shape = self.get_property("shape") 

669 if cur_shape and cur_shape.get("serial", 0)>=event.serial: 

670 shapelog("same or older xshape serial no: %#x (current=%#x)", event.serial, cur_shape.get("serial", 0)) 

671 return 

672 #remove serial before comparing dicts: 

673 cur_shape.pop("serial", None) 

674 #read new xshape: 

675 with xswallow: 

676 #should we pass the x and y offsets here? 

677 #v = self._read_xshape(event.x, event.y) 

678 if event.shaped: 

679 v = self._read_xshape() 

680 else: 

681 v = {} 

682 if cur_shape==v: 

683 shapelog("xshape unchanged") 

684 return 

685 v["serial"] = int(event.serial) 

686 shapelog("xshape updated with serial %#x", event.serial) 

687 self._internal_set_property("shape", v) 

688 

689 

690 def do_xpra_xkb_event(self, event): 

691 #X11: XKBNotify 

692 log("WindowModel.do_xpra_xkb_event(%r)" % event) 

693 if event.subtype!="bell": 

694 log.error("WindowModel.do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type)) 

695 return 

696 event.window_model = self 

697 self.emit("bell", event) 

698 

699 def do_xpra_client_message_event(self, event): 

700 #X11: ClientMessage 

701 log("do_xpra_client_message_event(%s)", event) 

702 if not event.data or len(event.data)!=5: 

703 log.warn("invalid event data: %s", event.data) 

704 return 

705 if not self.process_client_message_event(event): 

706 log.warn("do_xpra_client_message_event(%s) not handled", event) 

707 

708 

709 def do_xpra_focus_in_event(self, event): 

710 #X11: FocusIn 

711 grablog("focus_in_event(%s) mode=%s, detail=%s", 

712 event, GRAB_CONSTANTS.get(event.mode), DETAIL_CONSTANTS.get(event.detail, event.detail)) 

713 if event.mode==NotifyNormal and event.detail==NotifyNonlinearVirtual: 

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

715 else: 

716 self.may_emit_grab(event) 

717 

718 def do_xpra_focus_out_event(self, event): 

719 #X11: FocusOut 

720 grablog("focus_out_event(%s) mode=%s, detail=%s", 

721 event, GRAB_CONSTANTS.get(event.mode), DETAIL_CONSTANTS.get(event.detail, event.detail)) 

722 self.may_emit_grab(event) 

723 

724 def may_emit_grab(self, event): 

725 if event.mode==NotifyGrab: 

726 grablog("emitting grab on %s", self) 

727 self.emit("grab", event) 

728 if event.mode==NotifyUngrab: 

729 grablog("emitting ungrab on %s", self) 

730 self.emit("ungrab", event) 

731 

732 

733 def do_xpra_motion_event(self, event): 

734 self.emit("motion", event) 

735 

736 

737 ################################ 

738 # Actions 

739 ################################ 

740 

741 def raise_window(self): 

742 X11Window.XRaiseWindow(self.client_window.get_xid()) 

743 

744 def set_active(self): 

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

746 prop_set(root, "_NET_ACTIVE_WINDOW", "u32", self.xid) 

747 

748 

749 ################################ 

750 # Killing clients: 

751 ################################ 

752 

753 def request_close(self): 

754 if b"WM_DELETE_WINDOW" in self.get_property("protocols"): 

755 with xswallow: 

756 send_wm_delete_window(self.client_window) 

757 else: 

758 title = self.get_property("title") 

759 xid = self.get_property("xid") 

760 if FORCE_QUIT: 

761 log.info("window %#x ('%s') does not support WM_DELETE_WINDOW", xid, title) 

762 log.info(" using force_quit") 

763 # You don't wanna play ball? Then no more Mr. Nice Guy! 

764 self.force_quit() 

765 else: 

766 log.warn("window %#x ('%s') cannot be closed,", xid, title) 

767 log.warn(" it does not support WM_DELETE_WINDOW") 

768 log.warn(" and FORCE_QUIT is disabled") 

769 

770 def force_quit(self): 

771 pid = self.get_property("pid") 

772 machine = self.get_property("client-machine") 

773 from socket import gethostname 

774 localhost = gethostname() 

775 log("force_quit() pid=%s, machine=%s, localhost=%s", pid, machine, localhost) 

776 def XKill(): 

777 with xswallow: 

778 X11Window.XKillClient(self.xid) 

779 if pid > 0 and machine is not None and machine == localhost: 

780 if pid==os.getpid(): 

781 log.warn("force_quit() refusing to kill ourselves!") 

782 return 

783 if self._kill_count==0: 

784 #first time around: just send a SIGINT and hope for the best 

785 try: 

786 os.kill(pid, signal.SIGINT) 

787 except OSError: 

788 log.warn("failed to kill(SIGINT) client with pid %s", pid) 

789 else: 

790 #the more brutal way: SIGKILL + XKill 

791 try: 

792 os.kill(pid, signal.SIGKILL) 

793 except OSError: 

794 log.warn("failed to kill(SIGKILL) client with pid %s", pid) 

795 XKill() 

796 self._kill_count += 1 

797 return 

798 XKill()