More VNC clients!
This time, this one will split up a single VNC server input into smaller pieces to form a 3×3 grid of smaller VNC server images. It’ll automatically resize it to 640×480. That stuff is configurable in the defines at the top of the file. It works best when the ratio between the source image and the destination image is a whole number, (ie: 4×4 of 640×480 images and an input of 1280×960 has a 2:1 ratio), because it uses a simple “nearest neighbour” scaling algorithm.
The idea of this client is that you take some input into this, run a whole bunch of thin clients or low end machines connecting to each of the pieces of the screen, and align some cheap CRT monitors in a grid, creating a much bigger screen. While there are pretty huge bezels with CRT monitors, it is a very low budget way to create a 107cm (42″) display from a 3×3 array of cheap 36cm (14″) monitors, for example.
Be aware that some VNC servers implement JPEG compression, which looks really horrible when you scale it up (for example, Vine). This program also requires that your VNC server has no password.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | // vnc-splitter // Copyright 2010 Michael Farrell <http://micolous.id.au> // build with: gcc -o vnc-splitter vnc-splitter.c -lvncserver -lvncclient #include <stdio.h> #include <rfb/rfb.h> #include <rfb/rfbclient.h> // the height and width of the screens // this should match the aspect ratio of the input #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 // the amount of screens is this number squared #define SCREEN_SQUARE_SIZE 3 // where to start displays counting from. // the first screen number is this, and subsequent screens are other positions. #define DISPLAY_START 30 // output buffers for child vnc server processes rfbScreenInfoPtr output_screens[SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE]; static rfbBool resizeImage(rfbClient* client) { int width=client->width; int height=client->height; int depth=client->format.bitsPerPixel; fprintf(stderr, "ResizeImage fired: %ix%i %ibpp\n", width, height, depth); // change the screen resolution // deallocate any existing framebuffer if (client->frameBuffer != NULL) free (client->frameBuffer); client->frameBuffer = malloc(width * height * (depth / 8)); if (client->frameBuffer == NULL) { fprintf(stderr, "Cannot allocate memory for framebuffer!"); exit(1); } // we also need to tell libvncclient how it should put pixel data in there client->format.redShift = 8; client->format.greenShift = 16; client->format.blueShift = 24; client->format.redMax = 0xFF; client->format.greenMax = 0xFF; client->format.blueMax = 0xFF; // now we set the format and encoding method information back to libvncclient so it updates it's information. SetFormatAndEncodings(client); // report success to libvncclient return TRUE; } static void updateImage(rfbClient* client,int u_x,int u_y,int u_w,int u_h) { unsigned int x, y, src_x, src_y; unsigned int *dest; unsigned int *src; unsigned int max_x = SCREEN_SQUARE_SIZE * SCREEN_WIDTH; unsigned int max_y = SCREEN_SQUARE_SIZE * SCREEN_HEIGHT; // precalculate the region we actually need to update. unsigned int u_min_x = ((double)(u_x) / (double)(client->width)) * max_x; unsigned int u_min_y = ((double)(u_y) / (double)(client->height)) * max_y; unsigned int u_max_x = ((double)(u_x + u_w) / (double)(client->width)) * max_x; unsigned int u_max_y = ((double)(u_y + u_h) / (double)(client->height)) * max_y; unsigned char updated[SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE]; memset(updated, 0, SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE); // This scaling algorithm really sucks. It's a "nearest neighbour" scaling format. // It's really easy to implement, really fast, but looks absolutely terrible. // Try and end up with the image being blown up so that 1x1 on the original maps to 2x2 // on the destination. If it maps to a fraction of pixels things look really bad. for (y=u_min_y; y<u_max_y; y++) { for (x=u_min_x; x<u_max_x; x++) { // now find out where that maps to on the original framebuffer. src_x = (unsigned int)(((double)x / (double)max_x) * (double)client->width); src_y = (unsigned int)(((double)y / (double)max_y) * (double)client->height); // figure out where this pixel is unsigned int d = ((y / SCREEN_HEIGHT) * SCREEN_SQUARE_SIZE) + (x / SCREEN_WIDTH); // now create a pointer to where that pixel is stored dest = (unsigned int*)&output_screens[d]->frameBuffer[(((y % SCREEN_HEIGHT) * SCREEN_WIDTH) + (x % SCREEN_WIDTH)) * 4]; // and fix a pointer to that src = (unsigned int*)&client->frameBuffer[((src_y * client->width) + src_x) * 4]; // and copy the pixel *dest = *src; // mark that screen as updated updated[d] = 1; } } for (x=0; x<SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE; x++) if (updated[x]) // mark the entire screen as updated for now. this could really // use some proper optimisations. rfbMarkRectAsModified(output_screens[x], 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); } int main (int argc, char *argv[]) { fprintf(stderr, "vnc-splitter\n"); fprintf(stderr, "Copyright 2010 Michael Farrell <http://micolous.id.au>\n"); // create a 32-bpp "client". rfbClient* client = rfbGetClient(8,3,4); // tell the library we can handle being resized. client->canHandleNewFBSize = TRUE; client->MallocFrameBuffer = resizeImage; // handle framebuffer updates client->GotFrameBufferUpdate = updateImage; // we don't care about keyboard LEDs and tightvnc chat protocol. client->HandleKeyboardLedState = 0; client->HandleTextChat = 0; // make a blank cursor rfbCursorPtr cursor = rfbMakeXCursor(0, 0, "\0", "\0"); // create the vnc servers unsigned int id; for (id=0; id<SCREEN_SQUARE_SIZE * SCREEN_SQUARE_SIZE; id++) { output_screens[id] = rfbGetScreen(0, NULL, SCREEN_WIDTH, SCREEN_HEIGHT, 8, 3, 4); output_screens[id]->frameBuffer = malloc(SCREEN_WIDTH * SCREEN_HEIGHT * 4); output_screens[id]->serverFormat.redShift = 8; output_screens[id]->serverFormat.greenShift = 16; output_screens[id]->serverFormat.blueShift = 24; output_screens[id]->serverFormat.redMax = 0xFF; output_screens[id]->serverFormat.greenMax = 0xFF; output_screens[id]->serverFormat.blueMax = 0xFF; output_screens[id]->autoPort = FALSE; output_screens[id]->port = SERVER_PORT_OFFSET + DISPLAY_START + id; output_screens[id]->alwaysShared = TRUE; rfbSetCursor(output_screens[id], cursor); // create the server in another thread rfbInitServer(output_screens[id]); rfbRunEventLoop(output_screens[id],-1,TRUE); } // connect to the server fprintf(stderr, "Connecting to server...\n"); // rfbInitClient also allows us to use vncviewer-style commandline options. if(!rfbInitClient(client,&argc,argv)) return 1; // start pumping the loop. fprintf(stderr, "Pumping...\n"); clock_t last_frame = clock(); clock_t this_frame = last_frame; do { int i = WaitForMessage(client, 100); if (i < 0) // there was an issue getting the message, probably our socket was closed. return 0; if (i) // handle message if (!HandleRFBServerMessage(client)) // error handling message, die. return 0; } while (1); return 0; } |