/* * Copyright (C) 2007 - Mateus Cesar Groess * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #include <stdlib.h> #include <gtk/gtk.h> #include <gdk/gdkkeysyms.h> #include <rfb/rfbclient.h> static rfbClient *cl; static gchar *server_cut_text = NULL; static gboolean framebuffer_allocated = FALSE; /* Redraw the screen from the backing pixmap */ static gboolean expose_event (GtkWidget *widget, GdkEventExpose *event) { static GdkImage *image = NULL; if (framebuffer_allocated == FALSE) { rfbClientSetClientData (cl, gtk_init, widget); image = gdk_drawable_get_image (widget->window, 0, 0, widget->allocation.width, widget->allocation.height); cl->frameBuffer= image->mem; cl->width = widget->allocation.width; cl->height = widget->allocation.height; cl->format.bitsPerPixel = image->bits_per_pixel; cl->format.redShift = image->visual->red_shift; cl->format.greenShift = image->visual->green_shift; cl->format.blueShift = image->visual->blue_shift; cl->format.redMax = (1 << image->visual->red_prec) - 1; cl->format.greenMax = (1 << image->visual->green_prec) - 1; cl->format.blueMax = (1 << image->visual->blue_prec) - 1; SetFormatAndEncodings (cl); framebuffer_allocated = TRUE; } gdk_draw_image (GDK_DRAWABLE (widget->window), widget->style->fg_gc[gtk_widget_get_state(widget)], image, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } struct { int gdk; int rfb; } buttonMapping[] = { { GDK_BUTTON1_MASK, rfbButton1Mask }, { GDK_BUTTON2_MASK, rfbButton2Mask }, { GDK_BUTTON3_MASK, rfbButton3Mask }, { 0, 0 } }; static gboolean button_event (GtkWidget *widget, GdkEventButton *event) { int x, y; GdkModifierType state; int i, buttonMask; gdk_window_get_pointer (event->window, &x, &y, &state); for (buttonMask = 0, i = 0; buttonMapping[i].gdk; i++) if (state & buttonMapping[i].gdk) buttonMask |= buttonMapping[i].rfb; SendPointerEvent (cl, x, y, buttonMask); return TRUE; } static gboolean motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; int i, buttonMask; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = event->x; y = event->y; state = event->state; } for (buttonMask = 0, i = 0; buttonMapping[i].gdk; i++) if (state & buttonMapping[i].gdk) buttonMask |= buttonMapping[i].rfb; SendPointerEvent (cl, x, y, buttonMask); return TRUE; } static void got_cut_text (rfbClient *cl, const char *text, int textlen) { if (server_cut_text != NULL) { g_free (server_cut_text); server_cut_text = NULL; } server_cut_text = g_strdup (text); } void received_text_from_clipboard (GtkClipboard *clipboard, const gchar *text, gpointer data) { if (text) SendClientCutText (cl, (char *) text, strlen (text)); } static void clipboard_local_to_remote (GtkMenuItem *menuitem, gpointer user_data) { GtkClipboard *clipboard; clipboard = gtk_widget_get_clipboard (GTK_WIDGET (menuitem), GDK_SELECTION_CLIPBOARD); gtk_clipboard_request_text (clipboard, received_text_from_clipboard, NULL); } static void clipboard_remote_to_local (GtkMenuItem *menuitem, gpointer user_data) { GtkClipboard *clipboard; clipboard = gtk_widget_get_clipboard (GTK_WIDGET (menuitem), GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clipboard, server_cut_text, strlen (server_cut_text)); } static void request_screen_refresh (GtkMenuItem *menuitem, gpointer user_data) { SendFramebufferUpdateRequest (cl, 0, 0, cl->width, cl->height, FALSE); } static void send_f8 (GtkMenuItem *menuitem, gpointer user_data) { SendKeyEvent(cl, XK_F8, TRUE); SendKeyEvent(cl, XK_F8, FALSE); } static void send_crtl_alt_del (GtkMenuItem *menuitem, gpointer user_data) { SendKeyEvent(cl, XK_Control_L, TRUE); SendKeyEvent(cl, XK_Alt_L, TRUE); SendKeyEvent(cl, XK_Delete, TRUE); SendKeyEvent(cl, XK_Alt_L, FALSE); SendKeyEvent(cl, XK_Control_L, FALSE); SendKeyEvent(cl, XK_Delete, FALSE); } GtkWidget *dialog_connecting = NULL; static void show_connect_window(int argc, char **argv) { GtkWidget *label; char buf[256]; dialog_connecting = gtk_dialog_new_with_buttons ("VNC Viewer", NULL, GTK_DIALOG_DESTROY_WITH_PARENT, /*GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,*/ NULL); /* FIXME: this works only when address[:port] is at end of arg list */ char *server; if(argc==1) server = "localhost"; else server = argv[argc-1]; snprintf(buf, 255, "Connecting to %s...", server); label = gtk_label_new (buf); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog_connecting)->vbox), label); gtk_widget_show (dialog_connecting); while (gtk_events_pending ()) gtk_main_iteration (); } static void show_popup_menu() { GtkWidget *popup_menu; GtkWidget *menu_item; popup_menu = gtk_menu_new (); menu_item = gtk_menu_item_new_with_label ("Dismiss popup"); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); menu_item = gtk_menu_item_new_with_label ("Clipboard: local -> remote"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (clipboard_local_to_remote), NULL); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); menu_item = gtk_menu_item_new_with_label ("Clipboard: local <- remote"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (clipboard_remote_to_local), NULL); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); menu_item = gtk_menu_item_new_with_label ("Request refresh"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (request_screen_refresh), NULL); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); menu_item = gtk_menu_item_new_with_label ("Send ctrl-alt-del"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (send_crtl_alt_del), NULL); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); menu_item = gtk_menu_item_new_with_label ("Send F8"); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (send_f8), NULL); gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } static rfbKeySym gdkKey2rfbKeySym(guint keyval) { rfbKeySym k = 0; switch(keyval) { case GDK_BackSpace: k = XK_BackSpace; break; case GDK_Tab: k = XK_Tab; break; case GDK_Clear: k = XK_Clear; break; case GDK_Return: k = XK_Return; break; case GDK_Pause: k = XK_Pause; break; case GDK_Escape: k = XK_Escape; break; case GDK_space: k = XK_space; break; case GDK_Delete: k = XK_Delete; break; case GDK_KP_0: k = XK_KP_0; break; case GDK_KP_1: k = XK_KP_1; break; case GDK_KP_2: k = XK_KP_2; break; case GDK_KP_3: k = XK_KP_3; break; case GDK_KP_4: k = XK_KP_4; break; case GDK_KP_5: k = XK_KP_5; break; case GDK_KP_6: k = XK_KP_6; break; case GDK_KP_7: k = XK_KP_7; break; case GDK_KP_8: k = XK_KP_8; break; case GDK_KP_9: k = XK_KP_9; break; case GDK_KP_Decimal: k = XK_KP_Decimal; break; case GDK_KP_Divide: k = XK_KP_Divide; break; case GDK_KP_Multiply: k = XK_KP_Multiply; break; case GDK_KP_Subtract: k = XK_KP_Subtract; break; case GDK_KP_Add: k = XK_KP_Add; break; case GDK_KP_Enter: k = XK_KP_Enter; break; case GDK_KP_Equal: k = XK_KP_Equal; break; case GDK_Up: k = XK_Up; break; case GDK_Down: k = XK_Down; break; case GDK_Right: k = XK_Right; break; case GDK_Left: k = XK_Left; break; case GDK_Insert: k = XK_Insert; break; case GDK_Home: k = XK_Home; break; case GDK_End: k = XK_End; break; case GDK_Page_Up: k = XK_Page_Up; break; case GDK_Page_Down: k = XK_Page_Down; break; case GDK_F1: k = XK_F1; break; case GDK_F2: k = XK_F2; break; case GDK_F3: k = XK_F3; break; case GDK_F4: k = XK_F4; break; case GDK_F5: k = XK_F5; break; case GDK_F6: k = XK_F6; break; case GDK_F7: k = XK_F7; break; case GDK_F8: k = XK_F8; break; case GDK_F9: k = XK_F9; break; case GDK_F10: k = XK_F10; break; case GDK_F11: k = XK_F11; break; case GDK_F12: k = XK_F12; break; case GDK_F13: k = XK_F13; break; case GDK_F14: k = XK_F14; break; case GDK_F15: k = XK_F15; break; case GDK_Num_Lock: k = XK_Num_Lock; break; case GDK_Caps_Lock: k = XK_Caps_Lock; break; case GDK_Scroll_Lock: k = XK_Scroll_Lock; break; case GDK_Shift_R: k = XK_Shift_R; break; case GDK_Shift_L: k = XK_Shift_L; break; case GDK_Control_R: k = XK_Control_R; break; case GDK_Control_L: k = XK_Control_L; break; case GDK_Alt_R: k = XK_Alt_R; break; case GDK_Alt_L: k = XK_Alt_L; break; case GDK_Meta_R: k = XK_Meta_R; break; case GDK_Meta_L: k = XK_Meta_L; break; #if 0 /* TODO: find out keysyms */ case GDK_Super_L: k = XK_LSuper; break; /* left "windows" key */ case GDK_Super_R: k = XK_RSuper; break; /* right "windows" key */ case GDK_Multi_key: k = XK_Compose; break; #endif case GDK_Mode_switch: k = XK_Mode_switch; break; case GDK_Help: k = XK_Help; break; case GDK_Print: k = XK_Print; break; case GDK_Sys_Req: k = XK_Sys_Req; break; case GDK_Break: k = XK_Break; break; default: break; } if (k == 0) { if (keyval < 0x100) k = keyval; else rfbClientLog ("Unknown keysym: %d\n", keyval); } return k; } static gboolean key_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { if ((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_F8)) show_popup_menu(); else SendKeyEvent(cl, gdkKey2rfbKeySym (event->keyval), (event->type == GDK_KEY_PRESS) ? TRUE : FALSE); return FALSE; } void quit () { exit (0); } static rfbBool resize (rfbClient *client) { GtkWidget *window; GtkWidget *scrolled_window; GtkWidget *drawing_area=NULL; static char first=TRUE; int tmp_width, tmp_height; if (first) { first=FALSE; /* Create the drawing area */ drawing_area = gtk_drawing_area_new (); gtk_widget_set_size_request (GTK_WIDGET (drawing_area), client->width, client->height); /* Signals used to handle backing pixmap */ g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL); /* Event signals */ g_signal_connect (G_OBJECT (drawing_area), "motion-notify-event", G_CALLBACK (motion_notify_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "button-press-event", G_CALLBACK (button_event), NULL); g_signal_connect (G_OBJECT (drawing_area), "button-release-event", G_CALLBACK (button_event), NULL); gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); gtk_widget_show (drawing_area); scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport ( GTK_SCROLLED_WINDOW (scrolled_window), drawing_area); g_signal_connect (G_OBJECT (scrolled_window), "key-press-event", G_CALLBACK (key_event), NULL); g_signal_connect (G_OBJECT (scrolled_window), "key-release-event", G_CALLBACK (key_event), NULL); gtk_widget_show (scrolled_window); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), client->desktopName); gtk_container_add (GTK_CONTAINER (window), scrolled_window); tmp_width = (int) ( gdk_screen_get_width (gdk_screen_get_default ()) * 0.85); if (client->width > tmp_width) { tmp_height = (int) ( gdk_screen_get_height ( gdk_screen_get_default ()) * 0.85); gtk_widget_set_size_request (window, tmp_width, tmp_height); } else { gtk_widget_set_size_request (window, client->width + 2, client->height + 2); } g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (quit), NULL); gtk_widget_show (window); } else { gtk_widget_set_size_request (GTK_WIDGET (drawing_area), client->width, client->height); } return TRUE; } static void update (rfbClient *cl, int x, int y, int w, int h) { GtkWidget *drawing_area = rfbClientGetClientData (cl, gtk_init); if (drawing_area != NULL) gtk_widget_queue_draw_area (drawing_area, x, y, w, h); } static void kbd_leds (rfbClient *cl, int value, int pad) { /* note: pad is for future expansion 0=unused */ fprintf (stderr, "Led State= 0x%02X\n", value); fflush (stderr); } /* trivial support for textchat */ static void text_chat (rfbClient *cl, int value, char *text) { switch (value) { case rfbTextChatOpen: fprintf (stderr, "TextChat: We should open a textchat window!\n"); TextChatOpen (cl); break; case rfbTextChatClose: fprintf (stderr, "TextChat: We should close our window!\n"); break; case rfbTextChatFinished: fprintf (stderr, "TextChat: We should close our window!\n"); break; default: fprintf (stderr, "TextChat: Received \"%s\"\n", text); break; } fflush (stderr); } static gboolean on_entry_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { if (event->keyval == GDK_Escape) gtk_dialog_response (GTK_DIALOG(user_data), GTK_RESPONSE_REJECT); else if (event->keyval == GDK_Return) gtk_dialog_response (GTK_DIALOG(user_data), GTK_RESPONSE_ACCEPT); return FALSE; } static void GtkErrorLog (const char *format, ...) { GtkWidget *dialog, *label; va_list args; char buf[256]; if (dialog_connecting != NULL) { gtk_widget_destroy (dialog_connecting); dialog_connecting = NULL; } va_start (args, format); vsnprintf (buf, 255, format, args); va_end (args); if (g_utf8_validate (buf, strlen (buf), NULL)) { label = gtk_label_new (buf); } else { const gchar *charset; gchar *utf8; (void) g_get_charset (&charset); utf8 = g_convert_with_fallback (buf, strlen (buf), "UTF-8", charset, NULL, NULL, NULL, NULL); if (utf8) { label = gtk_label_new (utf8); g_free (utf8); } else { label = gtk_label_new (buf); g_warning ("Message Output is not in UTF-8" "nor in locale charset.\n"); } } dialog = gtk_dialog_new_with_buttons ("Error", NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); label = gtk_label_new (buf); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label); gtk_widget_show (dialog); switch (gtk_dialog_run (GTK_DIALOG (dialog))) { case GTK_RESPONSE_ACCEPT: break; default: break; } gtk_widget_destroy (dialog); } static void GtkDefaultLog (const char *format, ...) { va_list args; char buf[256]; time_t log_clock; va_start (args, format); time (&log_clock); strftime (buf, 255, "%d/%m/%Y %X ", localtime (&log_clock)); fprintf (stdout, buf); vfprintf (stdout, format, args); fflush (stdout); va_end (args); } static char * get_password (rfbClient *client) { GtkWidget *dialog, *entry; char *password; gtk_widget_destroy (dialog_connecting); dialog_connecting = NULL; dialog = gtk_dialog_new_with_buttons ("Password", NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); entry = gtk_entry_new (); gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); g_signal_connect (GTK_OBJECT(entry), "key-press-event", G_CALLBACK(on_entry_key_press_event), GTK_OBJECT (dialog)); gtk_widget_show (entry); gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), entry); gtk_widget_show (dialog); switch (gtk_dialog_run (GTK_DIALOG (dialog))) { case GTK_RESPONSE_ACCEPT: password = strdup (gtk_entry_get_text (GTK_ENTRY (entry))); break; default: password = NULL; break; } gtk_widget_destroy (dialog); return password; } int main (int argc, char *argv[]) { int i; GdkImage *image; rfbClientLog = GtkDefaultLog; rfbClientErr = GtkErrorLog; gtk_init (&argc, &argv); /* create a dummy image just to make use of its properties */ image = gdk_image_new (GDK_IMAGE_FASTEST, gdk_visual_get_system(), 200, 100); cl = rfbGetClient (image->depth / 3, 3, image->bpp); cl->format.redShift = image->visual->red_shift; cl->format.greenShift = image->visual->green_shift; cl->format.blueShift = image->visual->blue_shift; cl->format.redMax = (1 << image->visual->red_prec) - 1; cl->format.greenMax = (1 << image->visual->green_prec) - 1; cl->format.blueMax = (1 << image->visual->blue_prec) - 1; g_object_unref (image); cl->MallocFrameBuffer = resize; cl->canHandleNewFBSize = TRUE; cl->GotFrameBufferUpdate = update; cl->GotXCutText = got_cut_text; cl->HandleKeyboardLedState = kbd_leds; cl->HandleTextChat = text_chat; cl->GetPassword = get_password; show_connect_window (argc, argv); if (!rfbInitClient (cl, &argc, argv)) return 1; while (1) { while (gtk_events_pending ()) gtk_main_iteration (); i = WaitForMessage (cl, 500); if (i < 0) return 0; if (i && framebuffer_allocated == TRUE) if (!HandleRFBServerMessage(cl)) return 0; } gtk_main (); return 0; }