#include #include #include #include #include #include #include #include #include #include #include "x11.h" #include "vnc.h" #ifdef HAVE_GTK_VNC #include /* ------------------------------------------------------------------ */ enum vnc_state { CONN_NONE = 0, CONN_CONNECTING, CONN_CONNECTED, CONN_DISCONNECTED, }; struct vnc_window { /* gtk */ GtkAccelGroup *ac; GtkActionGroup *ag; GtkUIManager *ui; GtkWidget *win; GtkWidget *vnc; GtkWidget *line, *res, *mbutton, *popup; /* connection */ char display[100]; char hostname[80]; char tcpport[16]; char username[32]; char password[32]; /* state */ int input_grabbed; int width, height; enum vnc_state conn_state; /* config */ int standalone; int disconn_close; int fullscreen; int showpointer; int grab_pointer; int grab_keyboard; int debug; }; /* ------------------------------------------------------------------ */ /* helper functions */ static void vnc_window_texts(struct vnc_window *vnc) { const char *name = vnc_display_get_name(VNC_DISPLAY(vnc->vnc)); char ti[256]; char st[256]; char si[16] = "none"; switch (vnc->conn_state) { case CONN_NONE: snprintf(ti, sizeof(ti), "%s", g_get_application_name()); snprintf(st, sizeof(st), "VNC: idle"); break; case CONN_CONNECTING: snprintf(ti, sizeof(ti), "connecting (%s)", g_get_application_name()); snprintf(st, sizeof(st), "VNC: connecting to %s ...", vnc->display); break; case CONN_CONNECTED: snprintf(ti, sizeof(ti), "%s (%s)", name, g_get_application_name()); snprintf(st, sizeof(st), "VNC: \"%s\" at %s", name ?: "", vnc->display); snprintf(si, sizeof(si), "%dx%d", vnc->width, vnc->height); break; case CONN_DISCONNECTED: snprintf(ti, sizeof(ti), "%s", g_get_application_name()); snprintf(st, sizeof(st), "VNC: disconnected from %s.", vnc->display); break; } if (vnc->input_grabbed) snprintf(st, sizeof(st), "Press Ctrl-Alt to release input grab."); gtk_window_set_title(GTK_WINDOW(vnc->win), ti); gtk_label_set_text(GTK_LABEL(vnc->line), st); gtk_label_set_text(GTK_LABEL(vnc->res), si); } static void vnc_release(struct vnc_window *vnc) { if (NULL == vnc) return; free(vnc); } static void vnc_connect_to(struct vnc_window *vnc, char *hostname, int tcpport) { snprintf(vnc->display, sizeof(vnc->display), "%s:%d", hostname, tcpport - 5900); snprintf(vnc->hostname, sizeof(vnc->hostname),"%s", hostname); snprintf(vnc->tcpport, sizeof(vnc->tcpport),"%d", tcpport); vnc->conn_state = CONN_CONNECTING; vnc_window_texts(vnc); vnc_display_open_host(VNC_DISPLAY(vnc->vnc), vnc->hostname, vnc->tcpport); } static int user_getstring(GtkWidget *window, char *title, char *message, char *dest, int dlen, int hide) { GtkWidget *dialog, *label, *entry; const char *txt; int retval; /* Create the widgets */ dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); label = gtk_label_new(message); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); entry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(entry), dest); gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); if (hide) gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), entry); gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 10); #if 0 /* FIXME: doesn't work ... */ gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 10); #endif /* show and wait for response */ gtk_widget_show_all(dialog); switch (gtk_dialog_run(GTK_DIALOG(dialog))) { case GTK_RESPONSE_ACCEPT: txt = gtk_entry_get_text(GTK_ENTRY(entry)); snprintf(dest, dlen, "%s", txt); retval = 0; break; default: retval = -1; break; } gtk_widget_destroy(dialog); return retval; } /* ------------------------------------------------------------------ */ /* vnc widget callbacks */ static void vnc_connected(GtkWidget *vncdisplay, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); } static void vnc_initialized(GtkWidget *vncdisplay, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); vnc->conn_state = CONN_CONNECTED; vnc_window_texts(vnc); } static void vnc_disconnected(GtkWidget *vncdisplay, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); vnc->conn_state = CONN_DISCONNECTED; vnc_window_texts(vnc); if (vnc->disconn_close) gtk_widget_destroy(vnc->win); } static void vnc_grab(GtkWidget *vncdisplay, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); vnc->input_grabbed = 1; vnc_window_texts(vnc); } static void vnc_ungrab(GtkWidget *vncdisplay, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); vnc->input_grabbed = 0; vnc_window_texts(vnc); } static void vnc_desktop_resize(GtkWidget *vncdisplay, int x, int y, void *data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr, "%s (%dx%d)\n", __FUNCTION__, x, y); vnc->width = x; vnc->height = y; vnc_window_texts(vnc); } static void vnc_credential(GtkWidget *vncdisplay, GValueArray *credList, void *data) { struct vnc_window *vnc = data; char *val, msg[127], str[128]; int i, rc; for (i = 0 ; i < credList->n_values ; i++) { GValue *cred = g_value_array_get_nth(credList, i); switch (g_value_get_enum(cred)) { case VNC_DISPLAY_CREDENTIAL_USERNAME: if (strlen(vnc->username) == 0) { snprintf(msg, sizeof(msg), "Username for %s ?", vnc->display); rc = user_getstring(vnc->win, "Authentication", msg, str, sizeof(str), 0); if (0 != rc) return; snprintf(vnc->username, sizeof(vnc->username), "%s", str); } val = vnc->username; break; case VNC_DISPLAY_CREDENTIAL_PASSWORD: if (strlen(vnc->password) == 0) { snprintf(msg, sizeof(msg), "Password for %s ?", vnc->display); rc = user_getstring(vnc->win, "Authentication", msg, str, sizeof(str), 1); if (0 != rc) return; snprintf(vnc->password, sizeof(vnc->password), "%s", str); } val = vnc->password; break; case VNC_DISPLAY_CREDENTIAL_CLIENTNAME: val = "vnc"; break; default: fprintf(stderr, "can't handle credential type %d\n", g_value_get_enum(cred)); return; } vnc_display_set_credential(VNC_DISPLAY(vnc->vnc), g_value_get_enum(cred), val); } } /* ------------------------------------------------------------------ */ /* glib/gtk callbacks */ static void destroy_cb(GtkWidget *widget, gpointer data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr,"%s: called\n", __FUNCTION__); if (vnc->standalone) gtk_main_quit(); vnc_release(vnc); } static gboolean window_state_cb(GtkWidget *widget, GdkEventWindowState *event, gpointer data) { struct vnc_window *vnc = data; GtkWidget *item; if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { vnc->fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; if (vnc->debug) fprintf(stderr, "%s: fullscreen %s\n", __FUNCTION__, vnc->fullscreen ? "on" : "off"); item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/FullScreen"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->fullscreen); } return TRUE; } static void menu_btn(GtkWidget *widget, gpointer data) { struct vnc_window *vnc = data; gtk_menu_popup(GTK_MENU(vnc->popup), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } /* ------------------------------------------------------------------ */ static void menu_cb_full_screen(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; gboolean state = gtk_toggle_action_get_active(action); vnc->fullscreen = state; if (vnc->debug) fprintf(stderr, "%s: %s\n", __FUNCTION__, state ? "on" : "off"); if (vnc->fullscreen) gtk_window_fullscreen(GTK_WINDOW(vnc->win)); else gtk_window_unfullscreen(GTK_WINDOW(vnc->win)); } static void menu_cb_show_pointer(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; gboolean state = gtk_toggle_action_get_active(action); if (vnc->debug) fprintf(stderr, "%s: %s\n", __FUNCTION__, state ? "on" : "off"); vnc->showpointer = state; vnc_display_set_pointer_local(VNC_DISPLAY(vnc->vnc), state); } static void menu_cb_grab_pointer(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; gboolean state = gtk_toggle_action_get_active(action); if (vnc->debug) fprintf(stderr, "%s: %s\n", __FUNCTION__, state ? "on" : "off"); vnc->grab_pointer = state; vnc_display_set_pointer_grab(VNC_DISPLAY(vnc->vnc), state); } static void menu_cb_grab_keyboard(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; gboolean state = gtk_toggle_action_get_active(action); if (vnc->debug) fprintf(stderr, "%s: %s\n", __FUNCTION__, state ? "on" : "off"); vnc->grab_keyboard = state; vnc_display_set_keyboard_grab(VNC_DISPLAY(vnc->vnc), state); } static void menu_cb_connect(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; char str[128], hostname[65]; int rc, displayno, port; if (vnc->conn_state == CONN_CONNECTED) return; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); snprintf(str, sizeof(str), "%s", vnc->display); rc = user_getstring(vnc->win, "Connecting", "Connect to vnc display ?", str, sizeof(str), 0); if (0 != rc) return; if (2 == sscanf(str, "%64[^:]:%d", hostname, &displayno)) { port = displayno + 5900; goto connect; } if (2 == sscanf(str, "%64[^:]::%d", hostname, &port)) goto connect; return; connect: vnc_connect_to(vnc, hostname, port); } static void menu_cb_reconnect(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; if (vnc->conn_state != CONN_DISCONNECTED) return; if (vnc->debug) fprintf(stderr, "%s: %s %s\n", __FUNCTION__, vnc->hostname, vnc->tcpport); vnc->conn_state = CONN_CONNECTING; vnc_window_texts(vnc); vnc_display_open_host(VNC_DISPLAY(vnc->vnc), vnc->hostname, vnc->tcpport); } static void menu_cb_disconnect(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; if (vnc->conn_state != CONN_CONNECTED) return; if (vnc->debug) fprintf(stderr, "%s\n", __FUNCTION__); vnc_display_close(VNC_DISPLAY(vnc->vnc)); } static void menu_cb_about(GtkMenuItem *item, void *user_data) { static char *comments = "simple vnc client"; static char *copyright = "(c) 2005-2007 Gerd Hoffmann"; static char *authors[] = { "Gerd Hoffmann ", NULL }; struct vnc_window *vnc = user_data; gtk_show_about_dialog(GTK_WINDOW(vnc->win), "authors", authors, "comments", comments, "copyright", copyright, "logo-icon-name", GTK_STOCK_ABOUT, "version", VERSION, NULL); } static void menu_cb_quit(GtkMenuItem *item, void *user_data) { struct vnc_window *vnc = user_data; gtk_widget_destroy(vnc->win); } /* ------------------------------------------------------------------ */ static const GtkActionEntry entries[] = { { /* popup menu */ .name = "ConfMenu", .label = "Config", },{ /* menu items */ .name = "Connect", .stock_id = GTK_STOCK_CONNECT, .label = "Connect ...", .callback = G_CALLBACK(menu_cb_connect), },{ .name = "Reconnect", .label = "Reconnect", .callback = G_CALLBACK(menu_cb_reconnect), },{ .name = "Disconnect", .stock_id = GTK_STOCK_DISCONNECT, .label = "Disconnect", .callback = G_CALLBACK(menu_cb_disconnect), },{ .name = "About", .stock_id = GTK_STOCK_ABOUT, .label = "_About ...", .callback = G_CALLBACK(menu_cb_about), },{ .name = "Close", .stock_id = GTK_STOCK_QUIT, .label = "_Close", .tooltip = "Quit the job", .callback = G_CALLBACK(menu_cb_quit), } }; static const GtkToggleActionEntry tentries[] = { { .name = "FullScreen", .label = "_Fullscreen", .accelerator = "F11", .callback = G_CALLBACK(menu_cb_full_screen), },{ .name = "ShowPointer", .label = "Show _Pointer", .callback = G_CALLBACK(menu_cb_show_pointer), },{ .name = "GrabPointer", .label = "Grab Pointer", .callback = G_CALLBACK(menu_cb_grab_pointer), },{ .name = "GrabKeyboard", .label = "Grab Keyboard", .callback = G_CALLBACK(menu_cb_grab_keyboard), } }; static char ui_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " ""; /* ------------------------------------------------------------------ */ /* public API functions */ GtkWidget *vnc_open(char *hostname, int tcpport, unsigned long flags, int debug_level, const char *username, const char *password) { GtkWidget *vbox, *hbox, *frame, *item; GtkWidget *ebox, *align; GdkColor bg; GError *err; struct vnc_window *vnc; vnc = malloc(sizeof(*vnc)); if (NULL == vnc) goto err; memset(vnc,0,sizeof(*vnc)); vnc->standalone = (flags & VNC_FLAG_STANDALONE); vnc->showpointer = (flags & VNC_FLAG_SHOW_MOUSE); vnc->grab_pointer = (flags & VNC_FLAG_GRAB_MOUSE); vnc->grab_keyboard = (flags & VNC_FLAG_GRAB_KEYBOARD); vnc->disconn_close = (flags & VNC_FLAG_DISCONNECT_CLOSE); vnc->debug = debug_level; if (username) { snprintf(vnc->username, sizeof(vnc->username), "%s", username); } if (password) { snprintf(vnc->password, sizeof(vnc->password), "%s", password); } /* gtk toplevel */ vnc->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(vnc->win), "destroy", G_CALLBACK(destroy_cb), vnc); g_signal_connect(G_OBJECT(vnc->win), "window-state-event", G_CALLBACK(window_state_cb), vnc); gtk_window_set_default_size(GTK_WINDOW(vnc->win), 320, 200); /* vnc display widget */ vnc->vnc = vnc_display_new(); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-connected", GTK_SIGNAL_FUNC(vnc_connected), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-initialized", GTK_SIGNAL_FUNC(vnc_initialized), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-disconnected", GTK_SIGNAL_FUNC(vnc_disconnected), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-pointer-grab", GTK_SIGNAL_FUNC(vnc_grab), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-pointer-ungrab", GTK_SIGNAL_FUNC(vnc_ungrab), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-auth-credential", GTK_SIGNAL_FUNC(vnc_credential), vnc); gtk_signal_connect(GTK_OBJECT(vnc->vnc), "vnc-desktop-resize", GTK_SIGNAL_FUNC(vnc_desktop_resize), vnc); ebox = gtk_event_box_new(); gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), TRUE); gdk_color_parse("darkgray", &bg); gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, &bg); align = gtk_alignment_new(0.5, 0.5, 0,0); /* popup menu */ vnc->ui = gtk_ui_manager_new(); vnc->ag = gtk_action_group_new("MenuActions"); gtk_action_group_add_actions(vnc->ag, entries, G_N_ELEMENTS(entries), vnc); gtk_action_group_add_toggle_actions(vnc->ag, tentries, G_N_ELEMENTS(tentries), vnc); gtk_ui_manager_insert_action_group(vnc->ui, vnc->ag, 0); vnc->ac = gtk_ui_manager_get_accel_group(vnc->ui); gtk_window_add_accel_group(GTK_WINDOW(vnc->win), vnc->ac); err = NULL; if (!gtk_ui_manager_add_ui_from_string(vnc->ui, ui_xml, -1, &err)) { g_message("building menus failed: %s", err->message); g_error_free(err); exit(1); } vnc->popup = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu"); gtk_menu_set_title(GTK_MENU(vnc->popup), "Menu"); /* popup menu: initial state */ item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/ShowPointer"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->showpointer); vnc_display_set_pointer_local(VNC_DISPLAY(vnc->vnc), vnc->showpointer); item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/GrabPointer"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->grab_pointer); vnc_display_set_pointer_grab(VNC_DISPLAY(vnc->vnc), vnc->grab_pointer); item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/GrabKeyboard"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->grab_keyboard); vnc_display_set_keyboard_grab(VNC_DISPLAY(vnc->vnc), vnc->grab_keyboard); /* labels for the status line */ vnc->line = gtk_label_new("status line"); vnc->res = gtk_label_new("vnc screen resolution"); vnc->mbutton = gtk_button_new_with_label("menu"); g_signal_connect(G_OBJECT(vnc->mbutton), "clicked", G_CALLBACK(menu_btn), vnc); GTK_WIDGET_UNSET_FLAGS(vnc->mbutton, GTK_CAN_FOCUS); /* packing */ vbox = gtk_vbox_new(FALSE, 0); hbox = gtk_hbox_new(FALSE, 1); gtk_container_add(GTK_CONTAINER(vnc->win), vbox); gtk_container_add(GTK_CONTAINER(ebox), align); gtk_container_add(GTK_CONTAINER(align), vnc->vnc); gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, TRUE, 0); gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); frame = gtk_frame_new(NULL); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(frame), vnc->line); gtk_misc_set_alignment(GTK_MISC(vnc->line), 0, 0.5); gtk_misc_set_padding(GTK_MISC(vnc->line), 3, 1); frame = gtk_frame_new(NULL); gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(frame), vnc->res); gtk_misc_set_padding(GTK_MISC(vnc->res), 3, 1); gtk_box_pack_start(GTK_BOX(hbox), vnc->mbutton, FALSE, TRUE, 0); /* show window */ gtk_widget_show_all(vnc->win); vnc_window_texts(vnc); if (flags & VNC_FLAG_FULLSCREEN) gtk_window_fullscreen(GTK_WINDOW(vnc->win)); /* connect */ if (hostname) vnc_connect_to(vnc, hostname, tcpport); return vnc->win; err: vnc_release(vnc); return NULL; } #else /* HAVE_GTK_VNC */ GtkWidget *vnc_open(char *hostname, int tcpport, unsigned long flags, int debug_level, const char *username, const char *password) { fprintf(stderr, "compiled without VNC support, sorry\n"); return NULL; } #endif /* HAVE_GTK_VNC */