/* $Id: remote-x.c,v 1.2 2003/10/17 11:48:16 olle Exp $ */ /*************************************************************************** * * remote-x.c - control X11 remotely - version 0.0.1 * * compile: cc -o remote-x remote-x.c -L/usr/X11R6/lib -lX11 -lXtst * * authors: olle@nxs.se, fredrikwidlund@yahoo.com * * usage: remote-x hostname:dpy# [window id] * * tip: when in root window mode (no window id specified on cmdline), * hold left ctrl and alt to drag viewport around screen. * * bugs: when twm and fvwm resize/move/place windows, they grab the * whole XServer while waiting for mouse input. this means we * cannot reach it to send the needed mouse input. catch-22. * the XServer will not respond to any client calls until * *local* mouse input has ended the resize/move/place action. * ****************************************************************************/ /* Copyright (c) 2001, Olle Segerdahl , Fredrik Widlund All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the names of the authors nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #ifndef WIN32 #include #else #define err(e,s) do{fprintf(stderr,"ERR: %s\n",s);exit(e);}while(0) #endif #define CONSTRAINT(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) // colormap cache unsigned long cache[65536][3]; int ConvertXImageDepth(Display *display, Colormap colormap, XImage *image, int dpy2depth) { int i, j; // return zero if conversion not needed if (dpy2depth == image->depth) return 0; // expand 8 bpp ZPixmap to 24 bpp, using colormap if (dpy2depth >= 16 && image->depth == 8) { char *iptr, *optr, *ilptr, *olptr, *tmp; int obperlin, padbits; XColor color; // calculate bytes per output line obperlin = (image->width * 24 / 8); // calculate pixel padding bits padbits = (image->bytes_per_line * 8 / image->width) - image->depth; // setup data pointers ilptr = (char *) image->data; olptr = tmp = malloc(image->height * obperlin); if (!olptr) { fprintf(stderr,"Couldn't allocate memory for image conversion!\n"); return(1); } // for each line for (i=0; iheight; i++) { // setup temp data pointers iptr = ilptr; optr = olptr; // for each pixel for (j=0; jwidth; j++) { // convert 8 bit pixel value to monochrome to avoid using colormap // optr[0] = optr[1] = (*iptr & 0x01) ? 0xff : 0x00; color.pixel = (unsigned char) *iptr; // lookup rbg value if not cached if (cache[color.pixel][0] == -1) { XQueryColor(display, colormap, &color); cache[color.pixel][2] = color.red; cache[color.pixel][1] = color.green; cache[color.pixel][0] = color.blue; } optr[2] = (unsigned char)cache[color.pixel][2]; optr[1] = (unsigned char)cache[color.pixel][1]; optr[0] = (unsigned char)cache[color.pixel][0]; iptr++; optr+=3; // skip padding - nonexistant in 8 bit? //iptr += padbits / 8; } ilptr += image->bytes_per_line; olptr += obperlin; } free(image->data); image->data = (char *) tmp; image->red_mask = 0xff0000; image->green_mask = 0x00ff00; image->blue_mask = 0x0000ff; image->bytes_per_line = obperlin; image->depth = image->bits_per_pixel = 24; } // expand 16 bpp ZPixmap to 32/24 bpp if (dpy2depth >= 24 && image->depth == 16) { char *iptr, *optr, *ilptr, *olptr, *tmp; int obperlin, padbits; // calculate bytes per output line //obperlin = ((image->width * dpy2depth + image->bitmap_pad - 1) / image->bitmap_pad) * (image->bitmap_pad / 8); obperlin = (image->width * dpy2depth / 8); // calculate pixel padding bits padbits = (image->bytes_per_line * 8 / image->width) - image->depth; // setup data pointers ilptr = (char *) image->data; olptr = tmp = malloc(image->height * obperlin); if (!olptr) { fprintf(stderr,"Couldn't allocate memory for image conversion!\n"); return(1); } // for each line for (i=0; iheight; i++) { // setup temp data pointers iptr = ilptr; optr = olptr; // for each pixel for (j=0; jwidth; j++) { u_int16_t *w; w = (u_int16_t *) iptr; optr[2] = (*w & image->red_mask) >> 8; optr[1] = (*w & image->green_mask) >> 3; optr[0] = (*w & image->blue_mask) << 3; optr+=3; iptr+=2; // skip padding iptr += (padbits / 8); } ilptr += image->bytes_per_line; olptr += obperlin; } free(image->data); image->data = (char *) tmp; image->red_mask = 0xff0000; image->green_mask = 0x00ff00; image->blue_mask = 0x0000ff; image->bytes_per_line = obperlin; image->depth = image->bits_per_pixel = dpy2depth; } // compress 32/24 bpp ZPixmap to 16 bpp if (dpy2depth == 16 && image->depth >= 24) { char *iptr, *optr, *ilptr, *olptr, *tmp; int obperlin, padbits; // calculate bytes per output line obperlin = (image->width * dpy2depth) / 8; // calculate pixel padding bits padbits = (image->bytes_per_line * 8 / image->width) - image->depth; // set new color mask values image->red_mask = 0xf800; image->green_mask = 0x07c0; image->blue_mask = 0x001f; // setup data pointers ilptr = (char *) image->data; olptr = tmp = malloc(image->height * obperlin); if (!olptr) { fprintf(stderr,"Couldn't allocate memory for image conversion!\n"); return(1); } // for each line for (i=0; iheight; i++) { // setup temp data pointers iptr = ilptr; optr = olptr; // for each pixel for (j=0; jwidth; j++) { u_int16_t *w; w = (u_int16_t *) optr; *w = 0; *w |= (iptr[2] << 8) & image->red_mask; *w |= (iptr[1] << 3) & image->green_mask; *w |= (iptr[0] >> 3) & image->blue_mask; iptr+=3; optr+=2; // skip padding iptr += padbits / 8; } // advance data pointers to next line ilptr += image->bytes_per_line; olptr += obperlin; } free(image->data); image->data = (char *) tmp; image->bytes_per_line = obperlin; image->depth = image->bits_per_pixel = dpy2depth; } // return non-zero if conversion failed if (image->depth != dpy2depth) return 1; else return 0; } int main(int argc, char **argv) { char *display_name; Display *display, *display_remote; Window window, window_remote; XImage *image; XSetWindowAttributes attr; XWindowAttributes attributes, attributes_remote; XEvent event; int x, y, bx, by; int remote_root, ctrl_down, alt_down; int event_mask, check_event, f; char title[128], tmp_title[128]; // clear colormap cache for (x=0;x<65536;x++) cache[x][0]=-1; display_name = argv[1]; if (argc < 2) { fprintf(stdout, "usage: %s hostname:dpy# [window id]\n\n", argv[0]); return(0); } // open remote display display_remote = XOpenDisplay(display_name); if (!display_remote) err(1, "XOpenDisplay"); if (argc >= 3) { // in windowed mode, get window id from cmdline remote_root = 0; window_remote = strtoul(argv[2], NULL, 0); if (!window_remote) err(1,"Bad Window ID specified!"); else fprintf(stderr, "Starting remote-x in Window mode, Window ID = 0x%x\n\n", (unsigned int)window_remote); } else { // in root mode, get remote root window id remote_root = 1; window_remote = DefaultRootWindow(display_remote); if (!window_remote) err(1,"Couldn't get Root Window!"); else fprintf(stderr, "Starting remote-x in RootWindow mode, RootWindow ID = 0x%x\n\n", (unsigned int)window_remote); } // get remote window attributes XGetWindowAttributes(display_remote, window_remote, &attributes_remote); // check that the remote window is viewable if (attributes_remote.map_state != IsViewable) { fprintf(stderr,"Window 0x%x not in Viewable state!\n", (unsigned int)window_remote); return(1); } // open local display display = XOpenDisplay(NULL); if (!display) err(1, "XOpenDisplay"); // create local window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, (remote_root ? 320 : attributes_remote.width), (remote_root ? 240 : attributes_remote.height), 0, CopyFromParent, InputOutput, CopyFromParent, 0, &attr); if (!window) err(1, "XCreateWindow"); // set local window to recieve keyboard and mouse events event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; // | PointerMotionMask ; XSelectInput(display, window, event_mask); // set local window WM properties snprintf(title, 100, "remote-x: %s in %s mode", display_name, (remote_root ? "RootWindow" : "Window")); title[127]=0; XSetStandardProperties(display, window, title, "remote-x", None, argv, argc, NULL); // make local window visible on screen XMapWindow(display, window); // get local window attributes XGetWindowAttributes(display, window, &attributes); // warn about bad 8 bpp conversion if ((attributes_remote.depth == 8) && (attributes.depth != 8)) fprintf(stderr, "WARNING! Remote display uses 8 bits per pixel. Your display uses %d bpp.\n" " This program will try to convert 8 bpp images (badly) using a colormap.\n" " It would probably be a good idea to change your display depth to 8 bpp.\n\n" , attributes.depth); // start main program loop f = 0; x = 0; y = 0; bx = 0; by = 0; alt_down = 0; ctrl_down = 0; image = NULL; while(1) { // enter event handler while(1) { // check if there are events to process check_event = XCheckWindowEvent(display, window, event_mask, &event); // if no new events to process, continue with main program loop if (!check_event) break; // handle event switch(event.type) { case ConfigureNotify: break; case ButtonPress: case ButtonRelease: if (remote_root) { if (ctrl_down && alt_down) { // drag viewport around remote desktop if (event.type == ButtonPress) { bx = event.xbutton.x; by = event.xbutton.y; } else if (event.type == ButtonRelease) { x += bx - event.xbutton.x; y += by - event.xbutton.y; x = CONSTRAINT(x, attributes_remote.x, attributes_remote.x + attributes_remote.width - attributes.width); y = CONSTRAINT(y, attributes_remote.y, attributes_remote.y + attributes_remote.height - attributes.height); } } else { // position remote mouse pointer XTestFakeMotionEvent(display_remote, -1, x + event.xbutton.x, y + event.xbutton.y, CurrentTime); // send mouse button event XTestFakeButtonEvent(display_remote, event.xbutton.button, (event.type == ButtonPress ? 1 : 0), CurrentTime); } } else { // forward mouse event to remote window event.xbutton.x_root = attributes_remote.x + event.xbutton.x; event.xbutton.y_root = attributes_remote.y + event.xbutton.y; event.xany.display = display_remote; event.xany.window = window_remote; XSendEvent(display_remote, window_remote, 1, event_mask, &event); } break; case KeyPress: case KeyRelease: // save state of ctrl&alt modifiers if (event.xkey.keycode == 0x25) // this should be a keycode symbol! ctrl_down = (event.type == KeyPress) ? 1 : 0; if (event.xkey.keycode == 0x40) // this should be a keycode symbol! alt_down = (event.type == KeyPress) ? 1 : 0; // get keybord focus XSetInputFocus(display_remote, window_remote, RevertToParent, CurrentTime); // send keyevent to remote window XTestFakeKeyEvent(display_remote, event.xkey.keycode, (event.type == KeyPress ? 1 : 0), CurrentTime); break; case MotionNotify: if (remote_root) { if (!(ctrl_down && alt_down)) // move remote mouse pointer XTestFakeMotionEvent(display_remote, -1, x + event.xmotion.x, y + event.xmotion.y, CurrentTime); //fprintf(stderr, "\rMotion: x=%d,y=%d", x + event.xmotion.x, y + event.xmotion.y); } else { // forward mouse event to remote window event.xany.display = display_remote; event.xany.window = window_remote; XSendEvent(display_remote, window_remote, 1, event_mask, &event); } break; default: break; } // flush X11 event buffers XSync(display_remote, False); } if (remote_root) { // resize local window if too large for image if (attributes.width > attributes_remote.width) XResizeWindow(display, window, attributes_remote.width, attributes.height); if (attributes.height > attributes_remote.height) XResizeWindow(display, window, attributes.width, attributes_remote.height); } else { // resize local window if remote window size has changed if ((attributes.width != attributes_remote.width) || (attributes.height != attributes_remote.height)) XResizeWindow(display, window, attributes_remote.width, attributes_remote.height); } // get local and remote window attributes XGetWindowAttributes(display, window, &attributes); XGetWindowAttributes(display_remote, window_remote, &attributes_remote); // get updated image of remote window image = XGetImage(display_remote, window_remote, x, y, CONSTRAINT(attributes.width, 0, attributes_remote.width), CONSTRAINT(attributes.height, 0, attributes_remote.height), AllPlanes, ZPixmap); // convert image bitdepth if (ConvertXImageDepth(display_remote, attributes_remote.colormap, image, attributes.depth)) { fprintf(stderr, "ERROR! The remote display uses %d bits per pixel. Your display uses %d bpp.\n" " This program does not currently have the means to convert %d bpp to %d bpp.\n" " It would be a VERY good idea to change your display depth to %d bpp.\n\n", image->depth, attributes.depth, image->depth, attributes.depth, image->depth); return 1; } // update local window with new image XPutImage(display, window, DefaultGC(display, DefaultScreen(display)), image, 0, 0, 0, 0, attributes.width, attributes.height); // delete image of remote window if (image) XDestroyImage(image); // update frame counter in window title sprintf(tmp_title, "%s (frames: %u)", title, f++); XStoreName(display, window, tmp_title); } return(0); }