ia64/xen-unstable

annotate tools/ioemu/vnc.c @ 10805:2b1a49dd1273

[qemu] Use domain-name in VNC window title.
Set the VNC window title with domain-name.

Signed-off-by: Yang Xiaowei <xiaowei.yang@intel.com>
Signed-off-by: Christian Limpach <Christian.Limpach@xensource.com>
author chris@kneesaa.uk.xensource.com
date Wed Jul 26 14:26:03 2006 +0100 (2006-07-26)
parents 060025203f54
children 3e07ec30c445
rev   line source
chris@10732 1 /*
chris@10732 2 * QEMU VNC display driver
chris@10732 3 *
chris@10732 4 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
chris@10732 5 * Copyright (C) 2006 Fabrice Bellard
chris@10732 6 * Copyright (C) 2006 Christian Limpach <Christian.Limpach@xensource.com>
chris@10732 7 *
chris@10732 8 * Permission is hereby granted, free of charge, to any person obtaining a copy
chris@10732 9 * of this software and associated documentation files (the "Software"), to deal
chris@10732 10 * in the Software without restriction, including without limitation the rights
chris@10732 11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
chris@10732 12 * copies of the Software, and to permit persons to whom the Software is
chris@10732 13 * furnished to do so, subject to the following conditions:
chris@10732 14 *
chris@10732 15 * The above copyright notice and this permission notice shall be included in
chris@10732 16 * all copies or substantial portions of the Software.
chris@10732 17 *
chris@10732 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
chris@10732 19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
chris@10732 20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
chris@10732 21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
chris@10732 22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
chris@10732 23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
chris@10732 24 * THE SOFTWARE.
chris@10732 25 */
chris@10732 26
chris@10732 27 #include "vl.h"
chris@10732 28 #include "qemu_socket.h"
chris@10732 29
chris@10732 30 #define VNC_REFRESH_INTERVAL (1000 / 30)
chris@10732 31
chris@10732 32 #include "vnc_keysym.h"
chris@10732 33 #include "keymaps.c"
chris@10732 34
chris@10732 35 typedef struct Buffer
chris@10732 36 {
chris@10732 37 size_t capacity;
chris@10732 38 size_t offset;
chris@10732 39 char *buffer;
chris@10732 40 } Buffer;
chris@10732 41
chris@10732 42 typedef struct VncState VncState;
chris@10732 43
chris@10732 44 typedef int VncReadEvent(VncState *vs, char *data, size_t len);
chris@10732 45
chris@10732 46 struct VncState
chris@10732 47 {
chris@10732 48 QEMUTimer *timer;
chris@10732 49 int lsock;
chris@10732 50 int csock;
chris@10732 51 DisplayState *ds;
chris@10732 52 int need_update;
chris@10732 53 int width;
chris@10732 54 int height;
chris@10732 55 uint64_t *dirty_row; /* screen regions which are possibly dirty */
chris@10732 56 int dirty_pixel_shift;
chris@10732 57 uint64_t *update_row; /* outstanding updates */
chris@10732 58 int has_update; /* there's outstanding updates in the
chris@10732 59 * visible area */
chris@10732 60 char *old_data;
chris@10732 61 int depth;
chris@10732 62 int has_resize;
chris@10732 63 int has_hextile;
chris@10732 64 Buffer output;
chris@10732 65 Buffer input;
chris@10732 66 kbd_layout_t *kbd_layout;
chris@10732 67
chris@10732 68 VncReadEvent *read_handler;
chris@10732 69 size_t read_handler_expect;
chris@10732 70
chris@10732 71 int visible_x;
chris@10732 72 int visible_y;
chris@10732 73 int visible_w;
chris@10732 74 int visible_h;
chris@10732 75
chris@10732 76 int slow_client;
chris@10732 77 };
chris@10732 78
chris@10732 79 #define DIRTY_PIXEL_BITS 64
chris@10732 80 #define X2DP_DOWN(vs, x) ((x) >> (vs)->dirty_pixel_shift)
chris@10732 81 #define X2DP_UP(vs, x) \
chris@10732 82 (((x) + (1ULL << (vs)->dirty_pixel_shift) - 1) >> (vs)->dirty_pixel_shift)
chris@10732 83 #define DP2X(vs, x) ((x) << (vs)->dirty_pixel_shift)
chris@10732 84
chris@10732 85 /* TODO
chris@10732 86 1) Get the queue working for IO.
chris@10732 87 2) there is some weirdness when using the -S option (the screen is grey
chris@10732 88 and not totally invalidated
chris@10732 89 */
chris@10732 90
chris@10732 91 static void vnc_write(VncState *vs, const void *data, size_t len);
chris@10732 92 static void vnc_write_u32(VncState *vs, uint32_t value);
chris@10732 93 static void vnc_write_s32(VncState *vs, int32_t value);
chris@10732 94 static void vnc_write_u16(VncState *vs, uint16_t value);
chris@10732 95 static void vnc_write_u8(VncState *vs, uint8_t value);
chris@10732 96 static void vnc_flush(VncState *vs);
chris@10732 97 static void _vnc_update_client(void *opaque);
chris@10732 98 static void vnc_update_client(void *opaque);
chris@10732 99 static void vnc_client_read(void *opaque);
chris@10732 100 static void framebuffer_set_updated(VncState *vs, int x, int y, int w, int h);
chris@10732 101
chris@10732 102 static void set_bits_in_row(VncState *vs, uint64_t *row,
chris@10732 103 int x, int y, int w, int h)
chris@10732 104 {
chris@10732 105 int x1, x2;
chris@10732 106 uint64_t mask;
chris@10732 107
chris@10732 108 if (w == 0)
chris@10732 109 return;
chris@10732 110
chris@10732 111 x1 = X2DP_DOWN(vs, x);
chris@10732 112 x2 = X2DP_UP(vs, x + w);
chris@10732 113
chris@10732 114 if (X2DP_UP(vs, w) != DIRTY_PIXEL_BITS)
chris@10732 115 mask = ((1ULL << (x2 - x1)) - 1) << x1;
chris@10732 116 else
chris@10732 117 mask = ~(0ULL);
chris@10732 118
chris@10732 119 h += y;
chris@10732 120 for (; y < h; y++)
chris@10732 121 row[y] |= mask;
chris@10732 122 }
chris@10732 123
chris@10732 124 static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h)
chris@10732 125 {
chris@10732 126 VncState *vs = ds->opaque;
chris@10732 127
chris@10732 128 set_bits_in_row(vs, vs->dirty_row, x, y, w, h);
chris@10732 129 }
chris@10732 130
chris@10732 131 static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
chris@10732 132 int32_t encoding)
chris@10732 133 {
chris@10732 134 vnc_write_u16(vs, x);
chris@10732 135 vnc_write_u16(vs, y);
chris@10732 136 vnc_write_u16(vs, w);
chris@10732 137 vnc_write_u16(vs, h);
chris@10732 138
chris@10732 139 vnc_write_s32(vs, encoding);
chris@10732 140 }
chris@10732 141
chris@10732 142 static void vnc_dpy_resize(DisplayState *ds, int w, int h)
chris@10732 143 {
chris@10732 144 VncState *vs = ds->opaque;
chris@10732 145 int o;
chris@10732 146
chris@10732 147 ds->data = realloc(ds->data, w * h * vs->depth);
chris@10732 148 vs->old_data = realloc(vs->old_data, w * h * vs->depth);
chris@10732 149 vs->dirty_row = realloc(vs->dirty_row, h * sizeof(vs->dirty_row[0]));
chris@10732 150 vs->update_row = realloc(vs->update_row, h * sizeof(vs->dirty_row[0]));
chris@10732 151
chris@10732 152 if (ds->data == NULL || vs->old_data == NULL ||
chris@10732 153 vs->dirty_row == NULL || vs->update_row == NULL) {
chris@10732 154 fprintf(stderr, "vnc: memory allocation failed\n");
chris@10732 155 exit(1);
chris@10732 156 }
chris@10732 157
chris@10804 158 if (ds->depth != vs->depth * 8) {
chris@10804 159 ds->depth = vs->depth * 8;
chris@10804 160 set_color_table(ds);
chris@10804 161 }
chris@10732 162 ds->width = w;
chris@10732 163 ds->height = h;
chris@10732 164 ds->linesize = w * vs->depth;
chris@10732 165 if (vs->csock != -1 && vs->has_resize) {
chris@10732 166 vnc_write_u8(vs, 0); /* msg id */
chris@10732 167 vnc_write_u8(vs, 0);
chris@10732 168 vnc_write_u16(vs, 1); /* number of rects */
chris@10732 169 vnc_framebuffer_update(vs, 0, 0, ds->width, ds->height, -223);
chris@10732 170 vnc_flush(vs);
chris@10732 171 vs->width = ds->width;
chris@10732 172 vs->height = ds->height;
chris@10732 173 }
chris@10732 174 vs->dirty_pixel_shift = 0;
chris@10732 175 for (o = DIRTY_PIXEL_BITS; o < ds->width; o *= 2)
chris@10732 176 vs->dirty_pixel_shift++;
chris@10732 177 framebuffer_set_updated(vs, 0, 0, ds->width, ds->height);
chris@10732 178 }
chris@10732 179
chris@10732 180 static void send_framebuffer_update_raw(VncState *vs, int x, int y, int w, int h)
chris@10732 181 {
chris@10732 182 int i;
chris@10732 183 char *row;
chris@10732 184
chris@10732 185 vnc_framebuffer_update(vs, x, y, w, h, 0);
chris@10732 186
chris@10732 187 row = vs->ds->data + y * vs->ds->linesize + x * vs->depth;
chris@10732 188 for (i = 0; i < h; i++) {
chris@10732 189 vnc_write(vs, row, w * vs->depth);
chris@10732 190 row += vs->ds->linesize;
chris@10732 191 }
chris@10732 192 }
chris@10732 193
chris@10732 194 static void hextile_enc_cord(uint8_t *ptr, int x, int y, int w, int h)
chris@10732 195 {
chris@10732 196 ptr[0] = ((x & 0x0F) << 4) | (y & 0x0F);
chris@10732 197 ptr[1] = (((w - 1) & 0x0F) << 4) | ((h - 1) & 0x0F);
chris@10732 198 }
chris@10732 199
chris@10732 200 #define BPP 8
chris@10732 201 #include "vnchextile.h"
chris@10732 202 #undef BPP
chris@10732 203
chris@10732 204 #define BPP 16
chris@10732 205 #include "vnchextile.h"
chris@10732 206 #undef BPP
chris@10732 207
chris@10732 208 #define BPP 32
chris@10732 209 #include "vnchextile.h"
chris@10732 210 #undef BPP
chris@10732 211
chris@10732 212 static void send_framebuffer_update_hextile(VncState *vs, int x, int y, int w, int h)
chris@10732 213 {
chris@10732 214 int i, j;
chris@10732 215 int has_fg, has_bg;
chris@10732 216 uint32_t last_fg32, last_bg32;
chris@10732 217 uint16_t last_fg16, last_bg16;
chris@10732 218 uint8_t last_fg8, last_bg8;
chris@10732 219
chris@10732 220 vnc_framebuffer_update(vs, x, y, w, h, 5);
chris@10732 221
chris@10732 222 has_fg = has_bg = 0;
chris@10732 223 for (j = y; j < (y + h); j += 16) {
chris@10732 224 for (i = x; i < (x + w); i += 16) {
chris@10732 225 switch (vs->depth) {
chris@10732 226 case 1:
chris@10732 227 send_hextile_tile_8(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j),
chris@10732 228 &last_bg8, &last_fg8, &has_bg, &has_fg);
chris@10732 229 break;
chris@10732 230 case 2:
chris@10732 231 send_hextile_tile_16(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j),
chris@10732 232 &last_bg16, &last_fg16, &has_bg, &has_fg);
chris@10732 233 break;
chris@10732 234 case 4:
chris@10732 235 send_hextile_tile_32(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j),
chris@10732 236 &last_bg32, &last_fg32, &has_bg, &has_fg);
chris@10732 237 break;
chris@10732 238 default:
chris@10732 239 break;
chris@10732 240 }
chris@10732 241 }
chris@10732 242 }
chris@10732 243 }
chris@10732 244
chris@10732 245 static void send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
chris@10732 246 {
chris@10732 247 if (vs->has_hextile)
chris@10732 248 send_framebuffer_update_hextile(vs, x, y, w, h);
chris@10732 249 else
chris@10732 250 send_framebuffer_update_raw(vs, x, y, w, h);
chris@10732 251 }
chris@10732 252
chris@10732 253 static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h)
chris@10732 254 {
chris@10732 255 int src, dst;
chris@10732 256 char *src_row;
chris@10732 257 char *dst_row;
chris@10732 258 char *old_row;
chris@10732 259 int y = 0;
chris@10732 260 int pitch = ds->linesize;
chris@10732 261 VncState *vs = ds->opaque;
chris@10732 262 int updating_client = !vs->slow_client;
chris@10732 263
chris@10732 264 if (src_x < vs->visible_x || src_y < vs->visible_y ||
chris@10732 265 dst_x < vs->visible_x || dst_y < vs->visible_y ||
chris@10732 266 (src_x + w) > (vs->visible_x + vs->visible_w) ||
chris@10732 267 (src_y + h) > (vs->visible_y + vs->visible_h) ||
chris@10732 268 (dst_x + w) > (vs->visible_x + vs->visible_w) ||
chris@10732 269 (dst_y + h) > (vs->visible_y + vs->visible_h))
chris@10732 270 updating_client = 0;
chris@10732 271
chris@10732 272 if (updating_client) {
chris@10732 273 vs->need_update = 1;
chris@10732 274 _vnc_update_client(vs);
chris@10732 275 }
chris@10732 276
chris@10732 277 if (dst_y > src_y) {
chris@10732 278 y = h - 1;
chris@10732 279 pitch = -pitch;
chris@10732 280 }
chris@10732 281
chris@10732 282 src = (ds->linesize * (src_y + y) + vs->depth * src_x);
chris@10732 283 dst = (ds->linesize * (dst_y + y) + vs->depth * dst_x);
chris@10732 284
chris@10732 285 src_row = ds->data + src;
chris@10732 286 dst_row = ds->data + dst;
chris@10732 287 old_row = vs->old_data + dst;
chris@10732 288
chris@10732 289 for (y = 0; y < h; y++) {
chris@10732 290 memmove(old_row, src_row, w * vs->depth);
chris@10732 291 memmove(dst_row, src_row, w * vs->depth);
chris@10732 292 src_row += pitch;
chris@10732 293 dst_row += pitch;
chris@10732 294 old_row += pitch;
chris@10732 295 }
chris@10732 296
chris@10732 297 if (updating_client && vs->csock != -1 && !vs->has_update) {
chris@10732 298 vnc_write_u8(vs, 0); /* msg id */
chris@10732 299 vnc_write_u8(vs, 0);
chris@10732 300 vnc_write_u16(vs, 1); /* number of rects */
chris@10732 301 vnc_framebuffer_update(vs, dst_x, dst_y, w, h, 1);
chris@10732 302 vnc_write_u16(vs, src_x);
chris@10732 303 vnc_write_u16(vs, src_y);
chris@10732 304 vnc_flush(vs);
chris@10732 305 } else
chris@10732 306 framebuffer_set_updated(vs, dst_x, dst_y, w, h);
chris@10732 307 }
chris@10732 308
chris@10732 309 static int find_update_height(VncState *vs, int y, int maxy, int last_x, int x)
chris@10732 310 {
chris@10732 311 int h;
chris@10732 312
chris@10732 313 for (h = 1; y + h < maxy; h++) {
chris@10732 314 int tmp_x;
chris@10732 315 if (!(vs->update_row[y + h] & (1ULL << last_x)))
chris@10732 316 break;
chris@10732 317 for (tmp_x = last_x; tmp_x < x; tmp_x++)
chris@10732 318 vs->update_row[y + h] &= ~(1ULL << tmp_x);
chris@10732 319 }
chris@10732 320
chris@10732 321 return h;
chris@10732 322 }
chris@10732 323
chris@10732 324 static void _vnc_update_client(void *opaque)
chris@10732 325 {
chris@10732 326 VncState *vs = opaque;
chris@10732 327 int64_t now = qemu_get_clock(rt_clock);
chris@10732 328
chris@10732 329 if (vs->need_update && vs->csock != -1) {
chris@10732 330 int y;
chris@10732 331 char *row;
chris@10732 332 char *old_row;
chris@10732 333 uint64_t width_mask;
chris@10732 334 int n_rectangles;
chris@10732 335 int saved_offset;
chris@10732 336 int maxx, maxy;
chris@10732 337 int tile_bytes = vs->depth * DP2X(vs, 1);
chris@10732 338
chris@10732 339 if (vs->width != DP2X(vs, DIRTY_PIXEL_BITS))
chris@10732 340 width_mask = (1ULL << X2DP_UP(vs, vs->ds->width)) - 1;
chris@10732 341 else
chris@10732 342 width_mask = ~(0ULL);
chris@10732 343
chris@10732 344 /* Walk through the dirty map and eliminate tiles that
chris@10732 345 really aren't dirty */
chris@10732 346 row = vs->ds->data;
chris@10732 347 old_row = vs->old_data;
chris@10732 348
chris@10732 349 for (y = 0; y < vs->ds->height; y++) {
chris@10732 350 if (vs->dirty_row[y] & width_mask) {
chris@10732 351 int x;
chris@10732 352 char *ptr, *old_ptr;
chris@10732 353
chris@10732 354 ptr = row;
chris@10732 355 old_ptr = old_row;
chris@10732 356
chris@10732 357 for (x = 0; x < X2DP_UP(vs, vs->ds->width); x++) {
chris@10732 358 if (vs->dirty_row[y] & (1ULL << x)) {
chris@10732 359 if (memcmp(old_ptr, ptr, tile_bytes)) {
chris@10732 360 vs->has_update = 1;
chris@10732 361 vs->update_row[y] |= (1ULL << x);
chris@10732 362 memcpy(old_ptr, ptr, tile_bytes);
chris@10732 363 }
chris@10732 364 vs->dirty_row[y] &= ~(1ULL << x);
chris@10732 365 }
chris@10732 366
chris@10732 367 ptr += tile_bytes;
chris@10732 368 old_ptr += tile_bytes;
chris@10732 369 }
chris@10732 370 }
chris@10732 371
chris@10732 372 row += vs->ds->linesize;
chris@10732 373 old_row += vs->ds->linesize;
chris@10732 374 }
chris@10732 375
chris@10732 376 if (!vs->has_update || vs->visible_y >= vs->ds->height ||
chris@10732 377 vs->visible_x >= vs->ds->width)
chris@10732 378 goto out;
chris@10732 379
chris@10732 380 /* Count rectangles */
chris@10732 381 n_rectangles = 0;
chris@10732 382 vnc_write_u8(vs, 0); /* msg id */
chris@10732 383 vnc_write_u8(vs, 0);
chris@10732 384 saved_offset = vs->output.offset;
chris@10732 385 vnc_write_u16(vs, 0);
chris@10732 386
chris@10732 387 maxy = vs->visible_y + vs->visible_h;
chris@10732 388 if (maxy > vs->ds->height)
chris@10732 389 maxy = vs->ds->height;
chris@10732 390 maxx = vs->visible_x + vs->visible_w;
chris@10732 391 if (maxx > vs->ds->width)
chris@10732 392 maxx = vs->ds->width;
chris@10732 393
chris@10732 394 for (y = vs->visible_y; y < maxy; y++) {
chris@10732 395 int x;
chris@10732 396 int last_x = -1;
chris@10732 397 for (x = X2DP_DOWN(vs, vs->visible_x);
chris@10732 398 x < X2DP_UP(vs, maxx); x++) {
chris@10732 399 if (vs->update_row[y] & (1ULL << x)) {
chris@10732 400 if (last_x == -1)
chris@10732 401 last_x = x;
chris@10732 402 vs->update_row[y] &= ~(1ULL << x);
chris@10732 403 } else {
chris@10732 404 if (last_x != -1) {
chris@10732 405 int h = find_update_height(vs, y, maxy, last_x, x);
chris@10732 406 send_framebuffer_update(vs, DP2X(vs, last_x), y,
chris@10732 407 DP2X(vs, (x - last_x)), h);
chris@10732 408 n_rectangles++;
chris@10732 409 }
chris@10732 410 last_x = -1;
chris@10732 411 }
chris@10732 412 }
chris@10732 413 if (last_x != -1) {
chris@10732 414 int h = find_update_height(vs, y, maxy, last_x, x);
chris@10732 415 send_framebuffer_update(vs, DP2X(vs, last_x), y,
chris@10732 416 DP2X(vs, (x - last_x)), h);
chris@10732 417 n_rectangles++;
chris@10732 418 }
chris@10732 419 }
chris@10732 420 vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF;
chris@10732 421 vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
chris@10732 422
chris@10732 423 vs->has_update = 0;
chris@10732 424 vs->need_update = 0;
chris@10732 425 vnc_flush(vs);
chris@10732 426 vs->slow_client = 0;
chris@10732 427 } else
chris@10732 428 vs->slow_client = 1;
chris@10732 429
chris@10732 430 out:
chris@10732 431 qemu_mod_timer(vs->timer, now + VNC_REFRESH_INTERVAL);
chris@10732 432 }
chris@10732 433
chris@10732 434 static void vnc_update_client(void *opaque)
chris@10732 435 {
chris@10732 436 VncState *vs = opaque;
chris@10732 437
chris@10732 438 vs->ds->dpy_refresh(vs->ds);
chris@10732 439 _vnc_update_client(vs);
chris@10732 440 }
chris@10732 441
chris@10732 442 static void vnc_timer_init(VncState *vs)
chris@10732 443 {
chris@10732 444 if (vs->timer == NULL) {
chris@10732 445 vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs);
chris@10732 446 qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock));
chris@10732 447 }
chris@10732 448 }
chris@10732 449
chris@10732 450 static void vnc_dpy_refresh(DisplayState *ds)
chris@10732 451 {
chris@10732 452 vga_hw_update();
chris@10732 453 }
chris@10732 454
chris@10732 455 static int vnc_listen_poll(void *opaque)
chris@10732 456 {
chris@10732 457 VncState *vs = opaque;
chris@10732 458 if (vs->csock == -1)
chris@10732 459 return 1;
chris@10732 460 return 0;
chris@10732 461 }
chris@10732 462
chris@10732 463 static void buffer_reserve(Buffer *buffer, size_t len)
chris@10732 464 {
chris@10732 465 if ((buffer->capacity - buffer->offset) < len) {
chris@10732 466 buffer->capacity += (len + 1024);
chris@10732 467 buffer->buffer = realloc(buffer->buffer, buffer->capacity);
chris@10732 468 if (buffer->buffer == NULL) {
chris@10732 469 fprintf(stderr, "vnc: out of memory\n");
chris@10732 470 exit(1);
chris@10732 471 }
chris@10732 472 }
chris@10732 473 }
chris@10732 474
chris@10732 475 static int buffer_empty(Buffer *buffer)
chris@10732 476 {
chris@10732 477 return buffer->offset == 0;
chris@10732 478 }
chris@10732 479
chris@10732 480 static char *buffer_end(Buffer *buffer)
chris@10732 481 {
chris@10732 482 return buffer->buffer + buffer->offset;
chris@10732 483 }
chris@10732 484
chris@10732 485 static void buffer_reset(Buffer *buffer)
chris@10732 486 {
chris@10732 487 buffer->offset = 0;
chris@10732 488 }
chris@10732 489
chris@10732 490 static void buffer_append(Buffer *buffer, const void *data, size_t len)
chris@10732 491 {
chris@10732 492 memcpy(buffer->buffer + buffer->offset, data, len);
chris@10732 493 buffer->offset += len;
chris@10732 494 }
chris@10732 495
chris@10732 496 static int vnc_client_io_error(VncState *vs, int ret, int last_errno)
chris@10732 497 {
chris@10732 498 if (ret == 0 || ret == -1) {
chris@10732 499 if (ret == -1 && (last_errno == EINTR || last_errno == EAGAIN))
chris@10732 500 return 0;
chris@10732 501
chris@10732 502 qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
chris@10732 503 closesocket(vs->csock);
chris@10732 504 vs->csock = -1;
chris@10732 505 buffer_reset(&vs->input);
chris@10732 506 buffer_reset(&vs->output);
chris@10732 507 vs->need_update = 0;
chris@10732 508 return 0;
chris@10732 509 }
chris@10732 510 return ret;
chris@10732 511 }
chris@10732 512
chris@10732 513 static void vnc_client_error(VncState *vs)
chris@10732 514 {
chris@10732 515 vnc_client_io_error(vs, -1, EINVAL);
chris@10732 516 }
chris@10732 517
chris@10732 518 static void vnc_client_write(void *opaque)
chris@10732 519 {
chris@10732 520 ssize_t ret;
chris@10732 521 VncState *vs = opaque;
chris@10732 522
chris@10732 523 ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0);
chris@10732 524 ret = vnc_client_io_error(vs, ret, socket_error());
chris@10732 525 if (!ret)
chris@10732 526 return;
chris@10732 527
chris@10732 528 memmove(vs->output.buffer, vs->output.buffer + ret,
chris@10732 529 vs->output.offset - ret);
chris@10732 530 vs->output.offset -= ret;
chris@10732 531
chris@10732 532 if (vs->output.offset == 0)
chris@10732 533 qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
chris@10732 534 }
chris@10732 535
chris@10732 536 static void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
chris@10732 537 {
chris@10732 538 vs->read_handler = func;
chris@10732 539 vs->read_handler_expect = expecting;
chris@10732 540 }
chris@10732 541
chris@10732 542 static void vnc_client_read(void *opaque)
chris@10732 543 {
chris@10732 544 VncState *vs = opaque;
chris@10732 545 ssize_t ret;
chris@10732 546
chris@10732 547 buffer_reserve(&vs->input, 4096);
chris@10732 548
chris@10732 549 ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0);
chris@10732 550 ret = vnc_client_io_error(vs, ret, socket_error());
chris@10732 551 if (!ret)
chris@10732 552 return;
chris@10732 553
chris@10732 554 vs->input.offset += ret;
chris@10732 555
chris@10732 556 while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
chris@10732 557 size_t len = vs->read_handler_expect;
chris@10732 558 int ret;
chris@10732 559
chris@10732 560 ret = vs->read_handler(vs, vs->input.buffer, len);
chris@10732 561 if (vs->csock == -1)
chris@10732 562 return;
chris@10732 563
chris@10732 564 if (!ret) {
chris@10732 565 memmove(vs->input.buffer, vs->input.buffer + len,
chris@10732 566 vs->input.offset - len);
chris@10732 567 vs->input.offset -= len;
chris@10732 568 } else
chris@10732 569 vs->read_handler_expect = ret;
chris@10732 570 }
chris@10732 571 }
chris@10732 572
chris@10732 573 static void vnc_write(VncState *vs, const void *data, size_t len)
chris@10732 574 {
chris@10732 575 buffer_reserve(&vs->output, len);
chris@10732 576
chris@10732 577 if (buffer_empty(&vs->output))
chris@10732 578 qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read,
chris@10732 579 vnc_client_write, vs);
chris@10732 580
chris@10732 581 buffer_append(&vs->output, data, len);
chris@10732 582 }
chris@10732 583
chris@10732 584 static void vnc_write_s32(VncState *vs, int32_t value)
chris@10732 585 {
chris@10732 586 vnc_write_u32(vs, *(uint32_t *)&value);
chris@10732 587 }
chris@10732 588
chris@10732 589 static void vnc_write_u32(VncState *vs, uint32_t value)
chris@10732 590 {
chris@10732 591 uint8_t buf[4];
chris@10732 592
chris@10732 593 buf[0] = (value >> 24) & 0xFF;
chris@10732 594 buf[1] = (value >> 16) & 0xFF;
chris@10732 595 buf[2] = (value >> 8) & 0xFF;
chris@10732 596 buf[3] = value & 0xFF;
chris@10732 597
chris@10732 598 vnc_write(vs, buf, 4);
chris@10732 599 }
chris@10732 600
chris@10732 601 static void vnc_write_u16(VncState *vs, uint16_t value)
chris@10732 602 {
chris@10732 603 char buf[2];
chris@10732 604
chris@10732 605 buf[0] = (value >> 8) & 0xFF;
chris@10732 606 buf[1] = value & 0xFF;
chris@10732 607
chris@10732 608 vnc_write(vs, buf, 2);
chris@10732 609 }
chris@10732 610
chris@10732 611 static void vnc_write_u8(VncState *vs, uint8_t value)
chris@10732 612 {
chris@10732 613 vnc_write(vs, (char *)&value, 1);
chris@10732 614 }
chris@10732 615
chris@10732 616 static void vnc_flush(VncState *vs)
chris@10732 617 {
chris@10732 618 if (vs->output.offset)
chris@10732 619 vnc_client_write(vs);
chris@10732 620 }
chris@10732 621
chris@10732 622 static uint8_t read_u8(char *data, size_t offset)
chris@10732 623 {
chris@10732 624 return data[offset];
chris@10732 625 }
chris@10732 626
chris@10732 627 static uint16_t read_u16(char *data, size_t offset)
chris@10732 628 {
chris@10732 629 return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
chris@10732 630 }
chris@10732 631
chris@10732 632 static int32_t read_s32(char *data, size_t offset)
chris@10732 633 {
chris@10732 634 return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) |
chris@10732 635 (data[offset + 2] << 8) | data[offset + 3]);
chris@10732 636 }
chris@10732 637
chris@10732 638 static uint32_t read_u32(char *data, size_t offset)
chris@10732 639 {
chris@10732 640 return ((data[offset] << 24) | (data[offset + 1] << 16) |
chris@10732 641 (data[offset + 2] << 8) | data[offset + 3]);
chris@10732 642 }
chris@10732 643
chris@10732 644 static void client_cut_text(VncState *vs, size_t len, char *text)
chris@10732 645 {
chris@10732 646 }
chris@10732 647
chris@10732 648 static void pointer_event(VncState *vs, int button_mask, int x, int y)
chris@10732 649 {
chris@10732 650 int buttons = 0;
chris@10732 651 int dz = 0;
chris@10732 652
chris@10732 653 if (button_mask & 0x01)
chris@10732 654 buttons |= MOUSE_EVENT_LBUTTON;
chris@10732 655 if (button_mask & 0x02)
chris@10732 656 buttons |= MOUSE_EVENT_MBUTTON;
chris@10732 657 if (button_mask & 0x04)
chris@10732 658 buttons |= MOUSE_EVENT_RBUTTON;
chris@10732 659 if (button_mask & 0x08)
chris@10732 660 dz = -1;
chris@10732 661 if (button_mask & 0x10)
chris@10732 662 dz = 1;
chris@10732 663
chris@10732 664 if (kbd_mouse_is_absolute()) {
chris@10732 665 kbd_mouse_event(x * 0x7FFF / vs->ds->width,
chris@10732 666 y * 0x7FFF / vs->ds->height,
chris@10732 667 dz, buttons);
chris@10732 668 } else {
chris@10732 669 static int last_x = -1;
chris@10732 670 static int last_y = -1;
chris@10732 671
chris@10732 672 if (last_x != -1)
chris@10732 673 kbd_mouse_event(x - last_x, y - last_y, dz, buttons);
chris@10732 674
chris@10732 675 last_x = x;
chris@10732 676 last_y = y;
chris@10732 677 }
chris@10732 678 }
chris@10732 679
chris@10732 680 static void do_key_event(VncState *vs, int down, uint32_t sym)
chris@10732 681 {
chris@10732 682 int keycode;
chris@10732 683
chris@10732 684 keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF);
chris@10732 685
chris@10732 686 if (keycode & 0x80)
chris@10732 687 kbd_put_keycode(0xe0);
chris@10732 688 if (down)
chris@10732 689 kbd_put_keycode(keycode & 0x7f);
chris@10732 690 else
chris@10732 691 kbd_put_keycode(keycode | 0x80);
chris@10732 692 }
chris@10732 693
chris@10732 694 static void key_event(VncState *vs, int down, uint32_t sym)
chris@10732 695 {
chris@10732 696 if (sym >= 'A' && sym <= 'Z')
chris@10732 697 sym = sym - 'A' + 'a';
chris@10732 698 do_key_event(vs, down, sym);
chris@10732 699 }
chris@10732 700
chris@10732 701 static void framebuffer_set_updated(VncState *vs, int x, int y, int w, int h)
chris@10732 702 {
chris@10732 703
chris@10732 704 set_bits_in_row(vs, vs->update_row, x, y, w, h);
chris@10732 705
chris@10732 706 vs->has_update = 1;
chris@10732 707 }
chris@10732 708
chris@10732 709 static void framebuffer_update_request(VncState *vs, int incremental,
chris@10732 710 int x_position, int y_position,
chris@10732 711 int w, int h)
chris@10732 712 {
chris@10732 713 vs->need_update = 1;
chris@10732 714 if (!incremental)
chris@10732 715 framebuffer_set_updated(vs, x_position, y_position, w, h);
chris@10732 716 vs->visible_x = x_position;
chris@10732 717 vs->visible_y = y_position;
chris@10732 718 vs->visible_w = w;
chris@10732 719 vs->visible_h = h;
chris@10732 720 }
chris@10732 721
chris@10732 722 static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
chris@10732 723 {
chris@10732 724 int i;
chris@10732 725
chris@10732 726 vs->has_hextile = 0;
chris@10732 727 vs->has_resize = 0;
chris@10732 728 vs->ds->dpy_copy = NULL;
chris@10732 729
chris@10732 730 for (i = n_encodings - 1; i >= 0; i--) {
chris@10732 731 switch (encodings[i]) {
chris@10732 732 case 0: /* Raw */
chris@10732 733 vs->has_hextile = 0;
chris@10732 734 break;
chris@10732 735 case 1: /* CopyRect */
chris@10732 736 vs->ds->dpy_copy = vnc_copy;
chris@10732 737 break;
chris@10732 738 case 5: /* Hextile */
chris@10732 739 vs->has_hextile = 1;
chris@10732 740 break;
chris@10732 741 case -223: /* DesktopResize */
chris@10732 742 vs->has_resize = 1;
chris@10732 743 break;
chris@10732 744 default:
chris@10732 745 break;
chris@10732 746 }
chris@10732 747 }
chris@10732 748 }
chris@10732 749
chris@10732 750 static void set_pixel_format(VncState *vs,
chris@10732 751 int bits_per_pixel, int depth,
chris@10732 752 int big_endian_flag, int true_color_flag,
chris@10732 753 int red_max, int green_max, int blue_max,
chris@10732 754 int red_shift, int green_shift, int blue_shift)
chris@10732 755 {
chris@10732 756 switch (bits_per_pixel) {
chris@10732 757 case 32:
chris@10732 758 case 24:
chris@10732 759 vs->depth = 4;
chris@10732 760 break;
chris@10732 761 case 16:
chris@10732 762 vs->depth = 2;
chris@10732 763 break;
chris@10732 764 case 8:
chris@10732 765 vs->depth = 1;
chris@10732 766 break;
chris@10732 767 default:
chris@10732 768 vnc_client_error(vs);
chris@10732 769 break;
chris@10732 770 }
chris@10732 771
chris@10732 772 if (!true_color_flag)
chris@10732 773 vnc_client_error(vs);
chris@10732 774
chris@10732 775 vnc_dpy_resize(vs->ds, vs->ds->width, vs->ds->height);
chris@10732 776
chris@10732 777 vga_hw_invalidate();
chris@10732 778 vga_hw_update();
chris@10732 779 }
chris@10732 780
chris@10732 781 static int protocol_client_msg(VncState *vs, char *data, size_t len)
chris@10732 782 {
chris@10732 783 int i;
chris@10732 784 uint16_t limit;
chris@10732 785
chris@10732 786 switch (data[0]) {
chris@10732 787 case 0:
chris@10732 788 if (len == 1)
chris@10732 789 return 20;
chris@10732 790
chris@10732 791 set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5),
chris@10732 792 read_u8(data, 6), read_u8(data, 7),
chris@10732 793 read_u16(data, 8), read_u16(data, 10),
chris@10732 794 read_u16(data, 12), read_u8(data, 14),
chris@10732 795 read_u8(data, 15), read_u8(data, 16));
chris@10732 796 break;
chris@10732 797 case 2:
chris@10732 798 if (len == 1)
chris@10732 799 return 4;
chris@10732 800
chris@10732 801 if (len == 4)
chris@10732 802 return 4 + (read_u16(data, 2) * 4);
chris@10732 803
chris@10732 804 limit = read_u16(data, 2);
chris@10732 805 for (i = 0; i < limit; i++) {
chris@10732 806 int32_t val = read_s32(data, 4 + (i * 4));
chris@10732 807 memcpy(data + 4 + (i * 4), &val, sizeof(val));
chris@10732 808 }
chris@10732 809
chris@10732 810 set_encodings(vs, (int32_t *)(data + 4), limit);
chris@10732 811 break;
chris@10732 812 case 3:
chris@10732 813 if (len == 1)
chris@10732 814 return 10;
chris@10732 815
chris@10732 816 framebuffer_update_request(vs,
chris@10732 817 read_u8(data, 1), read_u16(data, 2), read_u16(data, 4),
chris@10732 818 read_u16(data, 6), read_u16(data, 8));
chris@10732 819 break;
chris@10732 820 case 4:
chris@10732 821 if (len == 1)
chris@10732 822 return 8;
chris@10732 823
chris@10732 824 key_event(vs, read_u8(data, 1), read_u32(data, 4));
chris@10732 825 break;
chris@10732 826 case 5:
chris@10732 827 if (len == 1)
chris@10732 828 return 6;
chris@10732 829
chris@10732 830 pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4));
chris@10732 831 break;
chris@10732 832 case 6:
chris@10732 833 if (len == 1)
chris@10732 834 return 8;
chris@10732 835
chris@10732 836 if (len == 8)
chris@10732 837 return 8 + read_u32(data, 4);
chris@10732 838
chris@10732 839 client_cut_text(vs, read_u32(data, 4), data + 8);
chris@10732 840 break;
chris@10732 841 default:
chris@10732 842 printf("Msg: %d\n", data[0]);
chris@10732 843 vnc_client_error(vs);
chris@10732 844 break;
chris@10732 845 }
chris@10732 846
chris@10732 847 vnc_read_when(vs, protocol_client_msg, 1);
chris@10732 848 return 0;
chris@10732 849 }
chris@10732 850
chris@10732 851 static int protocol_client_init(VncState *vs, char *data, size_t len)
chris@10732 852 {
chris@10805 853 size_t l;
chris@10732 854 char pad[3] = { 0, 0, 0 };
chris@10732 855
chris@10732 856 vs->width = vs->ds->width;
chris@10732 857 vs->height = vs->ds->height;
chris@10732 858 vnc_write_u16(vs, vs->ds->width);
chris@10732 859 vnc_write_u16(vs, vs->ds->height);
chris@10732 860
chris@10732 861 vnc_write_u8(vs, vs->depth * 8); /* bits-per-pixel */
chris@10732 862 vnc_write_u8(vs, vs->depth * 8); /* depth */
chris@10732 863 vnc_write_u8(vs, 0); /* big-endian-flag */
chris@10732 864 vnc_write_u8(vs, 1); /* true-color-flag */
chris@10732 865 if (vs->depth == 4) {
chris@10732 866 vnc_write_u16(vs, 0xFF); /* red-max */
chris@10732 867 vnc_write_u16(vs, 0xFF); /* green-max */
chris@10732 868 vnc_write_u16(vs, 0xFF); /* blue-max */
chris@10732 869 vnc_write_u8(vs, 16); /* red-shift */
chris@10732 870 vnc_write_u8(vs, 8); /* green-shift */
chris@10732 871 vnc_write_u8(vs, 0); /* blue-shift */
chris@10732 872 } else if (vs->depth == 2) {
chris@10732 873 vnc_write_u16(vs, 31); /* red-max */
chris@10732 874 vnc_write_u16(vs, 63); /* green-max */
chris@10732 875 vnc_write_u16(vs, 31); /* blue-max */
chris@10732 876 vnc_write_u8(vs, 11); /* red-shift */
chris@10732 877 vnc_write_u8(vs, 5); /* green-shift */
chris@10732 878 vnc_write_u8(vs, 0); /* blue-shift */
chris@10732 879 } else if (vs->depth == 1) {
chris@10732 880 vnc_write_u16(vs, 3); /* red-max */
chris@10732 881 vnc_write_u16(vs, 7); /* green-max */
chris@10732 882 vnc_write_u16(vs, 3); /* blue-max */
chris@10732 883 vnc_write_u8(vs, 5); /* red-shift */
chris@10732 884 vnc_write_u8(vs, 2); /* green-shift */
chris@10732 885 vnc_write_u8(vs, 0); /* blue-shift */
chris@10732 886 }
chris@10732 887
chris@10732 888 vnc_write(vs, pad, 3); /* padding */
chris@10732 889
chris@10805 890 l = strlen(domain_name);
chris@10805 891 vnc_write_u32(vs, l);
chris@10805 892 vnc_write(vs, domain_name, l);
chris@10805 893
chris@10732 894 vnc_flush(vs);
chris@10732 895
chris@10732 896 vnc_read_when(vs, protocol_client_msg, 1);
chris@10732 897
chris@10732 898 return 0;
chris@10732 899 }
chris@10732 900
chris@10732 901 static int protocol_version(VncState *vs, char *version, size_t len)
chris@10732 902 {
chris@10732 903 char local[13];
chris@10732 904 int maj, min;
chris@10732 905
chris@10732 906 memcpy(local, version, 12);
chris@10732 907 local[12] = 0;
chris@10732 908
chris@10732 909 if (sscanf(local, "RFB %03d.%03d\n", &maj, &min) != 2) {
chris@10732 910 vnc_client_error(vs);
chris@10732 911 return 0;
chris@10732 912 }
chris@10732 913
chris@10732 914 vnc_write_u32(vs, 1); /* None */
chris@10732 915 vnc_flush(vs);
chris@10732 916
chris@10732 917 vnc_read_when(vs, protocol_client_init, 1);
chris@10732 918
chris@10732 919 return 0;
chris@10732 920 }
chris@10732 921
chris@10732 922 static void vnc_listen_read(void *opaque)
chris@10732 923 {
chris@10732 924 VncState *vs = opaque;
chris@10732 925 struct sockaddr_in addr;
chris@10732 926 socklen_t addrlen = sizeof(addr);
chris@10732 927
chris@10732 928 vs->csock = accept(vs->lsock, (struct sockaddr *)&addr, &addrlen);
chris@10732 929 if (vs->csock != -1) {
chris@10732 930 socket_set_nonblock(vs->csock);
chris@10732 931 qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque);
chris@10732 932 vnc_write(vs, "RFB 003.003\n", 12);
chris@10732 933 vnc_flush(vs);
chris@10732 934 vnc_read_when(vs, protocol_version, 12);
chris@10732 935 framebuffer_set_updated(vs, 0, 0, vs->ds->width, vs->ds->height);
chris@10732 936 vs->has_resize = 0;
chris@10732 937 vs->has_hextile = 0;
chris@10732 938 vs->ds->dpy_copy = NULL;
chris@10732 939 vnc_timer_init(vs);
chris@10732 940 }
chris@10732 941 }
chris@10732 942
chris@10732 943 void vnc_display_init(DisplayState *ds, int display)
chris@10732 944 {
chris@10732 945 struct sockaddr_in addr;
chris@10732 946 int reuse_addr, ret;
chris@10732 947 VncState *vs;
chris@10732 948
chris@10732 949 vs = qemu_mallocz(sizeof(VncState));
chris@10732 950 if (!vs)
chris@10732 951 exit(1);
chris@10732 952
chris@10732 953 ds->opaque = vs;
chris@10732 954
chris@10732 955 vs->lsock = -1;
chris@10732 956 vs->csock = -1;
chris@10732 957 vs->depth = 4;
chris@10732 958
chris@10732 959 vs->ds = ds;
chris@10732 960
chris@10732 961 if (!keyboard_layout)
chris@10732 962 keyboard_layout = "en-us";
chris@10732 963
chris@10732 964 vs->kbd_layout = init_keyboard_layout(keyboard_layout);
chris@10732 965 if (!vs->kbd_layout)
chris@10732 966 exit(1);
chris@10732 967
chris@10732 968 vs->lsock = socket(PF_INET, SOCK_STREAM, 0);
chris@10732 969 if (vs->lsock == -1) {
chris@10732 970 fprintf(stderr, "Could not create socket\n");
chris@10732 971 exit(1);
chris@10732 972 }
chris@10732 973
chris@10732 974 addr.sin_family = AF_INET;
chris@10732 975 addr.sin_port = htons(5900 + display);
chris@10732 976 memset(&addr.sin_addr, 0, sizeof(addr.sin_addr));
chris@10732 977
chris@10732 978 reuse_addr = 1;
chris@10732 979 ret = setsockopt(vs->lsock, SOL_SOCKET, SO_REUSEADDR,
chris@10732 980 (const char *)&reuse_addr, sizeof(reuse_addr));
chris@10732 981 if (ret == -1) {
chris@10732 982 fprintf(stderr, "setsockopt() failed\n");
chris@10732 983 exit(1);
chris@10732 984 }
chris@10732 985
chris@10732 986 if (bind(vs->lsock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
chris@10732 987 fprintf(stderr, "bind() failed\n");
chris@10732 988 exit(1);
chris@10732 989 }
chris@10732 990
chris@10732 991 if (listen(vs->lsock, 1) == -1) {
chris@10732 992 fprintf(stderr, "listen() failed\n");
chris@10732 993 exit(1);
chris@10732 994 }
chris@10732 995
chris@10732 996 ret = qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read,
chris@10732 997 NULL, vs);
chris@10732 998 if (ret == -1)
chris@10732 999 exit(1);
chris@10732 1000
chris@10732 1001 vs->ds->data = NULL;
chris@10732 1002 vs->ds->dpy_update = vnc_dpy_update;
chris@10732 1003 vs->ds->dpy_resize = vnc_dpy_resize;
chris@10732 1004 vs->ds->dpy_refresh = vnc_dpy_refresh;
chris@10732 1005
chris@10732 1006 vnc_dpy_resize(vs->ds, 640, 400);
chris@10732 1007 }
chris@10770 1008
chris@10770 1009 int vnc_start_viewer(int port)
chris@10770 1010 {
chris@10770 1011 int pid;
chris@10770 1012 char s[16];
chris@10770 1013
chris@10770 1014 sprintf(s, ":%d", port);
chris@10770 1015
chris@10770 1016 switch (pid = fork()) {
chris@10770 1017 case -1:
chris@10770 1018 fprintf(stderr, "vncviewer failed fork\n");
chris@10770 1019 exit(1);
chris@10770 1020
chris@10770 1021 case 0: /* child */
chris@10770 1022 execlp("vncviewer", "vncviewer", s, 0);
chris@10770 1023 fprintf(stderr, "vncviewer execlp failed\n");
chris@10770 1024 exit(1);
chris@10770 1025
chris@10770 1026 default:
chris@10770 1027 return pid;
chris@10770 1028 }
chris@10770 1029 }