/* * 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #if HAVE_X11_EXTENSIONS_SHMPROTO_H #include #elif HAVE_X11_EXTENSIONS_SHMSTR_H #include #else #error Failed to find the right header for X11 MIT-SHM protocol definitions #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #define DBG(x) printf x #else #define DBG(x) #endif #define FORCE_FULL_REDRAW 0 struct display { Display *dpy; struct clone *clone; struct context *ctx; int damage_event, damage_error; int xfixes_event, xfixes_error; int rr_event, rr_error, rr_active; int xinerama_event, xinerama_error, xinerama_active; Window root; Visual *visual; Damage damage; int width; int height; int depth; XRenderPictFormat *root_format; XRenderPictFormat *rgb16_format; XRenderPictFormat *rgb24_format; int has_shm; int has_shm_pixmap; int shm_opcode; int shm_event; Cursor invisible_cursor; Cursor visible_cursor; XcursorImage cursor_image; int cursor_serial; int cursor_x; int cursor_y; int cursor_moved; int cursor_visible; int cursor; int flush; int send; int skip_clone; int skip_frame; }; struct output { struct display *display; Display *dpy; char *name; RROutput rr_output; RRCrtc rr_crtc; Window window; Picture win_picture; Picture pix_picture; Pixmap pixmap; GC gc; long serial; int use_shm; int use_shm_pixmap; XShmSegmentInfo shm; XRenderPictFormat *use_render; int x, y; XRRModeInfo mode; Rotation rotation; }; struct clone { struct clone *next; struct clone *active; struct output src, dst; long timestamp; XShmSegmentInfo shm; XImage image; int width, height, depth; struct { int x1, x2, y1, y2; } damaged; int rr_update; }; struct context { struct display *display; struct clone *clones; struct clone *active; struct pollfd *pfd; #define timer pfd[0].fd Display *record; int nclone; int ndisplay; int nfd; int timer_active; long timestamp; long configTimestamp; Atom singleton; char command[1024]; int command_continuation; }; static inline int is_power_of_2(unsigned long n) { return n && ((n & (n - 1)) == 0); } static int xlib_vendor_is_xorg(Display *dpy) { const char *const vendor = ServerVendor(dpy); return strstr(vendor, "X.Org") || strstr(vendor, "Xorg"); } static inline XRRScreenResources *_XRRGetScreenResourcesCurrent(Display *dpy, Window window) { XRRScreenResources *res; res = XRRGetScreenResourcesCurrent(dpy, window); if (res == NULL) res = XRRGetScreenResources(dpy, window); return res; } #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) { DBG(("X11 error from display %s, serial=%ld, error=%d, req=%d.%d\n", DisplayString(display), event->serial, event->error_code, event->request_code, event->minor_code)); _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; 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; } XSync(dpy, False); _x_error_occurred = 0; success = XShmAttach(dpy, &shm); XSync(dpy, False); has_shm = success && _x_error_occurred == 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 * Date: Wed Sep 14 09:58:34 2011 +0800 * * Remove the SendEvent bit (0x80) before doing range checks on event type. */ codes = 0; if (has_shm) codes = XInitExtension(dpy, SHMNAME); if (xlib_vendor_is_xorg(dpy) && VendorRelease(dpy) < XORG_VERSION_ENCODE(1,11,0,1)) codes = 0; if (codes) { XShmCompletionEvent e; memset(&e, 0, sizeof(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); if (_x_error_occurred == 0) { *shm_opcode = codes->major_opcode; *shm_event = codes->first_event; *shm_pixmap = has_pixmap; } } XShmDetach(dpy, &shm); shmctl(shm.shmid, IPC_RMID, NULL); shmdt(shm.shmaddr); return has_shm; } static int timerfd(int hz) { struct itimerspec it; int fd; fd = -1; #ifdef CLOCK_MONOTONIC_COARSE fd = timerfd_create(CLOCK_MONOTONIC_COARSE, TFD_NONBLOCK); #endif 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 context_init(struct context *ctx) { struct pollfd *pfd; memset(ctx, 0, sizeof(*ctx)); ctx->pfd = malloc(2*sizeof(struct pollfd)); if (ctx->pfd == NULL) return -ENOMEM; ctx->clones = malloc(sizeof(struct clone)); if (ctx->clones == NULL) return -ENOMEM; ctx->display = malloc(sizeof(struct display)); if (ctx->display == NULL) return -ENOMEM; pfd = memset(&ctx->pfd[ctx->nfd++], 0, sizeof(struct pollfd)); pfd->fd = timerfd(60); if (pfd->fd < 0) return pfd->fd; pfd->events = POLLIN; return 0; } static void context_enable_timer(struct context *ctx) { uint64_t count; DBG(("%s timer active? %d\n", __func__, ctx->timer_active)); if (ctx->timer_active) return; /* reset timer */ count = read(ctx->timer, &count, sizeof(count)); ctx->timer_active = 1; } static int add_fd(struct context *ctx, int fd) { struct pollfd *pfd; if (fd < 0) return fd; if (is_power_of_2(ctx->nfd)) { ctx->pfd = realloc(ctx->pfd, 2*ctx->nfd*sizeof(struct pollfd)); if (ctx->pfd == NULL) return -ENOMEM; } pfd = memset(&ctx->pfd[ctx->nfd++], 0, sizeof(struct pollfd)); pfd->fd = fd; pfd->events = POLLIN; return 0; } static void display_mark_flush(struct display *display) { DBG(("%s mark flush (flush=%d)\n", DisplayString(display->dpy), display->flush)); if (display->flush) return; context_enable_timer(display->ctx); display->flush = 1; } 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 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 void clone_update_edid(struct clone *clone) { unsigned long nitems, after; unsigned char *data; int format; Atom type; if (XRRGetOutputProperty(clone->dst.dpy, clone->dst.rr_output, XInternAtom(clone->dst.dpy, "EDID", False), 0, 100, False, False, AnyPropertyType, &type, &format, &nitems, &after, &data) == Success) { XRRChangeOutputProperty(clone->src.dpy, clone->src.rr_output, XInternAtom(clone->src.dpy, "EDID", False), type, format, PropModeReplace, data, nitems); } } static int clone_update_modes__randr(struct clone *clone) { XRRScreenResources *from_res = NULL, *to_res = NULL; XRROutputInfo *from_info = NULL, *to_info = NULL; int i, j, ret = ENOENT; assert(clone->src.rr_output); assert(clone->dst.rr_output); assert(clone->dst.display->rr_event); from_res = _XRRGetScreenResourcesCurrent(clone->dst.dpy, clone->dst.window); if (from_res == NULL) goto err; from_info = XRRGetOutputInfo(clone->dst.dpy, from_res, clone->dst.rr_output); if (from_info == NULL) goto err; DBG(("%s(%s-%s <- %s-%s): timestamp %ld (last %ld)\n", __func__, DisplayString(clone->src.dpy), clone->src.name, DisplayString(clone->dst.dpy), clone->dst.name, from_info->timestamp, clone->timestamp)); to_res = _XRRGetScreenResourcesCurrent(clone->src.dpy, clone->src.window); if (to_res == NULL) goto err; to_info = XRRGetOutputInfo(clone->src.dpy, to_res, clone->src.rr_output); if (to_info == NULL) goto err; if (clone->dst.rr_crtc == from_info->crtc) { for (i = 0; i < to_info->nmode; i++) { XRRModeInfo *mode, *old; mode = lookup_mode(to_res, to_info->modes[i]); if (mode == NULL) break; for (j = 0; j < from_info->nmode; j++) { old = lookup_mode(from_res, from_info->modes[j]); if (old && mode_equal(mode, old)) { mode = NULL; break; } } if (mode) break; } if (i == from_info->nmode && i == to_info->nmode) { DBG(("%s(%s-%s): no change in output\n", __func__, DisplayString(clone->src.dpy), clone->src.name)); goto done; } } clone->dst.rr_crtc = from_info->crtc; /* Clear all current UserModes on the output, including any active ones */ if (to_info->crtc) { DBG(("%s(%s-%s): disabling active CRTC\n", __func__, DisplayString(clone->src.dpy), clone->src.name)); XRRSetCrtcConfig(clone->src.dpy, to_res, to_info->crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); } for (i = 0; i < to_info->nmode; i++) { DBG(("%s(%s-%s): deleting mode %ld\n", __func__, DisplayString(clone->src.dpy), clone->src.name, (long)to_info->modes[i])); XRRDeleteOutputMode(clone->src.dpy, clone->src.rr_output, to_info->modes[i]); } clone->src.rr_crtc = 0; /* Create matching modes for the real output on the virtual */ XGrabServer(clone->src.dpy); for (i = 0; i < from_info->nmode; i++) { XRRModeInfo *mode, *old; RRMode id; mode = lookup_mode(from_res, from_info->modes[i]); if (mode == NULL) continue; for (j = 0; j < i; j++) { old = lookup_mode(from_res, from_info->modes[j]); if (old && mode_equal(mode, old)) { mode = NULL; break; } } if (mode == NULL) continue; id = 0; for (j = 0; j < to_res->nmode; j++) { old = &to_res->modes[j]; if (mode_equal(mode, old)) { id = old->id; DBG(("%s(%s-%s): reusing mode %ld: %s\n", __func__, DisplayString(clone->src.dpy), clone->src.name, id, mode->name)); 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", clone->src.name, (long)from_info->modes[i], mode->name); m.name = buf; id = XRRCreateMode(clone->src.dpy, clone->src.window, &m); DBG(("%s(%s-%s): adding mode %ld: %s\n", __func__, DisplayString(clone->src.dpy), clone->src.name, id, mode->name)); } XRRAddOutputMode(clone->src.dpy, clone->src.rr_output, id); } clone_update_edid(clone); XUngrabServer(clone->src.dpy); done: ret = 0; clone->timestamp = from_info->timestamp; err: if (to_info) XRRFreeOutputInfo(to_info); if (to_res) XRRFreeScreenResources(to_res); if (from_info) XRRFreeOutputInfo(from_info); if (from_res) XRRFreeScreenResources(from_res); return ret; } static int clone_update_modes__fixed(struct clone *clone) { char mode_name[80]; XRRScreenResources *res = NULL; XRROutputInfo *info = NULL; XRRModeInfo mode; RRMode id; int i, j, ret = ENOENT; assert(clone->src.rr_output); res = _XRRGetScreenResourcesCurrent(clone->src.dpy, clone->src.window); if (res == NULL) goto err; info = XRRGetOutputInfo(clone->src.dpy, res, clone->src.rr_output); if (info == NULL) goto err; XGrabServer(clone->src.dpy); /* Clear all current UserModes on the output, including any active ones */ if (info->crtc) { DBG(("%s(%s-%s): disabling active CRTC\n", __func__, DisplayString(clone->src.dpy), clone->src.name)); XRRSetCrtcConfig(clone->src.dpy, res, info->crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); } for (i = 0; i < info->nmode; i++) { DBG(("%s(%s-%s): deleting mode %ld\n", __func__, DisplayString(clone->src.dpy), clone->src.name, (long)info->modes[i])); XRRDeleteOutputMode(clone->src.dpy, clone->src.rr_output, info->modes[i]); } clone->src.rr_crtc = 0; /* Create matching mode for the real output on the virtual */ memset(&mode, 0, sizeof(mode)); mode.width = clone->width; mode.height = clone->height; mode.nameLength = sprintf(mode_name, "FAKE-%dx%d", mode.width, mode.height); mode.name = mode_name; id = 0; for (j = 0; j < res->nmode; j++) { if (mode_equal(&mode, &res->modes[j])) { id = res->modes[j].id; break; } } if (id == 0) id = XRRCreateMode(clone->src.dpy, clone->src.window, &mode); XRRAddOutputMode(clone->src.dpy, clone->src.rr_output, id); XUngrabServer(clone->src.dpy); ret = 0; err: if (info) XRRFreeOutputInfo(info); if (res) XRRFreeScreenResources(res); return ret; } static RROutput claim_virtual(struct display *display, char *output_name, int nclone) { char mode_name[] = "ClaimVirtualHead"; Display *dpy = display->dpy; XRRScreenResources *res; XRROutputInfo *output; XRRModeInfo mode; RRMode id; RROutput rr_output = 0; int i; DBG(("%s(%d)\n", __func__, nclone)); XGrabServer(dpy); res = _XRRGetScreenResourcesCurrent(dpy, display->root); if (res == NULL) goto out; sprintf(output_name, "VIRTUAL%d", nclone); for (i = rr_output = 0; rr_output == 0 && i < res->noutput; i++) { output = XRRGetOutputInfo(dpy, res, res->outputs[i]); if (output == NULL) continue; if (strcmp(output->name, output_name) == 0) rr_output = res->outputs[i]; XRRFreeOutputInfo(output); } for (i = id = 0; id == 0 && i < res->nmode; i++) { if (strcmp(res->modes[i].name, mode_name) == 0) id = res->modes[i].id; } XRRFreeScreenResources(res); DBG(("%s(%s): rr_output=%ld\n", __func__, output_name, (long)rr_output)); if (rr_output == 0) goto out; /* Set any mode on the VirtualHead to make the Xserver allocate another */ memset(&mode, 0, sizeof(mode)); mode.width = 1024; mode.height = 768; mode.name = mode_name; mode.nameLength = sizeof(mode_name) - 1; if (id == 0) id = XRRCreateMode(dpy, display->root, &mode); XRRAddOutputMode(dpy, rr_output, id); /* Force a redetection for the ddx to spot the new outputs */ res = XRRGetScreenResources(dpy, display->root); if (res == NULL) goto out; /* Some else may have interrupted us and installed that new mode! */ output = XRRGetOutputInfo(dpy, res, rr_output); if (output) { if (output->crtc) XRRSetCrtcConfig(dpy, res, output->crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); XRRFreeOutputInfo(output); } XRRFreeScreenResources(res); XRRDeleteOutputMode(dpy, rr_output, id); XRRDestroyMode(dpy, id); out: XUngrabServer(dpy); return rr_output; } static int stride_for_depth(int width, int depth) { if (depth == 24) depth = 32; return ((width * depth + 7) / 8 + 3) & ~3; } 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->xoffset = 0; image->byte_order = LSBFirst; image->bitmap_unit = 32; image->bitmap_bit_order = LSBFirst; image->bitmap_pad = 32; image->data = clone->shm.shmaddr; image->bytes_per_line = stride_for_depth(clone->width, clone->depth); switch (clone->depth) { case 24: image->red_mask = 0xff << 16; image->green_mask = 0xff << 8; image->blue_mask = 0xff << 0;; image->depth = 24; image->bits_per_pixel = 32; break; case 16: image->red_mask = 0x1f << 11; image->green_mask = 0x3f << 5; image->blue_mask = 0x1f << 0;; image->depth = 16; image->bits_per_pixel = 16; break; } ret = XInitImage(image); assert(ret); (void)ret; } static int mode_height(const XRRModeInfo *mode, Rotation rotation) { switch (rotation & 0xf) { case RR_Rotate_0: case RR_Rotate_180: return mode->height; case RR_Rotate_90: case RR_Rotate_270: return mode->width; default: return 0; } } static int mode_width(const XRRModeInfo *mode, Rotation rotation) { switch (rotation & 0xf) { case RR_Rotate_0: case RR_Rotate_180: return mode->width; case RR_Rotate_90: case RR_Rotate_270: return mode->height; default: return 0; } } static void output_init_xfer(struct clone *clone, struct output *output) { if (output->use_shm_pixmap) { DBG(("%s-%s: creating shm pixmap\n", DisplayString(output->dpy), output->name)); if (output->pixmap) XFreePixmap(output->dpy, output->pixmap); output->pixmap = XShmCreatePixmap(output->dpy, output->window, clone->shm.shmaddr, &output->shm, clone->width, clone->height, clone->depth); if (output->pix_picture) { XRenderFreePicture(output->dpy, output->pix_picture); output->pix_picture = None; } } if (output->use_render) { DBG(("%s-%s: creating picture\n", DisplayString(output->dpy), output->name)); if (output->win_picture == None) output->win_picture = XRenderCreatePicture(output->dpy, output->window, output->display->root_format, 0, NULL); if (output->pixmap == None) output->pixmap = XCreatePixmap(output->dpy, output->window, clone->width, clone->height, clone->depth); if (output->pix_picture == None) output->pix_picture = XRenderCreatePicture(output->dpy, output->pixmap, output->use_render, 0, NULL); } if (output->gc == None) { XGCValues gcv; gcv.graphics_exposures = False; gcv.subwindow_mode = IncludeInferiors; output->gc = XCreateGC(output->dpy, output->pixmap ?: output->window, GCGraphicsExposures | GCSubwindowMode, &gcv); } } static int clone_init_xfer(struct clone *clone) { int width, height; if (clone->src.mode.id == 0) { if (clone->width == 0 && clone->height == 0) return 0; clone->width = 0; clone->height = 0; if (clone->src.use_shm) XShmDetach(clone->src.dpy, &clone->src.shm); if (clone->dst.use_shm) XShmDetach(clone->dst.dpy, &clone->dst.shm); if (clone->shm.shmaddr) { shmdt(clone->shm.shmaddr); clone->shm.shmaddr = 0; } clone->damaged.x2 = clone->damaged.y2 = INT_MIN; clone->damaged.x1 = clone->damaged.y1 = INT_MAX; return 0; } width = mode_width(&clone->src.mode, clone->src.rotation); height = mode_height(&clone->src.mode, clone->src.rotation); if (width == clone->width && height == clone->height) return 0; DBG(("%s-%s create xfer, %dx%d\n", DisplayString(clone->dst.dpy), clone->dst.name, width, height)); clone->width = width; clone->height = height; if (clone->shm.shmaddr) shmdt(clone->shm.shmaddr); clone->shm.shmid = shmget(IPC_PRIVATE, height * stride_for_depth(width, clone->depth), 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; } init_image(clone); if (clone->src.use_shm) { clone->src.shm = clone->shm; clone->dst.shm.readOnly = False; XShmAttach(clone->src.dpy, &clone->src.shm); XSync(clone->src.dpy, False); } if (clone->dst.use_shm) { clone->dst.shm = clone->shm; clone->dst.shm.readOnly = True; XShmAttach(clone->dst.dpy, &clone->dst.shm); XSync(clone->dst.dpy, False); } shmctl(clone->shm.shmid, IPC_RMID, NULL); output_init_xfer(clone, &clone->src); output_init_xfer(clone, &clone->dst); clone->damaged.x1 = clone->src.x; clone->damaged.x2 = clone->src.x + width; clone->damaged.y1 = clone->src.y; clone->damaged.y2 = clone->src.y + height; display_mark_flush(clone->dst.display); return 0; } 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__randr(clone); clone->rr_update = 0; } static int context_update(struct context *ctx) { Display *dpy = ctx->display->dpy; XRRScreenResources *res; int context_changed = 0; int i, n; DBG(("%s\n", __func__)); res = _XRRGetScreenResourcesCurrent(dpy, ctx->display->root); if (res == NULL) return 0; DBG(("%s timestamp %ld (last %ld), config %ld (last %ld)\n", DisplayString(dpy), res->timestamp, ctx->timestamp, res->configTimestamp, ctx->configTimestamp)); if (res->timestamp == ctx->timestamp && res->configTimestamp == ctx->configTimestamp && res->timestamp != res->configTimestamp) { /* mutter be damned */ XRRFreeScreenResources(res); return 0; } ctx->timestamp = res->timestamp; ctx->configTimestamp = res->configTimestamp; for (n = 0; n < ctx->nclone; n++) { struct output *output = &ctx->clones[n].src; XRROutputInfo *o; XRRCrtcInfo *c; RRMode mode = 0; int changed = 0; o = XRRGetOutputInfo(dpy, res, output->rr_output); if (o == NULL) continue; c = NULL; if (o->crtc) c = XRRGetCrtcInfo(dpy, res, o->crtc); if (c) { DBG(("%s-%s: (x=%d, y=%d, rotation=%d, mode=%ld) -> (x=%d, y=%d, rotation=%d, mode=%ld)\n", DisplayString(dpy), output->name, output->x, output->y, output->rotation, output->mode.id, c->x, c->y, c->rotation, c->mode)); changed |= output->rotation != c->rotation; output->rotation = c->rotation; changed |= output->x != c->x; output->x = c->x; changed |= output->y != c->y; output->y = c->y; changed |= output->mode.id != mode; mode = c->mode; XRRFreeCrtcInfo(c); } else { DBG(("%s-%s: (x=%d, y=%d, rotation=%d, mode=%ld) -> off\n", DisplayString(dpy), output->name, output->x, output->y, output->rotation, output->mode.id)); } output->rr_crtc = o->crtc; XRRFreeOutputInfo(o); if (mode) { if (output->mode.id != mode) { for (i = 0; i < res->nmode; i++) { if (res->modes[i].id == mode) { output->mode = res->modes[i]; break; } } } } else { changed = output->mode.id != 0; output->mode.id = 0; } DBG(("%s-%s changed? %d\n", DisplayString(ctx->clones[n].dst.display->dpy), ctx->clones[n].dst.name, changed)); if (changed) clone_init_xfer(&ctx->clones[n]); context_changed |= changed; } XRRFreeScreenResources(res); DBG(("%s changed? %d\n", DisplayString(dpy), context_changed)); if (!context_changed) return 0; for (n = 1; n < ctx->ndisplay; n++) { struct display *display = &ctx->display[n]; struct clone *clone; int x1, x2, y1, y2; if (display->rr_active == 0) continue; x1 = y1 = INT_MAX; x2 = y2 = INT_MIN; for (clone = display->clone; clone; clone = clone->next) { struct output *output = &clone->src; int v; assert(clone->dst.display == display); if (output->mode.id == 0) continue; DBG(("%s: source %s enabled (%d, %d)x(%d, %d)\n", DisplayString(clone->dst.dpy), output->name, output->x, output->y, mode_width(&output->mode, output->rotation), mode_height(&output->mode, output->rotation))); if (output->x < x1) x1 = output->x; if (output->y < y1) y1 = output->y; v = (int)output->x + mode_width(&output->mode, output->rotation); if (v > x2) x2 = v; v = (int)output->y + mode_height(&output->mode, output->rotation); if (v > y2) y2 = v; } x2 -= x1; y2 -= y1; DBG(("%s fb bounds (%d, %d)x(%d, %d)\n", DisplayString(display->dpy), x1, y1, x2, y2)); res = _XRRGetScreenResourcesCurrent(display->dpy, display->root); if (res == NULL) continue; XGrabServer(display->dpy); DBG(("%s: current size %dx%d, need %dx%d\n", DisplayString(display->dpy), display->width, display->height, x2, y2)); if (display->width != x2 || display->height != y2) { /* When shrinking we have to manually resize the fb */ for (clone = display->clone; clone; clone = clone->next) { struct output *dst = &clone->dst; if (!dst->rr_crtc) continue; DBG(("%s: disabling output '%s'\n", DisplayString(dst->dpy), dst->name)); XRRSetCrtcConfig(dst->dpy, res, dst->rr_crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); dst->rr_crtc = 0; dst->mode.id = 0; } DBG(("%s: XRRSetScreenSize %dx%d\n", DisplayString(display->dpy), x2, y2)); XRRSetScreenSize(display->dpy, display->root, x2, y2, x2 * 96 / 25.4, y2 * 96 / 25.4); display->width = x2; display->height = y2; } for (clone = display->clone; clone; clone = clone->next) { struct output *src = &clone->src; struct output *dst = &clone->dst; XRROutputInfo *o; XRRPanning panning; struct clone *set; RRCrtc rr_crtc; Status ret; DBG(("%s: copying configuration from %s (mode=%ld: %s) to %s\n", DisplayString(dst->dpy), src->name, (long)src->mode.id, src->mode.name, dst->name)); if (src->mode.id == 0) { err: if (dst->rr_crtc) { DBG(("%s: disabling unused output '%s'\n", DisplayString(dst->dpy), dst->name)); XRRSetCrtcConfig(dst->dpy, res, dst->rr_crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); dst->rr_crtc = 0; dst->mode.id = 0; } continue; } dst->x = src->x - x1; dst->y = src->y - y1; dst->rotation = src->rotation; dst->mode = src->mode; dst->mode.id = 0; for (i = 0; i < res->nmode; i++) { if (mode_equal(&src->mode, &res->modes[i])) { dst->mode.id = res->modes[i].id; break; } } if (dst->mode.id == 0) { XRRModeInfo m; char buf[256]; RRMode id; /* XXX User names must be unique! */ m = src->mode; m.nameLength = snprintf(buf, sizeof(buf), "%s.%ld-%s", src->name, (long)src->mode.id, src->mode.name); m.name = buf; id = XRRCreateMode(dst->dpy, dst->window, &m); if (id) { DBG(("%s: adding mode %ld: %s to %s\n", DisplayString(dst->dpy), (long)id, src->mode.name, dst->name)); XRRAddOutputMode(dst->dpy, dst->rr_output, id); dst->mode.id = id; } else { DBG(("%s: failed to find suitable mode for %s\n", DisplayString(dst->dpy), dst->name)); goto err; } } rr_crtc = dst->rr_crtc; if (rr_crtc) { for (set = display->clone; set != clone; set = set->next) { if (set->dst.rr_crtc == rr_crtc) { DBG(("%s: CRTC reassigned from %s\n", DisplayString(dst->dpy), dst->name)); rr_crtc = 0; break; } } } if (rr_crtc == 0) { o = XRRGetOutputInfo(dst->dpy, res, dst->rr_output); for (i = 0; i < o->ncrtc; i++) { DBG(("%s: checking whether CRTC:%ld is available\n", DisplayString(dst->dpy), (long)o->crtcs[i])); for (set = display->clone; set != clone; set = set->next) { if (set->dst.rr_crtc == o->crtcs[i]) { DBG(("%s: CRTC:%ld already assigned to %s\n", DisplayString(dst->dpy), (long)o->crtcs[i], set->dst.name)); break; } } if (set == clone) { rr_crtc = o->crtcs[i]; break; } } XRRFreeOutputInfo(o); } if (rr_crtc == 0) { DBG(("%s: failed to find available CRTC for %s\n", DisplayString(dst->dpy), dst->name)); goto err; } DBG(("%s: enabling output '%s' (%d,%d)x(%d,%d), rotation %d, on CRTC:%ld, using mode %ld\n", DisplayString(dst->dpy), dst->name, dst->x, dst->y, dst->mode.width, dst->mode.height, dst->rotation, (long)rr_crtc, dst->mode.id)); ret = XRRSetCrtcConfig(dst->dpy, res, rr_crtc, CurrentTime, dst->x, dst->y, dst->mode.id, dst->rotation, &dst->rr_output, 1); DBG(("%s-%s: XRRSetCrtcConfig %s\n", DisplayString(dst->dpy), dst->name, ret ? "failed" : "success")); if (ret) goto err; ret = XRRSetPanning(dst->dpy, res, rr_crtc, memset(&panning, 0, sizeof(panning))); DBG(("%s-%s: XRRSetPanning %s\n", DisplayString(dst->dpy), dst->name, ret ? "failed" : "success")); dst->rr_crtc = rr_crtc; (void)ret; } XUngrabServer(display->dpy); XRRFreeScreenResources(res); } ctx->active = NULL; for (n = 0; n < ctx->nclone; n++) { struct clone *clone = &ctx->clones[n]; if (clone->dst.rr_crtc == 0) continue; clone->active = ctx->active; ctx->active = clone; } return 1; } 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 Cursor display_get_visible_cursor(struct display *display) { if (display->cursor_serial != display->cursor_image.size) { DBG(("%s updating cursor\n", DisplayString(display->dpy))); if (display->visible_cursor) XFreeCursor(display->dpy, display->visible_cursor); display->visible_cursor = XcursorImageLoadCursor(display->dpy, &display->cursor_image); display->cursor_serial = display->cursor_image.size; } return display->visible_cursor; } static void display_load_visible_cursor(struct display *display, XFixesCursorImage *cur) { unsigned long *src; /* XXX deep sigh */ XcursorPixel *dst; unsigned n; if (cur->width != display->cursor_image.width || cur->height != display->cursor_image.height) display->cursor_image.pixels = realloc(display->cursor_image.pixels, 4 * cur->width * cur->height); if (display->cursor_image.pixels == NULL) return; display->cursor_image.width = cur->width; display->cursor_image.height = cur->height; display->cursor_image.xhot = cur->xhot; display->cursor_image.yhot = cur->yhot; display->cursor_image.size++; n = cur->width*cur->height; src = cur->pixels; dst = display->cursor_image.pixels; while (n--) *dst++ = *src++; DBG(("%s marking cursor changed\n", DisplayString(display->dpy))); display->cursor_moved++; if (display->cursor != display->invisible_cursor) { display->cursor_visible++; context_enable_timer(display->ctx); } } static void display_cursor_move(struct display *display, int x, int y, int visible) { DBG(("%s cursor moved (visible=%d, (%d, %d))\n", DisplayString(display->dpy), visible, x, y)); display->cursor_moved++; display->cursor_visible += visible; if (visible) { display->cursor_x = x; display->cursor_y = y; } context_enable_timer(display->ctx); } static void display_flush_cursor(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; } DBG(("%s setting cursor position (%d, %d), visible? %d\n", DisplayString(display->dpy), x, y, display->cursor_visible)); XWarpPointer(display->dpy, None, display->root, 0, 0, 0, 0, x, y); cursor = None; if (display->cursor_visible) cursor = display_get_visible_cursor(display); if (cursor == None) cursor = display->invisible_cursor; if (cursor != display->cursor) { XDefineCursor(display->dpy, display->root, cursor); display->cursor = cursor; } display_mark_flush(display); display->cursor_moved = 0; display->cursor_visible = 0; } 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 int clone_output_init(struct clone *clone, struct output *output, struct display *display, const char *name, RROutput rr_output) { Display *dpy = display->dpy; int depth; DBG(("%s(%s, %s)\n", __func__, DisplayString(dpy), name)); output->name = strdup(name); if (output->name == NULL) return -ENOMEM; output->display = display; output->dpy = dpy; output->rr_output = rr_output; output->rotation = RR_Rotate_0; output->window = display->root; output->use_shm = display->has_shm; output->use_shm_pixmap = display->has_shm_pixmap; DBG(("%s-%s use shm? %d (use shm pixmap? %d)\n", DisplayString(dpy), name, display->has_shm, display->has_shm_pixmap)); depth = output->use_shm ? display->depth : 16; if (depth < clone->depth) clone->depth = depth; return 0; } static void ximage_prepare(XImage *image, int width, int height) { image->width = width; image->height = height; image->bytes_per_line = stride_for_depth(width, image->depth); } 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)); c->image.obdata = (char *)&c->src.shm; if (c->src.use_render) { XRenderComposite(c->src.dpy, PictOpSrc, c->src.win_picture, 0, c->src.pix_picture, clip->x, clip->y, 0, 0, 0, 0, clip->width, clip->height); if (c->src.use_shm_pixmap) { XSync(c->src.dpy, False); } else if (c->src.use_shm) { ximage_prepare(&c->image, clip->width, clip->height); XShmGetImage(c->src.dpy, c->src.pixmap, &c->image, clip->x, clip->y, AllPlanes); } else { ximage_prepare(&c->image, c->width, c->height); XGetSubImage(c->src.dpy, c->src.pixmap, clip->x, clip->y, clip->width, clip->height, AllPlanes, ZPixmap, &c->image, 0, 0); } } 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.use_shm) { ximage_prepare(&c->image, clip->width, clip->height); XShmGetImage(c->src.dpy, c->src.window, &c->image, clip->x, clip->y, AllPlanes); } else { ximage_prepare(&c->image, c->width, c->height); XGetSubImage(c->src.dpy, c->src.window, clip->x, clip->y, clip->width, clip->height, AllPlanes, ZPixmap, &c->image, 0, 0); } c->src.display->flush = 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)); c->image.obdata = (char *)&c->dst.shm; if (c->dst.use_render) { if (c->dst.use_shm_pixmap) { DBG(("%s-%s using SHM pixmap composite\n", DisplayString(c->dst.dpy), c->dst.name)); } else if (c->dst.use_shm) { DBG(("%s-%s using SHM image composite\n", DisplayString(c->dst.dpy), c->dst.name)); XShmPutImage(c->dst.dpy, c->dst.pixmap, c->dst.gc, &c->image, 0, 0, 0, 0, clip->width, clip->height, False); } else { DBG(("%s-%s using composite\n", DisplayString(c->dst.dpy), c->dst.name)); XPutImage(c->dst.dpy, c->dst.pixmap, c->dst.gc, &c->image, 0, 0, 0, 0, clip->width, clip->height); } if (c->dst.use_shm) c->dst.serial = NextRequest(c->dst.dpy); XRenderComposite(c->dst.dpy, PictOpSrc, c->dst.pix_picture, 0, c->dst.win_picture, 0, 0, 0, 0, clip->x, clip->y, clip->width, clip->height); c->dst.display->send |= c->dst.use_shm; } else if (c->dst.pixmap) { DBG(("%s-%s using SHM pixmap\n", DisplayString(c->dst.dpy), c->dst.name)); c->dst.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); c->dst.display->send = 1; } else if (c->dst.use_shm) { DBG(("%s-%s using SHM image\n", DisplayString(c->dst.dpy), c->dst.name)); 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 { DBG(("%s-%s using image\n", DisplayString(c->dst.dpy), c->dst.name)); 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; } display_mark_flush(c->dst.display); } static int clone_paint(struct clone *c) { XRectangle clip; DBG(("%s-%s paint clone, damaged (%d, %d), (%d, %d) [(%d, %d), (%d, %d)]\n", DisplayString(c->dst.dpy), c->dst.name, c->damaged.x1, c->damaged.y1, c->damaged.x2, c->damaged.y2, c->src.x, c->src.y, c->src.x + c->width, c->src.y + c->height)); 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; DBG(("%s-%s is damaged, last SHM serial: %ld, now %ld\n", DisplayString(c->dst.dpy), c->dst.name, (long)c->dst.serial, (long)LastKnownRequestProcessed(c->dst.dpy))); if (c->dst.serial > LastKnownRequestProcessed(c->dst.dpy)) { struct pollfd pfd; pfd.fd = ConnectionNumber(c->dst.dpy); pfd.events = POLLIN; XEventsQueued(c->dst.dpy, poll(&pfd, 1, 0) ? QueuedAfterReading : QueuedAfterFlush); if (c->dst.serial > LastKnownRequestProcessed(c->dst.dpy)) { c->dst.display->skip_clone++; return EAGAIN; } } c->dst.display->skip_clone = 0; c->dst.display->skip_frame = 0; if (FORCE_FULL_REDRAW) { c->damaged.x1 = c->src.x; c->damaged.y1 = c->src.y; c->damaged.x2 = c->src.x + c->width; c->damaged.y2 = c->src.y + c->height; } 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 ] [-b] []...\n", arg0); } static void record_callback(XPointer closure, XRecordInterceptData *data) { struct context *ctx = (struct context *)closure; DBG(("%s\n", __func__)); if (data->category == XRecordFromServer) { const xEvent *e = (const xEvent *)data->data; DBG(("%s -- from server, event type %d, root %ld (ours? %d)\n", __func__, e->u.u.type, (long)e->u.keyButtonPointer.root, ctx->display->root == e->u.keyButtonPointer.root)); if (e->u.u.type == MotionNotify && e->u.keyButtonPointer.root == ctx->display->root) { struct clone *clone; for (clone = ctx->active; clone; clone = clone->active) clone_move_cursor(clone, 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->dpy))); dpy = XOpenDisplay(DisplayString(ctx->display->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->record = dpy; return ConnectionNumber(dpy); } static int bad_visual(Visual *visual, int depth) { DBG(("%s? depth=%d, visual: class=%d, bits_per_rgb=%d, red_mask=%08lx, green_mask=%08lx, blue_mask=%08lx\n", __func__, depth, visual->class, visual->bits_per_rgb, visual->red_mask, visual->green_mask, visual->blue_mask)); if (!(visual->class == TrueColor || visual->class == DirectColor)) return 1; switch (depth) { case 16: return (/* visual->bits_per_rgb != 6 || */ visual->red_mask != 0x1f << 11 || visual->green_mask != 0x3f << 5 || visual->blue_mask != 0x1f << 0); case 24: return (/* visual->bits_per_rgb != 8 || */ visual->red_mask != 0xff << 16 || visual->green_mask != 0xff << 8 || visual->blue_mask != 0xff << 0); default: return 1; } } static XRenderPictFormat * find_xrender_format(Display *dpy, pixman_format_code_t format) { XRenderPictFormat tmpl; int mask; #define MASK(x) ((1<<(x))-1) memset(&tmpl, 0, sizeof(tmpl)); tmpl.depth = PIXMAN_FORMAT_DEPTH(format); mask = PictFormatType | PictFormatDepth; DBG(("%s(0x%08lx)\n", __func__, (long)format)); switch (PIXMAN_FORMAT_TYPE(format)) { case PIXMAN_TYPE_ARGB: tmpl.type = PictTypeDirect; if (PIXMAN_FORMAT_A(format)) { tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format)); tmpl.direct.alpha = (PIXMAN_FORMAT_R(format) + PIXMAN_FORMAT_G(format) + PIXMAN_FORMAT_B(format)); } tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format)); tmpl.direct.red = (PIXMAN_FORMAT_G(format) + PIXMAN_FORMAT_B(format)); tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format)); tmpl.direct.green = PIXMAN_FORMAT_B(format); tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format)); tmpl.direct.blue = 0; mask |= PictFormatRed | PictFormatRedMask; mask |= PictFormatGreen | PictFormatGreenMask; mask |= PictFormatBlue | PictFormatBlueMask; mask |= PictFormatAlpha | PictFormatAlphaMask; break; case PIXMAN_TYPE_ABGR: tmpl.type = PictTypeDirect; if (tmpl.direct.alphaMask) { tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format)); tmpl.direct.alpha = (PIXMAN_FORMAT_B(format) + PIXMAN_FORMAT_G(format) + PIXMAN_FORMAT_R(format)); } tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format)); tmpl.direct.blue = (PIXMAN_FORMAT_G(format) + PIXMAN_FORMAT_R(format)); tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format)); tmpl.direct.green = PIXMAN_FORMAT_R(format); tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format)); tmpl.direct.red = 0; mask |= PictFormatRed | PictFormatRedMask; mask |= PictFormatGreen | PictFormatGreenMask; mask |= PictFormatBlue | PictFormatBlueMask; mask |= PictFormatAlpha | PictFormatAlphaMask; break; case PIXMAN_TYPE_BGRA: tmpl.type = PictTypeDirect; tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format)); tmpl.direct.blue = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format)); tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format)); tmpl.direct.green = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format) - PIXMAN_FORMAT_G(format)); tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format)); tmpl.direct.red = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format) - PIXMAN_FORMAT_G(format) - PIXMAN_FORMAT_R(format)); if (tmpl.direct.alphaMask) { tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format)); tmpl.direct.alpha = 0; } mask |= PictFormatRed | PictFormatRedMask; mask |= PictFormatGreen | PictFormatGreenMask; mask |= PictFormatBlue | PictFormatBlueMask; mask |= PictFormatAlpha | PictFormatAlphaMask; break; case PIXMAN_TYPE_A: tmpl.type = PictTypeDirect; tmpl.direct.alpha = 0; tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format)); mask |= PictFormatAlpha | PictFormatAlphaMask; break; case PIXMAN_TYPE_COLOR: case PIXMAN_TYPE_GRAY: /* XXX Find matching visual/colormap */ tmpl.type = PictTypeIndexed; //tmpl.colormap = screen->visuals[PIXMAN_FORMAT_VIS(format)].vid; //mask |= PictFormatColormap; return NULL; } #undef MASK return XRenderFindFormat(dpy, mask, &tmpl, 0); } static int display_init_render(struct display *display, int depth, XRenderPictFormat **use_render) { Display *dpy = display->dpy; int major, minor; DBG(("%s is depth %d, want %d\n", DisplayString(dpy), display->depth, depth)); *use_render = 0; if (depth == display->depth && !bad_visual(display->visual, depth)) return 0; if (display->root_format == 0) { if (!XRenderQueryVersion(dpy, &major, &minor)) { fprintf(stderr, "Render extension not supported by %s\n", DisplayString(dpy)); return -EINVAL; } display->root_format = XRenderFindVisualFormat(dpy, display->visual); display->rgb16_format = find_xrender_format(dpy, PIXMAN_r5g6b5); display->rgb24_format = XRenderFindStandardFormat(dpy, PictStandardRGB24); DBG(("%s: root format=%lx, rgb16 format=%lx, rgb24 format=%lx\n", DisplayString(dpy), (long)display->root_format, (long)display->rgb16_format, (long)display->rgb24_format)); } switch (depth) { case 16: *use_render = display->rgb16_format; break; case 24: *use_render = display->rgb24_format; break; } if (*use_render == 0) return -ENOENT; return 0; } static int clone_init_depth(struct clone *clone) { int ret, depth; DBG(("%s-%s wants depth %d\n", DisplayString(clone->dst.dpy), clone->dst.name, clone->depth)); ret = -1; for (depth = clone->depth; depth <= 24; depth += 8) { ret = display_init_render(clone->src.display, depth, &clone->src.use_render); if (ret) continue; ret = display_init_render(clone->dst.display, depth, &clone->dst.use_render); if (ret) continue; break; } if (ret) return ret; DBG(("%s-%s using depth %d, requires xrender for src? %d, for dst? %d\n", DisplayString(clone->dst.dpy), clone->dst.name, clone->depth, clone->src.use_render != NULL, clone->dst.use_render != NULL)); return 0; } static int add_display(struct context *ctx, Display *dpy) { struct display *display; int first_display = ctx->ndisplay == 0; if (is_power_of_2(ctx->ndisplay)) { struct display *new_display; new_display = realloc(ctx->display, 2*ctx->ndisplay*sizeof(struct display)); if (new_display == NULL) return -ENOMEM; if (new_display != ctx->display) { int n; for (n = 0; n < ctx->nclone; n++) { struct clone *clone = &ctx->clones[n]; clone->src.display = new_display + (clone->src.display - ctx->display); clone->dst.display = new_display + (clone->dst.display - ctx->display); } } ctx->display = new_display; } display = memset(&ctx->display[ctx->ndisplay++], 0, sizeof(struct display)); display->dpy = dpy; display->ctx = ctx; display->root = DefaultRootWindow(dpy); display->depth = DefaultDepth(dpy, DefaultScreen(dpy)); display->visual = DefaultVisual(dpy, DefaultScreen(dpy)); display->has_shm = can_use_shm(dpy, display->root, &display->shm_event, &display->shm_opcode, &display->has_shm_pixmap); display->rr_active = XRRQueryExtension(dpy, &display->rr_event, &display->rr_error); if (XineramaQueryExtension(dpy, &display->xinerama_event, &display->xinerama_error)) display->xinerama_active = XineramaIsActive(dpy); /* first display (source) is slightly special */ if (!first_display) { display->invisible_cursor = display_load_invisible_cursor(display); display_cursor_move(display, 0, 0, 0); } return ConnectionNumber(dpy); } static int display_open(struct context *ctx, const char *name) { Display *dpy; int n; DBG(("%s(%s)\n", __func__, name)); dpy = XOpenDisplay(name); if (dpy == NULL) return -ECONNREFUSED; /* Prevent cloning the same display twice */ for (n = 0; n < ctx->ndisplay; n++) { if (strcmp(DisplayString(dpy), DisplayString(ctx->display[n].dpy)) == 0) { DBG(("%s %s is already connected\n", __func__, name)); XCloseDisplay(dpy); return -EBUSY; } } return add_display(ctx, dpy); } static int bumblebee_open(struct context *ctx) { char buf[256]; struct sockaddr_un addr; int fd, len; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { DBG(("%s unable to create a socket: %d\n", __func__, errno)); return -ECONNREFUSED; } 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) { DBG(("%s unable to create a socket: %d\n", __func__, errno)); 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) { DBG(("%s startup send/recv failed: %d\n", __func__, errno)); 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) { DBG(("%s query send/recv failed: %d\n", __func__, errno)); goto err; } buf[len] = '\0'; DBG(("%s query result '%s'\n", __func__, buf)); if (strncmp(buf, "Value: ", 7)) goto err; len = 7; while (buf[len] != '\n' && buf[len] != '\0') len++; buf[len] = '\0'; /* XXX We must keep the control socket open whilst we want to keep * the display around. * * So what we need to do is listen for new bumblee Xservers and * bind only for their duration. */ return display_open(ctx, buf+7); err: close(fd); return -ECONNREFUSED; } 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, XDamageReportBoundingBox); if (display->damage == 0) return EACCES; return 0; } static void display_reset_damage(struct display *display) { Damage damage; damage = XDamageCreate(display->dpy, display->root, XDamageReportBoundingBox); if (damage) { XDamageDestroy(display->dpy, display->damage); display->damage = damage; XFlush(display->dpy); display->flush = 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; DBG(("%s - randr version %d.%d\n", DisplayString(display->dpy), major, minor)); if (major > 1 || (major == 1 && minor >= 2)) XRRSelectInput(display->dpy, display->root, RROutputChangeNotifyMask); } static void rebuild_clones(struct context *ctx, struct clone *new_clones) { int n, m; for (n = 1; n < ctx->ndisplay; n++) { struct display *d = &ctx->display[n]; d->clone = NULL; for (m = 0; m < ctx->nclone; m++) { struct clone *c = &new_clones[m]; if (c->dst.display != d) continue; c->next = d->clone; d->clone = c; } } ctx->clones = new_clones; } static struct clone *add_clone(struct context *ctx) { if (is_power_of_2(ctx->nclone)) { struct clone *new_clones; new_clones = realloc(ctx->clones, 2*ctx->nclone*sizeof(struct clone)); if (new_clones == NULL) return NULL; if (new_clones != ctx->clones) rebuild_clones(ctx, new_clones); } return memset(&ctx->clones[ctx->nclone++], 0, sizeof(struct clone)); } static struct display *last_display(struct context *ctx) { return &ctx->display[ctx->ndisplay-1]; } static int last_display_add_clones__randr(struct context *ctx) { struct display *display = last_display(ctx); XRRScreenResources *res; char buf[80]; int i, ret; DBG(("%s(%s)\n", __func__, DisplayString(display->dpy))); display_init_randr_hpd(display); res = _XRRGetScreenResourcesCurrent(display->dpy, display->root); if (res == NULL) return -ENOMEM; DBG(("%s - noutputs=%d\n", DisplayString(display->dpy), res->noutput)); for (i = 0; i < res->noutput; i++) { XRROutputInfo *o = XRRGetOutputInfo(display->dpy, res, res->outputs[i]); struct clone *clone = add_clone(ctx); RROutput id; if (clone == NULL) return -ENOMEM; clone->depth = 24; clone->next = display->clone; display->clone = clone; id = claim_virtual(ctx->display, buf, ctx->nclone); if (id == 0) { fprintf(stderr, "Failed to find available VirtualHead \"%s\" for \"%s\" on display \"%s\"\n", buf, o->name, DisplayString(display->dpy)); return -ENOSPC; } ret = clone_output_init(clone, &clone->src, ctx->display, buf, id); if (ret) { fprintf(stderr, "Failed to add output \"%s\" on display \"%s\"\n", buf, DisplayString(ctx->display->dpy)); return ret; } ret = clone_output_init(clone, &clone->dst, display, o->name, res->outputs[i]); if (ret) { fprintf(stderr, "Failed to add output \"%s\" on display \"%s\"\n", o->name, DisplayString(display->dpy)); return ret; } ret = clone_init_depth(clone); if (ret) { fprintf(stderr, "Failed to negotiate image format for display \"%s\"\n", DisplayString(display->dpy)); return ret; } ret = clone_update_modes__randr(clone); if (ret) { fprintf(stderr, "Failed to clone output \"%s\" from display \"%s\"\n", o->name, DisplayString(display->dpy)); return ret; } if (o->crtc) { DBG(("%s - disabling active output\n", DisplayString(display->dpy))); XRRSetCrtcConfig(display->dpy, res, o->crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); } XRRFreeOutputInfo(o); } XRRFreeScreenResources(res); return 0; } static int last_display_add_clones__xinerama(struct context *ctx) { struct display *display = last_display(ctx); Display *dpy = display->dpy; XineramaScreenInfo *xi; char buf[80]; int n, count, ret; DBG(("%s(%s)\n", __func__, DisplayString(display->dpy))); count = 0; xi = XineramaQueryScreens(dpy, &count); for (n = 0; n < count; n++) { struct clone *clone = add_clone(ctx); RROutput id; if (clone == NULL) return -ENOMEM; if (xi[n].width == 0 || xi[n].height == 0) continue; clone->depth = 24; clone->next = display->clone; display->clone = clone; id = claim_virtual(ctx->display, buf, ctx->nclone); if (id == 0) { fprintf(stderr, "Failed to find available VirtualHead \"%s\" for Xinerama screen %d on display \"%s\"\n", buf, n, DisplayString(dpy)); } ret = clone_output_init(clone, &clone->src, ctx->display, buf, id); if (ret) { fprintf(stderr, "Failed to add Xinerama screen %d on display \"%s\"\n", n, DisplayString(ctx->display->dpy)); return ret; } sprintf(buf, "XINERAMA%d", n); ret = clone_output_init(clone, &clone->dst, display, buf, 0); if (ret) { fprintf(stderr, "Failed to add Xinerama screen %d on display \"%s\"\n", n, DisplayString(dpy)); return ret; } ret = clone_init_depth(clone); if (ret) { fprintf(stderr, "Failed to negotiate image format for display \"%s\"\n", DisplayString(display->dpy)); return ret; } /* Replace the modes on the local VIRTUAL output with the remote Screen */ clone->width = xi[n].width; clone->height = xi[n].height; clone->dst.x = xi[n].x_org; clone->dst.y = xi[n].y_org; clone->dst.rr_crtc = -1; ret = clone_update_modes__fixed(clone); if (ret) { fprintf(stderr, "Failed to clone Xinerama screen %d from display \"%s\"\n", n, DisplayString(display->dpy)); return ret; } clone->active = ctx->active; ctx->active = clone; } XFree(xi); return 0; } static int last_display_add_clones__display(struct context *ctx) { struct display *display = last_display(ctx); Display *dpy = display->dpy; struct clone *clone; Screen *scr; char buf[80]; int ret; RROutput id; DBG(("%s(%s)\n", __func__, DisplayString(dpy))); clone = add_clone(ctx); if (clone == NULL) return -ENOMEM; clone->depth = 24; clone->next = display->clone; display->clone = clone; id = claim_virtual(ctx->display, buf, ctx->nclone); if (id == 0) { fprintf(stderr, "Failed to find available VirtualHead \"%s\" for on display \"%s\"\n", buf, DisplayString(dpy)); } ret = clone_output_init(clone, &clone->src, ctx->display, buf, id); if (ret) { fprintf(stderr, "Failed to add display \"%s\"\n", DisplayString(ctx->display->dpy)); return ret; } sprintf(buf, "WHOLE"); ret = clone_output_init(clone, &clone->dst, display, buf, 0); if (ret) { fprintf(stderr, "Failed to add display \"%s\"\n", DisplayString(dpy)); return ret; } ret = clone_init_depth(clone); if (ret) { fprintf(stderr, "Failed to negotiate image format for display \"%s\"\n", DisplayString(dpy)); return ret; } /* Replace the modes on the local VIRTUAL output with the remote Screen */ scr = ScreenOfDisplay(dpy, DefaultScreen(dpy)); clone->width = scr->width; clone->height = scr->height; clone->dst.x = 0; clone->dst.y = 0; clone->dst.rr_crtc = -1; ret = clone_update_modes__fixed(clone); if (ret) { fprintf(stderr, "Failed to clone display \"%s\"\n", DisplayString(dpy)); return ret; } clone->active = ctx->active; ctx->active = clone; return 0; } static int last_display_add_clones(struct context *ctx) { struct display *display = last_display(ctx); display->width = DisplayWidth(display->dpy, DefaultScreen(display->dpy)); display->height = DisplayHeight(display->dpy, DefaultScreen(display->dpy)); DBG(("%s - initial size %dx%d\n", DisplayString(display->dpy), display->width, display->height)); if (display->rr_active) return last_display_add_clones__randr(ctx); if (display->xinerama_active) return last_display_add_clones__xinerama(ctx); return last_display_add_clones__display(ctx); } static int last_display_clone(struct context *ctx, int fd) { fd = add_fd(ctx, fd); if (fd < 0) return fd; fd = last_display_add_clones(ctx); if (fd) return fd; return 0; } static int first_display_has_singleton(struct context *ctx) { struct display *display = ctx->display; unsigned long nitems, bytes; unsigned char *prop; int format; Atom type; ctx->singleton = XInternAtom(display->dpy, "intel-virtual-output-singleton", False); XGetWindowProperty(display->dpy, display->root, ctx->singleton, 0, 0, 0, AnyPropertyType, &type, &format, &nitems, &bytes, &prop); DBG(("%s: singleton registered? %d\n", DisplayString(display->dpy), type != None)); return type != None; } static int first_display_wait_for_ack(struct context *ctx, int timeout, int id) { struct display *display = ctx->display; struct pollfd pfd; char expect[6]; /* "1234R\0" */ sprintf(expect, "%04xR", id); DBG(("%s: wait for act '%c%c%c%c%c'\n", DisplayString(display->dpy), expect[0], expect[1], expect[2], expect[3], expect[4])); XFlush(display->dpy); pfd.fd = ConnectionNumber(display->dpy); pfd.events = POLLIN; do { if (poll(&pfd, 1, timeout) <= 0) return -ETIME; while (XPending(display->dpy)) { XEvent e; XClientMessageEvent *cme; XNextEvent(display->dpy, &e); DBG(("%s: reading event type %d\n", DisplayString(display->dpy), e.type)); if (e.type != ClientMessage) continue; cme = (XClientMessageEvent *)&e; if (cme->message_type != ctx->singleton) continue; if (cme->format != 8) continue; DBG(("%s: client message '%c%c%c%c%c'\n", DisplayString(display->dpy), cme->data.b[0], cme->data.b[1], cme->data.b[2], cme->data.b[3], cme->data.b[4])); if (memcmp(cme->data.b, expect, 5)) continue; return -atoi(cme->data.b + 5); } } while (1); } #if defined(__GNUC__) && (__GNUC__ > 3) __attribute__((format(gnu_printf, 3, 4))) #endif static int first_display_send_command(struct context *ctx, int timeout, const char *format, ...) { struct display *display = ctx->display; char buf[1024], *b; int len, id; va_list va; id = rand() & 0xffff; sprintf(buf, "%04x", id); va_start(va, format); len = vsnprintf(buf+4, sizeof(buf)-4, format, va)+5; va_end(va); assert(len < sizeof(buf)); DBG(("%s: send command '%s'\n", DisplayString(display->dpy), buf)); b = buf; while (len) { XClientMessageEvent msg; int n = len; if (n > sizeof(msg.data.b)) n = sizeof(msg.data.b); len -= n; msg.type = ClientMessage; msg.serial = 0; msg.message_type = ctx->singleton; msg.format = 8; memcpy(msg.data.b, b, n); b += n; XSendEvent(display->dpy, display->root, False, PropertyChangeMask, (XEvent *)&msg); } return first_display_wait_for_ack(ctx, timeout, id); } static void first_display_reply(struct context *ctx, int result) { struct display *display = ctx->display; XClientMessageEvent msg; sprintf(msg.data.b, "%c%c%c%cR%d", ctx->command[0], ctx->command[1], ctx->command[2], ctx->command[3], -result); DBG(("%s: send reply '%s'\n", DisplayString(display->dpy), msg.data.b)); msg.type = ClientMessage; msg.serial = 0; msg.message_type = ctx->singleton; msg.format = 8; XSendEvent(display->dpy, display->root, False, PropertyChangeMask, (XEvent *)&msg); XFlush(display->dpy); } static void first_display_handle_command(struct context *ctx, const char *msg) { int len; DBG(("client message!\n")); for (len = 0; len < 20 && msg[len]; len++) ; if (ctx->command_continuation + len > sizeof(ctx->command)) { ctx->command_continuation = 0; return; } memcpy(ctx->command + ctx->command_continuation, msg, len); ctx->command_continuation += len; if (len < 20) { ctx->command[ctx->command_continuation] = 0; DBG(("client command complete! '%s'\n", ctx->command)); switch (ctx->command[4]) { case 'B': first_display_reply(ctx, last_display_clone(ctx, bumblebee_open(ctx))); break; case 'C': first_display_reply(ctx, last_display_clone(ctx, display_open(ctx, ctx->command + 5))); break; case 'P': first_display_reply(ctx, 0); break; case 'R': break; } ctx->command_continuation = 0; return; } } static int first_display_register_as_singleton(struct context *ctx) { struct display *display = ctx->display; struct pollfd pfd; XChangeProperty(display->dpy, display->root, ctx->singleton, XA_STRING, 8, PropModeReplace, (unsigned char *)".", 1); XFlush(display->dpy); /* And eat the notify (presuming that it is ours!) */ pfd.fd = ConnectionNumber(display->dpy); pfd.events = POLLIN; do { if (poll(&pfd, 1, 1000) <= 0) { fprintf(stderr, "Failed to register as singleton\n"); return EBUSY; } while (XPending(display->dpy)) { XEvent e; XNextEvent(display->dpy, &e); DBG(("%s: reading event type %d\n", DisplayString(display->dpy), e.type)); if (e.type == PropertyNotify && ((XPropertyEvent *)&e)->atom == ctx->singleton) return 0; } } while (1); } static void display_flush_send(struct display *display) { XShmCompletionEvent e; if (!display->send) return; DBG(("%s flushing send (serial now %ld) (has shm send? %d)\n", DisplayString(display->dpy), (long)NextRequest(display->dpy), display->shm_event)); display->send = 0; if (display->shm_event == 0) { XSync(display->dpy, False); display->flush = 0; return; } memset(&e, 0, sizeof(e)); e.type = display->shm_event; e.send_event = 1; e.drawable = display->root; e.major_code = display->shm_opcode; e.minor_code = X_ShmPutImage; XSendEvent(display->dpy, display->root, False, 0, (XEvent *)&e); display_mark_flush(display); } static void display_sync(struct display *display) { if (display->skip_clone == 0) return; if (display->skip_frame++ < 2) return; DBG(("%s forcing sync\n", DisplayString(display->dpy))); XSync(display->dpy, False); display->flush = 0; display->send = 0; /* Event tracking proven unreliable, disable */ display->shm_event = 0; } static void display_flush(struct display *display) { display_flush_cursor(display); display_flush_send(display); display_sync(display); if (!display->flush) return; DBG(("%s(%s)\n", __func__, DisplayString(display->dpy))); XFlush(display->dpy); display->flush = 0; } static int first_display_first_sibling(struct context *ctx) { const char *str, *colon; int dpy, scr, len; str = DisplayString(ctx->display->dpy); colon = strrchr(str, ':'); if (colon == NULL) return -1; if (sscanf(colon + 1, "%d.%d", &dpy, &scr) == 1) scr = 0; len = (colon - str) + 1; memcpy(ctx->command, str, len); len += sprintf(ctx->command + len, "%d.", dpy); ctx->command_continuation = len; return scr + 1; } static int first_display_sibling(struct context *ctx, int i) { if (i < 0) return 0; sprintf(ctx->command + ctx->command_continuation, "%d", i); return 1; } #define first_display_for_each_sibling(CTX, i) \ for (i = first_display_first_sibling(CTX); first_display_sibling(CTX, i); i++) static void context_cleanup(struct context *ctx) { Display *dpy = ctx->display->dpy; XRRScreenResources *res; int i, j; res = _XRRGetScreenResourcesCurrent(dpy, ctx->display->root); if (res == NULL) return; XGrabServer(dpy); for (i = 0; i < ctx->nclone; i++) { struct clone *clone = &ctx->clones[i]; XRROutputInfo *output; assert(clone->src.display == ctx->display); output = XRRGetOutputInfo(dpy, res, clone->src.rr_output); if (output == NULL) continue; if (output->crtc) XRRSetCrtcConfig(dpy, res, output->crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); for (j = 0; j < output->nmode; j++) XRRDeleteOutputMode(dpy, clone->src.rr_output, output->modes[j]); XRRFreeOutputInfo(output); } for (i = 0; i < res->nmode; i++) { if (strncmp(res->modes[i].name, "VIRTUAL", 7) == 0) { XRRDestroyMode(dpy, res->modes[i].id); continue; } if (strcmp(res->modes[i].name, "ClaimVirtualHead") == 0) { XRRDestroyMode(dpy, res->modes[i].id); continue; } } XUngrabServer(dpy); XRRFreeScreenResources(res); if (ctx->singleton) XDeleteProperty(dpy, ctx->display->root, ctx->singleton); XCloseDisplay(dpy); } static int done; static void signal_handler(int sig) { done = sig; } int main(int argc, char **argv) { struct context ctx; const char *src_name = NULL; uint64_t count; int daemonize = 1, bumblebee = 0, all = 0, singleton = 1; int i, ret, open, fail; signal(SIGPIPE, SIG_IGN); while ((i = getopt(argc, argv, "abd:fhS")) != -1) { switch (i) { case 'd': src_name = optarg; break; case 'f': daemonize = 0; break; case 'b': bumblebee = 1; break; case 'a': all = 1; break; case 'S': singleton = 0; break; case 'h': default: usage(argv[0]); exit(0); } } ret = context_init(&ctx); if (ret) return -ret; XSetErrorHandler(_check_error_handler); ret = add_fd(&ctx, display_open(&ctx, src_name)); if (ret) { fprintf(stderr, "Unable to connect to \"%s\".\n", src_name ?: getenv("DISPLAY") ?: ", set either the DISPLAY environment variable or pass -d on the commandline"); return -ret; } if (singleton) { XSelectInput(ctx.display->dpy, ctx.display->root, PropertyChangeMask); if (first_display_has_singleton(&ctx)) { DBG(("%s: pinging singleton\n", DisplayString(ctx.display->dpy))); ret = first_display_send_command(&ctx, 2000, "P"); if (ret) { if (ret != -ETIME) return -ret; DBG(("No reply from singleton; assuming control\n")); } else { DBG(("%s: singleton active, sending open commands\n", DisplayString(ctx.display->dpy))); open = fail = 0; for (i = optind; i < argc; i++) { ret = first_display_send_command(&ctx, 5000, "C%s", argv[i]); if (ret && ret != -EBUSY) { fprintf(stderr, "Unable to connect to \"%s\".\n", argv[i]); fail++; } else open++; } if (all || (optind == argc && !bumblebee)) { first_display_for_each_sibling(&ctx, i) { ret = first_display_send_command(&ctx, 5000, "C%s", ctx.command); if (ret && ret != -EBUSY) break; else open++; } } if (bumblebee || (optind == argc && !all)) { ret = first_display_send_command(&ctx, 5000, "B"); if (ret && ret != -EBUSY) { if (bumblebee) fprintf(stderr, "Unable to connect to bumblebee.\n"); fail++; } else open++; } return open || !fail ? 0 : ECONNREFUSED; } } ret = first_display_register_as_singleton(&ctx); if (ret) return ret; } ret = display_init_damage(ctx.display); if (ret) return ret; if ((ctx.display->rr_event | ctx.display->rr_error) == 0) { fprintf(stderr, "RandR extension not supported by %s\n", DisplayString(ctx.display->dpy)); return EINVAL; } XRRSelectInput(ctx.display->dpy, ctx.display->root, RRScreenChangeNotifyMask); XFixesSelectCursorInput(ctx.display->dpy, ctx.display->root, XFixesDisplayCursorNotifyMask); ret = add_fd(&ctx, record_mouse(&ctx)); if (ret) { fprintf(stderr, "XTEST extension not supported by display \"%s\"\n", DisplayString(ctx.display->dpy)); return -ret; } open = fail = 0; for (i = optind; i < argc; i++) { ret = last_display_clone(&ctx, display_open(&ctx, argv[i])); if (ret && ret != -EBUSY) { fprintf(stderr, "Unable to connect to \"%s\".\n", argv[i]); fail++; } else open++; } if (all || (optind == argc && !bumblebee)) { first_display_for_each_sibling(&ctx, i) { ret = last_display_clone(&ctx, display_open(&ctx, ctx.command)); if (ret && ret != -EBUSY) break; else open++; } } if (bumblebee || (optind == argc && !all)) { ret = last_display_clone(&ctx, bumblebee_open(&ctx)); if (ret && ret != -EBUSY) { if (bumblebee) fprintf(stderr, "Unable to connect to bumblebee.\n"); fail++; } else open++; } if (open == 0) return fail ? ECONNREFUSED : 0; if (daemonize && daemon(0, 0)) return EINVAL; signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); ctx.command_continuation = 0; while (!done) { XEvent e; int reconfigure = 0; int rr_update = 0; DBG(("polling - enable timer? %d, nfd=%d, ndisplay=%d\n", ctx.timer_active, ctx.nfd, ctx.ndisplay)); ret = poll(ctx.pfd + !ctx.timer_active, ctx.nfd - !ctx.timer_active, -1); if (ret <= 0) break; /* pfd[0] is the timer, pfd[1] is the local display, pfd[2] is the mouse, pfd[3+] are the remotes */ DBG(("poll reports %d fd awake\n", ret)); if (ctx.pfd[1].revents || XPending(ctx.display[0].dpy)) { DBG(("%s woken up\n", DisplayString(ctx.display[0].dpy))); do { XNextEvent(ctx.display->dpy, &e); if (e.type == ctx.display->damage_event + XDamageNotify ) { const XDamageNotifyEvent *de = (const XDamageNotifyEvent *)&e; struct clone *clone; DBG(("%s damaged: (%d, %d)x(%d, %d)\n", DisplayString(ctx.display->dpy), de->area.x, de->area.y, de->area.width, de->area.height)); for (clone = ctx.active; clone; clone = clone->next) clone_damage(clone, &de->area); if (ctx.active) context_enable_timer(&ctx); } else if (e.type == ctx.display->xfixes_event + XFixesCursorNotify) { XFixesCursorImage *cur; DBG(("%s cursor changed\n", DisplayString(ctx.display->dpy))); cur = XFixesGetCursorImage(ctx.display->dpy); if (cur == NULL) continue; for (i = 1; i < ctx.ndisplay; i++) display_load_visible_cursor(&ctx.display[i], cur); XFree(cur); } else if (e.type == ctx.display->rr_event + RRScreenChangeNotify) { DBG(("%s screen changed (reconfigure pending? %d)\n", DisplayString(ctx.display->dpy), reconfigure)); reconfigure = 1; } else if (e.type == PropertyNotify) { XPropertyEvent *pe = (XPropertyEvent *)&e; if (pe->atom == ctx.singleton) { DBG(("lost control of singleton\n")); return 0; } } else if (e.type == ClientMessage) { XClientMessageEvent *cme; DBG(("%s client message\n", DisplayString(ctx.display->dpy))); cme = (XClientMessageEvent *)&e; if (cme->message_type != ctx.singleton) continue; if (cme->format != 8) continue; first_display_handle_command(&ctx, cme->data.b); } else { DBG(("unknown event %d\n", e.type)); } } while (XEventsQueued(ctx.display->dpy, QueuedAfterReading)); } for (i = 1; i < ctx.ndisplay; i++) { if (ctx.pfd[i+2].revents == 0 && !XPending(ctx.display[i].dpy)) continue; DBG(("%s woken up\n", DisplayString(ctx.display[i].dpy))); do { XNextEvent(ctx.display[i].dpy, &e); DBG(("%s received event %d\n", DisplayString(ctx.display[i].dpy), e.type)); if (ctx.display[i].rr_active && e.type == ctx.display[i].rr_event + RRNotify) { XRRNotifyEvent *re = (XRRNotifyEvent *)&e; DBG(("%s received RRNotify, type %d\n", DisplayString(ctx.display[i].dpy), re->subtype)); if (re->subtype == RRNotify_OutputChange) { XRROutputPropertyNotifyEvent *ro = (XRROutputPropertyNotifyEvent *)re; struct clone *clone; DBG(("%s RRNotify_OutputChange, timestamp %ld\n", DisplayString(ctx.display[i].dpy), ro->timestamp)); for (clone = ctx.display[i].clone; clone; clone = clone->next) { if (clone->dst.rr_output == ro->output) rr_update = clone->rr_update = 1; } } } } while (XEventsQueued(ctx.display[i].dpy, QueuedAfterReading)); } if (rr_update) { for (i = 0; i < ctx.nclone; i++) clone_update(&ctx.clones[i]); } if (reconfigure && context_update(&ctx)) display_reset_damage(ctx.display); XPending(ctx.record); if (ctx.timer_active && read(ctx.timer, &count, sizeof(count)) > 0) { struct clone *clone; DBG(("%s timer expired (count=%ld)\n", DisplayString(ctx.display->dpy), (long)count)); ret = 0; if (ctx.active) { DBG(("%s clearing damage\n", DisplayString(ctx.display->dpy))); XDamageSubtract(ctx.display->dpy, ctx.display->damage, None, None); ctx.display->flush = 1; } for (clone = ctx.active; clone; clone = clone->active) ret |= clone_paint(clone); for (i = 0; i < ctx.ndisplay; i++) display_flush(&ctx.display[i]); DBG(("%s timer still active? %d\n", DisplayString(ctx.display->dpy), ret != 0)); ctx.timer_active = ret != 0; } } context_cleanup(&ctx); return 0; }