1 2/* 3 * Copyright (C) 2007 - Mateus Cesar Groess 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21#include <stdlib.h> 22#include <gtk/gtk.h> 23#include <gdk/gdkkeysyms.h> 24#include <rfb/rfbclient.h> 25 26static rfbClient *cl; 27static gchar *server_cut_text = NULL; 28static gboolean framebuffer_allocated = FALSE; 29 30/* Redraw the screen from the backing pixmap */ 31static gboolean expose_event (GtkWidget *widget, 32 GdkEventExpose *event) 33{ 34 static GdkImage *image = NULL; 35 36 if (framebuffer_allocated == FALSE) { 37 38 rfbClientSetClientData (cl, gtk_init, widget); 39 40 image = gdk_drawable_get_image (widget->window, 0, 0, 41 widget->allocation.width, 42 widget->allocation.height); 43 44 cl->frameBuffer= image->mem; 45 46 cl->width = widget->allocation.width; 47 cl->height = widget->allocation.height; 48 49 cl->format.bitsPerPixel = image->bits_per_pixel; 50 cl->format.redShift = image->visual->red_shift; 51 cl->format.greenShift = image->visual->green_shift; 52 cl->format.blueShift = image->visual->blue_shift; 53 54 cl->format.redMax = (1 << image->visual->red_prec) - 1; 55 cl->format.greenMax = (1 << image->visual->green_prec) - 1; 56 cl->format.blueMax = (1 << image->visual->blue_prec) - 1; 57 58 SetFormatAndEncodings (cl); 59 60 framebuffer_allocated = TRUE; 61 } 62 63 gdk_draw_image (GDK_DRAWABLE (widget->window), 64 widget->style->fg_gc[gtk_widget_get_state(widget)], 65 image, 66 event->area.x, event->area.y, 67 event->area.x, event->area.y, 68 event->area.width, event->area.height); 69 70 return FALSE; 71} 72 73struct { int gdk; int rfb; } buttonMapping[] = { 74 { GDK_BUTTON1_MASK, rfbButton1Mask }, 75 { GDK_BUTTON2_MASK, rfbButton2Mask }, 76 { GDK_BUTTON3_MASK, rfbButton3Mask }, 77 { 0, 0 } 78}; 79 80static gboolean button_event (GtkWidget *widget, 81 GdkEventButton *event) 82{ 83 int x, y; 84 GdkModifierType state; 85 int i, buttonMask; 86 87 gdk_window_get_pointer (event->window, &x, &y, &state); 88 89 for (buttonMask = 0, i = 0; buttonMapping[i].gdk; i++) 90 if (state & buttonMapping[i].gdk) 91 buttonMask |= buttonMapping[i].rfb; 92 SendPointerEvent (cl, x, y, buttonMask); 93 94 return TRUE; 95} 96 97static gboolean motion_notify_event (GtkWidget *widget, 98 GdkEventMotion *event) 99{ 100 int x, y; 101 GdkModifierType state; 102 int i, buttonMask; 103 104 if (event->is_hint) 105 gdk_window_get_pointer (event->window, &x, &y, &state); 106 else { 107 x = event->x; 108 y = event->y; 109 state = event->state; 110 } 111 112 for (buttonMask = 0, i = 0; buttonMapping[i].gdk; i++) 113 if (state & buttonMapping[i].gdk) 114 buttonMask |= buttonMapping[i].rfb; 115 SendPointerEvent (cl, x, y, buttonMask); 116 117 return TRUE; 118} 119 120static void got_cut_text (rfbClient *cl, const char *text, int textlen) 121{ 122 if (server_cut_text != NULL) { 123 g_free (server_cut_text); 124 server_cut_text = NULL; 125 } 126 127 server_cut_text = g_strdup (text); 128} 129 130void received_text_from_clipboard (GtkClipboard *clipboard, 131 const gchar *text, 132 gpointer data) 133{ 134 if (text) 135 SendClientCutText (cl, (char *) text, strlen (text)); 136} 137 138static void clipboard_local_to_remote (GtkMenuItem *menuitem, 139 gpointer user_data) 140{ 141 GtkClipboard *clipboard; 142 143 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (menuitem), 144 GDK_SELECTION_CLIPBOARD); 145 gtk_clipboard_request_text (clipboard, received_text_from_clipboard, 146 NULL); 147} 148 149static void clipboard_remote_to_local (GtkMenuItem *menuitem, 150 gpointer user_data) 151{ 152 GtkClipboard *clipboard; 153 154 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (menuitem), 155 GDK_SELECTION_CLIPBOARD); 156 157 gtk_clipboard_set_text (clipboard, server_cut_text, 158 strlen (server_cut_text)); 159} 160 161static void request_screen_refresh (GtkMenuItem *menuitem, 162 gpointer user_data) 163{ 164 SendFramebufferUpdateRequest (cl, 0, 0, cl->width, cl->height, FALSE); 165} 166 167static void send_f8 (GtkMenuItem *menuitem, 168 gpointer user_data) 169{ 170 SendKeyEvent(cl, XK_F8, TRUE); 171 SendKeyEvent(cl, XK_F8, FALSE); 172} 173 174static void send_crtl_alt_del (GtkMenuItem *menuitem, 175 gpointer user_data) 176{ 177 SendKeyEvent(cl, XK_Control_L, TRUE); 178 SendKeyEvent(cl, XK_Alt_L, TRUE); 179 SendKeyEvent(cl, XK_Delete, TRUE); 180 SendKeyEvent(cl, XK_Alt_L, FALSE); 181 SendKeyEvent(cl, XK_Control_L, FALSE); 182 SendKeyEvent(cl, XK_Delete, FALSE); 183} 184 185GtkWidget *dialog_connecting = NULL; 186 187static void show_connect_window(int argc, char **argv) 188{ 189 GtkWidget *label; 190 char buf[256]; 191 192 dialog_connecting = gtk_dialog_new_with_buttons ("VNC Viewer", 193 NULL, 194 GTK_DIALOG_DESTROY_WITH_PARENT, 195 /*GTK_STOCK_CANCEL, 196 GTK_RESPONSE_CANCEL,*/ 197 NULL); 198 199 /* FIXME: this works only when address[:port] is at end of arg list */ 200 char *server; 201 if(argc==1) 202 server = "localhost"; 203 else 204 server = argv[argc-1]; 205 snprintf(buf, 255, "Connecting to %s...", server); 206 207 label = gtk_label_new (buf); 208 gtk_widget_show (label); 209 210 gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog_connecting)->vbox), 211 label); 212 213 gtk_widget_show (dialog_connecting); 214 215 while (gtk_events_pending ()) 216 gtk_main_iteration (); 217} 218 219static void show_popup_menu() 220{ 221 GtkWidget *popup_menu; 222 GtkWidget *menu_item; 223 224 popup_menu = gtk_menu_new (); 225 226 menu_item = gtk_menu_item_new_with_label ("Dismiss popup"); 227 gtk_widget_show (menu_item); 228 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 229 230 menu_item = gtk_menu_item_new_with_label ("Clipboard: local -> remote"); 231 g_signal_connect (G_OBJECT (menu_item), "activate", 232 G_CALLBACK (clipboard_local_to_remote), NULL); 233 gtk_widget_show (menu_item); 234 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 235 236 menu_item = gtk_menu_item_new_with_label ("Clipboard: local <- remote"); 237 g_signal_connect (G_OBJECT (menu_item), "activate", 238 G_CALLBACK (clipboard_remote_to_local), NULL); 239 gtk_widget_show (menu_item); 240 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 241 242 menu_item = gtk_menu_item_new_with_label ("Request refresh"); 243 g_signal_connect (G_OBJECT (menu_item), "activate", 244 G_CALLBACK (request_screen_refresh), NULL); 245 gtk_widget_show (menu_item); 246 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 247 248 menu_item = gtk_menu_item_new_with_label ("Send ctrl-alt-del"); 249 g_signal_connect (G_OBJECT (menu_item), "activate", 250 G_CALLBACK (send_crtl_alt_del), NULL); 251 gtk_widget_show (menu_item); 252 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 253 254 menu_item = gtk_menu_item_new_with_label ("Send F8"); 255 g_signal_connect (G_OBJECT (menu_item), "activate", 256 G_CALLBACK (send_f8), NULL); 257 gtk_widget_show (menu_item); 258 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); 259 260 gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, NULL, NULL, 0, 261 gtk_get_current_event_time()); 262} 263 264static rfbKeySym gdkKey2rfbKeySym(guint keyval) 265{ 266 rfbKeySym k = 0; 267 switch(keyval) { 268 case GDK_BackSpace: k = XK_BackSpace; break; 269 case GDK_Tab: k = XK_Tab; break; 270 case GDK_Clear: k = XK_Clear; break; 271 case GDK_Return: k = XK_Return; break; 272 case GDK_Pause: k = XK_Pause; break; 273 case GDK_Escape: k = XK_Escape; break; 274 case GDK_space: k = XK_space; break; 275 case GDK_Delete: k = XK_Delete; break; 276 case GDK_KP_0: k = XK_KP_0; break; 277 case GDK_KP_1: k = XK_KP_1; break; 278 case GDK_KP_2: k = XK_KP_2; break; 279 case GDK_KP_3: k = XK_KP_3; break; 280 case GDK_KP_4: k = XK_KP_4; break; 281 case GDK_KP_5: k = XK_KP_5; break; 282 case GDK_KP_6: k = XK_KP_6; break; 283 case GDK_KP_7: k = XK_KP_7; break; 284 case GDK_KP_8: k = XK_KP_8; break; 285 case GDK_KP_9: k = XK_KP_9; break; 286 case GDK_KP_Decimal: k = XK_KP_Decimal; break; 287 case GDK_KP_Divide: k = XK_KP_Divide; break; 288 case GDK_KP_Multiply: k = XK_KP_Multiply; break; 289 case GDK_KP_Subtract: k = XK_KP_Subtract; break; 290 case GDK_KP_Add: k = XK_KP_Add; break; 291 case GDK_KP_Enter: k = XK_KP_Enter; break; 292 case GDK_KP_Equal: k = XK_KP_Equal; break; 293 case GDK_Up: k = XK_Up; break; 294 case GDK_Down: k = XK_Down; break; 295 case GDK_Right: k = XK_Right; break; 296 case GDK_Left: k = XK_Left; break; 297 case GDK_Insert: k = XK_Insert; break; 298 case GDK_Home: k = XK_Home; break; 299 case GDK_End: k = XK_End; break; 300 case GDK_Page_Up: k = XK_Page_Up; break; 301 case GDK_Page_Down: k = XK_Page_Down; break; 302 case GDK_F1: k = XK_F1; break; 303 case GDK_F2: k = XK_F2; break; 304 case GDK_F3: k = XK_F3; break; 305 case GDK_F4: k = XK_F4; break; 306 case GDK_F5: k = XK_F5; break; 307 case GDK_F6: k = XK_F6; break; 308 case GDK_F7: k = XK_F7; break; 309 case GDK_F8: k = XK_F8; break; 310 case GDK_F9: k = XK_F9; break; 311 case GDK_F10: k = XK_F10; break; 312 case GDK_F11: k = XK_F11; break; 313 case GDK_F12: k = XK_F12; break; 314 case GDK_F13: k = XK_F13; break; 315 case GDK_F14: k = XK_F14; break; 316 case GDK_F15: k = XK_F15; break; 317 case GDK_Num_Lock: k = XK_Num_Lock; break; 318 case GDK_Caps_Lock: k = XK_Caps_Lock; break; 319 case GDK_Scroll_Lock: k = XK_Scroll_Lock; break; 320 case GDK_Shift_R: k = XK_Shift_R; break; 321 case GDK_Shift_L: k = XK_Shift_L; break; 322 case GDK_Control_R: k = XK_Control_R; break; 323 case GDK_Control_L: k = XK_Control_L; break; 324 case GDK_Alt_R: k = XK_Alt_R; break; 325 case GDK_Alt_L: k = XK_Alt_L; break; 326 case GDK_Meta_R: k = XK_Meta_R; break; 327 case GDK_Meta_L: k = XK_Meta_L; break; 328#if 0 329 /* TODO: find out keysyms */ 330 case GDK_Super_L: k = XK_LSuper; break; /* left "windows" key */ 331 case GDK_Super_R: k = XK_RSuper; break; /* right "windows" key */ 332 case GDK_Multi_key: k = XK_Compose; break; 333#endif 334 case GDK_Mode_switch: k = XK_Mode_switch; break; 335 case GDK_Help: k = XK_Help; break; 336 case GDK_Print: k = XK_Print; break; 337 case GDK_Sys_Req: k = XK_Sys_Req; break; 338 case GDK_Break: k = XK_Break; break; 339 default: break; 340 } 341 if (k == 0) { 342 if (keyval < 0x100) 343 k = keyval; 344 else 345 rfbClientLog ("Unknown keysym: %d\n", keyval); 346 } 347 348 return k; 349} 350 351static gboolean key_event (GtkWidget *widget, GdkEventKey *event, 352 gpointer user_data) 353{ 354 if ((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_F8)) 355 show_popup_menu(); 356 else 357 SendKeyEvent(cl, gdkKey2rfbKeySym (event->keyval), 358 (event->type == GDK_KEY_PRESS) ? TRUE : FALSE); 359 return FALSE; 360} 361 362void quit () 363{ 364 exit (0); 365} 366 367static rfbBool resize (rfbClient *client) { 368 GtkWidget *window; 369 GtkWidget *scrolled_window; 370 GtkWidget *drawing_area=NULL; 371 static char first=TRUE; 372 int tmp_width, tmp_height; 373 374 if (first) { 375 first=FALSE; 376 377 /* Create the drawing area */ 378 379 drawing_area = gtk_drawing_area_new (); 380 gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 381 client->width, client->height); 382 383 /* Signals used to handle backing pixmap */ 384 385 g_signal_connect (G_OBJECT (drawing_area), "expose_event", 386 G_CALLBACK (expose_event), NULL); 387 388 /* Event signals */ 389 390 g_signal_connect (G_OBJECT (drawing_area), 391 "motion-notify-event", 392 G_CALLBACK (motion_notify_event), NULL); 393 g_signal_connect (G_OBJECT (drawing_area), 394 "button-press-event", 395 G_CALLBACK (button_event), NULL); 396 g_signal_connect (G_OBJECT (drawing_area), 397 "button-release-event", 398 G_CALLBACK (button_event), NULL); 399 400 gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK 401 | GDK_LEAVE_NOTIFY_MASK 402 | GDK_BUTTON_PRESS_MASK 403 | GDK_BUTTON_RELEASE_MASK 404 | GDK_POINTER_MOTION_MASK 405 | GDK_POINTER_MOTION_HINT_MASK); 406 407 gtk_widget_show (drawing_area); 408 409 scrolled_window = gtk_scrolled_window_new (NULL, NULL); 410 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), 411 GTK_POLICY_AUTOMATIC, 412 GTK_POLICY_AUTOMATIC); 413 gtk_scrolled_window_add_with_viewport ( 414 GTK_SCROLLED_WINDOW (scrolled_window), 415 drawing_area); 416 g_signal_connect (G_OBJECT (scrolled_window), 417 "key-press-event", G_CALLBACK (key_event), 418 NULL); 419 g_signal_connect (G_OBJECT (scrolled_window), 420 "key-release-event", G_CALLBACK (key_event), 421 NULL); 422 gtk_widget_show (scrolled_window); 423 424 window = gtk_window_new (GTK_WINDOW_TOPLEVEL); 425 gtk_window_set_title (GTK_WINDOW (window), client->desktopName); 426 gtk_container_add (GTK_CONTAINER (window), scrolled_window); 427 tmp_width = (int) ( 428 gdk_screen_get_width (gdk_screen_get_default ()) 429 * 0.85); 430 if (client->width > tmp_width) { 431 tmp_height = (int) ( 432 gdk_screen_get_height ( 433 gdk_screen_get_default ()) 434 * 0.85); 435 gtk_widget_set_size_request (window, 436 tmp_width, tmp_height); 437 } else { 438 gtk_widget_set_size_request (window, 439 client->width + 2, 440 client->height + 2); 441 } 442 443 g_signal_connect (G_OBJECT (window), "destroy", 444 G_CALLBACK (quit), NULL); 445 446 gtk_widget_show (window); 447 } else { 448 gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 449 client->width, client->height); 450 } 451 452 return TRUE; 453} 454 455static void update (rfbClient *cl, int x, int y, int w, int h) { 456 GtkWidget *drawing_area = rfbClientGetClientData (cl, gtk_init); 457 458 if (drawing_area != NULL) 459 gtk_widget_queue_draw_area (drawing_area, x, y, w, h); 460} 461 462static void kbd_leds (rfbClient *cl, int value, int pad) { 463 /* note: pad is for future expansion 0=unused */ 464 fprintf (stderr, "Led State= 0x%02X\n", value); 465 fflush (stderr); 466} 467 468/* trivial support for textchat */ 469static void text_chat (rfbClient *cl, int value, char *text) { 470 switch (value) { 471 case rfbTextChatOpen: 472 fprintf (stderr, "TextChat: We should open a textchat window!\n"); 473 TextChatOpen (cl); 474 break; 475 case rfbTextChatClose: 476 fprintf (stderr, "TextChat: We should close our window!\n"); 477 break; 478 case rfbTextChatFinished: 479 fprintf (stderr, "TextChat: We should close our window!\n"); 480 break; 481 default: 482 fprintf (stderr, "TextChat: Received \"%s\"\n", text); 483 break; 484 } 485 fflush (stderr); 486} 487 488static gboolean on_entry_key_press_event (GtkWidget *widget, GdkEventKey *event, 489 gpointer user_data) 490{ 491 if (event->keyval == GDK_Escape) 492 gtk_dialog_response (GTK_DIALOG(user_data), GTK_RESPONSE_REJECT); 493 else if (event->keyval == GDK_Return) 494 gtk_dialog_response (GTK_DIALOG(user_data), GTK_RESPONSE_ACCEPT); 495 496 return FALSE; 497} 498 499static void GtkErrorLog (const char *format, ...) 500{ 501 GtkWidget *dialog, *label; 502 va_list args; 503 char buf[256]; 504 505 if (dialog_connecting != NULL) { 506 gtk_widget_destroy (dialog_connecting); 507 dialog_connecting = NULL; 508 } 509 510 va_start (args, format); 511 vsnprintf (buf, 255, format, args); 512 va_end (args); 513 514 if (g_utf8_validate (buf, strlen (buf), NULL)) { 515 label = gtk_label_new (buf); 516 } else { 517 const gchar *charset; 518 gchar *utf8; 519 520 (void) g_get_charset (&charset); 521 utf8 = g_convert_with_fallback (buf, strlen (buf), "UTF-8", 522 charset, NULL, NULL, NULL, NULL); 523 524 if (utf8) { 525 label = gtk_label_new (utf8); 526 g_free (utf8); 527 } else { 528 label = gtk_label_new (buf); 529 g_warning ("Message Output is not in UTF-8" 530 "nor in locale charset.\n"); 531 } 532 } 533 534 dialog = gtk_dialog_new_with_buttons ("Error", 535 NULL, 536 GTK_DIALOG_DESTROY_WITH_PARENT, 537 GTK_STOCK_OK, 538 GTK_RESPONSE_ACCEPT, 539 NULL); 540 label = gtk_label_new (buf); 541 gtk_widget_show (label); 542 543 gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 544 label); 545 gtk_widget_show (dialog); 546 547 switch (gtk_dialog_run (GTK_DIALOG (dialog))) { 548 case GTK_RESPONSE_ACCEPT: 549 break; 550 default: 551 break; 552 } 553 gtk_widget_destroy (dialog); 554} 555 556static void GtkDefaultLog (const char *format, ...) 557{ 558 va_list args; 559 char buf[256]; 560 time_t log_clock; 561 562 va_start (args, format); 563 564 time (&log_clock); 565 strftime (buf, 255, "%d/%m/%Y %X ", localtime (&log_clock)); 566 fprintf (stdout, buf); 567 568 vfprintf (stdout, format, args); 569 fflush (stdout); 570 571 va_end (args); 572} 573 574static char * get_password (rfbClient *client) 575{ 576 GtkWidget *dialog, *entry; 577 char *password; 578 579 gtk_widget_destroy (dialog_connecting); 580 dialog_connecting = NULL; 581 582 dialog = gtk_dialog_new_with_buttons ("Password", 583 NULL, 584 GTK_DIALOG_DESTROY_WITH_PARENT, 585 GTK_STOCK_CANCEL, 586 GTK_RESPONSE_REJECT, 587 GTK_STOCK_OK, 588 GTK_RESPONSE_ACCEPT, 589 NULL); 590 entry = gtk_entry_new (); 591 gtk_entry_set_visibility (GTK_ENTRY (entry), 592 FALSE); 593 g_signal_connect (GTK_OBJECT(entry), "key-press-event", 594 G_CALLBACK(on_entry_key_press_event), 595 GTK_OBJECT (dialog)); 596 gtk_widget_show (entry); 597 598 gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 599 entry); 600 gtk_widget_show (dialog); 601 602 switch (gtk_dialog_run (GTK_DIALOG (dialog))) { 603 case GTK_RESPONSE_ACCEPT: 604 password = strdup (gtk_entry_get_text (GTK_ENTRY (entry))); 605 break; 606 default: 607 password = NULL; 608 break; 609 } 610 gtk_widget_destroy (dialog); 611 return password; 612} 613 614int main (int argc, char *argv[]) 615{ 616 int i; 617 GdkImage *image; 618 619 rfbClientLog = GtkDefaultLog; 620 rfbClientErr = GtkErrorLog; 621 622 gtk_init (&argc, &argv); 623 624 /* create a dummy image just to make use of its properties */ 625 image = gdk_image_new (GDK_IMAGE_FASTEST, gdk_visual_get_system(), 626 200, 100); 627 628 cl = rfbGetClient (image->depth / 3, 3, image->bpp); 629 630 cl->format.redShift = image->visual->red_shift; 631 cl->format.greenShift = image->visual->green_shift; 632 cl->format.blueShift = image->visual->blue_shift; 633 634 cl->format.redMax = (1 << image->visual->red_prec) - 1; 635 cl->format.greenMax = (1 << image->visual->green_prec) - 1; 636 cl->format.blueMax = (1 << image->visual->blue_prec) - 1; 637 638 g_object_unref (image); 639 640 cl->MallocFrameBuffer = resize; 641 cl->canHandleNewFBSize = TRUE; 642 cl->GotFrameBufferUpdate = update; 643 cl->GotXCutText = got_cut_text; 644 cl->HandleKeyboardLedState = kbd_leds; 645 cl->HandleTextChat = text_chat; 646 cl->GetPassword = get_password; 647 648 show_connect_window (argc, argv); 649 650 if (!rfbInitClient (cl, &argc, argv)) 651 return 1; 652 653 while (1) { 654 while (gtk_events_pending ()) 655 gtk_main_iteration (); 656 i = WaitForMessage (cl, 500); 657 if (i < 0) 658 return 0; 659 if (i && framebuffer_allocated == TRUE) 660 if (!HandleRFBServerMessage(cl)) 661 return 0; 662 } 663 664 gtk_main (); 665 666 return 0; 667} 668 669