diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | test/Makefile.am | 2 | ||||
-rw-r--r-- | test/virtual.conf | 36 | ||||
-rw-r--r-- | tools/.gitignore | 1 | ||||
-rw-r--r-- | tools/Makefile.am | 44 | ||||
-rw-r--r-- | tools/intel-virtual-output.man | 34 | ||||
-rw-r--r-- | tools/virtual.c | 1280 |
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; +} |