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-2019 Antoine Martin <antoine@xpra.org> 

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

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

6 

7import os 

8import sys 

9import logging 

10import weakref 

11# This module is used by non-GUI programs and thus must not import gtk. 

12 

13LOG_PREFIX = os.environ.get("XPRA_LOG_PREFIX", "") 

14LOG_FORMAT = os.environ.get("XPRA_LOG_FORMAT", "%(asctime)s %(message)s") 

15NOPREFIX_FORMAT = "%(message)s" 

16 

17DEBUG_MODULES = os.environ.get("XPRA_DEBUG_MODULES", "").split(",") 

18 

19logging.basicConfig(format=LOG_FORMAT) 

20logging.root.setLevel(logging.INFO) 

21 

22#so we can keep a reference to all the loggers in use 

23#we may have multiple loggers for the same key, so use a dict 

24#but we don't want to prevent garbage collection so use a list of weakrefs 

25all_loggers = dict() 

26def add_logger(categories, logger): 

27 global all_loggers 

28 categories = list(categories) 

29 categories.append("all") 

30 l = weakref.ref(logger) 

31 for cat in categories: 

32 all_loggers.setdefault(cat, set()).add(l) 

33 

34def get_all_loggers(): 

35 global all_loggers 

36 a = set() 

37 for loggers in all_loggers.values(): 

38 for logger in list(loggers): 

39 #weakref: 

40 instance = logger() 

41 if instance: 

42 a.add(instance) 

43 return a 

44 

45 

46debug_enabled_categories = set() 

47debug_disabled_categories = set() 

48 

49def get_debug_args(): 

50 args = [] 

51 if debug_enabled_categories: 

52 args += list(debug_enabled_categories) 

53 if debug_disabled_categories: 

54 for x in debug_disabled_categories: 

55 args.append("-%s" % x) 

56 return args 

57 

58class FullDebugContext: 

59 

60 def __enter__(self): 

61 global debug_enabled_categories 

62 self.debug_enabled_categories = debug_enabled_categories 

63 debug_enabled_categories.clear() 

64 debug_enabled_categories.add("all") 

65 self.enabled = [] 

66 for x in get_all_loggers(): 

67 if not x.is_debug_enabled(): 

68 self.enabled.append(x) 

69 x.enable_debug() 

70 

71 def __exit__(self, *_args): 

72 for x in self.enabled: 

73 x.disable_debug() 

74 global debug_enabled_categories 

75 debug_enabled_categories.clear() 

76 debug_enabled_categories.add(self.debug_enabled_categories) 

77 

78 

79def add_debug_category(*cat): 

80 remove_disabled_category(*cat) 

81 for c in cat: 

82 debug_enabled_categories.add(c) 

83 

84def remove_debug_category(*cat): 

85 for c in cat: 

86 if c in debug_enabled_categories: 

87 debug_enabled_categories.remove(c) 

88 

89def is_debug_enabled(category : str): 

90 if "all" in debug_enabled_categories: 

91 return True 

92 if category in debug_enabled_categories: 

93 return True 

94 return isenvdebug(category) or isenvdebug("ALL") 

95 

96 

97def add_disabled_category(*cat): 

98 remove_debug_category(*cat) 

99 for c in cat: 

100 debug_disabled_categories.add(c) 

101 

102def remove_disabled_category(*cat): 

103 for c in cat: 

104 if c in debug_disabled_categories: 

105 debug_disabled_categories.remove(c) 

106 

107 

108def get_loggers_for_categories(*cat): 

109 if not cat: 

110 return [] 

111 if "all" in cat: 

112 return get_all_loggers() 

113 cset = set(cat) 

114 matches = set() 

115 for l in get_all_loggers(): 

116 if set(l.categories).issuperset(cset): 

117 matches.add(l) 

118 return list(matches) 

119 

120def enable_debug_for(*cat): 

121 loggers = [] 

122 for l in get_loggers_for_categories(*cat): 

123 if not l.is_debug_enabled(): 

124 l.enable_debug() 

125 loggers.append(l) 

126 return loggers 

127 

128def disable_debug_for(*cat): 

129 loggers = [] 

130 for l in get_loggers_for_categories(*cat): 

131 if l.is_debug_enabled(): 

132 l.disable_debug() 

133 loggers.append(l) 

134 return loggers 

135 

136 

137default_level = logging.DEBUG 

138def set_default_level(level): 

139 global default_level 

140 default_level = level 

141 

142 

143def standard_logging(log, level, msg, *args, **kwargs): 

144 #this is just the regular logging: 

145 log(level, msg, *args, **kwargs) 

146 

147#this allows us to capture all logging and redirect it: 

148#the default 'standard_logging' uses the logger, 

149#but the client may inject its own handler here 

150global_logging_handler = standard_logging 

151 

152def set_global_logging_handler(h): 

153 assert callable(h) 

154 global global_logging_handler 

155 saved = global_logging_handler 

156 global_logging_handler = h 

157 return saved 

158 

159 

160def setloghandler(lh): 

161 logging.root.handlers = [] 

162 logging.root.addHandler(lh) 

163 

164def enable_color(to=sys.stdout, format_string=NOPREFIX_FORMAT): 

165 if not hasattr(to, "fileno"): 

166 #on win32 sys.stdout can be a "Blackhole", 

167 #which does not have a fileno 

168 return 

169 from xpra.colorstreamhandler import ColorStreamHandler 

170 csh = ColorStreamHandler(to) 

171 csh.setFormatter(logging.Formatter(format_string)) 

172 setloghandler(csh) 

173 

174def enable_format(format_string): 

175 try: 

176 logging.root.handlers[0].formatter = logging.Formatter(format_string) 

177 except (AttributeError, IndexError): 

178 pass 

179 

180 

181STRUCT_KNOWN_FILTERS = { 

182 "Client" : { 

183 "client" : "All client code", 

184 "paint" : "Client window paint code", 

185 "draw" : "Client draw packets", 

186 "cairo" : "Cairo paint code used with the GTK3 client", 

187 "opengl" : "Client OpenGL rendering", 

188 "info" : "About and Session info dialogs", 

189 "launcher" : "The client launcher program", 

190 }, 

191 "General" : { 

192 "clipboard" : "All clipboard operations", 

193 "notify" : "Notification forwarding", 

194 "tray" : "System Tray forwarding", 

195 "printing" : "Printing", 

196 "file" : "File transfers", 

197 "keyboard" : "Keyboard mapping and key event handling", 

198 "screen" : "Screen and workarea dimension", 

199 "fps" : "Frames per second", 

200 "xsettings" : "XSettings synchronization", 

201 "dbus" : "DBUS calls", 

202 "rpc" : "Remote Procedure Calls", 

203 "menu" : "Menus", 

204 "events" : "System and window events", 

205 }, 

206 "Window" : { 

207 "window" : "All window code", 

208 "damage" : "Window X11 repaint events", 

209 "geometry" : "Window geometry", 

210 "shape" : "Window shape forwarding (XShape)", 

211 "focus" : "Window focus", 

212 "workspace" : "Window workspace synchronization", 

213 "metadata" : "Window metadata", 

214 "alpha" : "Window Alpha channel (transparency)", 

215 "state" : "Window state", 

216 "icon" : "Window icons", 

217 "frame" : "Window frame", 

218 "grab" : "Window grabs (both keyboard and mouse)", 

219 "dragndrop" : "Window drag-n-drop events", 

220 "filters" : "Window filters", 

221 }, 

222 "Encoding" : { 

223 "codec" : "Codec loader and video helper", 

224 "loader" : "Pixel compression codec loader", 

225 "video" : "Video encoding", 

226 "score" : "Video pipeline scoring and selection", 

227 "encoding" : "Server side encoding selection and compression", 

228 "scaling" : "Picture scaling", 

229 "scroll" : "Scrolling detection and compression", 

230 "xor" : "XOR delta pre-compression", 

231 "subregion" : "Video subregion processing", 

232 "regiondetect" : "Video region detection", 

233 "regionrefresh" : "Video region refresh", 

234 "refresh" : "Refresh of lossy screen updates", 

235 "compress" : "Pixel compression (non video)", 

236 }, 

237 "Codec" : { 

238 #codecs: 

239 "csc" : "Colourspace conversion codecs", 

240 "cuda" : "CUDA device access (nvenc)", 

241 "cython" : "Cython CSC module", 

242 "swscale" : "swscale CSC module", 

243 "libyuv" : "libyuv CSC module", 

244 "decoder" : "All decoders", 

245 "encoder" : "All encoders", 

246 "avcodec" : "avcodec decoder", 

247 "libav" : "libav common code (used by swscale, avcodec and ffmpeg)", 

248 "ffmpeg" : "ffmpeg encoder", 

249 "pillow" : "Pillow encoder and decoder", 

250 "jpeg" : "JPEG codec", 

251 "vpx" : "libvpx encoder and decoder", 

252 "nvenc" : "nvenc hardware encoder", 

253 "nvfbc" : "nfbc screen capture", 

254 "x264" : "libx264 encoder", 

255 "x265" : "libx265 encoder", 

256 "webp" : "libwebp encoder and decoder", 

257 "webcam" : "webcam access", 

258 }, 

259 "Pointer" : { 

260 "mouse" : "Mouse motion", 

261 "cursor" : "Mouse cursor shape", 

262 }, 

263 "Misc" : { 

264 #libraries 

265 "gtk" : "All GTK code: bindings, client, etc", 

266 "util" : "All utility functions", 

267 "gobject" : "Command line clients", 

268 #server bits: 

269 "test" : "Test code", 

270 "verbose" : "Very verbose flag", 

271 #specific applications: 

272 }, 

273 "Network" : { 

274 #internal / network: 

275 "network" : "All network code", 

276 "bandwidth" : "Bandwidth detection and management", 

277 "ssh" : "SSH connections", 

278 "ssl" : "SSL connections", 

279 "http" : "HTTP requests", 

280 "rfb" : "RFB Protocol", 

281 "mmap" : "mmap transfers", 

282 "protocol" : "Packet input and output (formatting, parsing, sending and receiving)", 

283 "websocket" : "WebSocket layer", 

284 "named-pipe" : "Named pipe", 

285 "udp" : "UDP", 

286 "crypto" : "Encryption", 

287 "auth" : "Authentication", 

288 "upnp" : "UPnP", 

289 }, 

290 "Server" : { 

291 #Server: 

292 "server" : "All server code", 

293 "proxy" : "Proxy server", 

294 "shadow" : "Shadow server", 

295 "command" : "Server control channel", 

296 "timeout" : "Server timeouts", 

297 "exec" : "Executing commands", 

298 #server features: 

299 "mdns" : "mDNS session publishing", 

300 #server internals: 

301 "stats" : "Server statistics", 

302 "xshm" : "XShm pixel capture", 

303 }, 

304 "Sound" : { 

305 "sound" : "All sound", 

306 "gstreamer" : "GStreamer internal messages", 

307 "av-sync" : "Audio-video sync", 

308 }, 

309 "X11" : { 

310 "x11" : "All X11 code", 

311 "xinput" : "XInput bindings", 

312 "bindings" : "X11 Cython bindings", 

313 "core" : "X11 core bindings", 

314 "randr" : "X11 RandR bindings", 

315 "ximage" : "X11 XImage bindings", 

316 "error" : "X11 errors", 

317 }, 

318 "Platform" : { 

319 "platform" : "All platform support code", 

320 "import" : "Platform support import code", 

321 "osx" : "Mac OS X platform support code", 

322 "win32" : "Microsoft Windows platform support code", 

323 "posix" : "Posix platform code", 

324 }, 

325 } 

326 

327#flatten it: 

328KNOWN_FILTERS = {} 

329for d in STRUCT_KNOWN_FILTERS.values(): 

330 for k,v in d.items(): 

331 KNOWN_FILTERS[k] = v 

332 

333 

334def isenvdebug(category : str) -> bool: 

335 return os.environ.get("XPRA_%s_DEBUG" % category.upper().replace("-", "_").replace("+", "_"), "0")=="1" 

336 

337# A wrapper around 'logging' with some convenience stuff. In particular: 

338# -- You initialize it with a list of categories 

339# If unset, the default logging target is set to the name of the module where 

340# Logger() was called. 

341# -- Any of the categories can enable debug logging if the environment 

342# variable 'XPRA_${CATEGORY}_DEBUG' is set to "1" 

343# -- We also keep a list of debug_categories, so these can get enabled 

344# programatically too 

345# -- We keep track of which loggers are associated with each category, 

346# so we can enable/disable debug logging by category 

347# -- You can pass exc_info=True to any method, and sys.exc_info() will be 

348# substituted. 

349# -- __call__ is an alias for debug 

350# -- we bypass the logging system unless debugging is enabled for the logger, 

351# which is much faster than relying on the python logging code 

352 

353class Logger: 

354 def __init__(self, *categories): 

355 global default_level, debug_disabled_categories, KNOWN_FILTERS 

356 self.categories = list(categories) 

357 try: 

358 caller = sys._getframe(1).f_globals["__name__"] #pylint: disable=protected-access 

359 except AttributeError: 

360 caller = None 

361 if caller not in ("__main__", None): 

362 self.categories.insert(0, caller) 

363 self.logger = logging.getLogger(".".join(self.categories)) 

364 self.logger.setLevel(default_level) 

365 disabled = False 

366 enabled = False 

367 if caller in DEBUG_MODULES: 

368 enabled = True 

369 else: 

370 for cat in self.categories: 

371 if cat in debug_disabled_categories: 

372 disabled = True 

373 if is_debug_enabled(cat): 

374 enabled = True 

375 if len(categories)>1: 

376 #try all string permutations of those categories: 

377 # "keyboard", "events" -> "keyboard+events" or "events+keyboard" 

378 import itertools 

379 for cats in itertools.permutations(categories): 

380 cstr = "+".join(cats) 

381 if cstr in debug_disabled_categories: 

382 disabled = True 

383 if is_debug_enabled(cstr): 

384 enabled = True 

385 self.debug_enabled = enabled and not disabled 

386 #ready, keep track of it: 

387 add_logger(self.categories, self) 

388 for x in categories: 

389 if x not in KNOWN_FILTERS: 

390 self.warn("unknown logging category: %s", x) 

391 

392 def get_info(self) -> dict: 

393 return { 

394 "categories" : self.categories, 

395 "debug" : self.debug_enabled, 

396 "level" : self.logger.getEffectiveLevel(), 

397 } 

398 

399 def __repr__(self): 

400 return "Logger(%s)" % ", ".join(self.categories) 

401 

402 def is_debug_enabled(self) -> bool: 

403 return self.debug_enabled 

404 

405 def enable_debug(self): 

406 self.debug_enabled = True 

407 

408 def disable_debug(self): 

409 self.debug_enabled = False 

410 

411 

412 def log(self, level, msg : str, *args, **kwargs): 

413 if kwargs.get("exc_info") is True: 

414 ei = sys.exc_info() 

415 if ei!=(None, None, None): 

416 kwargs["exc_info"] = ei 

417 global global_logging_handler 

418 if LOG_PREFIX: 

419 msg = LOG_PREFIX+msg 

420 global_logging_handler(self.logger.log, level, msg, *args, **kwargs) 

421 

422 def __call__(self, msg : str, *args, **kwargs): 

423 if self.debug_enabled: 

424 self.log(logging.DEBUG, msg, *args, **kwargs) 

425 def debug(self, msg : str, *args, **kwargs): 

426 if self.debug_enabled: 

427 self.log(logging.DEBUG, msg, *args, **kwargs) 

428 def info(self, msg : str, *args, **kwargs): 

429 self.log(logging.INFO, msg, *args, **kwargs) 

430 def warn(self, msg : str, *args, **kwargs): 

431 self.log(logging.WARN, msg, *args, **kwargs) 

432 def error(self, msg : str, *args, **kwargs): 

433 self.log(logging.ERROR, msg, *args, **kwargs) 

434 

435 

436class CaptureHandler(logging.Handler): 

437 

438 def __init__(self): 

439 super().__init__(logging.DEBUG) 

440 self.records = [] 

441 

442 def handle(self, record): 

443 self.records.append(record) 

444 

445 def emit(self, record): 

446 self.records.append(record) 

447 

448 def createLock(self): 

449 self.lock = None 

450 

451class SIGPIPEStreamHandler(logging.StreamHandler): 

452 def flush(self): 

453 try: 

454 super().flush() 

455 except BrokenPipeError: 

456 pass 

457 

458 def emit(self, record): 

459 try: 

460 msg = self.format(record) 

461 stream = self.stream 

462 # issue 35046: merged two stream.writes into one. 

463 stream.write(msg + self.terminator) 

464 self.flush() 

465 except RecursionError: # See issue 36272 

466 raise 

467 except BrokenPipeError: 

468 pass 

469 except Exception: 

470 self.handleError(record)