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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

4# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com> 

5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

6# later version. See the file COPYING for details. 

7 

8import os 

9import re 

10 

11from xpra.client.client_widget_base import ClientWidgetBase 

12from xpra.os_util import bytestostr, OSX, WIN32, is_Wayland 

13from xpra.common import GRAVITY_STR 

14from xpra.util import typedict, envbool, envint, WORKSPACE_UNSET, WORKSPACE_NAMES 

15from xpra.log import Logger 

16 

17log = Logger("window") 

18plog = Logger("paint") 

19focuslog = Logger("focus") 

20mouselog = Logger("mouse") 

21workspacelog = Logger("workspace") 

22keylog = Logger("keyboard") 

23metalog = Logger("metadata") 

24geomlog = Logger("geometry") 

25iconlog = Logger("icon") 

26alphalog = Logger("alpha") 

27 

28 

29SIMULATE_MOUSE_DOWN = envbool("XPRA_SIMULATE_MOUSE_DOWN", True) 

30PROPERTIES_DEBUG = [x.strip() for x in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")] 

31SET_SIZE_CONSTRAINTS = envbool("XPRA_SET_SIZE_CONSTRAINTS", True) 

32DEFAULT_GRAVITY = envint("XPRA_DEFAULT_GRAVITY", 0) 

33OVERRIDE_GRAVITY = envint("XPRA_OVERRIDE_GRAVITY", 0) 

34 

35 

36class ClientWindowBase(ClientWidgetBase): 

37 

38 def __init__(self, client, group_leader, watcher_pid, wid, 

39 wx, wy, ww, wh, bw, bh, 

40 metadata, override_redirect, client_properties, 

41 border, max_window_size, default_cursor_data, pixel_depth, 

42 headerbar="no"): 

43 log("%s%s", type(self), 

44 (client, group_leader, watcher_pid, wid, 

45 wx, wy, ww, wh, bw, bh, 

46 metadata, override_redirect, client_properties, 

47 border, max_window_size, default_cursor_data, pixel_depth, 

48 headerbar)) 

49 super().__init__(client, watcher_pid, wid, metadata.boolget("has-alpha")) 

50 self._override_redirect = override_redirect 

51 self.group_leader = group_leader 

52 self._pos = (wx, wy) 

53 self._size = (ww, wh) 

54 self._client_properties = client_properties 

55 self._set_initial_position = metadata.boolget("set-initial-position", False) 

56 self.size_constraints = typedict() 

57 self.geometry_hints = {} 

58 self.content_type = "" 

59 self._fullscreen = None 

60 self._maximized = False 

61 self._above = False 

62 self._below = False 

63 self._shaded = False 

64 self._sticky = False 

65 self._skip_pager = False 

66 self._skip_taskbar = False 

67 self._iconified = False 

68 self._focused = False 

69 self.window_gravity = OVERRIDE_GRAVITY or DEFAULT_GRAVITY 

70 self.border = border 

71 self.cursor_data = None 

72 self.default_cursor_data = default_cursor_data 

73 self.max_window_size = max_window_size 

74 self.button_state = {} 

75 self.pixel_depth = pixel_depth #0 for default 

76 #window_offset is the delta between the location of the window requested by the server, 

77 #and where we actually end up mapping it on the client 

78 #(ie: when we reposition an OR window to ensure it is visible on screen) 

79 self.window_offset = None 

80 self.pending_refresh = [] 

81 self.headerbar = headerbar 

82 

83 self.init_window(metadata) 

84 self.setup_window(bw, bh) 

85 self.update_metadata(metadata) 

86 

87 def __repr__(self): 

88 return "ClientWindow(%s)" % self._id 

89 

90 def init_window(self, metadata): 

91 self._backing = None 

92 self._metadata = typedict() 

93 # used for only sending focus events *after* the window is mapped: 

94 self._been_mapped = False 

95 self._override_redirect_windows = [] 

96 def wn(w): 

97 return WORKSPACE_NAMES.get(w, w) 

98 workspace = typedict(self._client_properties).intget("workspace", None) 

99 workspacelog("init_window(..) workspace from client properties %s: %s", self._client_properties, wn(workspace)) 

100 if workspace is not None: 

101 #client properties override application specified workspace value on init only: 

102 metadata[b"workspace"] = workspace 

103 self._window_workspace = WORKSPACE_UNSET #will get set in set_metadata if present 

104 self._desktop_workspace = self.get_desktop_workspace() #pylint: disable=assignment-from-none 

105 workspacelog("init_window(..) workspace=%s, current workspace=%s", 

106 wn(self._window_workspace), wn(self._desktop_workspace)) 

107 if self.max_window_size and b"size-constraints" not in metadata: 

108 #this ensures that we will set size-constraints and honour max_window_size: 

109 metadata[b"size-constraints"] = {} 

110 #initialize gravity early: 

111 sc = typedict(metadata.dictget("size-constraints", {})) 

112 self.window_gravity = OVERRIDE_GRAVITY or sc.intget("gravity", DEFAULT_GRAVITY) 

113 self.set_decorated(metadata.boolget("decorations", True)) 

114 

115 

116 def get_info(self): 

117 attributes = [] 

118 if self._fullscreen: 

119 attributes.append("fullscreen") 

120 if self._maximized: 

121 attributes.append("maximized") 

122 if self._above: 

123 attributes.append("above") 

124 if self._below: 

125 attributes.append("below") 

126 if self._shaded: 

127 attributes.append("shaded") 

128 if self._sticky: 

129 attributes.append("sticky") 

130 if self._skip_pager: 

131 attributes.append("skip-pager") 

132 if self._skip_taskbar: 

133 attributes.append("skip-taskbar") 

134 if self._iconified: 

135 attributes.append("iconified") 

136 if self._focused: 

137 attributes.append("focused") 

138 info = super().get_info() 

139 info.update({ 

140 "override-redirect" : self._override_redirect, 

141 #"group-leader" : self.group_leader, 

142 "position" : self._pos, 

143 "size" : self._size, 

144 "client-properties" : self._client_properties, 

145 "set-initial-position" : self._set_initial_position, 

146 "size-constraints" : dict(self.size_constraints), 

147 "geometry-hints" : dict(self.geometry_hints), 

148 "content-type" : self.content_type, 

149 "attributes" : attributes, 

150 "gravity" : GRAVITY_STR.get(self.window_gravity), 

151 #"border" : self.border or "", 

152 #cursor_data 

153 "max-size" : self.max_window_size, 

154 "button-state" : self.button_state, 

155 "offset" : self.window_offset, 

156 }) 

157 return info 

158 

159 def get_desktop_workspace(self): 

160 return None 

161 

162 def get_window_workspace(self): 

163 return None 

164 

165 

166 def new_backing(self, bw, bh): 

167 backing_class = self.get_backing_class() 

168 log("new_backing(%s, %s) backing_class=%s", bw, bh, backing_class) 

169 assert backing_class is not None 

170 w, h = self._size 

171 self._backing = self.make_new_backing(backing_class, w, h, bw, bh) 

172 self._backing.border = self.border 

173 self._backing.default_cursor_data = self.default_cursor_data 

174 self._backing.gravity = self.window_gravity 

175 return self._backing._backing 

176 

177 

178 def destroy(self): 

179 #ensure we clear reference to other windows: 

180 self.group_leader = None 

181 self._override_redirect_windows = [] 

182 self._metadata = {} 

183 if self._backing: 

184 self._backing.close() 

185 self._backing = None 

186 

187 

188 def setup_window(self, bw, bh): 

189 self.new_backing(bw, bh) 

190 #tell the server about the encoding capabilities of this backing instance: 

191 #but don't bother if they're the same as what we sent as defaults 

192 #(with a bit of magic to collapse the missing namespace from encoding_defaults) 

193 backing_props = self._backing.get_encoding_properties() 

194 encoding_defaults = self._client.encoding_defaults 

195 for k in tuple(backing_props.keys()): 

196 v = backing_props[k] 

197 try: 

198 #ie: "encodings.rgb_formats" -> "rgb_formats" 

199 #ie: "encoding.full_csc_modes" -> "full_csc_modes" 

200 ek = k.split(".", 1)[1] 

201 except IndexError: 

202 ek = k 

203 dv = encoding_defaults.get(ek) 

204 if dv is not None and dv==v: 

205 del backing_props[k] 

206 self._client_properties.update(backing_props) 

207 

208 

209 def send(self, *args): 

210 self._client.send(*args) 

211 

212 def reset_icon(self): 

213 current_icon = self._current_icon 

214 iconlog("reset_icon() current icon=%s", current_icon) 

215 if current_icon: 

216 self.update_icon(current_icon) 

217 

218 def update_icon(self, img): 

219 raise NotImplementedError("override me!") 

220 

221 def apply_transient_for(self, wid): 

222 raise NotImplementedError("override me!") 

223 

224 def paint_spinner(self, context, area): 

225 raise NotImplementedError("override me!") 

226 

227 def _pointer_modifiers(self, event): 

228 raise NotImplementedError("override me!") 

229 

230 

231 def xget_u32_property(self, target, name): 

232 raise NotImplementedError("override me!") 

233 

234 

235 def is_OR(self): 

236 return self._override_redirect 

237 

238 

239 def update_metadata(self, metadata): 

240 metalog("update_metadata(%s)", metadata) 

241 if self._client.readonly: 

242 metadata.update(self._force_size_constraint(*self._size)) 

243 self._metadata.update(metadata) 

244 try: 

245 self.set_metadata(metadata) 

246 except Exception: 

247 metalog.warn("failed to set window metadata to '%s'", metadata, exc_info=True) 

248 

249 def _force_size_constraint(self, *size): 

250 return { 

251 b"size-constraints" : { 

252 b"maximum-size" : size, 

253 b"minimum-size" : size, 

254 b"base-size" : size, 

255 } 

256 } 

257 

258 def _get_window_title(self, metadata): 

259 try: 

260 title = bytestostr(self._client.title).replace("\0", "") 

261 if title.find("@")<0: 

262 return title 

263 #perform metadata variable substitutions: 

264 #full of py3k unicode headaches that don't need to be 

265 default_values = { 

266 "title" : "<untitled window>", 

267 "client-machine" : "<unknown machine>", 

268 "windowid" : str(self._id), 

269 "server-machine" : getattr(self._client, "_remote_hostname", None) or "<unknown machine>", 

270 "server-display" : getattr(self._client, "_remote_display", None) or "<unknown display>", 

271 } 

272 def getvar(var): 

273 #"hostname" is magic: 

274 #we try harder to find a useful value to show: 

275 if var in ("hostname", "hostinfo"): 

276 if var=="hostinfo" and getattr(self._client, "mmap_enabled", False): 

277 #this is a local connection for sure 

278 server_display = getattr(self._client, "server_display", None) 

279 if server_display: 

280 return server_display 

281 #try to find the hostname: 

282 proto = getattr(self._client, "_protocol", None) 

283 if proto: 

284 conn = getattr(proto, "_conn", None) 

285 if conn: 

286 hostname = conn.info.get("host") or bytestostr(conn.target) 

287 if hostname: 

288 return hostname 

289 for m in ("client-machine", "server-machine"): 

290 value = getvar(m) 

291 if value not in ( 

292 "localhost", 

293 "localhost.localdomain", 

294 "<unknown machine>", 

295 "", 

296 None): 

297 return value 

298 return "<unknown machine>" 

299 value = metadata.bytesget(var) or self._metadata.bytesget(var) 

300 if value is None: 

301 return default_values.get(var, "<unknown %s>" % var) 

302 try: 

303 return value.decode("utf-8") 

304 except UnicodeDecodeError: 

305 return str(value) 

306 def metadata_replace(match): 

307 atvar = match.group(0) #ie: '@title@' 

308 var = atvar[1:len(atvar)-1] #ie: 'title' 

309 if not var: 

310 #atvar = "@@" 

311 return "@" 

312 return getvar(var) 

313 sub = r"@[\w\-]*@" 

314 replaced = re.sub(sub, metadata_replace, title) 

315 metalog("re.sub%s=%s", (sub, metadata_replace, title), replaced) 

316 return replaced 

317 except Exception as e: 

318 log.error("Error parsing window title:") 

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

320 return "" 

321 

322 def set_metadata(self, metadata): 

323 metalog("set_metadata(%s)", metadata) 

324 debug_props = [x for x in PROPERTIES_DEBUG if x in metadata.keys()] 

325 for x in debug_props: 

326 metalog.info("set_metadata: %s=%s", x, metadata.get(x)) 

327 #WARNING: "class-instance" needs to go first because others may realize the window 

328 #(and GTK doesn't set the "class-instance" once the window is realized) 

329 if b"class-instance" in metadata: 

330 self.set_class_instance(*self._metadata.strtupleget("class-instance", ("xpra", "Xpra"), 2, 2)) 

331 self.reset_icon() 

332 

333 if b"title" in metadata: 

334 title = self._get_window_title(metadata) 

335 self.set_title(title) 

336 

337 if b"icon-title" in metadata: 

338 icon_title = metadata.strget("icon-title") 

339 self.set_icon_name(icon_title) 

340 #the DE may have reset the icon now, 

341 #force it to use the one we really want: 

342 self.reset_icon() 

343 

344 if b"size-constraints" in metadata: 

345 sc = typedict(metadata.dictget("size-constraints", {})) 

346 self.size_constraints = sc 

347 self._set_initial_position = sc.boolget("set-initial-position", self._set_initial_position) 

348 self.set_size_constraints(sc, self.max_window_size) 

349 

350 if b"set-initial-position" in metadata: 

351 #this should be redundant - but we keep it here for consistency 

352 self._set_initial_position = metadata.boolget("set-initial-position") 

353 

354 if b"transient-for" in metadata: 

355 wid = metadata.intget("transient-for", -1) 

356 self.apply_transient_for(wid) 

357 

358 if b"modal" in metadata: 

359 modal = metadata.boolget("modal") 

360 self.set_modal(modal) 

361 

362 #apply window-type hint if window has not been mapped yet: 

363 if b"window-type" in metadata and not self.get_mapped(): 

364 window_types = metadata.strtupleget("window-type") 

365 self.set_window_type(window_types) 

366 

367 if b"role" in metadata: 

368 role = metadata.strget("role") 

369 self.set_role(role) 

370 

371 if b"xid" in metadata: 

372 xid = metadata.strget("xid") 

373 self.set_xid(xid) 

374 

375 if b"opacity" in metadata: 

376 opacity = metadata.intget("opacity", -1) 

377 if opacity<0: 

378 opacity = 1 

379 else: 

380 opacity = min(1, opacity/0xffffffff) 

381 #requires gtk>=2.12! 

382 if hasattr(self, "set_opacity"): 

383 self.set_opacity(opacity) 

384 

385 if b"has-alpha" in metadata: 

386 new_alpha = metadata.boolget("has-alpha") 

387 if new_alpha!=self._has_alpha: 

388 l = alphalog 

389 if not WIN32: 

390 #win32 without opengl can't do transparency, 

391 #so it triggers too many warnings 

392 l = log.warn 

393 l("Warning: window %#x changed its transparency attribute", self._id) 

394 l(" from %s to %s, behaviour is undefined", self._has_alpha, new_alpha) 

395 self._has_alpha = new_alpha 

396 

397 if b"maximized" in metadata: 

398 maximized = metadata.boolget("maximized") 

399 if maximized!=self._maximized: 

400 self._maximized = maximized 

401 if maximized: 

402 self.maximize() 

403 else: 

404 self.unmaximize() 

405 

406 if b"fullscreen" in metadata: 

407 fullscreen = metadata.boolget("fullscreen") 

408 if self._fullscreen is None or self._fullscreen!=fullscreen: 

409 self._fullscreen = fullscreen 

410 self.set_fullscreen(fullscreen) 

411 

412 if b"iconic" in metadata: 

413 iconified = metadata.boolget("iconic") 

414 if self._iconified!=iconified: 

415 self._iconified = iconified 

416 if iconified: 

417 self.iconify() 

418 else: 

419 self.deiconify() 

420 

421 if b"decorations" in metadata: 

422 decorated = metadata.boolget("decorations", True) 

423 was_decorated = self.get_decorated() 

424 if WIN32 and decorated!=was_decorated: 

425 log.info("decorations flag toggled, now %s, re-initializing window", decorated) 

426 self.idle_add(self._client.reinit_window, self._id, self) 

427 else: 

428 self.set_decorated(metadata.boolget("decorations")) 

429 self.apply_geometry_hints(self.geometry_hints) 

430 

431 if b"above" in metadata: 

432 above = metadata.boolget("above") 

433 if self._above!=above: 

434 self._above = above 

435 self.set_keep_above(above) 

436 

437 if b"below" in metadata: 

438 below = metadata.boolget("below") 

439 if self._below!=below: 

440 self._below = below 

441 self.set_keep_below(below) 

442 

443 if b"shaded" in metadata: 

444 shaded = metadata.boolget("shaded") 

445 if self._shaded!=shaded: 

446 self._shaded = shaded 

447 self.set_shaded(shaded) 

448 

449 if b"sticky" in metadata: 

450 sticky = metadata.boolget("sticky") 

451 if self._sticky!=sticky: 

452 self._sticky = sticky 

453 if sticky: 

454 self.stick() 

455 else: 

456 self.unstick() 

457 

458 if b"skip-taskbar" in metadata: 

459 skip_taskbar = metadata.boolget("skip-taskbar") 

460 if self._skip_taskbar!=skip_taskbar: 

461 self._skip_taskbar = skip_taskbar 

462 self.set_skip_taskbar_hint(skip_taskbar) 

463 

464 if b"skip-pager" in metadata: 

465 skip_pager = metadata.boolget("skip-pager") 

466 if self._skip_pager!=skip_pager: 

467 self._skip_pager = skip_pager 

468 self.set_skip_pager_hint(skip_pager) 

469 

470 if b"workspace" in metadata: 

471 self.set_workspace(metadata.intget("workspace")) 

472 

473 if b"bypass-compositor" in metadata: 

474 self.set_bypass_compositor(metadata.intget("bypass-compositor")) 

475 

476 if b"strut" in metadata: 

477 self.set_strut(metadata.dictget("strut", {})) 

478 

479 if b"fullscreen-monitors" in metadata: 

480 self.set_fullscreen_monitors(metadata.inttupleget("fullscreen-monitors")) 

481 

482 if b"shape" in metadata: 

483 self.set_shape(metadata.dictget("shape", {})) 

484 

485 if b"command" in metadata: 

486 self.set_command(metadata.strget("command")) 

487 

488 if b"x11-property" in metadata: 

489 self.set_x11_property(*metadata.tupleget("x11-property")) 

490 

491 if b"content-type" in metadata: 

492 self.content_type = metadata.strget("content-type") 

493 

494 

495 def set_x11_property(self, *x11_property): 

496 pass 

497 

498 def set_command(self, command): 

499 pass 

500 

501 def set_class_instance(self, wmclass_name, wmclass_class): 

502 pass 

503 

504 def set_shape(self, shape): 

505 log("set_shape(%s) not implemented", shape) 

506 

507 def set_bypass_compositor(self, v): 

508 pass #see gtk client window base 

509 

510 def set_strut(self, strut): 

511 pass #see gtk client window base 

512 

513 def set_fullscreen_monitors(self, fsm): 

514 pass #see gtk client window base 

515 

516 def set_shaded(self, shaded): 

517 pass #see gtk client window base 

518 

519 

520 def reset_size_constraints(self): 

521 self.set_size_constraints(self.size_constraints, self.max_window_size) 

522 

523 def set_size_constraints(self, size_constraints, max_window_size): 

524 if not SET_SIZE_CONSTRAINTS: 

525 return 

526 geomlog("set_size_constraints(%s, %s)", size_constraints, max_window_size) 

527 hints = typedict() 

528 client = self._client 

529 for (a, h1, h2) in ( 

530 (b"maximum-size", b"max_width", b"max_height"), 

531 (b"minimum-size", b"min_width", b"min_height"), 

532 (b"base-size", b"base_width", b"base_height"), 

533 (b"increment", b"width_inc", b"height_inc"), 

534 ): 

535 v = size_constraints.intpair(a) 

536 geomlog("intpair(%s)=%s", a, v) 

537 if v: 

538 v1, v2 = v 

539 if a==b"maximum-size" and v1>=32000 and v2>=32000 and WIN32: 

540 #causes problems, see #2714 

541 continue 

542 sv1 = client.sx(v1) 

543 sv2 = client.sy(v2) 

544 if a in (b"base-size", b"increment"): 

545 #rounding is not allowed for these values 

546 fsv1 = client.fsx(v1) 

547 fsv2 = client.fsy(v2) 

548 def closetoint(v): 

549 #tolerate some rounding error: 

550 #(ie: 2:3 scaling may not give an integer without a tiny bit of rounding) 

551 return abs(int(v)-v)<0.00001 

552 if not closetoint(fsv1) or not closetoint(fsv2): 

553 #the scaled value is not close to an int, 

554 #so we can't honour it: 

555 geomlog("cannot honour '%s' due to scaling, scaled values are not both integers: %s, %s", 

556 a, fsv1, fsv2) 

557 continue 

558 hints[h1], hints[h2] = sv1, sv2 

559 if not OSX: 

560 for (a, h) in ( 

561 (b"minimum-aspect-ratio", b"min_aspect"), 

562 (b"maximum-aspect-ratio", b"max_aspect"), 

563 ): 

564 v = size_constraints.intpair(a) 

565 if v: 

566 v1, v2 = v 

567 hints[h] = (v1*self._client.xscale)/(v2*self._client.yscale) 

568 #apply max-size override if needed: 

569 w,h = max_window_size 

570 if w>0 and h>0 and not self._fullscreen: 

571 #get the min size, if there is one: 

572 minw = max(1, hints.intget(b"min_width", 1)) 

573 minh = max(1, hints.intget(b"min_height", 1)) 

574 #the actual max size is: 

575 # * greater than the min-size 

576 # * the lowest of the max-size set by the application and the one we have 

577 # * ensure we honour the other hints, and round the max-size down if needed: 

578 #according to the GTK docs: 

579 #allowed window widths are base_width + width_inc * N where N is any integer 

580 #allowed window heights are base_height + width_inc * N where N is any integer 

581 maxw = hints.intget(b"max_width", 32768) 

582 maxh = hints.intget(b"max_height", 32768) 

583 maxw = max(minw, min(w, maxw)) 

584 maxh = max(minh, min(h, maxh)) 

585 rw = (maxw - hints.intget(b"base_width", 0)) % max(hints.intget(b"width_inc", 1), 1) 

586 rh = (maxh - hints.intget(b"base_height", 0)) % max(hints.intget(b"height_inc", 1), 1) 

587 maxw -= rw 

588 maxh -= rh 

589 #if the hints combination is invalid, it's possible that we'll end up 

590 #not honouring "base" + "inc", but honouring just "min" instead: 

591 maxw = max(minw, maxw) 

592 maxh = max(minh, maxh) 

593 geomlog("modified hints for max window size %s: %s (rw=%s, rh=%s) -> max=%sx%s", 

594 max_window_size, hints, rw, rh, maxw, maxh) 

595 #ensure we don't have duplicates with bytes / strings, 

596 #and that keys are always "bytes": 

597 #(in practice this code should never fire, just here as a reminder) 

598 for x in ("max_width", "max_height"): 

599 hints.pop(x, None) 

600 #bug 2214: GTK3 on win32 gets confused if we specify a large max-size 

601 # and it will mess up maximizing the window 

602 if not WIN32 or (maxw<32000 or maxh<32000): 

603 hints[b"max_width"] = maxw 

604 hints[b"max_height"] = maxh 

605 try: 

606 geomlog("calling: %s(%s)", self.apply_geometry_hints, hints) 

607 #save them so the window hooks can use the last value used: 

608 self.geometry_hints = hints 

609 self.apply_geometry_hints(hints) 

610 except Exception: 

611 geomlog("set_size_constraints%s", (size_constraints, max_window_size), exc_info=True) 

612 geomlog.error("Error setting window hints:") 

613 for k,v in hints.items(): 

614 geomlog.error(" %s=%s", bytestostr(k), v) 

615 geomlog.error(" from size constraints:") 

616 for k,v in size_constraints.items(): 

617 geomlog.error(" %s=%s", k, v) 

618 self.window_gravity = OVERRIDE_GRAVITY or size_constraints.intget("gravity", DEFAULT_GRAVITY) 

619 b = self._backing 

620 if b: 

621 b.gravity = self.window_gravity 

622 

623 

624 def set_window_type(self, window_types): 

625 pass 

626 

627 def set_workspace(self, workspace): 

628 pass 

629 

630 def set_fullscreen(self, fullscreen): 

631 pass 

632 

633 def set_xid(self, xid): 

634 pass 

635 

636 

637 def toggle_debug(self, *_args): 

638 b = self._backing 

639 log.info("toggling debug on backing %s for window %i", b, self._id) 

640 if not b: 

641 return 

642 if b.paint_box_line_width>0: 

643 b.paint_box_line_width = 0 

644 else: 

645 b.paint_box_line_width = b.default_paint_box_line_width 

646 

647 def increase_quality(self, *_args): 

648 if self._client.quality>0: 

649 #change fixed quality: 

650 self._client.quality = min(100, self._client.quality + 10) 

651 self._client.send_quality() 

652 log("new quality=%s", self._client.quality) 

653 else: 

654 self._client.min_quality = min(100, self._client.min_quality + 10) 

655 self._client.send_min_quality() 

656 log("new min-quality=%s", self._client.min_quality) 

657 

658 def decrease_quality(self, *_args): 

659 if self._client.quality>0: 

660 #change fixed quality: 

661 self._client.quality = max(1, self._client.quality - 10) 

662 self._client.send_quality() 

663 log("new quality=%s", self._client.quality) 

664 else: 

665 self._client.min_quality = max(0, self._client.min_quality - 10) 

666 self._client.send_min_quality() 

667 log("new min-quality=%s", self._client.min_quality) 

668 

669 def increase_speed(self, *_args): 

670 if self._client.speed>0: 

671 #change fixed speed: 

672 self._client.speed = min(100, self._client.speed + 10) 

673 self._client.send_speed() 

674 log("new speed=%s", self._client.speed) 

675 else: 

676 self._client.min_speed = min(100, self._client.min_speed + 10) 

677 self._client.send_min_speed() 

678 log("new min-speed=%s", self._client.min_speed) 

679 

680 def decrease_speed(self, *_args): 

681 if self._client.speed>0: 

682 #change fixed speed: 

683 self._client.speed = max(1, self._client.speed - 10) 

684 self._client.send_speed() 

685 log("new speed=%s", self._client.speed) 

686 else: 

687 self._client.min_speed = max(0, self._client.min_speed - 10) 

688 self._client.send_min_speed() 

689 log("new min-speed=%s", self._client.min_speed) 

690 

691 def scaleup(self, *_args): 

692 self._client.scaleup() 

693 

694 def scaledown(self, *_args): 

695 self._client.scaledown() 

696 

697 def scalingoff(self): 

698 self._client.scalingoff() 

699 

700 def scalereset(self, *_args): 

701 self._client.scalereset() 

702 

703 def magic_key(self, *args): 

704 b = self.border 

705 if b: 

706 b.toggle() 

707 log("magic_key%s border=%s", args, b) 

708 self.repaint(0, 0, *self._size) 

709 

710 def repaint(self, x, y, w, h): 

711 #self.queue_draw_area(0, 0, *self._size) 

712 raise NotImplementedError("no repaint on %s", type(self)) 

713 

714 def refresh_window(self, *args): 

715 log("refresh_window(%s) wid=%s", args, self._id) 

716 self._client.send_refresh(self._id) 

717 

718 def refresh_all_windows(self, *_args): 

719 #this method is only here because we may want to fire it 

720 #from a --key-shortcut action and the event is delivered to 

721 #the "ClientWindow" 

722 self._client.send_refresh_all() 

723 

724 def draw_region(self, x, y, width, height, coding, img_data, rowstride, _packet_sequence, options, callbacks): 

725 """ Note: this runs from the draw thread (not UI thread) """ 

726 backing = self._backing 

727 if not backing: 

728 log("draw_region: window %s has no backing, gone?", self._id) 

729 from xpra.client.window_backing_base import fire_paint_callbacks 

730 fire_paint_callbacks(callbacks, -1, "no backing") 

731 return 

732 #only register this callback if we actually need it: 

733 if backing.draw_needs_refresh: 

734 if not backing.repaint_all: 

735 self.pending_refresh.append((x, y, width, height)) 

736 if options.intget("flush", 0)==0: 

737 callbacks.append(self.after_draw_refresh) 

738 if coding=="void": 

739 fire_paint_callbacks(callbacks) 

740 return 

741 backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks) 

742 

743 def after_draw_refresh(self, success, message=""): 

744 plog("after_draw_refresh(%s, %s) pending_refresh=%s", 

745 success, message, self.pending_refresh) 

746 backing = self._backing 

747 if not backing: 

748 return 

749 if backing.repaint_all or self._client.xscale!=1 or self._client.yscale!=1 or is_Wayland(): 

750 #easy: just repaint the whole window: 

751 rw, rh = self.get_size() 

752 self.idle_add(self.repaint, 0, 0, rw, rh) 

753 return 

754 pr = self.pending_refresh 

755 self.pending_refresh = [] 

756 for x, y, w, h in pr: 

757 rx, ry, rw, rh = self._client.srect(x, y, w, h) 

758 if self.window_offset: 

759 rx += self.window_offset[0] 

760 ry += self.window_offset[1] 

761 self.idle_add(self.repaint, rx, ry, rw, rh) 

762 

763 def eos(self): 

764 """ Note: this runs from the draw thread (not UI thread) """ 

765 backing = self._backing 

766 if backing: 

767 backing.eos() 

768 

769 def spinner(self, _ok): 

770 if not self.can_have_spinner(): 

771 return 

772 log("spinner(%s) queueing redraw") 

773 #with normal windows, we just queue a draw request 

774 #and let the expose event paint the spinner 

775 w, h = self.get_size() 

776 self.repaint(0, 0, w, h) 

777 

778 def can_have_spinner(self): 

779 if self._backing is None: 

780 return False 

781 window_types = self._metadata.strtupleget("window-type") 

782 if not window_types: 

783 return False 

784 return ("NORMAL" in window_types) or \ 

785 ("DIALOG" in window_types) or \ 

786 ("SPLASH" in window_types) 

787 

788 

789 def _unfocus(self): 

790 focuslog("_unfocus() wid=%s, focused=%s", self._id, self._client._focused) 

791 if self._client._focused==self._id: 

792 self._client.update_focus(self._id, False) 

793 

794 def quit(self): 

795 self._client.quit(0) 

796 

797 def void(self): 

798 pass 

799 

800 def show_window_info(self, *args): 

801 from xpra.client.gtk_base.window_info import WindowInfo 

802 wi = WindowInfo(self._client, self) 

803 wi.show() 

804 

805 def show_session_info(self, *args): 

806 self._client.show_session_info(*args) 

807 

808 def show_menu(self, *args): 

809 self._client.show_menu(*args) 

810 

811 def show_start_new_command(self, *args): 

812 self._client.show_start_new_command(*args) 

813 

814 def show_bug_report(self, *args): 

815 self._client.show_bug_report(*args) 

816 

817 def show_file_upload(self, *args): 

818 self._client.show_file_upload(*args) 

819 

820 

821 def log(self, message=""): 

822 log.info(message) 

823 

824 

825 def keyboard_layout_changed(self, *args): 

826 #used by win32 hooks to tell us about keyboard layout changes for this window 

827 keylog("keyboard_layout_changed%s", args) 

828 self._client.window_keyboard_layout_changed(self) 

829 

830 

831 def dbus_call(self, *args, **kwargs): 

832 #alias for rpc_call using dbus as rpc_type, see UIXpraClient.dbus_call 

833 if not self._client.server_dbus_proxy: 

834 log.error("Error: cannot send remote dbus call:") 

835 log.error(" this server does not support dbus-proxying") 

836 return 

837 rpc_args = [self._id]+args 

838 self._client.rpc_call("dbus", rpc_args, **kwargs) 

839 

840 

841 def get_mouse_event_wid(self, _x, _y): 

842 #overriden in GTKClientWindowBase 

843 return self._id 

844 

845 def _do_motion_notify_event(self, event): 

846 if self._client.readonly or self._client.server_readonly or not self._client.server_pointer: 

847 return 

848 pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event) 

849 wid = self.get_mouse_event_wid(*pointer) 

850 mouselog("do_motion_notify_event(%s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, relative pointer=%s, modifiers=%s, buttons=%s", event, wid, self._client._focused, self._id, self._device_info(event), pointer, relative_pointer, modifiers, buttons) 

851 pdata = pointer 

852 if self._client.server_pointer_relative: 

853 pdata = list(pointer)+list(relative_pointer) 

854 packet = ["pointer-position", wid, pdata, modifiers, buttons] 

855 self._client.send_mouse_position(packet) 

856 

857 def _device_info(self, event): 

858 try: 

859 return event.device.get_name() 

860 except AttributeError: 

861 return "" 

862 

863 def _button_action(self, button, event, depressed, *args): 

864 if self._client.readonly or self._client.server_readonly or not self._client.server_pointer: 

865 return 

866 pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event) 

867 wid = self.get_mouse_event_wid(*pointer) 

868 mouselog("_button_action(%s, %s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s", 

869 button, event, depressed, wid, self._client._focused, self._id, self._device_info(event), pointer, modifiers, buttons) 

870 #map wheel buttons via translation table to support inverted axes: 

871 server_button = button 

872 if button>3: 

873 server_button = self._client.wheel_map.get(button) 

874 if not server_button: 

875 return 

876 server_buttons = [] 

877 for b in buttons: 

878 if b>3: 

879 sb = self._client.wheel_map.get(button) 

880 if not sb: 

881 continue 

882 b = sb 

883 server_buttons.append(b) 

884 pdata = pointer 

885 if self._client.server_pointer_relative: 

886 pdata = list(pointer)+list(relative_pointer) 

887 def send_button(pressed): 

888 self._client.send_button(wid, server_button, pressed, pdata, modifiers, server_buttons, *args) 

889 pressed_state = self.button_state.get(button, False) 

890 if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False: 

891 mouselog("button action: simulating a missing mouse-down event for window %s before sending the mouse-up event", wid) 

892 #(needed for some dialogs on win32): 

893 send_button(True) 

894 self.button_state[button] = depressed 

895 send_button(depressed) 

896 

897 def do_button_press_event(self, event): 

898 self._button_action(event.button, event, True) 

899 

900 def do_button_release_event(self, event): 

901 self._button_action(event.button, event, False)