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# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

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

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

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

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

8 

9import os 

10 

11from xpra.os_util import bytestostr, strtobytes, hexstr 

12from xpra.util import typedict, envbool, iround 

13from xpra.gtk_common.error import xswallow, xsync, xlog 

14from xpra.x11.x11_server_core import X11ServerCore, XTestPointerDevice 

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

16from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString, BLACKLISTED_XSETTINGS 

17from xpra.log import Logger 

18 

19log = Logger("x11", "server") 

20mouselog = Logger("x11", "server", "mouse") 

21screenlog = Logger("server", "screen") 

22dbuslog = Logger("dbus") 

23 

24X11Keyboard = X11KeyboardBindings() 

25 

26SCALED_FONT_ANTIALIAS = envbool("XPRA_SCALED_FONT_ANTIALIAS", False) 

27SYNC_ICC = envbool("XPRA_SYNC_ICC", True) 

28 

29 

30def _get_antialias_hintstyle(antialias): 

31 hintstyle = antialias.strget("hintstyle", "").lower() 

32 if hintstyle in ("hintnone", "hintslight", "hintmedium", "hintfull"): 

33 #X11 clients can give us what we need directly: 

34 return hintstyle 

35 #win32 style contrast value: 

36 contrast = antialias.intget("contrast", -1) 

37 if contrast>1600: 

38 return "hintfull" 

39 if contrast>1000: 

40 return "hintmedium" 

41 if contrast>0: 

42 return "hintslight" 

43 return "hintnone" 

44 

45 

46class X11ServerBase(X11ServerCore): 

47 """ 

48 Base class for X11 servers, 

49 adds uinput, icc and xsettings synchronization to the X11ServerCore class 

50 (see XpraServer or DesktopServer for actual implementations) 

51 """ 

52 

53 def __init__(self, clobber): 

54 self.clobber = clobber 

55 X11ServerCore.__init__(self) 

56 self._default_xsettings = {} 

57 self._settings = {} 

58 self.double_click_time = 0 

59 self.double_click_distance = 0 

60 self.dpi = 0 

61 self.default_dpi = 0 

62 self._xsettings_manager = None 

63 self._xsettings_enabled = False 

64 self.display_pid = 0 

65 self.icc_profile = b"" 

66 

67 def do_init(self, opts): 

68 super().do_init(opts) 

69 self._xsettings_enabled = opts.xsettings 

70 if self._xsettings_enabled: 

71 from xpra.x11.xsettings import XSettingsHelper 

72 self._default_xsettings = XSettingsHelper().get_settings() 

73 log("_default_xsettings=%s", self._default_xsettings) 

74 self.init_all_server_settings() 

75 

76 

77 def init_display_pid(self, pid): 

78 if pid: 

79 from xpra.scripts.server import _save_int 

80 _save_int(b"_XPRA_SERVER_PID", pid) 

81 else: 

82 from xpra.scripts.server import _get_int 

83 pid = _get_int(b"_XPRA_SERVER_PID") 

84 if not pid: 

85 log.info("xvfb pid not found") 

86 else: 

87 log.info("xvfb pid=%i", pid) 

88 self.display_pid = pid 

89 

90 def kill_display(self): 

91 if self.display_pid: 

92 from xpra.server import EXITING_CODE 

93 if self._upgrading==EXITING_CODE: 

94 log.info("exiting: not cleaning up Xvfb") 

95 elif self._upgrading: 

96 log.info("upgrading: not cleaning up Xvfb") 

97 else: 

98 from xpra.x11.vfb_util import kill_xvfb 

99 kill_xvfb(self.display_pid) 

100 

101 

102 def do_cleanup(self): 

103 X11ServerCore.do_cleanup(self) 

104 self.kill_display() 

105 

106 

107 def configure_best_screen_size(self): 

108 root_w, root_h = X11ServerCore.configure_best_screen_size(self) 

109 if self.touchpad_device: 

110 self.touchpad_device.root_w = root_w 

111 self.touchpad_device.root_h = root_h 

112 return root_w, root_h 

113 

114 

115 def init_dbus(self, dbus_pid, dbus_env): 

116 try: 

117 from xpra.server.dbus import dbus_start 

118 assert dbus_start 

119 except ImportError as e: 

120 log("init_dbus(%s, %s)", dbus_pid, dbus_env, exc_info=True) 

121 log.warn("Warning: cannot initialize dbus") 

122 log.warn(" %s", e) 

123 return 

124 from xpra.server.dbus.dbus_start import ( 

125 get_saved_dbus_pid, get_saved_dbus_env, 

126 save_dbus_pid, save_dbus_env, 

127 ) 

128 super().init_dbus(dbus_pid, dbus_env) 

129 if self.clobber: 

130 #get the saved pids and env 

131 self.dbus_pid = get_saved_dbus_pid() 

132 self.dbus_env = get_saved_dbus_env() 

133 dbuslog("retrieved existing dbus attributes: %s, %s", self.dbus_pid, self.dbus_env) 

134 if self.dbus_env: 

135 os.environ.update(self.dbus_env) 

136 else: 

137 #now we can save values on the display 

138 #(we cannot access gtk3 until dbus has started up) 

139 if self.dbus_pid: 

140 save_dbus_pid(self.dbus_pid) 

141 if self.dbus_env: 

142 save_dbus_env(self.dbus_env) 

143 

144 

145 def last_client_exited(self): 

146 self.reset_settings() 

147 X11ServerCore.last_client_exited(self) 

148 

149 def init_virtual_devices(self, devices): 

150 #(this runs in the main thread - before the main loop starts) 

151 #for the time being, we only use the pointer if there is one: 

152 pointer = devices.get("pointer") 

153 touchpad = devices.get("touchpad") 

154 mouselog("init_virtual_devices(%s) got pointer=%s, touchpad=%s", devices, pointer, touchpad) 

155 self.input_devices = "xtest" 

156 if pointer: 

157 uinput_device = pointer.get("uinput") 

158 device_path = pointer.get("device") 

159 if uinput_device: 

160 from xpra.x11.uinput_device import UInputPointerDevice 

161 self.input_devices = "uinput" 

162 self.pointer_device = UInputPointerDevice(uinput_device, device_path) 

163 self.verify_uinput_pointer_device() 

164 if self.input_devices=="uinput" and touchpad: 

165 uinput_device = touchpad.get("uinput") 

166 device_path = touchpad.get("device") 

167 if uinput_device: 

168 from xpra.x11.uinput_device import UInputTouchpadDevice 

169 root_w, root_h = self.get_root_window_size() 

170 self.touchpad_device = UInputTouchpadDevice(uinput_device, device_path, root_w, root_h) 

171 try: 

172 mouselog.info("pointer device emulation using %s", str(self.pointer_device).replace("PointerDevice", "")) 

173 except Exception as e: 

174 mouselog("cannot get pointer device class from %s: %s", self.pointer_device, e) 

175 

176 def verify_uinput_pointer_device(self): 

177 xtest = XTestPointerDevice() 

178 ox, oy = 100, 100 

179 with xlog: 

180 xtest.move_pointer(0, ox, oy) 

181 nx, ny = 200, 200 

182 self.pointer_device.move_pointer(0, nx, ny) 

183 def verify_uinput_moved(): 

184 pos = None #@UnusedVariable 

185 with xswallow: 

186 pos = X11Keyboard.query_pointer() 

187 mouselog("X11Keyboard.query_pointer=%s", pos) 

188 if pos==(ox, oy): 

189 mouselog.warn("Warning: %s failed verification", self.pointer_device) 

190 mouselog.warn(" expected pointer at %s, now at %s", (nx, ny), pos) 

191 mouselog.warn(" usign XTest fallback") 

192 self.pointer_device = xtest 

193 self.input_devices = "xtest" 

194 self.timeout_add(1000, verify_uinput_moved) 

195 

196 

197 def dpi_changed(self): 

198 #re-apply the same settings, which will apply the new dpi override to it: 

199 self.update_server_settings() 

200 

201 

202 def get_info(self, proto=None, client_uuids=None): 

203 info = super().get_info(proto=proto, client_uuids=client_uuids) 

204 display_info = info.setdefault("display", {}) 

205 if self.display_pid: 

206 display_info["pid"] = self.display_pid 

207 display_info["icc"] = self.get_icc_info() 

208 return info 

209 

210 def get_icc_info(self) -> dict: 

211 icc_info = { 

212 "sync" : SYNC_ICC, 

213 } 

214 if SYNC_ICC: 

215 icc_info["profile"] = hexstr(self.icc_profile) 

216 return icc_info 

217 

218 def set_icc_profile(self): 

219 if not SYNC_ICC: 

220 return 

221 ui_clients = [s for s in self._server_sources.values() if s.ui_client] 

222 if len(ui_clients)!=1: 

223 screenlog("%i UI clients, resetting ICC profile to default", len(ui_clients)) 

224 self.reset_icc_profile() 

225 return 

226 icc = typedict(ui_clients[0].icc) 

227 data = None 

228 for x in ("data", "icc-data", "icc-profile"): 

229 data = icc.strget(x) 

230 if data: 

231 break 

232 if not data: 

233 screenlog("no icc data found in %s", icc) 

234 self.reset_icc_profile() 

235 return 

236 screenlog("set_icc_profile() icc data for %s: %s (%i bytes)", 

237 ui_clients[0], hexstr(data or ""), len(data or "")) 

238 self.icc_profile = data 

239 from xpra.x11.gtk_x11.prop import prop_set 

240 prop_set(self.root_window, "_ICC_PROFILE", ["u32"], [ord(x) for x in data]) 

241 prop_set(self.root_window, "_ICC_PROFILE_IN_X_VERSION", "u32", 0*100+4) #0.4 -> 0*100+4*1 

242 

243 def reset_icc_profile(self): 

244 screenlog("reset_icc_profile()") 

245 from xpra.x11.gtk_x11.prop import prop_del 

246 prop_del(self.root_window, "_ICC_PROFILE") 

247 prop_del(self.root_window, "_ICC_PROFILE_IN_X_VERSION") 

248 self.icc_profile = b"" 

249 

250 

251 def reset_settings(self): 

252 if not self._xsettings_enabled: 

253 return 

254 log("resetting xsettings to: %s", self._default_xsettings) 

255 self.set_xsettings(self._default_xsettings or (0, ())) 

256 

257 def set_xsettings(self, v): 

258 if not self._xsettings_enabled: 

259 return 

260 log("set_xsettings(%s)", v) 

261 with xsync: 

262 if self._xsettings_manager is None: 

263 from xpra.x11.xsettings import XSettingsManager 

264 self._xsettings_manager = XSettingsManager() 

265 self._xsettings_manager.set_settings(v) 

266 

267 def init_all_server_settings(self): 

268 log("init_all_server_settings() dpi=%i, default_dpi=%i", self.dpi, self.default_dpi) 

269 #almost like update_all, except we use the default_dpi, 

270 #since this is called before the first client connects 

271 self.do_update_server_settings({ 

272 b"resource-manager" : b"", 

273 b"xsettings-blob" : (0, []) 

274 }, reset = True, dpi = self.default_dpi, cursor_size=24) 

275 

276 def update_all_server_settings(self, reset=False): 

277 self.update_server_settings({ 

278 b"resource-manager" : b"", 

279 b"xsettings-blob" : (0, []), 

280 }, reset=reset) 

281 

282 def update_server_settings(self, settings=None, reset=False): 

283 self.do_update_server_settings(settings or self._settings, reset, 

284 self.dpi, self.double_click_time, self.double_click_distance, self.antialias, self.cursor_size) 

285 

286 def do_update_server_settings(self, settings, reset=False, 

287 dpi=0, double_click_time=0, double_click_distance=(-1, -1), antialias={}, cursor_size=-1): 

288 if not self._xsettings_enabled: 

289 log("ignoring xsettings update: %s", settings) 

290 return 

291 if reset: 

292 #FIXME: preserve serial? (what happens when we change values which had the same serial?) 

293 self.reset_settings() 

294 self._settings = {} 

295 if self._default_xsettings: 

296 #try to parse default xsettings into a dict: 

297 try: 

298 for _, prop_name, value, _ in self._default_xsettings[1]: 

299 self._settings[prop_name] = value 

300 except Exception as e: 

301 log("failed to parse %s", self._default_xsettings) 

302 log.warn("Warning: failed to parse default XSettings:") 

303 log.warn(" %s", e) 

304 old_settings = dict(self._settings) 

305 log("server_settings: old=%r, updating with=%r", old_settings, settings) 

306 log("overrides: dpi=%s, double click time=%s, double click distance=%s", 

307 dpi, double_click_time, double_click_distance) 

308 log("overrides: antialias=%s", antialias) 

309 self._settings.update(settings) 

310 for k, v in settings.items(): 

311 #cook the "resource-manager" value to add the DPI and/or antialias values: 

312 if k==b"resource-manager" and (dpi>0 or antialias or cursor_size>0): 

313 value = bytestostr(v) 

314 #parse the resources into a dict: 

315 values={} 

316 options = value.split("\n") 

317 for option in options: 

318 if not option: 

319 continue 

320 parts = option.split(":\t", 1) 

321 if len(parts)!=2: 

322 log("skipped invalid option: '%s'", option) 

323 continue 

324 if parts[0] in BLACKLISTED_XSETTINGS: 

325 log("skipped blacklisted option: '%s'", option) 

326 continue 

327 values[parts[0]] = parts[1] 

328 if cursor_size>0: 

329 values["Xcursor.size"] = cursor_size 

330 if dpi>0: 

331 values["Xft.dpi"] = dpi 

332 values["Xft/DPI"] = dpi*1024 

333 values["gnome.Xft/DPI"] = dpi*1024 

334 if antialias: 

335 ad = typedict(antialias) 

336 subpixel_order = "none" 

337 sss = tuple(self._server_sources.values()) 

338 if len(sss)==1: 

339 #only honour sub-pixel hinting if a single client is connected 

340 #and only when it is not using any scaling (or overriden with SCALED_FONT_ANTIALIAS): 

341 ss = sss[0] 

342 ds_unscaled = getattr(ss, "desktop_size_unscaled", None) 

343 ds_scaled = getattr(ss, "desktop_size", None) 

344 if SCALED_FONT_ANTIALIAS or (not ds_unscaled or ds_unscaled==ds_scaled): 

345 subpixel_order = ad.strget("orientation", "none").lower() 

346 values.update({ 

347 "Xft.antialias" : ad.intget("enabled", -1), 

348 "Xft.hinting" : ad.intget("hinting", -1), 

349 "Xft.rgba" : subpixel_order, 

350 "Xft.hintstyle" : _get_antialias_hintstyle(ad)}) 

351 log("server_settings: resource-manager values=%r", values) 

352 #convert the dict back into a resource string: 

353 value = '' 

354 for vk, vv in values.items(): 

355 value += "%s:\t%s\n" % (vk, vv) 

356 #record the actual value used 

357 self._settings[b"resource-manager"] = value 

358 v = value.encode("utf-8") 

359 

360 #cook xsettings to add various settings: 

361 #(as those may not be present in xsettings on some platforms.. like win32 and osx) 

362 if k==b"xsettings-blob" and \ 

363 (self.double_click_time>0 or self.double_click_distance!=(-1, -1) or antialias or dpi>0): 

364 #start by removing blacklisted options: 

365 def filter_blacklisted(): 

366 serial, values = v 

367 new_values = [] 

368 for _t,_n,_v,_s in values: 

369 if bytestostr(_n) in BLACKLISTED_XSETTINGS: 

370 log("skipped blacklisted option %s", (_t, _n, _v, _s)) 

371 else: 

372 new_values.append((_t, _n, _v, _s)) 

373 return serial, new_values 

374 v = filter_blacklisted() 

375 def set_xsettings_value(name, value_type, value): 

376 #remove existing one, if any: 

377 serial, values = v 

378 new_values = [(_t,_n,_v,_s) for (_t,_n,_v,_s) in values if _n!=name] 

379 new_values.append((value_type, name, value, 0)) 

380 return serial, new_values 

381 def set_xsettings_int(name, value): 

382 if value<0: #not set, return v unchanged 

383 return v 

384 return set_xsettings_value(name, XSettingsTypeInteger, value) 

385 if dpi>0: 

386 v = set_xsettings_int(b"Xft/DPI", dpi*1024) 

387 if double_click_time>0: 

388 v = set_xsettings_int(b"Net/DoubleClickTime", self.double_click_time) 

389 if antialias: 

390 ad = typedict(antialias) 

391 v = set_xsettings_int(b"Xft/Antialias", ad.intget("enabled", -1)) 

392 v = set_xsettings_int(b"Xft/Hinting", ad.intget("hinting", -1)) 

393 v = set_xsettings_value(b"Xft/RGBA", XSettingsTypeString, ad.strget("orientation", "none").lower()) 

394 v = set_xsettings_value(b"Xft/HintStyle", XSettingsTypeString, _get_antialias_hintstyle(ad)) 

395 if double_click_distance!=(-1, -1): 

396 #some platforms give us a value for each axis, 

397 #but X11 only has one, so take the average 

398 try: 

399 x,y = double_click_distance 

400 if x>0 and y>0: 

401 d = iround((x+y)/2.0) 

402 d = max(1, min(128, d)) #sanitize it a bit 

403 v = set_xsettings_int(b"Net/DoubleClickDistance", d) 

404 except Exception as e: 

405 log.warn("error setting double click distance from %s: %s", double_click_distance, e) 

406 

407 if k not in old_settings or v != old_settings[k]: 

408 if k == b"xsettings-blob": 

409 self.set_xsettings(v) 

410 elif k == b"resource-manager": 

411 from xpra.x11.gtk_x11.prop import prop_set 

412 p = "RESOURCE_MANAGER" 

413 log("server_settings: setting %s to %r", p, v) 

414 prop_set(self.root_window, p, "latin1", strtobytes(v).decode("latin1")) 

415 else: 

416 log.warn("Warning: unexpected setting '%s'", bytestostr(k))