From 7c824f438cd87dab3f53730a8be0d8957d4107e2 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 12 May 2013 12:28:03 +0000 Subject: [PATCH] preliminary support for transparent windows using the gtk2 Pixmap backing client: * windows expose an "has-alpha" metadata attribute so we know the backing needs the alpha channel - store this property in the metadata cache since it cannot be changed (can it?) * if enabled, the client also sets the window colormap to "rgba" * assume rgb32 is always available for now: this needs to be tested for differently for client and server, and even for different types of clients / different types of window backings * shadow servers disable alpha * use get_best_encoding to select a suitable encoding for handling transparency (tested only with "png" and raw "rgb32") * handle transparency in screenshots too * change "contents-handle" to return a PixmapWrapper object which can get pixel data from the X11 server by calling XGetImage * extract pixels from the XImage using PIL git-svn-id: https://xpra.org/svn/Xpra/trunk@3368 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/client/client_window_base.py | 19 +++- src/xpra/client/gtk2/client_window.py | 10 ++ src/xpra/client/gtk2/pixmap_backing.py | 30 ++++-- src/xpra/client/gtk2/window_backing.py | 4 +- src/xpra/client/gtk3/client_window.py | 4 +- src/xpra/client/gtk_base/cairo_backing.py | 4 +- src/xpra/client/qt4/pixmap_backing.py | 4 +- src/xpra/client/window_backing_base.py | 21 ++-- src/xpra/gtk_common/pixbuf_to_rgb.py | 2 +- src/xpra/scripts/config.py | 2 +- src/xpra/server/shadow_server_base.py | 5 +- src/xpra/server/source.py | 5 +- src/xpra/server/window_source.py | 38 ++++--- src/xpra/x11/gtk_x11/composite.py | 6 +- src/xpra/x11/gtk_x11/gdk_bindings.pyx | 124 ++++++++++++++++++---- src/xpra/x11/gtk_x11/window.py | 52 ++++++++- src/xpra/x11/server.py | 27 +++-- 17 files changed, 272 insertions(+), 85 deletions(-) diff --git a/src/xpra/client/client_window_base.py b/src/xpra/client/client_window_base.py index 9226d7421f..95084c0dbf 100644 --- a/src/xpra/client/client_window_base.py +++ b/src/xpra/client/client_window_base.py @@ -63,7 +63,7 @@ def init_window(self, metadata): self._been_mapped = False self._override_redirect_windows = [] self.update_metadata(metadata) - + def setup_window(self): self.new_backing(*self._size) @@ -71,6 +71,7 @@ def setup_window(self): def make_new_backing(self, backing_class, w, h): w = max(1, w) h = max(1, h) + has_alpha = self._metadata.get("has-alpha", False) lock = None backing = self._backing if backing: @@ -79,13 +80,16 @@ def make_new_backing(self, backing_class, w, h): if lock: lock.acquire() if backing is None: + bc = backing_class if USE_FAKE_BACKING: from xpra.client.fake_window_backing import FakeBacking - backing_class = FakeBacking - backing = backing_class(self._id, w, h) + bc = FakeBacking + self.debug("make_new_backing(%s, %s, %s) effective backing class=%s", backing_class, w, h, bc) + backing = bc(self._id, w, h, has_alpha) if self._client.mmap_enabled: backing.enable_mmap(self._client.mmap) - backing.init(w, h) + self.debug("make_new_backing(%s, %s, %s) init with alpha=%s", backing_class, w, h, has_alpha) + backing.init(w, h, has_alpha) finally: if lock: lock.release() @@ -218,6 +222,10 @@ def metadata_replace(match): xid = self._metadata.get("xid") self.set_xid(xid) + if "has-alpha" in self._metadata: + has_alpha = self._metadata.get("has-alpha") + self.set_alpha(has_alpha) + def set_window_type(self, window_types): self.debug("set_window_type(%s)", window_types) hints = 0 @@ -228,6 +236,9 @@ def set_window_type(self, window_types): self.debug("setting window type to %s - %s", window_type, hint) self.set_type_hint(hint) + def set_alpha(self, has_alpha): + pass + def set_xid(self, xid): pass diff --git a/src/xpra/client/gtk2/client_window.py b/src/xpra/client/gtk2/client_window.py index e2956e42a8..9858ecc11a 100644 --- a/src/xpra/client/gtk2/client_window.py +++ b/src/xpra/client/gtk2/client_window.py @@ -70,6 +70,16 @@ def init_window(self, metadata): gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) GTKClientWindowBase.init_window(self, metadata) + def set_alpha(self, has_alpha): + self.debug("set_alpha(%s)", has_alpha) + if has_alpha: + screen = self.get_screen() + rgba = screen.get_rgba_colormap() + if rgba is None: + self.error("cannot handle window transparency!") + else: + self.set_colormap(rgba) + def new_backing(self, w, h): self._backing = self.make_new_backing(BACKING_CLASS, w, h) diff --git a/src/xpra/client/gtk2/pixmap_backing.py b/src/xpra/client/gtk2/pixmap_backing.py index d394aa093e..28440205e6 100644 --- a/src/xpra/client/gtk2/pixmap_backing.py +++ b/src/xpra/client/gtk2/pixmap_backing.py @@ -4,6 +4,7 @@ # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. +import gtk from gtk import gdk import cairo @@ -17,13 +18,23 @@ """ class PixmapBacking(GTK2WindowBacking): - def __init__(self, wid, w, h): - GTK2WindowBacking.__init__(self, wid, w, h) + def __init__(self, wid, w, h, has_alpha): + GTK2WindowBacking.__init__(self, wid, w, h, has_alpha) - def init(self, w, h): + def init(self, w, h, has_alpha): old_backing = self._backing assert w<32768 and h<32768, "dimensions too big: %sx%s" % (w, h) - self._backing = gdk.Pixmap(gdk.get_default_root_window(), w, h) + self._has_alpha = has_alpha + if has_alpha: + self._backing = gdk.Pixmap(None, w, h, 32) + screen = self._backing.get_screen() + rgba = screen.get_rgba_colormap() + if rgba is not None: + self._backing.set_colormap(rgba) + else: + self._has_alpha = False + if not self._has_alpha: + self._backing = gdk.Pixmap(gdk.get_default_root_window(), w, h) cr = self._backing.cairo_create() cr.set_source_rgb(1, 1, 1) if old_backing is not None: @@ -55,6 +66,13 @@ def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, cal return True def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, callbacks): - gc = self._backing.new_gc() - self._backing.draw_rgb_32_image(gc, x, y, width, height, gdk.RGB_DITHER_NONE, img_data, rowstride) + log.debug("do_paint_rgb32(%s bytes, %s, %s, %s, %s, %s, %s, %s)", len(img_data), x, y, width, height, rowstride, options, callbacks) + #log.info("data head=%s", [hex(ord(v))[2:] for v in list(img_data[:500])]) + pixbuf = gdk.pixbuf_new_from_data(img_data, gtk.gdk.COLORSPACE_RGB, True, 8, width, height, rowstride) + log.debug("do_paint_rgb32(..) backing depth=%s", self._backing.get_depth()) + cr = self._backing.cairo_create() + cr.rectangle(x, y, width, height) + cr.set_source_pixbuf(pixbuf, x, y) + cr.set_operator(cairo.OPERATOR_SOURCE) + cr.paint() return True diff --git a/src/xpra/client/gtk2/window_backing.py b/src/xpra/client/gtk2/window_backing.py index fdd7bbc161..0d8dedc1c9 100644 --- a/src/xpra/client/gtk2/window_backing.py +++ b/src/xpra/client/gtk2/window_backing.py @@ -25,10 +25,10 @@ """ class GTK2WindowBacking(GTKWindowBacking): - def __init__(self, wid, w, h): + def __init__(self, wid, w, h, has_alpha): GTKWindowBacking.__init__(self, wid) - def init(self, w, h): + def init(self, w, h, has_alpha): raise Exception("override me!") diff --git a/src/xpra/client/gtk3/client_window.py b/src/xpra/client/gtk3/client_window.py index 0fd4ec725e..d81b610943 100644 --- a/src/xpra/client/gtk3/client_window.py +++ b/src/xpra/client/gtk3/client_window.py @@ -38,8 +38,8 @@ def init_window(self): gtk.Window.__init__(self) GTKClientWindowBase.init_window(self) - def new_backing(self, w, h): - self._backing = self.make_new_backing(CairoBacking, w, h) + def new_backing(self, w, h, has_alpha): + self._backing = self.make_new_backing(CairoBacking, w, h, has_alpha) def xget_u32_property(self, target, name): diff --git a/src/xpra/client/gtk_base/cairo_backing.py b/src/xpra/client/gtk_base/cairo_backing.py index 7312a1e752..2e49084834 100644 --- a/src/xpra/client/gtk_base/cairo_backing.py +++ b/src/xpra/client/gtk_base/cairo_backing.py @@ -29,10 +29,10 @@ This is a complete waste of CPU! Please complain to pycairo. """ class CairoBacking(GTKWindowBacking): - def __init__(self, wid, w, h): + def __init__(self, wid, w, h, has_alpha): GTKWindowBacking.__init__(self, wid) - def init(self, w, h): + def init(self, w, h, has_alpha): old_backing = self._backing #should we honour self.depth here? self._backing = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) diff --git a/src/xpra/client/qt4/pixmap_backing.py b/src/xpra/client/qt4/pixmap_backing.py index 44518b0a86..2a3c439ecd 100644 --- a/src/xpra/client/qt4/pixmap_backing.py +++ b/src/xpra/client/qt4/pixmap_backing.py @@ -20,10 +20,10 @@ """ class QtPixmapBacking(WindowBackingBase): - def __init__(self, wid, w, h): + def __init__(self, wid, w, h, has_alpha): WindowBackingBase.__init__(self, wid, getQtScheduler().idle_add) - def init(self, w, h): + def init(self, w, h, has_alpha): #TODO: repaint from old backing! #old_backing = self._backing assert w<32768 and h<32768, "dimensions too big: %sx%s" % (w, h) diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index edd640c471..b112834cba 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -53,6 +53,7 @@ class WindowBackingBase(object): def __init__(self, wid, idle_add): self.wid = wid self.idle_add = idle_add + self._has_alpha = False self._backing = None self._last_pixmap_data = None self._video_use_swscale = True @@ -118,16 +119,24 @@ def process_delta(self, raw_data, width, height, rowstride, options): def paint_image(self, coding, img_data, x, y, width, height, rowstride, options, callbacks): """ can be called from any thread """ + #log("paint_image(%s, %s bytes, %s, %s, %s, %s, %s, %s, %s)", coding, len(img_data), x, y, width, height, rowstride, options, callbacks) assert coding in ENCODINGS, "encoding %s is not supported!" % coding assert has_PIL buf = BytesIOClass(img_data) img = Image.open(buf) - assert img.mode=="RGB", "invalid image mode: %s" % img.mode - raw_data = img.tostring("raw", "RGB") - #PIL flattens the data to a continuous straightforward RGB format: - rowstride = width*3 - img_data = self.process_delta(raw_data, width, height, rowstride, options) - self.idle_add(self.do_paint_rgb24, img_data, x, y, width, height, rowstride, options, callbacks) + assert img.mode in ("RGB", "RGBA"), "invalid image mode: %s" % img.mode + raw_data = img.tostring("raw", img.mode) + if img.mode=="RGB": + #PIL flattens the data to a continuous straightforward RGB format: + if rowstride<=0: + rowstride = width*3 + img_data = self.process_delta(raw_data, width, height, rowstride, options) + self.idle_add(self.do_paint_rgb24, img_data, x, y, width, height, rowstride, options, callbacks) + elif img.mode=="RGBA": + if rowstride<=0: + rowstride = width*4 + img_data = self.process_delta(raw_data, width, height, rowstride, options) + self.idle_add(self.do_paint_rgb32, img_data, x, y, width, height, rowstride, options, callbacks) return False def paint_webp(self, img_data, x, y, width, height, rowstride, options, callbacks): diff --git a/src/xpra/gtk_common/pixbuf_to_rgb.py b/src/xpra/gtk_common/pixbuf_to_rgb.py index b2c8a01bf9..79cd4ac2c7 100644 --- a/src/xpra/gtk_common/pixbuf_to_rgb.py +++ b/src/xpra/gtk_common/pixbuf_to_rgb.py @@ -40,4 +40,4 @@ def get_rgb_rawdata(pixmap, x, y, width, height, logger=None): int(1000*(time.time()-start)), colormap.get_visual().depth) raw_data = pixbuf.get_pixels() rowstride = pixbuf.get_rowstride() - return (x, y, width, height, raw_data, rowstride) + return (x, y, width, height, raw_data, "RGB", rowstride) diff --git a/src/xpra/scripts/config.py b/src/xpra/scripts/config.py index 2899232266..d1af2cb0a5 100644 --- a/src/xpra/scripts/config.py +++ b/src/xpra/scripts/config.py @@ -23,7 +23,7 @@ def warn(msg): _has_PIL = False #if you use gtk3, you *must* have PIL installed so we can handle rgb24... -ENCODINGS = ["rgb24"] +ENCODINGS = ["rgb24", "rgb32"] if _has_PIL: ENCODINGS.append("png") ENCODINGS.append("jpeg") diff --git a/src/xpra/server/shadow_server_base.py b/src/xpra/server/shadow_server_base.py index 50ec7b095e..6556e85af3 100644 --- a/src/xpra/server/shadow_server_base.py +++ b/src/xpra/server/shadow_server_base.py @@ -44,6 +44,9 @@ def is_tray(self): def is_OR(self): return False + def has_alpha(self): + return False + def acknowledge_changes(self): pass @@ -145,7 +148,7 @@ def send_windows_and_cursors(self, ss): window = self._id_to_window[wid] assert window == self.root_window_model w, h = self.root.get_size() - props = ("title", "client-machine", "size-hints", "window-type") + props = ("title", "client-machine", "size-hints", "window-type", "has-alpha") ss.new_window("new-window", wid, window, 0, 0, w, h, props, self.client_properties.get(ss.uuid)) #ss.send_cursor(self.cursor_data) diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index 2420721a12..08342a5a93 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -466,13 +466,12 @@ def set_screen_sizes(self, screen_sizes): def _make_metadata(self, wid, window, propname): cache = self.window_metdata_cache.setdefault(wid, {}) if len(cache)==0: - #these never change and are never queried, - #but we want to report on them via "xpra info", - #so populate them here + #these never change so populate them here just once cache["override-redirect"] = window.is_OR() cache["tray"] = window.is_tray() if get_xwindow: cache["xid"] = hex(get_xwindow(window.client_window)) + cache["has-alpha"] = window.has_alpha() if propname in cache: return cache.copy() props = self.do_make_metadata(window, propname) diff --git a/src/xpra/server/window_source.py b/src/xpra/server/window_source.py index 5b687a61b6..38d38f9e5d 100644 --- a/src/xpra/server/window_source.py +++ b/src/xpra/server/window_source.py @@ -490,9 +490,9 @@ def send_full_screen_update(): self.process_damage_region(damage_time, window, x, y, w, h, actual_encoding, options) def get_best_encoding(self, batching, window, pixel_count, ww, wh, current_encoding): - return self.do_get_best_encoding(batching, window.is_tray(), window.is_OR(), pixel_count, ww, wh, current_encoding) + return self.do_get_best_encoding(batching, window.has_alpha(), window.is_tray(), window.is_OR(), pixel_count, ww, wh, current_encoding) - def do_get_best_encoding(self, batching, is_tray, is_OR, pixel_count, ww, wh, current_encoding): + def do_get_best_encoding(self, batching, has_alpha, is_tray, is_OR, pixel_count, ww, wh, current_encoding): """ decide whether we send a full screen update using the video encoder or if a small lossless region(s) is a better choice @@ -501,6 +501,10 @@ def switch(): coding = self.find_common_lossless_encoder(current_encoding, ww*wh) debug("temporarily switching to %s encoder for %s pixels", coding, pixel_count) return coding + if has_alpha and current_encoding not in ("png", "rgb32"): + for x in ("png", "rgb32"): + if x in ENCODINGS: + return x if is_tray: #tray needs a lossless encoder return switch() @@ -583,9 +587,9 @@ def do_process_damage_region(self, damage_time, window, x, y, w, h, coding, opti return if self.is_cancelled(sequence): return - px, py, pw, ph, rgb_data, rowstride = rgb + px, py, pw, ph, rgb_data, rgb_format, rowstride = rgb process_damage_time = time.time() - data = (damage_time, process_damage_time, self.wid, px, py, pw, ph, coding, rgb_data, rowstride, sequence, options) + data = (damage_time, process_damage_time, self.wid, px, py, pw, ph, coding, rgb_data, rgb_format, rowstride, sequence, options) self._sequence += 1 debug("process_damage_regions: adding pixel data %s to queue, elapsed time: %.1f ms", data[:6], 1000*(time.time()-damage_time)) def make_data_packet_cb(*args): @@ -710,7 +714,7 @@ def damage_packet_acked(self, damage_packet_sequence, width, height, decode_time if self._damage_delayed is not None and self._damage_delayed_expired: gobject.idle_add(self.may_send_delayed) - def make_data_packet(self, damage_time, process_damage_time, wid, x, y, w, h, coding, rgbdata, rowstride, sequence, options): + def make_data_packet(self, damage_time, process_damage_time, wid, x, y, w, h, coding, rgbdata, rgb_format, rowstride, sequence, options): """ Picture encoding - non-UI thread. Converts a damage item picked from the 'damage_data_queue' @@ -730,7 +734,7 @@ def make_data_packet(self, damage_time, process_damage_time, wid, x, y, w, h, co assert rgbdata, "data is missing" debug("make_data_packet: damage data: %s", (wid, x, y, w, h, coding)) start = time.time() - if self._mmap and self._mmap_size>0 and len(rgbdata)>256: + if self._mmap and self._mmap_size>0 and len(rgbdata)>256 and rgb_format=="RGB": #try with mmap (will change coding to "mmap" if it succeeds) coding, data = self.mmap_send(coding, rgbdata) else: @@ -744,8 +748,12 @@ def make_data_packet(self, damage_time, process_damage_time, wid, x, y, w, h, co delta = lsequence data = xor_str(rgbdata, ldata) + if rgb_format.upper()!="RGB" and rgb_format.upper()!="RGBX": + assert rgb_format.upper()=="RGBA", "invalid rgb format: %s" % rgb_format + assert coding in ("rgb32", "png", ), "invalid encoding for %s: %s" % (rgb_format, coding) + if coding in ("jpeg", "png"): - data, client_options = self.PIL_encode(w, h, coding, data, rowstride, options) + data, client_options = self.PIL_encode(w, h, coding, data, rgb_format, rowstride, options) elif coding=="x264": #x264 needs sizes divisible by 2: w = w & 0xFFFE @@ -754,8 +762,8 @@ def make_data_packet(self, damage_time, process_damage_time, wid, x, y, w, h, co data, client_options = self.video_encode(wid, x, y, w, h, coding, data, rowstride, options) elif coding=="vpx": data, client_options = self.video_encode(wid, x, y, w, h, coding, data, rowstride, options) - elif coding=="rgb24": - data, client_options = self.rgb24_encode(data) + elif coding=="rgb24" or coding=="rgb32": + data, client_options = self.rgb_encode(coding, data) elif coding=="webp": data, client_options = self.webp_encode(w, h, data, rowstride, options) elif coding=="mmap": @@ -798,19 +806,19 @@ def webp_encode(self, w, h, data, rowstride, options): q = min(99, max(1, q)) return Compressed("webp", str(EncodeRGB(image, quality=q).data)), {"quality" : q} - def rgb24_encode(self, data): + def rgb_encode(self, coding, data): #compress here and return a wrapper so network code knows it is already zlib compressed: - zlib = zlib_compress("rgb24", data) + zlib = zlib_compress(coding, data) if not self.encoding_client_options or not self.supports_rgb24zlib: return zlib, {} #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) - return Compressed("rgb24", zlib.data), {"zlib" : zlib.level} + return Compressed(coding, zlib.data), {"zlib" : zlib.level} - def PIL_encode(self, w, h, coding, data, rowstride, options): + def PIL_encode(self, w, h, coding, data, rgb_format, rowstride, options): assert coding in ENCODINGS import Image - im = Image.fromstring("RGB", (w, h), data, "raw", "RGB", rowstride) + im = Image.fromstring(rgb_format, (w, h), data, "raw", rgb_format, rowstride) buf = StringIOClass() client_options = {} if coding=="jpeg": @@ -823,7 +831,7 @@ def PIL_encode(self, w, h, coding, data, rowstride, options): client_options["quality"] = q else: assert coding=="png" - debug("sending as %s", coding) + debug("sending as %s, mode=%s", coding, im.mode) #transparency = False #transparency=transparency im.save(buf, coding.upper()) diff --git a/src/xpra/x11/gtk_x11/composite.py b/src/xpra/x11/gtk_x11/composite.py index 4c23e8784d..21f4020657 100644 --- a/src/xpra/x11/gtk_x11/composite.py +++ b/src/xpra/x11/gtk_x11/composite.py @@ -156,11 +156,7 @@ def set_pixmap(): return self._contents_handle def do_get_property_contents(self, name): - handle = self.get_property("contents-handle") - if handle is None: - return None - else: - return handle.pixmap + return self.get_property("contents-handle") def do_xpra_unmap_event(self, *args): self.invalidate_pixmap() diff --git a/src/xpra/x11/gtk_x11/gdk_bindings.pyx b/src/xpra/x11/gtk_x11/gdk_bindings.pyx index 05a3297a12..e4e03f5805 100644 --- a/src/xpra/x11/gtk_x11/gdk_bindings.pyx +++ b/src/xpra/x11/gtk_x11/gdk_bindings.pyx @@ -133,6 +133,7 @@ cdef extern from "X11/Xlib.h": ctypedef CARD32 Time ctypedef CARD32 VisualID + ctypedef CARD32 Colormap ctypedef struct Visual: void *ext_data #XExtData *ext_data; /* hook for extension to hang data */ @@ -245,6 +246,40 @@ cdef extern from "X11/Xlib.h": Window * root, Window * parent, Window ** children, unsigned int * nchildren) + ctypedef char* XPointer + + ctypedef struct XImage: + int width + int height + int xoffset # number of pixels offset in X direction + int format # XYBitmap, XYPixmap, ZPixmap + char *data # pointer to image data + int byte_order # data byte order, LSBFirst, MSBFirst + int bitmap_unit # quant. of scanline 8, 16, 32 + int bitmap_bit_order # LSBFirst, MSBFirst + int bitmap_pad # 8, 16, 32 either XY or ZPixmap + int depth # depth of image + int bytes_per_line # accelerator to next scanline + int bits_per_pixel # bits per pixel (ZPixmap) + unsigned long red_mask # bits in z arrangement + unsigned long green_mask + unsigned long blue_mask + XPointer *obdata + void *funcs + + unsigned long AllPlanes + int XYPixmap + int ZPixmap + int MSBFirst + int LSBFirst + + XImage *XGetImage(Display *display, Drawable d, + int x, int y, unsigned int width, unsigned int height, + unsigned long plane_mask, int format) + + void XDestroyImage(XImage *ximage) + + cdef extern from "X11/extensions/xfixeswire.h": unsigned int XFixesCursorNotify unsigned long XFixesDisplayCursorNotifyMask @@ -529,34 +564,79 @@ def calc_constrained_size(width, height, hints): return (new_width, new_height, vis_width, vis_height) - - -class _PixmapCleanupHandler(object): - "Reference count a GdkPixmap that needs explicit cleanup." - def __init__(self, pixmap): - self.pixmap = pixmap +class PixmapWrapper(object): + "Reference count an X Pixmap that needs explicit cleanup." + def __init__(self, display, colormap, xpixmap): + self.display = display + self.colormap = colormap + self.xpixmap = xpixmap + + def get_pixmap(self): + assert self.xpixmap + gpixmap = gtk.gdk.pixmap_foreign_new_for_display(self.display, self.xpixmap) + if gpixmap is None: + # Can't always actually get a pixmap, e.g. if window is not yet mapped + # or if it has disappeared. In such cases we might not actually see + # an X error yet, but xpixmap will actually point to an invalid + # Pixmap, and pixmap_foreign_new_for_display will fail when it tries + # to look up that pixmap's dimensions, and return None. + return None + gpixmap.set_colormap(self.colormap) + return gpixmap + + def get_pixels(self, x, y, width, height): + if self.xpixmap is None: + log.warn("PixmapWrapper.get_pixels() xpixmap=%s", self.xpixmap) + return None + return get_pixels(self.display, self.xpixmap, x, y, width, height) def __del__(self): - if self.pixmap is not None: - XFreePixmap(get_xdisplay_for(self.pixmap), self.pixmap.xid) - self.pixmap = None + if self.xpixmap is not None: + XFreePixmap(get_xdisplay_for(self.display), self.xpixmap) + self.xpixmap = None + +SBFirst = { + MSBFirst : "MSBFirst", + LSBFirst : "LSBFirst" + } + +cdef get_pixels(display, xpixmap, x, y, width, height): + cdef Display * xdisplay #@DuplicatedSignature + cdef XImage* ximage + xdisplay = get_xdisplay_for(display) + ximage = XGetImage(xdisplay, xpixmap, x, y, width, height, AllPlanes, ZPixmap) + if ximage==NULL: + log.error("get_pixels(..) failed to get XImage for xpixmap %s", xpixmap) + return None + depth = ximage.depth + #rowstride = ximage.bytes_per_line + #size = w * ximage.bytes_per_line + #rowstride = w * ximage.depth/8 + rowstride = ximage.bytes_per_line + size = rowstride * height + data = ximage.data[:size] + bitmap_bit_order = SBFirst.get(ximage.bitmap_bit_order, "unknown") + byte_order = SBFirst.get(ximage.byte_order, "unknown") + big_endian = ximage.byte_order==MSBFirst + #log.info("get_pixels(%s) byte_order=%s, bitmap_unit=%s, bitmap_bit_order=%s, bitmap_pad=%s, xoffset=%s, masks: red=%s, green=%s, blue=%s", + # xpixmap, byte_order, ximage.bitmap_unit, bitmap_bit_order, ximage.bitmap_pad, ximage.xoffset, hex(ximage.red_mask), hex(ximage.green_mask), hex(ximage.blue_mask)) + #log.info("get_pixels(%s) XImage depth=%s, width=%s, height=%s, bytes_per_line=%s, size=%s", + # xpixmap, ximage.depth, ximage.width, ximage.height, ximage.bytes_per_line, size) + XDestroyImage(ximage) + return depth, width, height, rowstride, big_endian, data + def xcomposite_name_window_pixmap(window): + cdef Display * display #@DuplicatedSignature + display = get_xdisplay_for(window) _ensure_XComposite_support(window) - xpixmap = XCompositeNameWindowPixmap(get_xdisplay_for(window), - get_xwindow(window)) - gpixmap = gtk.gdk.pixmap_foreign_new_for_display(get_display_for(window), - xpixmap) - if gpixmap is None: - # Can't always actually get a pixmap, e.g. if window is not yet mapped - # or if it has disappeared. In such cases we might not actually see - # an X error yet, but xpixmap will actually point to an invalid - # Pixmap, and pixmap_foreign_new_for_display will fail when it tries - # to look up that pixmap's dimensions, and return None. + xpixmap = XCompositeNameWindowPixmap(display, get_xwindow(window)) + w, h = window.get_size() + if xpixmap==XNone: return None - else: - gpixmap.set_colormap(window.get_colormap()) - return _PixmapCleanupHandler(gpixmap) + colormap = window.get_colormap() + return PixmapWrapper(get_display_for(window), colormap, xpixmap) + def _ensure_XComposite_support(display_source): # We need NameWindowPixmap, but we don't need the overlay window diff --git a/src/xpra/x11/gtk_x11/window.py b/src/xpra/x11/gtk_x11/window.py index f733ccf6ab..c031a50df8 100644 --- a/src/xpra/x11/gtk_x11/window.py +++ b/src/xpra/x11/gtk_x11/window.py @@ -368,11 +368,54 @@ def _guess_window_type(self, transient_for): def is_tray(self): return False + def has_alpha(self): + log.debug("has_alpha() client_window.depth=%s", self.client_window.get_depth()) + return self.client_window.get_depth()==32 + def get_rgb_rawdata(self, x, y, width, height): - pixmap = self.get_property("client-contents") - if pixmap is None: - log.debug("get_rgb_rawdata: pixmap is None for window %s", hex(get_xwindow(self.client_window))) + handle = self.get_property("client-contents") + log.debug("get_rgb_rawdata(%s, %s, %s, %s) handle=%s", x, y, width, height, handle) + if handle is None: + log.debug("get_rgb_rawdata(..) pixmap is None for window %s", hex(get_xwindow(self.client_window))) return None + + pixels = trap.call_synced(handle.get_pixels, x, y, width, height) + if pixels: + import Image + depth, w, h, rowstride, big_endian, data = pixels + #log.info("get_rgb_rawdata(..) get_pixels=%s", (depth, w, h, rowstride, big_endian, "%s bytes" % len(data))) + #log.info("get_rgb_rawdata(..) head=%s", [hex(ord(v)) for v in data[:100]]) + if depth==24: + if big_endian: + imode = "XRGB" + else: + imode = "BGRX" + smode = "RGB" + omode = "RGB" + orowstride = rowstride*3/4 + #rowstride = w*3 + elif depth==32: + if big_endian: + imode = "ARGB" + else: + imode = "BGRA" + smode = "RGBA" + omode = "RGBA" + orowstride = rowstride + #RGBa? + else: + raise Exception("unhandled depth: %s", depth) + im = Image.fromstring(smode, (w, h), data, "raw", imode, rowstride) + if omode!=smode: + im = im.convert(omode) + # tmp = im.convert("RGB") + # tmp.save("./window-rgba-%s.png" % time.time(), "PNG") + pixels = im.tostring("raw", omode) + #log.debug("depth=%s, returning %s %s pixels", depth, len(pixels), omode) + return x, y, w, h, im.tostring(), omode, orowstride + + pixmap = handle.get_pixmap() + log.info("get_rgb_rawdata(..) get_pixels() returned None, trying to use the pixmap fallback %s", pixmap) return get_rgb_rawdata(pixmap, x, y, width, height, logger=log) @@ -441,6 +484,9 @@ def __init__(self, client_window): def is_tray(self): return True + def has_alpha(self): + return True #assume that we do + def _read_initial_properties(self): pass diff --git a/src/xpra/x11/server.py b/src/xpra/x11/server.py index bf8d417db0..27fa8ee26e 100644 --- a/src/xpra/x11/server.py +++ b/src/xpra/x11/server.py @@ -363,8 +363,9 @@ def _or_window_geometry_changed(self, window, pspec=None): # These are the names of WindowModel properties that, when they change, # trigger updates in the xpra window metadata: - _all_metadata = ("title", "pid", "size-hints", "class-instance", "icon", "client-machine", "transient-for", "window-type", "modal", "xid") - _OR_metadata = ("transient-for", "window-type", "xid") + _all_metadata = ("title", "pid", "size-hints", "class-instance", "icon", "client-machine", "modal", + "transient-for", "window-type", "xid", "has-alpha") + _OR_metadata = ("transient-for", "window-type", "xid", "has-alpha") @@ -588,8 +589,11 @@ def do_make_screenshot_packet(self): log("screenshot: len(%s.get_rgb_data(..))=%s", window, len(data)) if data is None: continue - px, py, pw, ph, raw_data, rowstride = data - item = (wid, px, py, pw, ph, raw_data, rowstride) + px, py, pw, ph, raw_data, rgb_format, rowstride = data + if rgb_format.upper() not in ("RGB", "RGBA"): + log.warn("window pixels for window %s in unhandled format: %s", wid, rgb_format) + continue + item = (wid, px, py, pw, ph, raw_data, rgb_format, rowstride) if window.is_OR(): OR_regions.append(item) elif self._has_focus==wid: @@ -603,17 +607,20 @@ def do_make_screenshot_packet(self): return ["screenshot", 0, 0, "png", -1, ""] log("screenshot: found regions=%s, OR_regions=%s", regions, OR_regions) #in theory, we could run the rest in a non-UI thread since we're done with GTK.. - minx = min([x for (_,x,_,_,_,_,_) in all_regions]) - miny = min([y for (_,_,y,_,_,_,_) in all_regions]) - maxx = max([(x+w) for (_,x,_,w,_,_,_) in all_regions]) - maxy = max([(y+h) for (_,_,y,_,h,_,_) in all_regions]) + minx = min([x for (_,x,_,_,_,_,_,_) in all_regions]) + miny = min([y for (_,_,y,_,_,_,_,_) in all_regions]) + maxx = max([(x+w) for (_,x,_,w,_,_,_,_) in all_regions]) + maxy = max([(y+h) for (_,_,y,_,h,_,_,_) in all_regions]) width = maxx-minx height = maxy-miny log("screenshot: %sx%s, min x=%s y=%s", width, height, minx, miny) import Image image = Image.new("RGBA", (width, height)) - for wid, x, y, w, h, raw_data, rowstride in reversed(all_regions): - window_image = Image.fromstring("RGB", (w, h), raw_data, "raw", "RGB", rowstride) + for wid, x, y, w, h, raw_data, rgb_format, rowstride in reversed(all_regions): + assert rgb_format in ("RGB", "RGBA"), "invalid pixel format for window %s: %s" % (wid, rgb_format) + window_image = Image.fromstring(rgb_format, (w, h), raw_data, "raw", rgb_format, rowstride) + if rgb_format!="RGBA": + window_image = window_image.convert("RGB") tx = x-minx ty = y-miny image.paste(window_image, (tx, ty))