summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2013-08-30 20:24:41 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2013-08-31 15:49:01 +0100
commit8067255dc9185e85b110254ffbea4d9682d3aa2d (patch)
tree33fc2f53d732c90ce714be592755e1f51dd52c06
parentdbf5751b5bc69e2515018a73540d1fb6dddc4502 (diff)
tools: Add intel-virtual-output to extend the local desktop with remote outputs
Based on the original implementation (hybrid-screenclone) by Tomáš Janoušek, and Bumblebee integration by Kevin Puetz. intel-virtual-output utilizes local VirtualHeads to present a contiguous desktop to the local display manager, but maps the drawing on those outputs to the remote display, and provides bidirectional RandR proxy so that you can resize the remote display and configure it within your desktop. The remote display should also send hotplug events back to the local desktop, for reconfiguration on the fly. Ideally the remote display is a discrete GPU on the same host so that we can use local Shared Memory transport and avoid sending data over the wire (though it will work in that setup). Ideally you would have userptr support to provide zero-copy rendering between the GPUs, or have dma-buf (in which case you would be using PRIME). For remote rendering, no compression is done so this fares worse than VNC. Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac3
-rw-r--r--test/Makefile.am2
-rw-r--r--test/virtual.conf36
-rw-r--r--tools/.gitignore1
-rw-r--r--tools/Makefile.am44
-rw-r--r--tools/intel-virtual-output.man34
-rw-r--r--tools/virtual.c1280
8 files changed, 1400 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index 2b3b5d4b..6bb48548 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,7 +20,7 @@
ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4
-SUBDIRS = man xvmc src
+SUBDIRS = man xvmc src tools
MAINTAINERCLEANFILES = ChangeLog INSTALL
diff --git a/configure.ac b/configure.ac
index 1e73c0c1..d0d1a5e6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -166,6 +166,8 @@ fi
PKG_CHECK_MODULES(X11, [x11 xrender xext pixman-1], [x11=yes], [x11=no])
AM_CONDITIONAL(HAVE_X11, test x$x11 = xyes)
+PKG_CHECK_MODULES(TOOL, [xrandr xdamage xfixes xcursor xtst xext x11], [tools=yes], [tools=no])
+
AH_TOP([#include "xorg-server.h"])
# Define a configure option for an alternate module directory
@@ -574,6 +576,7 @@ AC_CONFIG_FILES([
xvmc/shader/mc/Makefile
xvmc/shader/vld/Makefile
test/Makefile
+ tools/Makefile
])
AC_OUTPUT
diff --git a/test/Makefile.am b/test/Makefile.am
index f51967bd..893fa7d9 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -44,5 +44,5 @@ vsync.avi: mkvsync.sh
clean-vsync-avi:
rm -rf vsync.avi .build.tmp
-EXTRA_DIST = README mkvsync.sh tearing.mp4
+EXTRA_DIST = README mkvsync.sh tearing.mp4 virtual.conf
clean-local: clean-vsync-avi
diff --git a/test/virtual.conf b/test/virtual.conf
new file mode 100644
index 00000000..f9d037f2
--- /dev/null
+++ b/test/virtual.conf
@@ -0,0 +1,36 @@
+Section "Device"
+ Identifier "Device0"
+ Driver "intel"
+ Option "ZaphodHeads" "LVDS1"
+ Option "VirtualHeads" "1"
+ BusID "PCI:0:2:0"
+ Screen 0
+EndSection
+
+Section "Device"
+ Identifier "Device1"
+ Driver "intel"
+ Option "ZaphodHeads" "HDMI1"
+ BusID "PCI:0:2:0"
+ Screen 1
+EndSection
+
+Section "Screen"
+ Identifier "Screen0"
+ Device "Device0"
+EndSection
+
+Section "Screen"
+ Identifier "Screen1"
+ Device "Device1"
+EndSection
+
+Section "ServerFlags"
+ Option "AutoAddGPU" "False"
+EndSection
+
+Section "ServerLayout"
+ Identifier "ServerLayout0"
+ Screen 0 "Screen0" 0 0
+ Screen 1 "Screen1" 0 0
+EndSection
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 00000000..72150ff9
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1 @@
+intel-virtual-output
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 00000000..ff9f067b
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,44 @@
+# Copyright 2005 Adam Jackson.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# on the rights to use, copy, modify, merge, publish, distribute, sub
+# license, and/or sell copies of the Software, and to permit persons to whom
+# the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+# ADAM JACKSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+AM_CFLAGS = \
+ @CWARNFLAGS@ \
+ @TOOL_CFLAGS@ \
+ @NOWARNFLAGS@ \
+ $(NULL)
+
+bin_PROGRAMS = intel-virtual-output
+
+intel_virtual_output_SOURCES = \
+ virtual.c \
+ $(NULL)
+
+intel_virtual_output_LDADD = @TOOL_LIBS@
+
+drivermandir = $(DRIVER_MAN_DIR)
+driverman_DATA = intel-virtual-output.$(DRIVER_MAN_SUFFIX)
+
+EXTRA_DIST = intel-virtual-output.man
+CLEANFILES = $(driverman_DATA)
+
+# String replacements in MAN_SUBSTS now come from xorg-macros.m4 via configure
+SUFFIXES = .$(DRIVER_MAN_SUFFIX) .man
+.man.$(DRIVER_MAN_SUFFIX):
+ $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@
diff --git a/tools/intel-virtual-output.man b/tools/intel-virtual-output.man
new file mode 100644
index 00000000..7a2e6262
--- /dev/null
+++ b/tools/intel-virtual-output.man
@@ -0,0 +1,34 @@
+.\" shorthand for double quote that works everywhere.
+.ds q \N'34'
+.TH intel-virtual-output __drivermansuffix__ __vendorversion__
+.SH NAME
+intel-virtual-output \- Utility for connecting the Integrated Intel GPU to discrete outputs
+.SH SYNOPSIS
+.nf
+.B "Section \*qDevice\*q"
+.BI " Identifier \*q" devname \*q
+.B " Driver \*qintel\*q"
+.B " Option \*qVirtualHeads\*q" \*q<number>\*q
+\ \ ...
+.B EndSection
+.B ""
+.B "intel-virtual-output [<remote display>] <output name>..."
+.fi
+.SH DESCRIPTION
+.B intel-virtual-output
+is a utility for Intel integrated graphics chipsets on hybrid systems.
+The tool connects pre-allocated VirtualHeads to a remote output, allowing
+the primary display to extend onto the remote outputs.
+
+.SH REPORTING BUGS
+
+The xf86-video-intel driver is part of the X.Org and Freedesktop.org
+umbrella projects. Details on bug reporting can be found at
+http://www.intellinuxgraphics.org/how_to_report_bug.html. Mailing
+lists are also commonly used to report experiences and ask questions
+about configuration and other topics. See lists.freedesktop.org for
+more information (the xorg@lists.freedesktop.org mailing list is the
+most appropriate place to ask X.Org and driver related questions).
+
+.SH "SEE ALSO"
+intel(__drivermansuffix__), __xservername__(__appmansuffix__), __xconfigfile__(__filemansuffix__), Xserver(__appmansuffix__), X(__miscmansuffix__)
diff --git a/tools/virtual.c b/tools/virtual.c
new file mode 100644
index 00000000..e9f5e641
--- /dev/null
+++ b/tools/virtual.c
@@ -0,0 +1,1280 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <X11/Xlib.h>
+
+#include <X11/Xlibint.h>
+#include <X11/extensions/XShm.h>
+#include <X11/extensions/shmproto.h>
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/record.h>
+#include <X11/Xcursor/Xcursor.h>
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/timerfd.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <limits.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#if 0
+#define DBG(x) printf x
+#else
+#define DBG(x)
+#endif
+
+struct display {
+ Display *dpy;
+
+ int damage_event, damage_error;
+ int xfixes_event, xfixes_error;
+ int rr_event, rr_error;
+ Window root;
+ Damage damage;
+
+ Cursor invisible_cursor;
+ Cursor visible_cursor;
+
+ int cursor_x;
+ int cursor_y;
+ int cursor_moved;
+ int cursor_visible;
+ int cursor;
+
+ int flush;
+};
+
+struct output {
+ struct display *display;
+ Display *dpy;
+ char *name;
+ RROutput rr_output;
+ XShmSegmentInfo shm;
+ Window window;
+ Picture picture;
+ Pixmap pixmap;
+ GC gc;
+
+ int depth;
+ int serial;
+
+ int has_shm;
+ int has_shm_pixmap;
+ int shm_opcode;
+ int shm_event;
+
+ int x, y;
+ XRRModeInfo mode;
+ Rotation rotation;
+};
+
+struct clone {
+ struct output src, dst;
+
+ XShmSegmentInfo shm;
+ XImage image;
+
+ int width, height;
+ struct { int x1, x2, y1, y2; } damaged;
+ int rr_update;
+};
+
+struct context {
+ struct display *display;
+ struct clone *clones;
+ int num_clones;
+ int num_display;
+};
+
+static int xlib_vendor_is_xorg(Display *dpy)
+{
+ const char *const vendor = ServerVendor(dpy);
+ return strstr(vendor, "X.Org") || strstr(vendor, "Xorg");
+}
+
+#define XORG_VERSION_ENCODE(major,minor,patch,snap) \
+ (((major) * 10000000) + ((minor) * 100000) + ((patch) * 1000) + snap)
+
+static int _x_error_occurred;
+
+static int
+_check_error_handler(Display *display,
+ XErrorEvent *event)
+{
+ _x_error_occurred = 1;
+ return False; /* ignored */
+}
+
+static int
+can_use_shm(Display *dpy,
+ Window window,
+ int *shm_event,
+ int *shm_opcode,
+ int *shm_pixmap)
+{
+ XShmSegmentInfo shm;
+ int (*old_handler)(Display *display, XErrorEvent *event);
+ Status success;
+ XExtCodes *codes;
+ int major, minor, has_shm, has_pixmap;
+
+ if (!XShmQueryExtension(dpy))
+ return 0;
+
+ XShmQueryVersion(dpy, &major, &minor, &has_pixmap);
+
+ shm.shmid = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600);
+ if (shm.shmid == -1)
+ return 0;
+
+ shm.readOnly = 0;
+ shm.shmaddr = shmat (shm.shmid, NULL, 0);
+ if (shm.shmaddr == (char *) -1) {
+ shmctl(shm.shmid, IPC_RMID, NULL);
+ return 0;
+ }
+
+ _x_error_occurred = 0;
+
+ XSync(dpy, False);
+ old_handler = XSetErrorHandler(_check_error_handler);
+
+ success = XShmAttach(dpy, &shm);
+ XSync(dpy, False);
+
+ has_shm = success && _x_error_occurred == 0;
+
+ codes = XInitExtension(dpy, SHMNAME);
+ if (codes == NULL)
+ has_pixmap = 0;
+
+ /* As libXext sets the SEND_EVENT bit in the ShmCompletionEvent,
+ * the Xserver may crash if it does not take care when processing
+ * the event type. For instance versions of Xorg prior to 1.11.1
+ * exhibited this bug, and was fixed by:
+ *
+ * commit 2d2dce558d24eeea0eb011ec9ebaa6c5c2273c39
+ * Author: Sam Spilsbury <sam.spilsbury@canonical.com>
+ * Date: Wed Sep 14 09:58:34 2011 +0800
+ *
+ * Remove the SendEvent bit (0x80) before doing range checks on event type.
+ */
+ if (has_pixmap &&
+ xlib_vendor_is_xorg(dpy) &&
+ VendorRelease(dpy) < XORG_VERSION_ENCODE(1,11,0,1))
+ has_pixmap = 0;
+
+ if (has_pixmap) {
+ XShmCompletionEvent e;
+
+ e.type = codes->first_event;
+ e.send_event = 1;
+ e.serial = 1;
+ e.drawable = window;
+ e.major_code = codes->major_opcode;
+ e.minor_code = X_ShmPutImage;
+
+ e.shmseg = shm.shmid;
+ e.offset = 0;
+
+ XSendEvent(dpy, e.drawable, False, 0, (XEvent *)&e);
+ XSync(dpy, False);
+
+ has_pixmap = _x_error_occurred == 0;
+ }
+
+ if (success)
+ XShmDetach(dpy, &shm);
+
+ XSync(dpy, False);
+ XSetErrorHandler(old_handler);
+
+ shmctl(shm.shmid, IPC_RMID, NULL);
+ shmdt(shm.shmaddr);
+
+ if (has_pixmap) {
+ *shm_opcode = codes->major_opcode;
+ *shm_event = codes->first_event;
+ *shm_pixmap = has_pixmap;
+ }
+
+ return has_shm;
+}
+
+static int mode_equal(const XRRModeInfo *a, const XRRModeInfo *b)
+{
+ return (a->width == b->width &&
+ a->height == b->height &&
+ a->dotClock == b->dotClock &&
+ a->hSyncStart == b->hSyncStart &&
+ a->hSyncEnd == b->hSyncEnd &&
+ a->hTotal == b->hTotal &&
+ a->hSkew == b->hSkew &&
+ a->vSyncStart == b->vSyncStart &&
+ a->vSyncEnd == b->vSyncEnd &&
+ a->vTotal == b->vTotal &&
+ a->modeFlags == b->modeFlags);
+}
+
+static RROutput find_output(Display *dpy, const char *name)
+{
+ XRRScreenResources *res;
+ RROutput ret = 0;
+ int i;
+
+ res = XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
+ if (res == NULL)
+ return 0;
+
+ for (i = 0; ret == 0 && i < res->noutput; i++) {
+ XRROutputInfo *o = XRRGetOutputInfo(dpy, res, res->outputs[i]);
+ if (strcmp(o->name, name) == 0)
+ ret = res->outputs[i];
+ XRRFreeOutputInfo(o);
+ }
+ XRRFreeScreenResources(res);
+
+ return ret;
+}
+
+static XRRModeInfo *lookup_mode(XRRScreenResources *res, int id)
+{
+ int i;
+
+ for (i = 0; i < res->nmode; i++) {
+ if (res->modes[i].id == id)
+ return &res->modes[i];
+ }
+
+ return NULL;
+}
+
+static int clone_update_modes(struct output *src, struct output *dst)
+{
+ XRRScreenResources *src_res, *dst_res;
+ XRROutputInfo *src_info, *dst_info;
+ int i, j, ret = ENOENT;
+
+ assert(src->rr_output);
+ assert(dst->rr_output);
+
+ src_res = XRRGetScreenResources(src->dpy, src->window);
+ if (src_res == NULL)
+ goto err;
+
+ src_info = XRRGetOutputInfo(src->dpy, src_res, src->rr_output);
+ if (src_info == NULL)
+ goto err;
+
+ dst_res = XRRGetScreenResourcesCurrent(dst->dpy, dst->window);
+ if (dst_res == NULL)
+ goto err;
+
+ dst_info = XRRGetOutputInfo(dst->dpy, dst_res, dst->rr_output);
+ if (dst_info == NULL)
+ goto err;
+
+ /* Clear all current UserModes on the output, including any active ones */
+ if (dst_info->crtc)
+ XRRSetCrtcConfig(dst->dpy, dst_res, dst_info->crtc, CurrentTime,
+ 0, 0, None, RR_Rotate_0, NULL, 0);
+ for (i = 0; i < dst_info->nmode; i++)
+ XRRDeleteOutputMode(dst->dpy, dst->rr_output, dst_info->modes[i]);
+
+ /* Create matching modes for the real output on the virtual */
+ for (i = 0; i < src_info->nmode; i++) {
+ XRRModeInfo *mode, *old;
+ RRMode id;
+
+ mode = lookup_mode(src_res, src_info->modes[i]);
+ if (mode == NULL)
+ continue;
+ for (j = 0; j < i; j++) {
+ old = lookup_mode(src_res, src_info->modes[j]);
+ if (old && mode_equal(mode, old)) {
+ mode = NULL;
+ break;
+ }
+ }
+ if (mode == NULL)
+ continue;
+
+ id = 0;
+ for (j = 0; j < dst_res->nmode; j++) {
+ old = &dst_res->modes[j];
+ if (mode_equal(mode, old)) {
+ id = old->id;
+ break;
+ }
+ }
+ if (id == 0) {
+ XRRModeInfo m;
+ char buf[256];
+
+ /* XXX User names must be unique! */
+ m = *mode;
+ m.nameLength = snprintf(buf, sizeof(buf),
+ "%s.%ld-%s", dst->name, (long)src_info->modes[i], mode->name);
+ m.name = buf;
+
+ id = XRRCreateMode(dst->dpy, dst->window, &m);
+ }
+
+ XRRAddOutputMode(dst->dpy, dst->rr_output, id);
+ }
+ ret = 0;
+
+err:
+ if (dst_info)
+ XRRFreeOutputInfo(dst_info);
+ if (dst_res)
+ XRRFreeScreenResources(dst_res);
+ if (src_info)
+ XRRFreeOutputInfo(src_info);
+ if (src_res)
+ XRRFreeScreenResources(src_res);
+
+ return ret;
+}
+
+static int get_current_config(struct output *output)
+{
+ XRRScreenResources *res;
+ int i, ret = ENOENT;
+ XRROutputInfo *o;
+ RRMode mode = 0;
+
+ res = XRRGetScreenResourcesCurrent(output->dpy, output->window);
+ if (res == NULL)
+ return ENOMEM;
+
+ o = XRRGetOutputInfo(output->dpy, res, output->rr_output);
+ if (o) {
+ XRRCrtcInfo *c = NULL;
+ if (o->crtc)
+ c = XRRGetCrtcInfo(output->dpy, res, o->crtc);
+ if (c) {
+ output->rotation = c->rotation;
+ output->x = c->x;
+ output->y = c->y;
+ mode = c->mode;
+ ret = 0;
+ } else
+ ret = ENOMEM;
+ XRRFreeOutputInfo(o);
+ }
+ if (ret == 0) {
+ ret = EINVAL;
+ for (i = 0; ret == EINVAL && i < res->nmode; i++) {
+ XRRModeInfo *m = &res->modes[i];
+ if (m->id != mode)
+ continue;
+
+ output->mode = *m;
+ ret = 0;
+ }
+ }
+ XRRFreeScreenResources(res);
+
+ return ret;
+}
+
+static int set_config(struct output *output, const struct output *config)
+{
+ XRRScreenResources *res;
+ XRROutputInfo *o;
+ XRRCrtcInfo *c;
+ int i, ret = ENOENT;
+ RRCrtc rr_crtc = 0;
+ RRMode rr_mode = 0;
+ int x = 0, y = 0;
+
+ res = XRRGetScreenResourcesCurrent(output->dpy, output->window);
+ if (res == NULL)
+ return ENOMEM;
+
+ o = XRRGetOutputInfo(output->dpy, res, res->outputs[i]);
+ if (o) {
+ rr_crtc = o->crtcs[0];
+ XRRFreeOutputInfo(o);
+ }
+ if (rr_crtc == 0)
+ goto err;
+
+ c = XRRGetCrtcInfo(output->dpy, res, rr_crtc);
+ if (c) {
+ x = c->x;
+ y = c->y;
+ XRRFreeCrtcInfo(c);
+ }
+
+ for (i = 0; i < res->nmode; i++) {
+ if (mode_equal(&config->mode, &res->modes[i])) {
+ rr_mode = res->modes[i].id;
+ break;
+ }
+ }
+ if (rr_mode == 0)
+ goto err;
+
+ XRRSetCrtcConfig(output->dpy, res, rr_crtc, CurrentTime,
+ x, y, rr_mode, config->rotation,
+ &output->rr_output, 1);
+ output->x = x;
+ output->y = y;
+ output->rotation = config->rotation;
+ output->mode = config->mode;
+ output->display->flush = 1;
+ ret = 0;
+
+err:
+ XRRFreeScreenResources(res);
+ return ret;
+}
+
+static void clone_update(struct clone *clone)
+{
+ if (!clone->rr_update)
+ return;
+
+ DBG(("%s-%s cloning modes\n",
+ DisplayString(clone->dst.dpy), clone->dst.name));
+
+ clone_update_modes(&clone->dst, &clone->src);
+ clone->rr_update = 0;
+}
+
+static Cursor display_load_invisible_cursor(struct display *display)
+{
+ char zero[8] = {};
+ XColor black = {};
+ Pixmap bitmap = XCreateBitmapFromData(display->dpy, display->root, zero, 8, 8);
+ return XCreatePixmapCursor(display->dpy, bitmap, bitmap, &black, &black, 0, 0);
+}
+
+static void display_load_visible_cursor(struct display *display, XFixesCursorImage *cur)
+{
+ XcursorImage image;
+
+ memset(&image, 0, sizeof(image));
+ image.width = cur->width;
+ image.height = cur->height;
+ image.size = image.width;
+ if (image.height > image.size)
+ image.size = image.height;
+ image.xhot = cur->xhot;
+ image.yhot = cur->yhot;
+ image.pixels = (void *)cur->pixels;
+
+ if (display->visible_cursor)
+ XFreeCursor(display->dpy, display->visible_cursor);
+
+ DBG(("%s updating cursor\n", DisplayString(display->dpy)));
+ display->visible_cursor = XcursorImageLoadCursor(display->dpy, &image);
+
+ display->cursor_moved++;
+ display->cursor_visible += display->cursor != display->invisible_cursor;
+}
+
+static void display_start_cursor_move(struct display *display)
+{
+ display->cursor_moved = 0;
+ display->cursor_visible = 0;
+}
+
+static void display_cursor_move(struct display *display, int x, int y, int visible)
+{
+ display->cursor_moved++;
+ display->cursor_visible += visible;
+ if (visible) {
+ display->cursor_x = x;
+ display->cursor_y = y;
+ }
+}
+
+static void display_end_cursor_move(struct display *display)
+{
+ Cursor cursor;
+ int x, y;
+
+ if (!display->cursor_moved)
+ return;
+
+ if (display->cursor_visible) {
+ x = display->cursor_x;
+ y = display->cursor_y;
+ } else {
+ x = display->cursor_x++ & 31;
+ y = display->cursor_y++ & 31;
+ }
+
+ XWarpPointer(display->dpy, None, display->root, 0, 0, 0, 0, x, y);
+ display->flush = 1;
+
+ cursor = None;
+ if (display->cursor_visible)
+ cursor = display->visible_cursor;
+ if (cursor == None)
+ cursor = display->invisible_cursor;
+ if (cursor != display->cursor) {
+ XDefineCursor(display->dpy, display->root, cursor);
+ display->cursor = cursor;
+ }
+}
+
+static void clone_move_cursor(struct clone *c, int x, int y)
+{
+ int visible;
+
+ DBG(("%s-%s moving cursor (%d, %d) [(%d, %d), (%d, %d)]\n",
+ DisplayString(c->dst.dpy), c->dst.name,
+ x, y,
+ c->src.x, c->src.y,
+ c->src.x + c->width, c->src.y + c->height));
+
+ visible = (x >= c->src.x && x < c->src.x + c->width &&
+ y >= c->src.y && y < c->src.y + c->height);
+
+ x += c->dst.x - c->src.x;
+ y += c->dst.y - c->src.y;
+
+ display_cursor_move(c->dst.display, x, y, visible);
+}
+
+static void init_image(struct clone *clone)
+{
+ XImage *image = &clone->image;
+ int ret;
+
+ image->width = clone->width;
+ image->height = clone->height;
+ image->format = ZPixmap;
+ image->byte_order = LSBFirst;
+ image->bitmap_unit = 32;
+ image->bitmap_bit_order = LSBFirst;
+ image->red_mask = 0xff << 16;
+ image->green_mask = 0xff << 8;
+ image->blue_mask = 0xff << 0;;
+ image->xoffset = 0;
+ image->bitmap_pad = 32;
+ image->depth = 24;
+ image->data = clone->shm.shmaddr;
+ image->bytes_per_line = 4*clone->width;
+ image->bits_per_pixel = 32;
+
+ ret = XInitImage(image);
+ assert(ret);
+}
+
+static int clone_create_xfer(struct clone *clone)
+{
+ DBG(("%s-%s create xfer\n",
+ DisplayString(clone->dst.dpy), clone->dst.name));
+
+ clone->width = clone->src.mode.width;
+ clone->height = clone->src.mode.height;
+
+ if (clone->shm.shmaddr)
+ shmdt(clone->shm.shmaddr);
+
+ clone->shm.shmid = shmget(IPC_PRIVATE, clone->height * clone->width * 4, IPC_CREAT | 0666);
+ if (clone->shm.shmid == -1)
+ return errno;
+
+ clone->shm.shmaddr = shmat(clone->shm.shmid, 0, 0);
+ if (clone->shm.shmaddr == (char *) -1) {
+ shmctl(clone->shm.shmid, IPC_RMID, NULL);
+ return ENOMEM;
+ }
+
+ clone->shm.readOnly = 0;
+
+ init_image(clone);
+
+ if (clone->src.has_shm) {
+ XShmAttach(clone->src.dpy, &clone->shm);
+ XSync(clone->src.dpy, False);
+ }
+ if (clone->dst.has_shm) {
+ XShmAttach(clone->dst.dpy, &clone->shm);
+ XSync(clone->dst.dpy, False);
+ }
+
+ shmctl(clone->shm.shmid, IPC_RMID, NULL);
+
+ if (clone->src.has_shm_pixmap) {
+ clone->src.pixmap = XShmCreatePixmap(clone->src.dpy, clone->src.window,
+ clone->shm.shmaddr, &clone->shm,
+ clone->width, clone->height, clone->src.depth);
+ }
+
+ if (clone->dst.has_shm_pixmap) {
+ clone->dst.pixmap = XShmCreatePixmap(clone->dst.dpy, clone->dst.window,
+ clone->shm.shmaddr, &clone->shm,
+ clone->width, clone->height, clone->dst.depth);
+ }
+
+ clone->damaged.x1 = clone->src.x;
+ clone->damaged.x2 = clone->src.x + clone->width;
+ clone->damaged.y1 = clone->src.y;
+ clone->damaged.y2 = clone->src.y + clone->height;
+
+ clone->dst.display->flush = 1;
+ return 0;
+}
+
+static int clone_output(struct clone *clone)
+{
+ int ret;
+
+ DBG(("%s-%s clone %s\n",
+ DisplayString(clone->dst.dpy), clone->dst.name, clone->src.name));
+
+ ret = get_current_config(&clone->src);
+ if (ret)
+ return ret;
+
+ set_config(&clone->dst, &clone->src);
+
+ if (clone->src.mode.width != clone->width ||
+ clone->src.mode.height != clone->height)
+ clone_create_xfer(clone);
+
+ clone->damaged.x1 = clone->src.x;
+ clone->damaged.x2 = clone->src.x + clone->width;
+ clone->damaged.y1 = clone->src.y;
+ clone->damaged.y2 = clone->src.y + clone->height;
+
+ return 0;
+}
+
+static int clone_output_init(struct clone *clone, struct output *output,
+ struct display *display, const char *name)
+{
+ Display *dpy = display->dpy;
+ XGCValues gcv;
+
+ DBG(("%s(%s, %s)\n", __func__, DisplayString(dpy), name));
+
+ output->display = display;
+ output->dpy = dpy;
+
+ output->rr_output = find_output(dpy, name);
+ if (output->rr_output == 0)
+ return ENOENT;
+
+ output->name = strdup(name);
+ if (output->name == NULL)
+ return ENOMEM;
+
+ output->depth = DefaultDepth(dpy, DefaultScreen(dpy));
+ output->window = display->root;
+ output->has_shm = can_use_shm(dpy, output->window,
+ &output->shm_event,
+ &output->shm_opcode,
+ &output->has_shm_pixmap);
+
+ gcv.graphics_exposures = False;
+ gcv.subwindow_mode = IncludeInferiors;
+
+ output->gc = XCreateGC(dpy, output->window, GCGraphicsExposures | GCSubwindowMode, &gcv);
+
+ return 0;
+}
+
+static void send_shm(struct output *o, int serial)
+{
+ XShmCompletionEvent e;
+
+ e.type = o->shm_event;
+ e.send_event = 1;
+ e.serial = serial;
+ e.drawable = o->pixmap;
+ e.major_code = o->shm_opcode;
+ e.minor_code = X_ShmPutImage;
+ e.shmseg = 0;
+ e.offset = 0;
+
+ XSendEvent(o->dpy, o->window, False, 0, (XEvent *)&e);
+ o->serial = serial;
+}
+
+static void get_src(struct clone *c, const XRectangle *clip)
+{
+ DBG(("%s-%s get_src(%d,%d)x(%d,%d)\n", DisplayString(c->dst.dpy), c->dst.name,
+ clip->x, clip->y, clip->width, clip->height));
+ if (c->src.picture) {
+ } else if (c->src.pixmap) {
+ XCopyArea(c->src.dpy, c->src.window, c->src.pixmap, c->src.gc,
+ clip->x, clip->y,
+ clip->width, clip->height,
+ 0, 0);
+ XSync(c->src.dpy, False);
+ } else if (c->src.has_shm) {
+ c->image.width = clip->width;
+ c->image.height = clip->height;
+ c->image.obdata = (char *)&c->shm;
+ XShmGetImage(c->src.dpy, c->src.window, &c->image,
+ clip->x, clip->y, AllPlanes);
+ } else {
+ c->image.width = c->width;
+ c->image.height = c->height;
+ c->image.obdata = 0;
+ XGetSubImage(c->src.dpy, c->src.window,
+ clip->x, clip->y, clip->width, clip->height,
+ AllPlanes, ZPixmap,
+ &c->image, 0, 0);
+ }
+}
+
+static void put_dst(struct clone *c, const XRectangle *clip)
+{
+ DBG(("%s-%s put_dst(%d,%d)x(%d,%d)\n", DisplayString(c->dst.dpy), c->dst.name,
+ clip->x, clip->y, clip->width, clip->height));
+ if (c->dst.picture) {
+ } else if (c->dst.pixmap) {
+ int serial = NextRequest(c->dst.dpy);
+ XCopyArea(c->dst.dpy, c->dst.pixmap, c->dst.window, c->dst.gc,
+ 0, 0,
+ clip->width, clip->height,
+ clip->x, clip->y);
+ send_shm(&c->dst, serial);
+ } else if (c->dst.has_shm) {
+ c->image.width = clip->width;
+ c->image.height = clip->height;
+ c->image.obdata = (char *)&c->shm;
+ c->dst.serial = NextRequest(c->dst.dpy);
+ XShmPutImage(c->dst.dpy, c->dst.window, c->dst.gc, &c->image,
+ 0, 0,
+ clip->x, clip->y,
+ clip->width, clip->height,
+ True);
+ } else {
+ c->image.width = c->width;
+ c->image.height = c->height;
+ c->image.obdata = 0;
+ XPutImage(c->dst.dpy, c->dst.window, c->dst.gc, &c->image,
+ 0, 0,
+ clip->x, clip->y,
+ clip->width, clip->height);
+ c->dst.serial = 0;
+ }
+
+ c->dst.display->flush = 1;
+}
+
+static int clone_paint(struct clone *c)
+{
+ XRectangle clip;
+
+ DBG(("%s-%s paint clone\n",
+ DisplayString(c->dst.dpy), c->dst.name));
+
+ if (c->damaged.x1 < c->src.x)
+ c->damaged.x1 = c->src.x;
+ if (c->damaged.x2 > c->src.x + c->width)
+ c->damaged.x2 = c->src.x + c->width;
+ if (c->damaged.x2 <= c->damaged.x1)
+ goto done;
+
+ if (c->damaged.y1 < c->src.y)
+ c->damaged.y1 = c->src.y;
+ if (c->damaged.y2 > c->src.y + c->height)
+ c->damaged.y2 = c->src.y + c->height;
+ if (c->damaged.y2 <= c->damaged.y1)
+ goto done;
+
+ if (c->dst.serial > LastKnownRequestProcessed(c->dst.dpy))
+ return EAGAIN;
+
+ clip.x = c->damaged.x1;
+ clip.y = c->damaged.y1;
+ clip.width = c->damaged.x2 - c->damaged.x1;
+ clip.height = c->damaged.y2 - c->damaged.y1;
+ get_src(c, &clip);
+
+ clip.x += c->dst.x - c->src.x;
+ clip.y += c->dst.y - c->src.y;
+ put_dst(c, &clip);
+
+done:
+ c->damaged.x2 = c->damaged.y2 = INT_MIN;
+ c->damaged.x1 = c->damaged.y1 = INT_MAX;
+ return 0;
+}
+
+static void clone_damage(struct clone *c, const XRectangle *rec)
+{
+ if (rec->x < c->damaged.x1)
+ c->damaged.x1 = rec->x;
+ if (rec->x + rec->width > c->damaged.x2)
+ c->damaged.x2 = rec->x + rec->width;
+ if (rec->y < c->damaged.y1)
+ c->damaged.y1 = rec->y;
+ if (rec->y + rec->height > c->damaged.y2)
+ c->damaged.y2 = rec->y + rec->height;
+}
+
+static void usage(const char *arg0)
+{
+ printf("usage: %s [-d <source display>] [<target display>] <target output>...\n", arg0);
+}
+
+static int bumblebee_open(struct display *display)
+{
+ char buf[256];
+ struct sockaddr_un addr;
+ int fd, len;
+
+ fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ goto err;
+
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, optarg && *optarg ? optarg : "/var/run/bumblebee.socket");
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ goto err;
+
+ /* Ask bumblebee to start the second server */
+ buf[0] = 'C';
+ if (send(fd, &buf, 1, 0) != 1 || (len = recv(fd, &buf, 255, 0)) <= 0) {
+ close(fd);
+ goto err;
+ }
+ buf[len] = '\0';
+
+ /* Query the display name */
+ strcpy(buf, "Q VirtualDisplay");
+ if (send(fd, buf, 17, 0) != 17 || (len = recv(fd, buf, 255, 0)) <= 0)
+ goto err;
+ buf[len] = '\0';
+ close(fd);
+
+ if (strncmp(buf, "Value: ", 7))
+ goto err;
+
+ while (isspace(buf[--len]))
+ buf[len] = '\0';
+
+ display->dpy = XOpenDisplay(buf+7);
+ if (display->dpy == NULL) {
+ fprintf(stderr, "Unable to connect to bumblebee Xserver on %s\n", buf+7);
+ return -ECONNREFUSED;
+ }
+
+ if (!XRRQueryExtension(display->dpy, &display->rr_event, &display->rr_error)) {
+ fprintf(stderr, "RandR extension not supported by bumblebee Xserver\n");
+ return -EINVAL;
+ }
+
+ return ConnectionNumber(display->dpy);
+
+err:
+ fprintf(stderr, "Unable to connect to bumblebee\n");
+ return -ECONNREFUSED;
+}
+
+static void record_callback(XPointer closure, XRecordInterceptData *data)
+{
+ struct context *ctx = (struct context *)closure;
+ int n;
+
+ if (data->category == XRecordFromServer) {
+ const xEvent *e = (const xEvent *)data->data;
+
+ if (e->u.u.type == MotionNotify) {
+ for (n = 0; n < ctx->num_clones; n++)
+ clone_move_cursor(&ctx->clones[n],
+ e->u.keyButtonPointer.rootX,
+ e->u.keyButtonPointer.rootY);
+ }
+ }
+
+ XRecordFreeData(data);
+}
+
+static int record_mouse(struct context *ctx)
+{
+ Display *dpy;
+ XRecordRange *rr;
+ XRecordClientSpec rcs;
+ XRecordContext rc;
+
+ DBG(("%s(%s)\n", __func__, DisplayString(ctx->display[0].dpy)));
+
+ dpy = XOpenDisplay(DisplayString(ctx->display[0].dpy));
+ if (dpy == NULL)
+ return -ECONNREFUSED;
+
+ rr = XRecordAllocRange();
+ if (rr == NULL)
+ return -ENOMEM;
+
+ rr->device_events.first = rr->device_events.last = MotionNotify;
+
+ rcs = XRecordAllClients;
+ rc = XRecordCreateContext(dpy, 0, &rcs, 1, &rr, 1);
+
+ XSync(dpy, False);
+
+ if (!XRecordEnableContextAsync(dpy, rc, record_callback, (XPointer)ctx))
+ return -EINVAL;
+
+ ctx->display[ctx->num_display].dpy = dpy;
+ return ConnectionNumber(dpy);
+}
+
+static int timer(int hz)
+{
+ struct itimerspec it;
+ int fd;
+
+ fd = timerfd_create(CLOCK_MONOTONIC_COARSE, TFD_NONBLOCK);
+ if (fd < 0)
+ fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (fd < 0)
+ return -ETIME;
+
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_nsec = 1000000000 / hz;
+ it.it_value = it.it_interval;
+ if (timerfd_settime(fd, 0, &it, NULL) < 0) {
+ close(fd);
+ return -ETIME;
+ }
+
+ return fd;
+}
+
+static int display_init(struct display *display, const char *name)
+{
+ DBG(("%s(%s)\n", __func__, name));
+
+ display->dpy = XOpenDisplay(name);
+ if (display->dpy == NULL) {
+ fprintf(stderr, "Unable to connect to %s\n", name);
+ return -ECONNREFUSED;
+ }
+
+ display->root = DefaultRootWindow(display->dpy);
+
+ if (!XRRQueryExtension(display->dpy, &display->rr_event, &display->rr_error)) {
+ fprintf(stderr, "RandR extension not supported by %s\n", DisplayString(display->dpy));
+ return -EINVAL;
+ }
+
+ display->invisible_cursor = display_load_invisible_cursor(display);
+ display->cursor = None;
+
+ return ConnectionNumber(display->dpy);
+}
+
+static int display_init_damage(struct display *display)
+{
+ DBG(("%s(%s)\n", __func__, DisplayString(display->dpy)));
+
+ if (!XDamageQueryExtension(display->dpy, &display->damage_event, &display->damage_error) ||
+ !XFixesQueryExtension(display->dpy, &display->xfixes_event, &display->xfixes_error)) {
+ fprintf(stderr, "Damage/Fixes extension not supported by %s\n", DisplayString(display->dpy));
+ return EINVAL;
+ }
+
+ display->damage = XDamageCreate(display->dpy, display->root, XDamageReportRawRectangles);
+ if (display->damage == 0)
+ return EACCES;
+
+ return 0;
+}
+
+static void display_init_randr_hpd(struct display *display)
+{
+ int major, minor;
+
+ DBG(("%s(%s)\n", __func__, DisplayString(display->dpy)));
+
+ if (!XRRQueryVersion(display->dpy, &major, &minor))
+ return;
+
+ if (major > 1 || (major == 1 && minor >= 2))
+ XRRSelectInput(display->dpy, display->root, RROutputChangeNotifyMask);
+}
+
+static void display_flush(struct display *display)
+{
+ if (!display->flush)
+ return;
+
+ DBG(("%s(%s)\n", __func__, DisplayString(display->dpy)));
+
+ XFlush(display->dpy);
+ display->flush = 0;
+}
+
+static int context_init(struct context *ctx, int num_clones)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->display = calloc(num_clones+2, sizeof(struct display));
+ if (ctx->display == NULL)
+ return ENOMEM;
+
+ ctx->clones = calloc(num_clones, sizeof(struct clone));
+ if (ctx->clones == NULL)
+ return ENOMEM;
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct context ctx;
+ const char *src_name = NULL;
+ struct pollfd *pfd;
+ uint64_t count;
+ int nfd, enable_timer = 0;
+ int i, ret, daemonize = 1;
+
+ while ((i = getopt(argc, argv, "d:fh")) != -1) {
+ switch (i) {
+ case 'd':
+ src_name = optarg;
+ break;
+ case 'f':
+ daemonize = 0;
+ break;
+ case 'h':
+ default:
+ usage(argv[0]);
+ exit(0);
+ }
+ }
+
+ count = argc - optind;
+ if (count < 2) {
+ usage(argv[0]);
+ exit(EINVAL);
+ }
+
+ pfd = malloc(sizeof(struct pollfd) * (count+3));
+ if (pfd == NULL)
+ return ENOMEM;
+
+ ret = context_init(&ctx, count);
+ if (ret)
+ return ret;
+
+ nfd = 1;
+ pfd->events = POLLIN;
+ pfd->fd = display_init(&ctx.display[0], src_name);
+ if (pfd->fd < 0)
+ return -pfd->fd;
+
+ ret = display_init_damage(&ctx.display[0]);
+ if (ret)
+ return ret;
+
+ XRRSelectInput(ctx.display[0].dpy, ctx.display[0].root, RRScreenChangeNotifyMask);
+ XFixesSelectCursorInput(ctx.display[0].dpy, ctx.display[0].root, XFixesDisplayCursorNotifyMask);
+ ctx.num_display++;
+
+ for (i = optind; i < argc; i++) {
+ char buf[80];
+
+ if (strchr(argv[i], ':')) {
+ pfd[nfd].fd = display_init(&ctx.display[ctx.num_display++], argv[i]);
+ if (pfd[nfd].fd < 0)
+ return -pfd[nfd].fd;
+ pfd[nfd].events = POLLIN;
+ nfd++;
+
+ display_init_randr_hpd(&ctx.display[ctx.num_display-1]);
+ continue;
+ }
+ if (nfd == 1) {
+ pfd[nfd].fd = bumblebee_open(&ctx.display[ctx.num_display++]);
+ if (pfd[nfd].fd < 0)
+ return -pfd[nfd].fd;
+ pfd[nfd].events = POLLIN;
+ nfd++;
+
+ display_init_randr_hpd(&ctx.display[ctx.num_display-1]);
+ }
+
+ sprintf(buf, "VIRTUAL%d", ctx.num_clones+1);
+ ret = clone_output_init(&ctx.clones[ctx.num_clones], &ctx.clones[ctx.num_clones].src, &ctx.display[0], buf);
+ if (ret) {
+ while (++i < argc)
+ ctx.num_clones += strchr(argv[i], ':') == NULL;
+ fprintf(stderr,
+ "No preallocated VirtualHead found for argv[i].\n"
+ "Please increase the number of VirtualHeads in xorg.conf:\n"
+ " Section \"Device\"\n"
+ " Identifier \"<identifier>\"\n"
+ " Driver \"intel\"\n"
+ " Option \"VirtualHeads\" \"%d\"\n"
+ " ...\n"
+ " EndSection\n", ctx.num_clones+1);
+ return ret;
+ }
+
+ ret = clone_output_init(&ctx.clones[ctx.num_clones], &ctx.clones[ctx.num_clones].dst, &ctx.display[ctx.num_display-1], argv[i]);
+ if (ret) {
+ fprintf(stderr, "Unable to find output \"%s\" on display \"%s\"\n",
+ argv[i], DisplayString(ctx.display[nfd-1].dpy));
+ return ret;
+ }
+
+ ret = clone_update_modes(&ctx.clones[ctx.num_clones].dst, &ctx.clones[ctx.num_clones].src);
+ if (ret) {
+ fprintf(stderr, "Failed to clone output \"%s\" from display \"%s\"\n",
+ argv[i], DisplayString(ctx.display[nfd-1].dpy));
+ return ret;
+ }
+
+ ctx.num_clones++;
+ }
+
+ pfd[nfd].fd = record_mouse(&ctx);
+ if (pfd[nfd].fd < 0) {
+ fprintf(stderr, "XTEST extension not supported by display \"%s\"\n", DisplayString(ctx.display[0].dpy));
+ return -pfd[nfd].fd;
+ }
+ pfd[nfd].events = POLLIN;
+ nfd++;
+
+ pfd[nfd].fd = timer(60);
+ if (pfd[nfd].fd < 0) {
+ fprintf(stderr, "Failed to setup timer\n");
+ return -pfd[nfd].fd;
+ }
+ pfd[nfd].events = POLLIN;
+
+ if (daemonize && daemon(0, 0))
+ return EINVAL;
+
+ while (1) {
+ XEvent e;
+
+ ret = poll(pfd, nfd + enable_timer, -1);
+ if (ret <= 0)
+ break;
+
+ for (i = 1; i < ctx.num_display; i++)
+ display_start_cursor_move(&ctx.display[i]);
+
+ if (pfd[0].revents) {
+ int damaged = 0;
+
+ do {
+ XNextEvent(ctx.display[0].dpy, &e);
+
+ if (e.type == ctx.display[0].damage_event + XDamageNotify ) {
+ const XDamageNotifyEvent *de = (const XDamageNotifyEvent *)&e;
+ for (i = 0; i < ctx.num_clones; i++)
+ clone_damage(&ctx.clones[i], &de->area);
+ if (!enable_timer)
+ enable_timer = read(pfd[nfd].fd, &count, sizeof(count)) > 0;
+ damaged++;
+ } else if (e.type == ctx.display[0].xfixes_event + XFixesCursorNotify) {
+ XFixesCursorImage *cur;
+
+ cur = XFixesGetCursorImage(ctx.display[0].dpy);
+ if (cur == NULL)
+ continue;
+
+ for (i = 1; i < ctx.num_display; i++)
+ display_load_visible_cursor(&ctx.display[i], cur);
+
+ XFree(cur);
+ } else if (e.type == ctx.display[0].rr_event + RRScreenChangeNotify) {
+ for (i = 0; i < ctx.num_clones; i++)
+ (void)clone_output(&ctx.clones[i]);
+ } else {
+ DBG(("unknown event %d\n", e.type));
+ }
+ } while (XPending(ctx.display[0].dpy) || poll(pfd, 1, 0) > 0);
+
+ if (damaged)
+ XDamageSubtract(ctx.display[0].dpy, ctx.display[0].damage, None, None);
+ ret--;
+ }
+
+ for (i = 1; ret && i < ctx.num_display; i++) {
+ if (pfd[i].revents == 0)
+ continue;
+
+ do {
+ XNextEvent(ctx.display[i].dpy, &e);
+
+ if (e.type == ctx.display[i].rr_event + RRNotify) {
+ XRRNotifyEvent *re = (XRRNotifyEvent *)&e;
+ if (re->subtype == RRNotify_OutputChange) {
+ XRROutputPropertyNotifyEvent *ro = (XRROutputPropertyNotifyEvent *)re;
+ int j;
+
+ for (j = 0; j < ctx.num_clones; j++) {
+ if (ctx.clones[j].dst.display == &ctx.display[i] &&
+ ctx.clones[j].dst.rr_output == ro->output)
+ ctx.clones[j].rr_update = 1;
+ }
+ }
+ }
+ } while (XPending(ctx.display[i].dpy) || poll(&pfd[i], 1, 0) > 0);
+
+ ret--;
+ }
+
+ for (i = 0; i < ctx.num_clones; i++)
+ clone_update(&ctx.clones[i]);
+
+ if (enable_timer && read(pfd[nfd].fd, &count, sizeof(count)) > 0 && count > 0) {
+ ret = 0;
+ for (i = 0; i < ctx.num_clones; i++)
+ ret |= clone_paint(&ctx.clones[i]);
+ enable_timer = ret != 0;
+ }
+
+ XPending(ctx.display[ctx.num_display].dpy);
+
+ for (i = 1; i < ctx.num_display; i++)
+ display_end_cursor_move(&ctx.display[i]);
+
+ for (i = 0; i < ctx.num_display; i++)
+ display_flush(&ctx.display[i]);
+ }
+
+ return 0;
+}