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