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#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3# This file is part of Xpra. 

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

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 

8from xpra.x11.x11_server_core import X11ServerCore 

9from xpra.os_util import monotonic_time, is_Wayland, get_loaded_kernel_modules 

10from xpra.util import ( 

11 envbool, envint, merge_dicts, 

12 XPRA_DISPLAY_NOTIFICATION_ID, XPRA_SHADOWWAYLAND_NOTIFICATION_ID, 

13 ) 

14from xpra.server.shadow.gtk_shadow_server_base import GTKShadowServerBase 

15from xpra.server.shadow.gtk_root_window_model import GTKImageCapture 

16from xpra.server.shadow.shadow_server_base import ShadowServerBase 

17from xpra.x11.bindings.ximage import XImageBindings #@UnresolvedImport 

18from xpra.gtk_common.error import xsync, xlog 

19from xpra.log import Logger 

20 

21log = Logger("x11", "shadow") 

22 

23XImage = XImageBindings() 

24 

25USE_XSHM = envbool("XPRA_XSHM", True) 

26POLL_CURSOR = envint("XPRA_POLL_CURSOR", 20) 

27USE_NVFBC = envbool("XPRA_NVFBC", True) 

28USE_NVFBC_CUDA = envbool("XPRA_NVFBC_CUDA", True) 

29if USE_NVFBC: 

30 try: 

31 from xpra.codecs.nvfbc.fbc_capture_linux import ( #@UnresolvedImport 

32 init_module, NvFBC_SysCapture, NvFBC_CUDACapture, 

33 ) 

34 init_module() 

35 except Exception: 

36 log("NvFBC Capture is not available", exc_info=True) 

37 USE_NVFBC = False 

38 

39 

40class XImageCapture: 

41 def __init__(self, xwindow): 

42 self.xshm = None 

43 self.xwindow = xwindow 

44 assert USE_XSHM and XImage.has_XShm(), "no XShm support" 

45 if is_Wayland(): 

46 log.warn("Warning: shadow servers do not support Wayland") 

47 log.warn(" please switch to X11 for shadow support") 

48 

49 def __repr__(self): 

50 return "XImageCapture(%#x)" % self.xwindow 

51 

52 def clean(self): 

53 self.close_xshm() 

54 

55 def close_xshm(self): 

56 xshm = self.xshm 

57 if self.xshm: 

58 self.xshm = None 

59 with xlog: 

60 xshm.cleanup() 

61 

62 def _err(self, e, op="capture pixels"): 

63 if getattr(e, "msg", None)=="BadMatch": 

64 log("BadMatch - temporary error in %s of window #%x", op, self.xwindow, exc_info=True) 

65 else: 

66 log.warn("Warning: failed to %s of window %#x:", self.xwindow) 

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

68 self.close_xshm() 

69 

70 def refresh(self): 

71 if self.xshm: 

72 #discard to ensure we will call XShmGetImage next time around 

73 self.xshm.discard() 

74 return True 

75 try: 

76 with xsync: 

77 log("%s.refresh() xshm=%s", self, self.xshm) 

78 self.xshm = XImage.get_XShmWrapper(self.xwindow) 

79 self.xshm.setup() 

80 except Exception as e: 

81 self.xshm = None 

82 self._err(e, "xshm setup") 

83 return True 

84 

85 def get_image(self, x, y, width, height): 

86 if self.xshm is None: 

87 log("no xshm, cannot get image") 

88 return None 

89 try: 

90 start = monotonic_time() 

91 with xsync: 

92 log("X11 shadow get_image, xshm=%s", self.xshm) 

93 image = self.xshm.get_image(self.xwindow, x, y, width, height) 

94 return image 

95 except Exception as e: 

96 self._err(e) 

97 return None 

98 finally: 

99 end = monotonic_time() 

100 log("X11 shadow captured %s pixels at %i MPixels/s using %s", 

101 width*height, (width*height/(end-start))//1024//1024, ["GTK", "XSHM"][USE_XSHM]) 

102 

103 

104def setup_capture(window): 

105 ww, wh = window.get_geometry()[2:4] 

106 capture = None 

107 if USE_NVFBC: 

108 try: 

109 log("setup_capture(%s) USE_NVFBC_CUDA=%s", window, USE_NVFBC_CUDA) 

110 if USE_NVFBC_CUDA: 

111 capture = NvFBC_CUDACapture() 

112 else: 

113 capture = NvFBC_SysCapture() 

114 capture.init_context(ww, wh) 

115 capture.refresh() 

116 image = capture.get_image(0, 0, ww, wh) 

117 assert image, "test capture failed" 

118 except Exception as e: 

119 log("get_image() NvFBC test failed", exc_info=True) 

120 log("not using %s: %s", capture, e) 

121 capture = None 

122 if not capture and XImage.has_XShm() and USE_XSHM: 

123 capture = XImageCapture(window.get_xid()) 

124 if not capture: 

125 capture = GTKImageCapture(window) 

126 log("setup_capture(%s)=%s", window, capture) 

127 return capture 

128 

129 

130#FIXME: warning: this class inherits from ServerBase twice.. 

131#so many calls will happen twice there (__init__ and init) 

132class ShadowX11Server(GTKShadowServerBase, X11ServerCore): 

133 

134 def __init__(self): 

135 GTKShadowServerBase.__init__(self) 

136 X11ServerCore.__init__(self) 

137 self.session_type = "shadow" 

138 

139 def init(self, opts): 

140 GTKShadowServerBase.init(self, opts) 

141 #don't call init on X11ServerCore, 

142 #this would call up to GTKServerBase.init(opts) again: 

143 X11ServerCore.do_init(self, opts) 

144 

145 def init_fake_xinerama(self): 

146 #don't enable fake xinerama with shadow servers, 

147 #we want to keep whatever settings they have 

148 self.libfakeXinerama_so = None 

149 

150 

151 def cleanup(self): 

152 GTKShadowServerBase.cleanup(self) 

153 X11ServerCore.cleanup(self) #@UndefinedVariable 

154 

155 

156 def setup_capture(self): 

157 return setup_capture(self.root) 

158 

159 

160 def client_startup_complete(self, ss): 

161 super().client_startup_complete(ss) 

162 log("is_Wayland()=%s", is_Wayland()) 

163 if is_Wayland(): 

164 ss.may_notify(XPRA_SHADOWWAYLAND_NOTIFICATION_ID, 

165 "Wayland Shadow Server", 

166 "This shadow session is running under wayland,\n"+ 

167 "the screen scraping will probably come up empty", 

168 icon_name="unticked") 

169 

170 

171 def last_client_exited(self): 

172 GTKShadowServerBase.last_client_exited(self) 

173 X11ServerCore.last_client_exited(self) 

174 

175 

176 def do_get_cursor_data(self): 

177 return X11ServerCore.get_cursor_data(self) 

178 

179 

180 def send_initial_data(self, ss, c, send_ui, share_count): 

181 super().send_initial_data(ss, c, send_ui, share_count) 

182 if getattr(ss, "ui_client", True) and getattr(ss, "send_windows", True): 

183 self.verify_capture(ss) 

184 

185 def verify_capture(self, ss): 

186 #verify capture works: 

187 log("verify_capture(%s)", ss) 

188 try: 

189 capture = GTKImageCapture(self.root) 

190 bdata = capture.take_screenshot()[-1] 

191 nid = XPRA_DISPLAY_NOTIFICATION_ID 

192 title = body = "" 

193 if any(b!=0 for b in bdata): 

194 log("verify_capture(%s) succeeded", ss) 

195 if is_Wayland(): 

196 title = "Wayland Session Warning" 

197 body = "Wayland sessions are not supported,\n"+\ 

198 "the screen capture is likely to be empty" 

199 else: 

200 log.warn("Warning: shadow screen capture is blank") 

201 body = "The shadow display capture is blank" 

202 if get_loaded_kernel_modules("vboxguest", "vboxvideo"): 

203 body += "\nthis may be caused by the VirtualBox video driver." 

204 title = "Shadow Capture Failure" 

205 log("verify_capture: title=%r, body=%r", ss, title, body) 

206 if title and body: 

207 ss.may_notify(nid, title, body, icon_name="server") 

208 except Exception as e: 

209 ss.may_notify(nid, "Shadow Error", "Error shadowing the display:\n%s" % e, icon_name="bugs") 

210 

211 

212 def make_hello(self, source): 

213 capabilities = X11ServerCore.make_hello(self, source) 

214 capabilities.update(GTKShadowServerBase.make_hello(self, source)) 

215 capabilities["server_type"] = "Python/gtk2/x11-shadow" 

216 return capabilities 

217 

218 def get_info(self, proto, *_args): 

219 info = X11ServerCore.get_info(self, proto) 

220 merge_dicts(info, ShadowServerBase.get_info(self, proto)) 

221 info.setdefault("features", {})["shadow"] = True 

222 info.setdefault("server", {})["type"] = "Python/gtk3/x11-shadow" 

223 return info 

224 

225 def do_make_screenshot_packet(self): 

226 capture = GTKImageCapture(self.root) 

227 w, h, encoding, rowstride, data = capture.take_screenshot() 

228 assert encoding=="png" #use fixed encoding for now 

229 from xpra.net.compression import Compressed 

230 return ["screenshot", w, h, encoding, rowstride, Compressed(encoding, data)] 

231 

232 

233def main(filename): 

234 from io import BytesIO 

235 from xpra.os_util import memoryview_to_bytes 

236 from xpra.gtk_common.gtk_util import get_default_root_window, get_root_size 

237 root = get_default_root_window() 

238 capture = setup_capture(root) 

239 capture.refresh() 

240 w, h = get_root_size() 

241 image = capture.get_image(0, 0, w, h) 

242 from PIL import Image 

243 fmt = image.get_pixel_format().replace("X", "A") 

244 pixels = memoryview_to_bytes(image.get_pixels()) 

245 log("converting %i bytes in format %s to RGBA", len(pixels), fmt) 

246 if len(fmt)==3: 

247 target = "RGB" 

248 else: 

249 target = "RGBA" 

250 pil_image = Image.frombuffer(target, (w, h), pixels, "raw", fmt, image.get_rowstride()) 

251 if target!="RGB": 

252 pil_image = pil_image.convert("RGB") 

253 buf = BytesIO() 

254 pil_image.save(buf, "png") 

255 data = buf.getvalue() 

256 buf.close() 

257 with open(filename, "wb") as f: 

258 f.write(data) 

259 return 0 

260 

261if __name__ == "__main__": 

262 import sys 

263 if len(sys.argv)!=2: 

264 print("usage: %s filename.png" % sys.argv[0]) 

265 v = 1 

266 else: 

267 v = main(sys.argv[1]) 

268 sys.exit(v)