summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
+}