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 

7# Goal: make it as easy and efficient as possible to manage the X errors that 

8# a WM is inevitably susceptible to. (E.g., if a window goes away while we 

9# are working on it.) On the one hand, we want to parcel operations into as 

10# broad chunks as possible that at treated as succeeding or failing as a whole 

11# (e.g., "setting up a new window", we don't really care how much was 

12# accomplished before the failure occurred). On the other, we do want to 

13# check for X errors often, for use in debugging (esp., this makes it more 

14# useful to run with -sync). 

15# 

16# The solution is to keep a stack of how deep we are in "transaction-like" 

17# operations -- a transaction is a series of operations where we don't care if 

18# we don't find about the failures until the end. We only sync when exiting a 

19# top-level transaction. 

20# 

21# The _synced and _unsynced variants differ in whether they assume the X 

22# connection was left in a synchronized state by the code they called (e.g., 

23# if the last operation was an XGetProperty, then there is no need for us to 

24# do another XSync). 

25# 

26# (In this modern world, with WM's either on the same machine or over 

27# super-fast connections to the X server, everything running on fast 

28# computers... does being this careful to avoid sync's actually matter?) 

29 

30import traceback 

31from gi.repository import Gdk 

32 

33from xpra.util import envbool 

34from xpra.os_util import is_main_thread 

35from xpra.log import Logger 

36 

37__all__ = ["XError", "trap", "xsync", "xswallow"] 

38 

39#run xpra in synchronized mode to debug X11 errors: 

40XPRA_SYNCHRONIZE = envbool("XPRA_SYNCHRONIZE", False) 

41XPRA_LOG_SYNC = envbool("XPRA_LOG_SYNC", False) 

42VERIFY_MAIN_THREAD = envbool("XPRA_VERIFY_MAIN_THREAD", True) 

43LOG_NESTED_XTRAP = envbool("XPRA_LOG_NESTED_XTRAP", False) 

44 

45log = Logger("x11", "util") 

46elog = Logger("x11", "util", "error") 

47 

48 

49if not VERIFY_MAIN_THREAD: 

50 def verify_main_thread(): 

51 return 

52else: 

53 def verify_main_thread(): 

54 if not is_main_thread(): 

55 import threading 

56 log.error("Error: invalid access from thread %s", threading.current_thread()) 

57 traceback.print_stack() 

58 verify_main_thread() 

59 

60 

61class XError(Exception): 

62 def __init__(self, message): 

63 Exception.__init__(self) 

64 self.msg = get_X_error(message) 

65 

66 def __str__(self): 

67 return "XError: %s" % self.msg 

68 

69 

70xerror_to_name = None 

71def get_X_error(xerror): 

72 global xerror_to_name 

73 if not isinstance(xerror, int): 

74 return xerror 

75 try: 

76 from xpra.x11.bindings.window_bindings import constants #@UnresolvedImport 

77 if xerror_to_name is None: 

78 xerror_to_name = {} 

79 for name,code in constants.items(): # @UndefinedVariable 

80 if name=="Success" or name.startswith("Bad"): 

81 xerror_to_name[code] = name 

82 log("get_X_error(..) initialized error names: %s", xerror_to_name) 

83 if xerror in xerror_to_name: 

84 return xerror_to_name.get(xerror) 

85 from xpra.x11.bindings.core_bindings import X11CoreBindings #@UnresolvedImport 

86 return X11CoreBindings().get_error_text(xerror) 

87 except Exception as e: 

88 log.error("get_X_error(%s) %s", xerror, e, exc_info=True) 

89 return xerror 

90 

91 

92# gdk has its own depth tracking stuff, but we have to duplicate it here to 

93# minimize calls to XSync. 

94class _ErrorManager: 

95 def __init__(self): 

96 self.depth = 0 

97 

98 def Xenter(self): 

99 assert self.depth >= 0 

100 verify_main_thread() 

101 Gdk.error_trap_push() 

102 if XPRA_LOG_SYNC: 

103 log("X11trap.enter at level %i", self.depth) 

104 if LOG_NESTED_XTRAP and self.depth>0: 

105 for x in traceback.extract_stack(): 

106 log("%s", x) 

107 self.depth += 1 

108 

109 def Xexit(self, need_sync=True): 

110 assert self.depth >= 0 

111 self.depth -= 1 

112 if XPRA_LOG_SYNC: 

113 log("X11trap.exit at level %i, need_sync=%s", self.depth, need_sync) 

114 if self.depth == 0 and need_sync: 

115 Gdk.flush() 

116 # This is a Xlib error constant (Success == 0) 

117 error = Gdk.error_trap_pop() 

118 if error: 

119 raise XError(error) 

120 

121 def _call(self, need_sync, fun, args, kwargs): 

122 # Goal: call the function. In all conditions, call _exit exactly once 

123 # on the way out. However, if we are exiting because of an exception, 

124 # then probably that exception is more informative than any XError 

125 # that might also be raised, so suppress the XError in that case. 

126 value = None 

127 try: 

128 self.Xenter() 

129 value = fun(*args, **kwargs) 

130 except Exception as e: 

131 elog("_call%s", (need_sync, fun, args, kwargs), exc_info=True) 

132 log("_call%s %s", (need_sync, fun, args, kwargs), e) 

133 try: 

134 self.Xexit(need_sync) 

135 except XError as ee: 

136 log("XError %s detected while already in unwind; discarding", 

137 ee, exc_info=True) 

138 raise 

139 self.Xexit(need_sync) 

140 return value 

141 

142 def call_unsynced(self, fun, *args, **kwargs): 

143 return self._call(False, fun, args, kwargs) 

144 

145 def call_synced(self, fun, *args, **kwargs): 

146 return self._call(True, fun, args, kwargs) 

147 

148 if XPRA_SYNCHRONIZE: 

149 call = call_synced 

150 else: 

151 call = call_unsynced 

152 

153 def swallow_unsynced(self, fun, *args, **kwargs): 

154 try: 

155 self.call_unsynced(fun, *args, **kwargs) 

156 return True 

157 except XError: 

158 log("Ignoring X error on %s", 

159 fun, exc_info=True) 

160 return False 

161 

162 def swallow_synced(self, fun, *args, **kwargs): 

163 try: 

164 self.call_synced(fun, *args, **kwargs) 

165 return True 

166 except XError: 

167 log("Ignoring X error on %s", 

168 fun, exc_info=True) 

169 return False 

170 

171 if XPRA_SYNCHRONIZE: 

172 swallow = swallow_synced 

173 else: 

174 swallow = swallow_unsynced 

175 

176 def assert_out(self): 

177 assert self.depth == 0 

178 

179trap = _ErrorManager() 

180 

181 

182class XSyncContext: 

183 

184 def __enter__(self): 

185 trap.Xenter() 

186 

187 def __exit__(self, e_typ, _e_val, _trcbak): 

188 #log("xsync.exit%s", (e_typ, e_val, trcbak)) 

189 try: 

190 trap.Xexit() 

191 except XError: 

192 if e_typ is None: 

193 #we are not handling an exception yet, so raise this one: 

194 raise 

195 log("XError detected while already in unwind; discarding", 

196 exc_info=True) 

197 #raise the original exception: 

198 return False 

199 

200xsync = XSyncContext() 

201 

202 

203class XSwallowContext: 

204 

205 def __enter__(self): 

206 trap.Xenter() 

207 

208 def __exit__(self, e_typ, e_val, trcbak): 

209 if e_typ: 

210 log("xswallow.exit%s", (e_typ, e_val, trcbak), exc_info=True) 

211 try: 

212 trap.Xexit() 

213 except XError: 

214 log("XError detected while already in unwind; discarding", 

215 exc_info=True) 

216 #don't raise exceptions: 

217 return True 

218 

219xswallow = XSwallowContext() 

220 

221 

222class XLogContext: 

223 

224 def __enter__(self): 

225 trap.Xenter() 

226 

227 def __exit__(self, e_typ, e_val, trcbak): 

228 if e_typ: 

229 log.error("XError: %s, %s", e_typ, e_val, exc_info=True) 

230 try: 

231 trap.Xexit() 

232 except XError: 

233 log("XError detected while already in unwind; discarding", 

234 exc_info=True) 

235 #don't raise exceptions: 

236 return True 

237 

238xlog = XLogContext() 

239 

240 

241def verify_sync(): 

242 if trap.depth<=0: 

243 log.error("Error: unmanaged X11 context") 

244 stack = traceback.extract_stack()[:-1] 

245 s = traceback.format_list(stack) 

246 for x in s: 

247 for v in x.splitlines(): 

248 log.error(" %s", v) 

249 #raise Exception("unmanaged context")