Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/shadow_x11_server.py : 65%
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.
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
21log = Logger("x11", "shadow")
23XImage = XImageBindings()
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
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")
49 def __repr__(self):
50 return "XImageCapture(%#x)" % self.xwindow
52 def clean(self):
53 self.close_xshm()
55 def close_xshm(self):
56 xshm = self.xshm
57 if self.xshm:
58 self.xshm = None
59 with xlog:
60 xshm.cleanup()
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()
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
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])
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
130#FIXME: warning: this class inherits from ServerBase twice..
131#so many calls will happen twice there (__init__ and init)
132class ShadowX11Server(GTKShadowServerBase, X11ServerCore):
134 def __init__(self):
135 GTKShadowServerBase.__init__(self)
136 X11ServerCore.__init__(self)
137 self.session_type = "shadow"
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)
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
151 def cleanup(self):
152 GTKShadowServerBase.cleanup(self)
153 X11ServerCore.cleanup(self) #@UndefinedVariable
156 def setup_capture(self):
157 return setup_capture(self.root)
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")
171 def last_client_exited(self):
172 GTKShadowServerBase.last_client_exited(self)
173 X11ServerCore.last_client_exited(self)
176 def do_get_cursor_data(self):
177 return X11ServerCore.get_cursor_data(self)
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)
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")
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
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
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)]
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
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)