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) 2010-2020 Antoine Martin <antoine@xpra.org> 

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

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

5 

6import os 

7 

8from xpra.exit_codes import EXIT_INTERNAL_ERROR 

9from xpra.platform.features import REINIT_WINDOWS 

10from xpra.platform.gui import ( 

11 get_antialias_info, get_icc_info, get_display_icc_info, show_desktop, get_cursor_size, 

12 get_xdpi, get_ydpi, get_number_of_desktops, get_desktop_names, get_wm_name, 

13 ) 

14from xpra.scripts.main import check_display 

15from xpra.scripts.config import FALSE_OPTIONS 

16from xpra.net.common import MAX_PACKET_SIZE 

17from xpra.os_util import monotonic_time 

18from xpra.util import ( 

19 iround, envint, envfloat, envbool, log_screen_sizes, engs, flatten_dict, typedict, 

20 XPRA_SCALING_NOTIFICATION_ID, 

21 ) 

22from xpra.client.mixins.stub_client_mixin import StubClientMixin 

23from xpra.log import Logger 

24 

25log = Logger("screen") 

26workspacelog = Logger("client", "workspace") 

27scalinglog = Logger("scaling") 

28 

29MONITOR_CHANGE_REINIT = envint("XPRA_MONITOR_CHANGE_REINIT") 

30 

31 

32MIN_SCALING = envfloat("XPRA_MIN_SCALING", "0.1") 

33MAX_SCALING = envfloat("XPRA_MAX_SCALING", "8") 

34SCALING_OPTIONS = [float(x) for x in os.environ.get("XPRA_TRAY_SCALING_OPTIONS", "0.25,0.5,0.666,1,1.25,1.5,2.0,3.0,4.0,5.0").split(",") if float(x)>=MIN_SCALING and float(x)<=MAX_SCALING] 

35SCALING_EMBARGO_TIME = int(os.environ.get("XPRA_SCALING_EMBARGO_TIME", "1000"))/1000.0 

36SYNC_ICC = envbool("XPRA_SYNC_ICC", True) 

37 

38 

39def r4cmp(v, rounding=1000.0): #ignore small differences in floats for scale values 

40 return iround(v*rounding) 

41def fequ(v1, v2): 

42 return r4cmp(v1)==r4cmp(v2) 

43 

44 

45""" 

46Utility superclass for clients that handle a desktop / display 

47Adds client-side scaling handling 

48""" 

49class DisplayClient(StubClientMixin): 

50 __signals__ = ["scaling-changed"] 

51 

52 def __init__(self): 

53 check_display() 

54 StubClientMixin.__init__(self) 

55 self.dpi = 0 

56 self.can_scale = False 

57 self.initial_scaling = 1, 1 

58 self.xscale, self.yscale = self.initial_scaling 

59 self.scale_change_embargo = float("inf") 

60 self.desktop_fullscreen = False 

61 self.desktop_scaling = False 

62 self.screen_size_change_timer = None 

63 

64 self.server_desktop_size = None 

65 self.server_actual_desktop_size = None 

66 self.server_max_desktop_size = None 

67 self.server_display = None 

68 self.server_randr = False 

69 self.server_opengl = None 

70 

71 

72 def init(self, opts): 

73 self.desktop_fullscreen = opts.desktop_fullscreen 

74 self.desktop_scaling = opts.desktop_scaling 

75 self.dpi = int(opts.dpi) 

76 self.can_scale = opts.desktop_scaling not in FALSE_OPTIONS 

77 if self.can_scale: 

78 self.parse_scaling(opts.desktop_scaling) 

79 

80 def parse_scaling(self, desktop_scaling): 

81 root_w, root_h = self.get_root_size() 

82 from xpra.client.scaling_parser import parse_scaling 

83 self.initial_scaling = parse_scaling(desktop_scaling, root_w, root_h, MIN_SCALING, MAX_SCALING) 

84 self.xscale, self.yscale = self.initial_scaling 

85 

86 def cleanup(self): 

87 ssct = self.screen_size_change_timer 

88 if ssct: 

89 self.screen_size_change_timer = None 

90 self.source_remove(ssct) 

91 

92 

93 def get_screen_sizes(self, xscale=1, yscale=1): 

94 raise NotImplementedError() 

95 

96 def get_root_size(self): 

97 raise NotImplementedError() 

98 

99 

100 def get_info(self): 

101 screen = self.get_screen_caps() 

102 screen["scaling"] = self.get_scaling_caps() 

103 screen["dpi"] = self.get_dpi_caps() 

104 info = { 

105 "screen" : screen, 

106 } 

107 return info 

108 

109 ###################################################################### 

110 # hello: 

111 def get_caps(self) -> dict: 

112 caps = { 

113 "randr_notify" : True, 

114 "show-desktop" : True, 

115 } 

116 wm_name = get_wm_name() 

117 if wm_name: 

118 caps["wm_name"] = wm_name.encode("utf8") 

119 

120 self._last_screen_settings = self.get_screen_settings() 

121 root_w, root_h, sss, ndesktops, desktop_names, u_root_w, u_root_h, _, _ = self._last_screen_settings 

122 if u_root_w and u_root_h: 

123 caps["desktop_size"] = self.cp(u_root_w, u_root_h) 

124 if ndesktops: 

125 caps["desktops"] = ndesktops 

126 caps["desktop.names"] = tuple(x.encode("utf8") for x in desktop_names) 

127 

128 ss = self.get_screen_sizes() 

129 self._current_screen_sizes = ss 

130 

131 log.info(" desktop size is %sx%s with %s screen%s:", u_root_w, u_root_h, len(ss), engs(ss)) 

132 log_screen_sizes(u_root_w, u_root_h, ss) 

133 if self.xscale!=1 or self.yscale!=1: 

134 caps["screen_sizes.unscaled"] = ss 

135 caps["desktop_size.unscaled"] = u_root_w, u_root_h 

136 root_w, root_h = self.cp(u_root_w, u_root_h) 

137 if fequ(self.xscale, self.yscale): 

138 sinfo = "%i%%" % iround(self.xscale*100) 

139 else: 

140 sinfo = "%i%% x %i%%" % (iround(self.xscale*100), iround(self.yscale*100)) 

141 scaled_up = u_root_w>root_w or u_root_h>root_h 

142 log.info(" %sscaled to %s, virtual screen size: %ix%i", 

143 "up" if scaled_up else "down", sinfo, root_w, root_h) 

144 log_screen_sizes(root_w, root_h, sss) 

145 else: 

146 root_w, root_h = u_root_w, u_root_h 

147 sss = ss 

148 caps["screen_sizes"] = sss 

149 

150 caps.update(self.get_screen_caps()) 

151 caps.update(flatten_dict({ 

152 "dpi" : self.get_dpi_caps(), 

153 "screen-scaling" : self.get_scaling_caps(), 

154 })) 

155 return caps 

156 

157 def get_dpi_caps(self) -> dict: 

158 #command line (or config file) override supplied: 

159 caps = {} 

160 dpi = 0 

161 if self.dpi>0: 

162 #scale it: 

163 dpi = iround((self.cx(self.dpi) + self.cy(self.dpi))/2.0) 

164 else: 

165 #not supplied, use platform detection code: 

166 #platforms may also provide per-axis dpi (later win32 versions do) 

167 xdpi = self.get_xdpi() 

168 ydpi = self.get_ydpi() 

169 log("xdpi=%i, ydpi=%i", xdpi, ydpi) 

170 if xdpi>0 and ydpi>0: 

171 xdpi = self.cx(xdpi) 

172 ydpi = self.cy(ydpi) 

173 dpi = iround((xdpi+ydpi)/2.0) 

174 caps = { 

175 "x" : xdpi, 

176 "y" : ydpi, 

177 } 

178 if dpi: 

179 caps[""] = dpi 

180 log("get_dpi_caps()=%s", caps) 

181 return caps 

182 

183 def get_scaling_caps(self) -> dict: 

184 return { 

185 "" : True, 

186 "enabled" : self.xscale!=1 or self.yscale!=1, 

187 "values" : (int(1000*self.xscale), int(1000*self.yscale)), 

188 } 

189 

190 def get_screen_caps(self) -> dict: 

191 caps = { 

192 "antialias" : get_antialias_info(), 

193 "cursor" : { 

194 "size" : int(2*get_cursor_size()/(self.xscale+self.yscale)), 

195 }, 

196 } 

197 if SYNC_ICC: 

198 caps.update({ 

199 "icc" : self.get_icc_info(), 

200 "display-icc" : self.get_display_icc_info(), 

201 }) 

202 return caps 

203 

204 #this is the format we should be moving towards 

205 #with proper namespace: 

206 #def get_info(self) -> dict: 

207 # sinfo = self.get_screen_caps() 

208 # sinfo["scaling"] = self.get_scaling_caps() 

209 # sinfo["dpi"] = self.get_dpi_caps() 

210 # return { 

211 # "desktop" : self.get_desktop_caps(), 

212 # "screen" : sinfo, 

213 # } 

214 

215 #def get_desktop_info(self): 

216 # caps = { 

217 # "show" : True, 

218 # } 

219 # wm_name = get_wm_name() 

220 # if wm_name: 

221 # caps["wm_name"] = wm_name 

222 # _, _, sss, ndesktops, desktop_names, u_root_w, u_root_h, xdpi, ydpi = self._last_screen_settings 

223 # caps["unscaled-size"] = u_root_w, u_root_h 

224 # caps["size"] = self.cp(u_root_w, u_root_h) 

225 # caps["dpi"] = (xdpi, ydpi) 

226 # caps["count"] = ndesktops 

227 # caps["names"] = desktop_names 

228 # caps["screens"] = len(sss) 

229 

230 

231 def parse_server_capabilities(self, c : typedict) -> bool: 

232 self.server_display = c.strget("display") 

233 self.server_desktop_size = c.intpair("desktop_size") 

234 log("server desktop size=%s", self.server_desktop_size) 

235 self.server_max_desktop_size = c.intpair("max_desktop_size") 

236 self.server_actual_desktop_size = c.intpair("actual_desktop_size") 

237 log("server actual desktop size=%s", self.server_actual_desktop_size) 

238 self.server_randr = c.boolget("resize_screen") 

239 log("server has randr: %s", self.server_randr) 

240 self.server_opengl = c.dictget("opengl") 

241 return True 

242 

243 def process_ui_capabilities(self, c : typedict): 

244 self.server_is_desktop = c.boolget("shadow") or c.boolget("desktop") 

245 skip_vfb_size_check = False #if we decide not to use scaling, skip warnings 

246 if not fequ(self.xscale, 1.0) or not fequ(self.yscale, 1.0): 

247 #scaling is used, make sure that we need it and that the server can support it 

248 #(without rounding support, size-hints can cause resize loops) 

249 if self.server_is_desktop and not self.desktop_fullscreen: 

250 #don't honour auto mode in this case 

251 if self.desktop_scaling=="auto": 

252 log.info(" not scaling a %s server", c.strget("type", "shadow")) 

253 skip_vfb_size_check = self.xscale>1 or self.yscale>1 

254 self.scale_change_embargo = 0 

255 self.scalingoff() 

256 if self.can_scale: 

257 self.may_adjust_scaling() 

258 if not self.server_is_desktop and not skip_vfb_size_check and self.server_max_desktop_size: 

259 avail_w, avail_h = self.server_max_desktop_size 

260 root_w, root_h = self.get_root_size() 

261 log("validating server_max_desktop_size=%s vs root size=%s", 

262 self.server_max_desktop_size, (root_w, root_h)) 

263 if self.cx(root_w)!=root_w or self.cy(root_h)!=root_h: 

264 log(" root size scaled to %s", (self.cx(root_w), self.cy(root_h))) 

265 if self.cx(root_w)>(avail_w+1) or self.cy(root_h)>(avail_h+1): 

266 log.warn("Server's virtual screen is too small") 

267 log.warn(" server: %sx%s vs client: %sx%s", avail_w, avail_h, self.cx(root_w), self.cy(root_h)) 

268 log.warn(" you may see strange behavior,") 

269 log.warn(" please see https://xpra.org/trac/wiki/Xdummy#Configuration") 

270 #now that we have the server's screen info, allow scale changes: 

271 self.scale_change_embargo = 0 

272 self.set_max_packet_size() 

273 

274 def set_max_packet_size(self): 

275 root_w, root_h = self.cp(*self.get_root_size()) 

276 maxw, maxh = root_w, root_h 

277 try: 

278 server_w, server_h = self.server_actual_desktop_size 

279 maxw = max(root_w, server_w) 

280 maxh = max(root_h, server_h) 

281 except ValueError: 

282 pass 

283 if maxw<=0 or maxh<=0 or maxw>=32768 or maxh>=32768: 

284 message = "invalid maximum desktop size: %ix%i" % (maxw, maxh) 

285 log(message) 

286 self.quit(EXIT_INTERNAL_ERROR) 

287 raise SystemExit(message) 

288 if maxw>=16384 or maxh>=16384: 

289 log.warn("Warning: the desktop size is extremely large: %ix%i", maxw, maxh) 

290 #max packet size to accomodate 

291 # a full screen RGBX (32 bits) uncompressed image 

292 # also with enough headroom for some metadata (4k) 

293 p = self._protocol 

294 if p: 

295 #we can't assume to have a real ClientConnection object: 

296 p.max_packet_size = max(MAX_PACKET_SIZE, maxw*maxh*4 + 4*1024) 

297 p.abs_max_packet_size = maxw*maxh*4*4 + 4*1024 

298 log("maximum packet size set to %i", p.max_packet_size) 

299 

300 

301 def has_transparency(self) -> bool: 

302 return False 

303 

304 def get_icc_info(self) -> dict: 

305 return get_icc_info() 

306 

307 def get_display_icc_info(self) -> dict: 

308 return get_display_icc_info() 

309 

310 def _process_show_desktop(self, packet): 

311 show = packet[1] 

312 log("calling %s(%s)", show_desktop, show) 

313 show_desktop(show) 

314 

315 def _process_desktop_size(self, packet): 

316 root_w, root_h, max_w, max_h = packet[1:5] 

317 log("server has resized the desktop to: %sx%s (max %sx%s)", root_w, root_h, max_w, max_h) 

318 self.server_max_desktop_size = max_w, max_h 

319 self.server_actual_desktop_size = root_w, root_h 

320 if self.can_scale: 

321 self.may_adjust_scaling() 

322 

323 

324 def may_adjust_scaling(self): 

325 log("may_adjust_scaling() server_is_desktop=%s, desktop_fullscreen=%s", 

326 self.server_is_desktop, self.desktop_fullscreen) 

327 if self.server_is_desktop and not self.desktop_fullscreen: 

328 #don't try to make it fit 

329 return 

330 assert self.can_scale 

331 max_w, max_h = self.server_max_desktop_size #ie: server limited to 8192x4096? 

332 w, h = self.get_root_size() #ie: 5760, 2160 

333 sw, sh = self.cp(w, h) #ie: upscaled to: 11520x4320 

334 scalinglog("may_adjust_scaling() server max desktop size=%s, server actual desktop size=%s", 

335 self.server_max_desktop_size, self.server_actual_desktop_size) 

336 scalinglog("may_adjust_scaling() client root size=%s", self.get_root_size()) 

337 scalinglog(" scaled client root size using %sx%s: %s", self.xscale, self.yscale, (sw, sh)) 

338 #server size is too small for the client screen size with the current scaling value, 

339 #calculate the minimum scaling to fit it: 

340 def clamp(v): 

341 return max(MIN_SCALING, min(MAX_SCALING, v)) 

342 if self.desktop_fullscreen: 

343 sw, sh = self.server_actual_desktop_size 

344 x = clamp(w/sw) 

345 y = clamp(h/sh) 

346 else: 

347 if sw<(max_w+1) and sh<(max_h+1): 

348 #no change needed 

349 return 

350 x = clamp(w/max_w) 

351 y = clamp(h/max_h) 

352 #avoid wonky scaling: 

353 if not 0.75<x/y<1.25: 

354 x = y = min(x, y) 

355 def mint(v): 

356 #prefer int over float, 

357 #and even tolerate a 0.1% difference to get it: 

358 if iround(v)*1000==iround(v*1000): 

359 return int(v) 

360 return v 

361 self.xscale = mint(x) 

362 self.yscale = mint(y) 

363 scalinglog(" xscale=%s, yscale=%s", self.xscale, self.yscale) 

364 #to use the same scale for both axes: 

365 #self.xscale = mint(max(x, y)) 

366 #self.yscale = self.xscale 

367 summary = "Desktop scaling adjusted to accomodate the server" 

368 xstr = ("%.3f" % self.xscale).rstrip("0") 

369 ystr = ("%.3f" % self.yscale).rstrip("0") 

370 messages = [ 

371 "server desktop size is %ix%i" % (max_w, max_h), 

372 "using scaling factor %s x %s" % (xstr, ystr), 

373 ] 

374 self.may_notify(XPRA_SCALING_NOTIFICATION_ID, summary, "\n".join(messages), icon_name="scaling") 

375 scalinglog.warn("Warning: %s", summary) 

376 for m in messages: 

377 scalinglog.warn(" %s", m) 

378 self.emit("scaling-changed") 

379 

380 

381 ###################################################################### 

382 # screen scaling: 

383 def fsx(self, v): 

384 """ convert X coordinate from server to client """ 

385 return v*self.xscale 

386 def fsy(self, v): 

387 """ convert Y coordinate from server to client """ 

388 return v*self.yscale 

389 def sx(self, v) -> int: 

390 """ convert X coordinate from server to client """ 

391 return iround(self.fsx(v)) 

392 def sy(self, v) -> int: 

393 """ convert Y coordinate from server to client """ 

394 return iround(self.fsy(v)) 

395 def srect(self, x, y, w, h): 

396 """ convert rectangle coordinates from server to client """ 

397 return self.sx(x), self.sy(y), self.sx(w), self.sy(h) 

398 def sp(self, x, y): 

399 """ convert X,Y coordinates from server to client """ 

400 return self.sx(x), self.sy(y) 

401 

402 def cx(self, v) -> int: 

403 """ convert X coordinate from client to server """ 

404 return iround(v/self.xscale) 

405 def cy(self, v) -> int: 

406 """ convert Y coordinate from client to server """ 

407 return iround(v/self.yscale) 

408 def crect(self, x, y, w, h): 

409 """ convert rectangle coordinates from client to server """ 

410 return self.cx(x), self.cy(y), self.cx(w), self.cy(h) 

411 def cp(self, x, y): 

412 """ convert X,Y coordinates from client to server """ 

413 return self.cx(x), self.cy(y) 

414 

415 

416 ###################################################################### 

417 # desktop, screen and scaling: 

418 def desktops_changed(self, *args): 

419 workspacelog("desktops_changed%s", args) 

420 self.screen_size_changed(*args) 

421 

422 def workspace_changed(self, *args): 

423 workspacelog("workspace_changed%s", args) 

424 for win in self._id_to_window.values(): 

425 win.workspace_changed() 

426 

427 def screen_size_changed(self, *args): 

428 log("screen_size_changed(%s) timer=%s", args, self.screen_size_change_timer) 

429 if self.screen_size_change_timer: 

430 return 

431 #update via timer so the data is more likely to be final (up to date) when we query it, 

432 #some properties (like _NET_WORKAREA for X11 clients via xposix "ClientExtras") may 

433 #trigger multiple calls to screen_size_changed, delayed by some amount 

434 #(sometimes up to 1s..) 

435 delay = 1000 

436 #if we are suspending, wait longer: 

437 #(better chance that the suspend-resume cycle will have completed) 

438 if self._suspended_at>0 and self._suspended_at-monotonic_time()<5*1000: 

439 delay = 5*1000 

440 self.screen_size_change_timer = self.timeout_add(delay, self.do_process_screen_size_change) 

441 

442 def do_process_screen_size_change(self): 

443 self.screen_size_change_timer = None 

444 self.update_screen_size() 

445 log("do_process_screen_size_change() MONITOR_CHANGE_REINIT=%s, REINIT_WINDOWS=%s", 

446 MONITOR_CHANGE_REINIT, REINIT_WINDOWS) 

447 if MONITOR_CHANGE_REINIT and MONITOR_CHANGE_REINIT=="0": 

448 return 

449 if MONITOR_CHANGE_REINIT or REINIT_WINDOWS: 

450 log.info("screen size change: will reinit the windows") 

451 self.reinit_windows() 

452 self.reinit_window_icons() 

453 

454 

455 def get_screen_settings(self): 

456 u_root_w, u_root_h = self.get_root_size() 

457 root_w, root_h = self.cp(u_root_w, u_root_h) 

458 self._current_screen_sizes = self.get_screen_sizes() 

459 sss = self.get_screen_sizes(self.xscale, self.yscale) 

460 ndesktops = get_number_of_desktops() 

461 desktop_names = get_desktop_names() 

462 log("get_screen_settings() sizes=%s, %s desktops: %s", sss, ndesktops, desktop_names) 

463 if self.dpi>0: 

464 #use command line value supplied, but scale it: 

465 xdpi = ydpi = self.dpi 

466 log("get_screen_settings() dpi=%s", self.dpi) 

467 else: 

468 #not supplied, use platform detection code: 

469 xdpi = self.get_xdpi() 

470 ydpi = self.get_ydpi() 

471 log("get_screen_settings() xdpi=%s, ydpi=%s", get_xdpi(), get_ydpi()) 

472 xdpi = self.cx(xdpi) 

473 ydpi = self.cy(ydpi) 

474 log("get_screen_settings() scaled: xdpi=%s, ydpi=%s", xdpi, ydpi) 

475 return (root_w, root_h, sss, ndesktops, desktop_names, u_root_w, u_root_h, xdpi, ydpi) 

476 

477 def update_screen_size(self): 

478 self.screen_size_change_timer = None 

479 screen_settings = self.get_screen_settings() 

480 log("update_screen_size() new settings=%s", screen_settings) 

481 log("update_screen_size() current settings=%s", self._last_screen_settings) 

482 if self._last_screen_settings==screen_settings: 

483 log("screen size unchanged") 

484 return 

485 root_w, root_h, sss = screen_settings[:3] 

486 log.info("sending updated screen size to server: %sx%s with %s screens", root_w, root_h, len(sss)) 

487 log_screen_sizes(root_w, root_h, sss) 

488 if self.server_desktop_size: 

489 self.send("desktop_size", *screen_settings) 

490 self._last_screen_settings = screen_settings 

491 #update the max packet size (may have gone up): 

492 self.set_max_packet_size() 

493 

494 def get_xdpi(self) -> int: 

495 return get_xdpi() 

496 

497 def get_ydpi(self) -> int: 

498 return get_ydpi() 

499 

500 

501 def scaleup(self): 

502 scaling = max(self.xscale, self.yscale) 

503 options = [v for v in SCALING_OPTIONS if r4cmp(v, 10)>r4cmp(scaling, 10)] 

504 scalinglog("scaleup() options>%s : %s", r4cmp(scaling, 1000)/1000.0, options) 

505 if options: 

506 self._scaleto(min(options)) 

507 

508 def scaledown(self): 

509 scaling = max(self.xscale, self.yscale) 

510 options = [v for v in SCALING_OPTIONS if r4cmp(v, 10)<r4cmp(scaling, 10)] 

511 scalinglog("scaledown() options<%s : %s", r4cmp(scaling, 1000)/1000.0, options) 

512 if options: 

513 self._scaleto(max(options)) 

514 

515 def _scaleto(self, new_scaling): 

516 scaling = max(self.xscale, self.yscale) 

517 scalinglog("_scaleto(%s) current value=%s", r4cmp(new_scaling, 1000)/1000.0, r4cmp(scaling, 1000)/1000.0) 

518 if new_scaling>0: 

519 self.scale_change(new_scaling/scaling, new_scaling/scaling) 

520 

521 def scalingoff(self): 

522 self.scaleset(1, 1) 

523 

524 def scalereset(self): 

525 self.scaleset(*self.initial_scaling) 

526 

527 def scaleset(self, xscale=1, yscale=1): 

528 scalinglog("scaleset(%s, %s) current scaling: %s, %s", xscale, yscale, self.xscale, self.yscale) 

529 self.scale_change(xscale/self.xscale, yscale/self.yscale) 

530 

531 def scale_change(self, xchange=1, ychange=1): 

532 scalinglog("scale_change(%s, %s)", xchange, ychange) 

533 if self.server_is_desktop and self.desktop_fullscreen: 

534 scalinglog("scale_change(%s, %s) ignored, fullscreen shadow mode is active", xchange, ychange) 

535 return 

536 if not self.can_scale: 

537 scalinglog("scale_change(%s, %s) ignored, scaling is disabled", xchange, ychange) 

538 return 

539 if self.screen_size_change_timer: 

540 scalinglog("scale_change(%s, %s) screen size change is already pending", xchange, ychange) 

541 return 

542 if monotonic_time()<self.scale_change_embargo: 

543 scalinglog("scale_change(%s, %s) screen size change not permitted during embargo time - try again", 

544 xchange, ychange) 

545 return 

546 def clamp(v): 

547 return max(MIN_SCALING, min(MAX_SCALING, v)) 

548 xscale = clamp(self.xscale*xchange) 

549 yscale = clamp(self.yscale*ychange) 

550 scalinglog("scale_change xscale: clamp(%s*%s)=%s", self.xscale, xchange, xscale) 

551 scalinglog("scale_change yscale: clamp(%s*%s)=%s", self.yscale, ychange, yscale) 

552 if fequ(xscale, self.xscale) and fequ(yscale, self.yscale): 

553 scalinglog("scaling unchanged: %sx%s", self.xscale, self.yscale) 

554 return 

555 #re-calculate change values against clamped scale: 

556 xchange = xscale / self.xscale 

557 ychange = yscale / self.yscale 

558 #check against maximum server supported size: 

559 maxw, maxh = self.server_max_desktop_size 

560 root_w, root_h = self.get_root_size() 

561 sw = int(root_w / xscale) 

562 sh = int(root_h / yscale) 

563 scalinglog("scale_change root size=%s x %s, scaled to %s x %s", root_w, root_h, sw, sh) 

564 scalinglog("scale_change max server desktop size=%s x %s", maxw, maxh) 

565 if not self.server_is_desktop and (sw>(maxw+1) or sh>(maxh+1)): 

566 #would overflow.. 

567 summary = "Invalid Scale Factor" 

568 messages = [ 

569 "cannot scale by %i%% x %i%% or lower" % ((100*xscale), (100*yscale)), 

570 "the scaled client screen %i x %i -> %i x %i" % (root_w, root_h, sw, sh), 

571 " would overflow the server's screen: %i x %i" % (maxw, maxh), 

572 ] 

573 self.may_notify(XPRA_SCALING_NOTIFICATION_ID, summary, "\n".join(messages), icon_name="scaling") 

574 scalinglog.warn("Warning: %s", summary) 

575 for m in messages: 

576 scalinglog.warn(" %s", m) 

577 return 

578 self.xscale = xscale 

579 self.yscale = yscale 

580 scalinglog("scale_change new scaling: %sx%s, change: %sx%s", self.xscale, self.yscale, xchange, ychange) 

581 self.scale_reinit(xchange, ychange) 

582 

583 def scale_reinit(self, xchange=1.0, ychange=1.0): 

584 #wait at least one second before changing again: 

585 self.scale_change_embargo = monotonic_time()+SCALING_EMBARGO_TIME 

586 if fequ(self.xscale, self.yscale): 

587 scalinglog.info("setting scaling to %i%%:", iround(100*self.xscale)) 

588 else: 

589 scalinglog.info("setting scaling to %i%% x %i%%:", iround(100*self.xscale), iround(100*self.yscale)) 

590 self.update_screen_size() 

591 #re-initialize all the windows with their new size 

592 def new_size_fn(w, h): 

593 minx, miny = 16384, 16384 

594 if self.max_window_size!=(0, 0): 

595 minx, miny = self.max_window_size 

596 return max(1, min(minx, iround(w*xchange))), max(1, min(miny, iround(h*ychange))) 

597 self.resize_windows(new_size_fn) 

598 self.reinit_window_icons() 

599 self.emit("scaling-changed") 

600 

601 

602 def init_authenticated_packet_handlers(self): 

603 self.add_packet_handler("show-desktop", self._process_show_desktop) 

604 self.add_packet_handler("desktop_size", self._process_desktop_size)