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

7import os 

8from gi.repository import GObject, Gdk 

9 

10from xpra.util import envbool 

11from xpra.common import MAX_WINDOW_SIZE 

12from xpra.gtk_common.error import xsync, xswallow 

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

14from xpra.x11.window_info import window_name, window_info 

15from xpra.gtk_common.gobject_util import no_arg_signal, one_arg_signal 

16from xpra.gtk_common.gtk_util import get_default_root_window, GDKWindow 

17from xpra.x11.common import Unmanageable 

18from xpra.x11.gtk_x11.selection import ManagerSelection 

19from xpra.x11.models.window import WindowModel, configure_bits 

20from xpra.x11.gtk_x11.world_window import WorldWindow, destroy_world_window 

21from xpra.x11.gtk_x11.gdk_bindings import ( 

22 add_event_receiver, #@UnresolvedImport 

23 add_fallback_receiver, remove_fallback_receiver, #@UnresolvedImport 

24 get_children, #@UnresolvedImport 

25 ) 

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

27from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport 

28from xpra.log import Logger 

29 

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

31 

32X11Window = X11WindowBindings() 

33X11Keyboard = X11KeyboardBindings() 

34 

35focuslog = Logger("x11", "window", "focus") 

36screenlog = Logger("x11", "window", "screen") 

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

38 

39CWX = constants["CWX"] 

40CWY = constants["CWY"] 

41CWWidth = constants["CWWidth"] 

42CWHeight = constants["CWHeight"] 

43 

44NotifyPointerRoot = constants["NotifyPointerRoot"] 

45NotifyDetailNone = constants["NotifyDetailNone"] 

46 

47LOG_MANAGE_FAILURES = envbool("XPRA_LOG_MANAGE_FAILURES", False) 

48 

49NO_NET_SUPPORTED = os.environ.get("XPRA_NO_NET_SUPPORTED", "").split(",") 

50 

51DEFAULT_NET_SUPPORTED = [ 

52 "_NET_SUPPORTED", # a bit redundant, perhaps... 

53 "_NET_SUPPORTING_WM_CHECK", 

54 "_NET_WM_FULL_PLACEMENT", 

55 "_NET_WM_HANDLED_ICONS", 

56 "_NET_CLIENT_LIST", 

57 "_NET_CLIENT_LIST_STACKING", 

58 "_NET_DESKTOP_VIEWPORT", 

59 "_NET_DESKTOP_GEOMETRY", 

60 "_NET_NUMBER_OF_DESKTOPS", 

61 "_NET_DESKTOP_NAMES", 

62 "_NET_WORKAREA", 

63 "_NET_ACTIVE_WINDOW", 

64 "_NET_CURRENT_DESKTOP", 

65 

66 "WM_NAME", "_NET_WM_NAME", 

67 "WM_ICON_NAME", "_NET_WM_ICON_NAME", 

68 "WM_CLASS", 

69 "WM_PROTOCOLS", 

70 "_NET_WM_PID", 

71 "WM_CLIENT_MACHINE", 

72 "WM_STATE", 

73 

74 "_NET_WM_FULLSCREEN_MONITORS", 

75 

76 "_NET_WM_ALLOWED_ACTIONS", 

77 "_NET_WM_ACTION_CLOSE", 

78 "_NET_WM_ACTION_FULLSCREEN", 

79 

80 # We don't actually use _NET_WM_USER_TIME at all (yet), but it is 

81 # important to say we support the _NET_WM_USER_TIME_WINDOW property, 

82 # because this tells applications that they do not need to constantly 

83 # ping any pagers etc. that might be running -- see EWMH for details. 

84 # (Though it's not clear that any applications actually take advantage 

85 # of this yet.) 

86 "_NET_WM_USER_TIME", 

87 "_NET_WM_USER_TIME_WINDOW", 

88 # Not fully: 

89 "WM_HINTS", 

90 "WM_NORMAL_HINTS", 

91 "WM_TRANSIENT_FOR", 

92 "_NET_WM_STRUT", 

93 "_NET_WM_STRUT_PARTIAL" 

94 "_NET_WM_ICON", 

95 

96 "_NET_CLOSE_WINDOW", 

97 

98 # These aren't supported in any particularly meaningful way, but hey. 

99 "_NET_WM_WINDOW_TYPE", 

100 "_NET_WM_WINDOW_TYPE_NORMAL", 

101 "_NET_WM_WINDOW_TYPE_DESKTOP", 

102 "_NET_WM_WINDOW_TYPE_DOCK", 

103 "_NET_WM_WINDOW_TYPE_TOOLBAR", 

104 "_NET_WM_WINDOW_TYPE_MENU", 

105 "_NET_WM_WINDOW_TYPE_UTILITY", 

106 "_NET_WM_WINDOW_TYPE_SPLASH", 

107 "_NET_WM_WINDOW_TYPE_DIALOG", 

108 "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", 

109 "_NET_WM_WINDOW_TYPE_POPUP_MENU", 

110 "_NET_WM_WINDOW_TYPE_TOOLTIP", 

111 "_NET_WM_WINDOW_TYPE_NOTIFICATION", 

112 "_NET_WM_WINDOW_TYPE_COMBO", 

113 # "_NET_WM_WINDOW_TYPE_DND", 

114 

115 "_NET_WM_STATE", 

116 "_NET_WM_STATE_DEMANDS_ATTENTION", 

117 "_NET_WM_STATE_MODAL", 

118 # More states to support: 

119 "_NET_WM_STATE_STICKY", 

120 "_NET_WM_STATE_MAXIMIZED_VERT", 

121 "_NET_WM_STATE_MAXIMIZED_HORZ", 

122 "_NET_WM_STATE_SHADED", 

123 "_NET_WM_STATE_SKIP_TASKBAR", 

124 "_NET_WM_STATE_SKIP_PAGER", 

125 "_NET_WM_STATE_HIDDEN", 

126 "_NET_WM_STATE_FULLSCREEN", 

127 "_NET_WM_STATE_ABOVE", 

128 "_NET_WM_STATE_BELOW", 

129 "_NET_WM_STATE_FOCUSED", 

130 

131 "_NET_WM_DESKTOP", 

132 

133 "_NET_WM_MOVERESIZE", 

134 "_NET_MOVERESIZE_WINDOW", 

135 

136 "_MOTIF_WM_HINTS", 

137 "_MOTIF_WM_INFO", 

138 

139 "_NET_REQUEST_FRAME_EXTENTS", 

140 "_NET_RESTACK_WINDOW", 

141 ] 

142FRAME_EXTENTS = envbool("XPRA_FRAME_EXTENTS", True) 

143if FRAME_EXTENTS: 

144 DEFAULT_NET_SUPPORTED.append("_NET_FRAME_EXTENTS") 

145 

146NET_SUPPORTED = [x for x in DEFAULT_NET_SUPPORTED if x not in NO_NET_SUPPORTED] 

147 

148DEFAULT_SIZE_CONSTRAINTS = (0, 0, MAX_WINDOW_SIZE, MAX_WINDOW_SIZE) 

149 

150 

151class Wm(GObject.GObject): 

152 

153 __gproperties__ = { 

154 "windows": (GObject.TYPE_PYOBJECT, 

155 "Set of managed windows (as WindowModels)", "", 

156 GObject.ParamFlags.READABLE), 

157 "toplevel": (GObject.TYPE_PYOBJECT, 

158 "Toplevel container widget for the display", "", 

159 GObject.ParamFlags.READABLE), 

160 } 

161 __gsignals__ = { 

162 # Public use: 

163 # A new window has shown up: 

164 "new-window": one_arg_signal, 

165 "show-desktop": one_arg_signal, 

166 # You can emit this to cause the WM to quit, or the WM may 

167 # spontaneously raise it if another WM takes over the display. By 

168 # default, unmanages all windows: 

169 "quit": no_arg_signal, 

170 

171 # Mostly intended for internal use: 

172 "child-map-request-event": one_arg_signal, 

173 "child-configure-request-event": one_arg_signal, 

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

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

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

177 "xpra-xkb-event": one_arg_signal, 

178 } 

179 

180 def __init__(self, replace_other_wm, wm_name, display=None): 

181 GObject.GObject.__init__(self) 

182 

183 if display is None: 

184 display = Gdk.Display.get_default() 

185 self._display = display 

186 self._root = self._display.get_default_screen().get_root_window() 

187 self._wm_name = wm_name 

188 self._ewmh_window = None 

189 

190 self._windows = {} 

191 # EWMH says we have to know the order of our windows oldest to 

192 # youngest... 

193 self._windows_in_order = [] 

194 

195 # Become the Official Window Manager of this year's display: 

196 self._wm_selection = ManagerSelection("WM_S0") 

197 self._cm_wm_selection = ManagerSelection("_NET_WM_CM_S0") 

198 self._wm_selection.connect("selection-lost", self._lost_wm_selection) 

199 self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) 

200 # May throw AlreadyOwned: 

201 if replace_other_wm: 

202 mode = self._wm_selection.FORCE 

203 else: 

204 mode = self._wm_selection.IF_UNOWNED 

205 self._wm_selection.acquire(mode) 

206 self._cm_wm_selection.acquire(mode) 

207 

208 # Set up the necessary EWMH properties on the root window. 

209 self._setup_ewmh_window() 

210 # Start with just one desktop: 

211 self.set_desktop_list((u"Main", )) 

212 self.set_current_desktop(0) 

213 # Start with the full display as workarea: 

214 root_w, root_h = get_default_root_window().get_geometry()[2:4] 

215 self.root_set("_NET_SUPPORTED", ["atom"], NET_SUPPORTED) 

216 self.set_workarea(0, 0, root_w, root_h) 

217 self.set_desktop_geometry(root_w, root_h) 

218 self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) 

219 

220 self.size_constraints = DEFAULT_SIZE_CONSTRAINTS 

221 

222 # Load up our full-screen widget 

223 self._world_window = WorldWindow(self._display.get_default_screen()) 

224 self.notify("toplevel") 

225 self._world_window.show_all() 

226 

227 # Okay, ready to select for SubstructureRedirect and then load in all 

228 # the existing clients. 

229 add_event_receiver(self._root, self) 

230 add_fallback_receiver("xpra-client-message-event", self) 

231 #when reparenting, the events may get sent 

232 #to a window that is already destroyed 

233 #and we don't want to miss those events, so: 

234 add_fallback_receiver("child-map-request-event", self) 

235 rxid = self._root.get_xid() 

236 X11Window.substructureRedirect(rxid) 

237 

238 for w in get_children(self._root): 

239 # Checking for FOREIGN here filters out anything that we've 

240 # created ourselves (like, say, the world window), and checking 

241 # for mapped filters out any withdrawn windows. 

242 xid = w.get_xid() 

243 if (w.get_window_type() == Gdk.WindowType.FOREIGN 

244 and not X11Window.is_override_redirect(xid) 

245 and X11Window.is_mapped(xid)): 

246 log("Wm managing pre-existing child window %#x", xid) 

247 self._manage_client(w) 

248 

249 # Also watch for focus change events on the root window 

250 X11Window.selectFocusChange(rxid) 

251 X11Keyboard.selectBellNotification(True) 

252 

253 # FIXME: 

254 # Need viewport abstraction for _NET_CURRENT_DESKTOP... 

255 # Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA 

256 # (and notifications for both) 

257 

258 def root_set(self, *args): 

259 prop_set(self._root, *args) 

260 

261 def root_get(self, *args): 

262 return prop_get(self._root, *args) 

263 

264 def set_dpi(self, xdpi, ydpi): 

265 #this is used by some newer versions of the dummy driver (xf86-driver-dummy) 

266 #(and will not be honoured by anything else..) 

267 self.root_set("dummy-constant-xdpi", "u32", xdpi) 

268 self.root_set("dummy-constant-ydpi", "u32", ydpi) 

269 screenlog("set_dpi(%i, %i)", xdpi, ydpi) 

270 

271 def set_workarea(self, x, y, width, height): 

272 v = [x, y, width, height] 

273 screenlog("_NET_WORKAREA=%s", v) 

274 self.root_set("_NET_WORKAREA", ["u32"], v) 

275 

276 def set_desktop_geometry(self, width, height): 

277 v = [width, height] 

278 screenlog("_NET_DESKTOP_GEOMETRY=%s", v) 

279 self.root_set("_NET_DESKTOP_GEOMETRY", ["u32"], v) 

280 #update all the windows: 

281 for model in self._windows.values(): 

282 model.update_desktop_geometry(width, height) 

283 

284 def set_size_constraints(self, minw=0, minh=0, maxw=MAX_WINDOW_SIZE, maxh=MAX_WINDOW_SIZE): 

285 log("set_size_constraints%s", (minw, minh, maxw, maxh)) 

286 self.size_constraints = minw, minh, maxw, maxh 

287 #update all the windows: 

288 for model in self._windows.values(): 

289 model.update_size_constraints(minw, minh, maxw, maxh) 

290 

291 

292 def set_default_frame_extents(self, v): 

293 framelog("set_default_frame_extents(%s)", v) 

294 if not v or len(v)!=4: 

295 v = (0, 0, 0, 0) 

296 self.root_set("DEFAULT_NET_FRAME_EXTENTS", ["u32"], v) 

297 #update the models that are using the global default value: 

298 for win in self._windows.values(): 

299 if win.is_OR() or win.is_tray(): 

300 continue 

301 cur = win.get_property("frame") 

302 if cur is None: 

303 win._handle_frame_changed() 

304 

305 

306 def do_get_property(self, pspec): 

307 if pspec.name == "windows": 

308 return frozenset(self._windows.values()) 

309 if pspec.name == "toplevel": 

310 return self._world_window 

311 assert False 

312 

313 # This is in some sense the key entry point to the entire WM program. We 

314 # have detected a new client window, and start managing it: 

315 def _manage_client(self, gdkwindow): 

316 if not gdkwindow: 

317 return 

318 if gdkwindow in self._windows: 

319 #already managed 

320 return 

321 try: 

322 with xsync: 

323 log("_manage_client(%s)", gdkwindow) 

324 desktop_geometry = self.root_get("_NET_DESKTOP_GEOMETRY", ["u32"], True, False) 

325 win = WindowModel(self._root, gdkwindow, desktop_geometry, self.size_constraints) 

326 except Exception as e: 

327 if LOG_MANAGE_FAILURES or not isinstance(e, Unmanageable): 

328 l = log.warn 

329 else: 

330 l = log 

331 l("Warning: failed to manage client window %#x:", gdkwindow.get_xid()) 

332 l(" %s", e) 

333 l("", exc_info=True) 

334 with xswallow: 

335 l(" window name: %s", window_name(gdkwindow)) 

336 l(" window info: %s", window_info(gdkwindow)) 

337 else: 

338 win.managed_connect("unmanaged", self._handle_client_unmanaged) 

339 self._windows[gdkwindow] = win 

340 self._windows_in_order.append(gdkwindow) 

341 self.notify("windows") 

342 self._update_window_list() 

343 self.emit("new-window", win) 

344 

345 def _handle_client_unmanaged(self, window, _wm_exiting): 

346 gdkwindow = window.get_property("client-window") 

347 assert gdkwindow in self._windows 

348 del self._windows[gdkwindow] 

349 self._windows_in_order.remove(gdkwindow) 

350 self._update_window_list() 

351 self.notify("windows") 

352 

353 def _update_window_list(self, *_args): 

354 # Ignore errors because not all the windows may still exist; if so, 

355 # then it's okay to leave the lists out of date for a moment, because 

356 # in a moment we'll get a signal telling us about the window that 

357 # doesn't exist anymore, will remove it from the list, and then call 

358 # _update_window_list again. 

359 with xswallow: 

360 self.root_set("_NET_CLIENT_LIST", ["window"], self._windows_in_order) 

361 # This is a lie, but we don't maintain a stacking order, so... 

362 self.root_set("_NET_CLIENT_LIST_STACKING", ["window"], self._windows_in_order) 

363 

364 def do_xpra_client_message_event(self, event): 

365 # FIXME 

366 # Need to listen for: 

367 # _NET_ACTIVE_WINDOW 

368 # _NET_CURRENT_DESKTOP 

369 # _NET_WM_PING responses 

370 # and maybe: 

371 # _NET_WM_STATE 

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

373 if event.message_type=="_NET_SHOWING_DESKTOP": 

374 show = bool(event.data[0]) 

375 self.emit("show-desktop", show) 

376 elif event.message_type=="_NET_REQUEST_FRAME_EXTENTS" and FRAME_EXTENTS: 

377 #if we're here, that means the window model does not exist 

378 #(or it would have processed the event) 

379 #so this must be a an unmapped window 

380 frame = (0, 0, 0, 0) 

381 with xswallow: 

382 if not X11Window.is_override_redirect(event.window.get_xid()): 

383 #use the global default: 

384 frame = prop_get(self._root, "DEFAULT_NET_FRAME_EXTENTS", ["u32"], ignore_errors=True) 

385 if not frame: 

386 #fallback: 

387 frame = (0, 0, 0, 0) 

388 framelog("_NET_REQUEST_FRAME_EXTENTS: setting _NET_FRAME_EXTENTS=%s on %#x", 

389 frame, event.window.get_xid()) 

390 prop_set(event.window, "_NET_FRAME_EXTENTS", ["u32"], frame) 

391 

392 def _lost_wm_selection(self, selection): 

393 log.info("Lost WM selection %s, exiting", selection) 

394 self.emit("quit") 

395 

396 def do_quit(self): 

397 self.cleanup() 

398 

399 def cleanup(self): 

400 remove_fallback_receiver("xpra-client-message-event", self) 

401 remove_fallback_receiver("child-map-request-event", self) 

402 for win in tuple(self._windows.values()): 

403 win.unmanage(True) 

404 with xswallow: 

405 prop_del(self._ewmh_window, "_NET_SUPPORTING_WM_CHECK") 

406 prop_del(self._ewmh_window, "_NET_WM_NAME") 

407 destroy_world_window() 

408 

409 

410 def do_child_map_request_event(self, event): 

411 log("Found a potential client") 

412 self._manage_client(event.window) 

413 

414 def do_child_configure_request_event(self, event): 

415 # The point of this method is to handle configure requests on 

416 # withdrawn windows. We simply allow them to move/resize any way they 

417 # want. This is harmless because the window isn't visible anyway (and 

418 # apps can create unmapped windows with whatever coordinates they want 

419 # anyway, no harm in letting them move existing ones around), and it 

420 # means that when the window actually gets mapped, we have more 

421 # accurate info on what the app is actually requesting. 

422 model = self._windows.get(event.window) 

423 if model: 

424 #the window has been reparented already, 

425 #but we're getting the configure request event on the root window 

426 #forward it to the model 

427 log("do_child_configure_request_event(%s) value_mask=%s, forwarding to %s", 

428 event, configure_bits(event.value_mask), model) 

429 model.do_child_configure_request_event(event) 

430 return 

431 log("do_child_configure_request_event(%s) value_mask=%s, reconfigure on withdrawn window", 

432 event, configure_bits(event.value_mask)) 

433 with xswallow: 

434 xid = event.window.get_xid() 

435 x, y, w, h = X11Window.getGeometry(xid)[:4] 

436 if event.value_mask & CWX: 

437 x = event.x 

438 if event.value_mask & CWY: 

439 y = event.y 

440 if event.value_mask & CWWidth: 

441 w = event.width 

442 if event.value_mask & CWHeight: 

443 h = event.height 

444 if event.value_mask & (CWX | CWY | CWWidth | CWHeight): 

445 log("updated window geometry for window %#x from %s to %s", 

446 xid, X11Window.getGeometry(xid)[:4], (x, y, w, h)) 

447 X11Window.configureAndNotify(xid, x, y, w, h, event.value_mask) 

448 

449 def do_xpra_focus_in_event(self, event): 

450 # The purpose of this function is to detect when the focus mode has 

451 # gone to PointerRoot or None, so that it can be given back to 

452 # something real. This is easy to detect -- a FocusIn event with 

453 # detail PointerRoot or None is generated on the root window. 

454 focuslog("wm.do_xpra_focus_in_event(%s)", event) 

455 if event.detail in (NotifyPointerRoot, NotifyDetailNone) and self._world_window: 

456 self._world_window.reset_x_focus() 

457 

458 def do_xpra_focus_out_event(self, event): 

459 focuslog("wm.do_xpra_focus_out_event(%s) XGetInputFocus=%s", event, X11Window.XGetInputFocus()) 

460 

461 def set_desktop_list(self, desktops): 

462 log("set_desktop_list(%s)", desktops) 

463 self.root_set("_NET_NUMBER_OF_DESKTOPS", "u32", len(desktops)) 

464 self.root_set("_NET_DESKTOP_NAMES", ["utf8"], desktops) 

465 

466 def set_current_desktop(self, index): 

467 self.root_set("_NET_CURRENT_DESKTOP", "u32", index) 

468 

469 def _setup_ewmh_window(self): 

470 # Set up a 1x1 invisible unmapped window, with which to participate in 

471 # EWMH's _NET_SUPPORTING_WM_CHECK protocol. The only important things 

472 # about this window are the _NET_SUPPORTING_WM_CHECK property, and 

473 # its title (which is supposed to be the name of the window manager). 

474 

475 # NB, GDK will do strange things to this window. We don't want to use 

476 # it for anything. (In particular, it will call XSelectInput on it, 

477 # which is fine normally when GDK is running in a client, but since it 

478 # happens to be using the same connection as we the WM, it will 

479 # clobber any XSelectInput calls that *we* might have wanted to make 

480 # on this window.) Also, GDK might silently swallow all events that 

481 # are detected on it, anyway. 

482 self._ewmh_window = GDKWindow(self._root, wclass=Gdk.WindowWindowClass.INPUT_ONLY, title=self._wm_name) 

483 prop_set(self._ewmh_window, "_NET_SUPPORTING_WM_CHECK", 

484 "window", self._ewmh_window) 

485 self.root_set("_NET_SUPPORTING_WM_CHECK", "window", self._ewmh_window) 

486 self.root_set("_NET_WM_NAME", "utf8", self._wm_name) 

487 

488 def get_net_wm_name(self): 

489 try: 

490 return prop_get(self._ewmh_window, "_NET_WM_NAME", "utf8", ignore_errors=False, raise_xerrors=False) 

491 except Exception as e: 

492 log.error("error querying _NET_WM_NAME: %s", e) 

493 

494GObject.type_register(Wm)