#include #include #include #include #include #include #include #include #include #include #include #include #include #include "x11.h" #include "vnc.h" #ifdef HAVE_VNCCLIENT #include static int debug_libvnc; /* ------------------------------------------------------------------ */ struct vnc_window { /* vnc connection */ char display[128]; rfbClient *client; GIOChannel *ch; guint id; /* gtk */ GtkWidget *win; GtkWidget *draw; GtkWidget *line, *res, *kbd, *ptr, *view; GdkCursor *on,*off; int filter_installed; int input_grabbed; /* x11 */ XImage *ximage; void *shm; GC gc; Display *dpy; unsigned char keydown[32]; /* config */ int viewonly; int standalone; int showpointer; int uskbd; int debug; }; /* ------------------------------------------------------------------ */ /* data tables */ rfbKeySym linux_uskbd[][2] = { #include "linux-uskbd.h" }; static int linux_uskbd_size = sizeof(linux_uskbd)/sizeof(linux_uskbd[0]); /* ------------------------------------------------------------------ */ /* prototypes */ static GdkFilterReturn event_filter(GdkXEvent *gdkxevent, GdkEvent *gtkevent, gpointer data); /* ------------------------------------------------------------------ */ /* helper functions */ static void XAddInput(Display *dpy, Window win, long mask) { XWindowAttributes attr; XGetWindowAttributes(dpy, win, &attr); XSelectInput(dpy, win, attr.your_event_mask | mask); } static GdkCursor* empty_cursor(void) { static char bits[32]; GdkCursor *cursor; GdkPixmap *pixmap; GdkColor fg = { 0, 0, 0, 0 }; GdkColor bg = { 0, 0, 0, 0 }; pixmap = gdk_bitmap_create_from_data(NULL, bits, 16, 16); cursor = gdk_cursor_new_from_pixmap(pixmap, pixmap, &fg, &bg, 0, 0); gdk_pixmap_unref(pixmap); return cursor; } static void vnc_blit(struct vnc_window *vnc, char *reason, int x, int y, int w, int h) { Window win = gdk_x11_drawable_get_xid(vnc->draw->window); XGCValues values; if (vnc->debug) fprintf(stderr, "%s [%s]: %dx%d+%d+%d\n", __FUNCTION__, reason, w, h, x, y); if (x > vnc->ximage->width) return; if (y > vnc->ximage->height) return; if (x+w > vnc->ximage->width) w = vnc->ximage->width - x; if (y+h > vnc->ximage->height) h = vnc->ximage->height - y; if (!vnc->gc) vnc->gc = XCreateGC(vnc->dpy, win, 0, &values); XPUTIMAGE(vnc->dpy, win, vnc->gc, vnc->ximage, x,y, x,y, w,h); } static void vnc_window_texts(struct vnc_window *vnc) { char textline[256]; if (vnc->client->desktopName && strlen(vnc->client->desktopName)) gtk_window_set_title(GTK_WINDOW(vnc->win), vnc->client->desktopName); else gtk_window_set_title(GTK_WINDOW(vnc->win), "Untitled VNC session"); if (vnc->input_grabbed) { gtk_label_set_text(GTK_LABEL(vnc->line), "Press Ctrl-Alt to release input grab."); } else { snprintf(textline, sizeof(textline), "VNC connection to display %s, desktop \"%s\"", vnc->display, vnc->client->desktopName ?: ""); gtk_label_set_text(GTK_LABEL(vnc->line), textline); } snprintf(textline, sizeof(textline), "%dx%d", vnc->client->width, vnc->client->height); gtk_label_set_text(GTK_LABEL(vnc->res), textline); gtk_button_set_label(GTK_BUTTON(vnc->kbd), vnc->uskbd ? "kbd: us" : "kbd: local"); gtk_button_set_label(GTK_BUTTON(vnc->ptr), vnc->showpointer ? "ptr: visible" : "ptr: hidden"); gtk_button_set_label(GTK_BUTTON(vnc->view), vnc->viewonly ? "viewonly" : "input ok"); } static void vnc_window_conf(struct vnc_window *vnc) { if (!vnc->draw) return; vnc_window_texts(vnc); gtk_widget_set_size_request(vnc->draw, vnc->client->width, vnc->client->height); if (vnc->draw->window) { gdk_window_set_cursor(vnc->draw->window, vnc->showpointer ? vnc->on : vnc->off); XAddInput(vnc->dpy, gdk_x11_drawable_get_xid(vnc->draw->window), KeymapStateMask); } } static void vnc_release(struct vnc_window *vnc) { if (NULL == vnc) return; #if 0 /* FIXME: segfaults ??? */ if (vnc->client) rfbClientCleanup(vnc->client); #endif if (vnc->filter_installed) gdk_window_remove_filter(vnc->draw->window, event_filter, vnc); if (vnc->id) g_source_destroy(g_main_context_find_source_by_id (g_main_context_default(), vnc->id)); if (vnc->ximage) x11_destroy_ximage(vnc->dpy, vnc->ximage, vnc->shm); if (vnc->gc) XFreeGC(vnc->dpy, vnc->gc); free(vnc); } static void grab_input(struct vnc_window *vnc, guint32 time) { if (vnc->viewonly) return; if (vnc->input_grabbed) return; gdk_pointer_grab(vnc->draw->window, FALSE, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, vnc->draw->window, NULL, time); gdk_keyboard_grab(vnc->draw->window, FALSE, time); vnc->input_grabbed = 1; vnc_window_texts(vnc); } static void ungrab_input(struct vnc_window *vnc, guint32 time) { if (!vnc->input_grabbed) return; gdk_pointer_ungrab(time); gdk_keyboard_ungrab(time); vnc->input_grabbed = 0; vnc_window_texts(vnc); } /* ------------------------------------------------------------------ */ /* libvncclient callbacks */ static rfbBool vnc_resize(rfbClient* client) { struct vnc_window *vnc = rfbClientGetClientData(client, vnc_open); if (vnc->debug) fprintf(stderr, "%s: %dx%d\n", __FUNCTION__, client->width, client->height); if (vnc->ximage) { if (vnc->ximage->width == client->width && vnc->ximage->height == client->height) { if (vnc->debug) fprintf(stderr, "%s: no size change, early exit\n", __FUNCTION__); return TRUE; } x11_destroy_ximage(vnc->dpy, vnc->ximage, vnc->shm); vnc->ximage = NULL; } vnc->ximage = x11_create_ximage(vnc->dpy, client->width, client->height, &vnc->shm); if (NULL == vnc->ximage) { fprintf(stderr, "Oops: creating ximage failed\n"); goto out; } client->width = vnc->ximage->bytes_per_line / (vnc->ximage->bits_per_pixel / 8); client->frameBuffer = (void*)vnc->ximage->data; client->format.bitsPerPixel = vnc->ximage->bits_per_pixel; client->format.redShift = x11_red_shift; client->format.greenShift = x11_green_shift; client->format.blueShift = x11_blue_shift; client->format.redMax = (1 << x11_red_bits) - 1; client->format.greenMax = (1 << x11_green_bits) - 1; client->format.blueMax = (1 << x11_blue_bits) - 1; SetFormatAndEncodings(client); vnc_window_conf(vnc); out: return TRUE; } static void vnc_update(rfbClient* cl, int x, int y, int w, int h) { struct vnc_window *vnc = rfbClientGetClientData(cl, vnc_open); if (!GTK_WIDGET_DRAWABLE(vnc->draw)) return; vnc_blit(vnc, "update", x,y, w,h); } #ifdef HAVE_VNC_TEXT static void vnc_textchat(rfbClient* cl, int value, char *text) { switch(value) { case rfbTextChatOpen: fprintf(stderr,"%s: Open\n", __FUNCTION__); break; case rfbTextChatClose: case rfbTextChatFinished: fprintf(stderr,"%s: Close/Finished\n", __FUNCTION__); break; default: fprintf(stderr,"%s: \"%s\"\n", __FUNCTION__, text); break; } } #endif static char *vnc_passwd(rfbClient* cl) { struct vnc_window *vnc = rfbClientGetClientData(cl, vnc_open); GtkWidget *dialog, *label, *entry; char *passwd = NULL; const char *txt; char message[256]; if (vnc->debug) fprintf(stderr,"%s: called\n", __FUNCTION__); /* Create the widgets */ dialog = gtk_dialog_new_with_buttons("Password needed", vnc->win ? GTK_WINDOW(vnc->win) : NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); snprintf(message, sizeof(message), "Please enter vnc screen password for \"%s\".", vnc->client->desktopName ? : "unknown"); label = gtk_label_new(message); entry = gtk_entry_new(); gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 10); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), entry); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); /* 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)); passwd = strdup(txt); if (vnc->debug) fprintf(stderr,"%s: OK: \"%s\"\n", __FUNCTION__, passwd); break; default: if (vnc->debug) fprintf(stderr,"%s: canceled\n", __FUNCTION__); passwd = strdup(""); break; } gtk_widget_destroy(dialog); return passwd; } static void vnc_log(const char *format, ...) { va_list args; if (!debug_libvnc) return; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } /* ------------------------------------------------------------------ */ /* glib/gtk callbacks */ static gboolean vnc_data_cb(GIOChannel *source, GIOCondition condition, gpointer data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr,"%s: called\n", __FUNCTION__); if (!HandleRFBServerMessage(vnc->client)) { /* server closed connection */ g_source_destroy(g_main_context_find_source_by_id (g_main_context_default(), vnc->id)); vnc->id = 0; gtk_widget_destroy(vnc->win); } return TRUE; } 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 expose_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data) { struct vnc_window *vnc = data; if (NULL == vnc->ximage) return FALSE; #if 0 vnc_blit(vnc, "expose", event->area.x, event->area.y, event->area.width, event->area.height); #else SendFramebufferUpdateRequest(vnc->client, event->area.x, event->area.y, event->area.width, event->area.height, FALSE); #endif return TRUE; } static void send_mouse(struct vnc_window *vnc, int x, int y, int x11state, int x11press, int x11release) { static const struct { int rfbmask; int x11mask; int x11nr; } buttons[] = { { .rfbmask = rfbButton1Mask, .x11mask = Button1Mask, .x11nr = Button1, },{ .rfbmask = rfbButton2Mask, .x11mask = Button2Mask, .x11nr = Button2, },{ .rfbmask = rfbButton3Mask, .x11mask = Button3Mask, .x11nr = Button3, },{ .rfbmask = rfbButton4Mask, .x11mask = Button4Mask, .x11nr = Button4, },{ .rfbmask = rfbButton5Mask, .x11mask = Button5Mask, .x11nr = Button5, } }; int i, rfbstate = 0; if (vnc->viewonly) return; for (i = 0; i < sizeof(buttons)/sizeof(buttons[0]); i++) { if (x11state & buttons[i].x11mask) rfbstate |= buttons[i].rfbmask; if (x11press == buttons[i].x11nr) rfbstate |= buttons[i].rfbmask; if (x11release == buttons[i].x11nr) rfbstate &= ~buttons[i].rfbmask; } if (vnc->debug) fprintf(stderr,"%s: +%d+%d x11state 0x%x rfbstate 0x%x\n", __FUNCTION__, x, y, x11state, rfbstate); SendPointerEvent(vnc->client, x, y, rfbstate); } static gboolean button_cb(GtkWidget *widget, GdkEventButton *event, gpointer data) { struct vnc_window *vnc = data; switch (event->type) { case GDK_BUTTON_PRESS: send_mouse(vnc, event->x, event->y, event->state, event->button, 0); grab_input(vnc, event->time); break; case GDK_BUTTON_RELEASE: send_mouse(vnc, event->x, event->y, event->state, 0, event->button); break; default: /* keep gcc happy */ break; } return TRUE; } static gboolean motion_cb(GtkWidget *widget, GdkEventMotion *event, gpointer data) { struct vnc_window *vnc = data; send_mouse(vnc, event->x, event->y, event->state, 0, 0); return TRUE; } static void key_local(struct vnc_window *vnc, GdkEventKey *event) { int keydown; if (vnc->debug) fprintf(stderr,"%s[%d]: called: keysym %d\n", __FUNCTION__, event->type, event->keyval); keydown = (8 == event->type); if (!vnc->viewonly) SendKeyEvent(vnc->client, event->keyval, keydown ? TRUE : FALSE); } static void key_uskbd(struct vnc_window *vnc, GdkEventKey *event) { rfbKeySym keysym = 0; int shift,keydown; keydown = (8 == event->type); if (event->hardware_keycode < 256) { int by = event->hardware_keycode / 8; int bi = event->hardware_keycode % 8; if (keydown) vnc->keydown[by] |= (1 << bi); else vnc->keydown[by] &= ~(1 << bi); } shift = (event->state & GDK_SHIFT_MASK) ? 1 : 0; if (event->hardware_keycode < linux_uskbd_size) { keysym = linux_uskbd[event->hardware_keycode][shift]; if (0 == keysym) keysym = linux_uskbd[event->hardware_keycode][0]; } if (vnc->debug || 0 == keysym) fprintf(stderr,"%s[%d]: called: keycode %d => keysym %d\n", __FUNCTION__, event->type, event->hardware_keycode, keysym); if (keysym && !vnc->viewonly) SendKeyEvent(vnc->client, keysym, keydown ? TRUE : FALSE); } static gboolean key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) { struct vnc_window *vnc = data; int mask = GDK_CONTROL_MASK | GDK_MOD1_MASK; if (mask == (event->state & mask)) ungrab_input(vnc, event->time); if (vnc->uskbd) key_uskbd(vnc, event); else key_local(vnc, event); return TRUE; } static void kbd_btn(GtkWidget *widget, gpointer data) { struct vnc_window *vnc = data; vnc->uskbd = !vnc->uskbd; vnc_window_texts(vnc); } static void ptr_btn(GtkWidget *widget, gpointer data) { struct vnc_window *vnc = data; vnc->showpointer = !vnc->showpointer; if (vnc->draw->window) gdk_window_set_cursor(vnc->draw->window, vnc->showpointer ? vnc->on : vnc->off); vnc_window_texts(vnc); } static void view_btn(GtkWidget *widget, gpointer data) { struct vnc_window *vnc = data; vnc->viewonly = !vnc->viewonly; vnc_window_texts(vnc); } static GdkFilterReturn event_filter(GdkXEvent *gdkxevent, GdkEvent *gtkevent, gpointer data) { struct vnc_window *vnc = data; XEvent *xevent = gdkxevent; int by, bi, keydown, keycode; rfbKeySym keysym = 0; switch (xevent->type) { case KeymapNotify: if (!vnc->uskbd) return GDK_FILTER_REMOVE; for (by = 0; by < 32; by++) { if (vnc->keydown[by] == xevent->xkeymap.key_vector[by]) continue; for (bi = 0; bi < 8; bi++) { if ((vnc->keydown[by] & (1 << bi)) == (xevent->xkeymap.key_vector[by] & (1 << bi))) continue; keydown = xevent->xkeymap.key_vector[by] & (1 << bi); keycode = by * 8 + bi; keysym = linux_uskbd[keycode][0]; if (!keysym) continue; if (vnc->debug) fprintf(stderr,"%s: KeymapNotify: %-7s %3d\n", __FUNCTION__, keydown ? "press" : "release", keycode); if (!vnc->viewonly) SendKeyEvent(vnc->client, keysym, keydown ? TRUE : FALSE); } } memcpy(vnc->keydown, xevent->xkeymap.key_vector, 32); return GDK_FILTER_REMOVE; default: return GDK_FILTER_CONTINUE; } } /* ------------------------------------------------------------------ */ /* public API functions */ GtkWidget *vnc_open(char *hostname, int tcpport, unsigned long flags, int debug_level) { GtkWidget *vbox, *hbox, *frame; char *argv[] = { "vnc-client", NULL, NULL }; int argc = sizeof(argv)/sizeof(argv[0]) -1; struct vnc_window *vnc; int rc; 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->uskbd = (flags & VNC_FLAG_US_KBD); vnc->viewonly = (flags & VNC_FLAG_VIEW_ONLY); vnc->debug = debug_level; debug_libvnc = debug_level; /* x11 */ vnc->dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); if (NULL == x11_info) if (0 != x11_color_init(vnc->dpy)) goto err; /* rfb client */ snprintf(vnc->display, sizeof(vnc->display), "%s:%d", hostname, tcpport - 5900); argv[1] = vnc->display; fprintf(stderr, "%s: connecting to %s\n", __FUNCTION__, vnc->display); if (8 == x11_red_bits) vnc->client = rfbGetClient(8,3,4); else vnc->client = rfbGetClient(5,3,2); if (NULL == vnc->client) goto err; rfbClientSetClientData(vnc->client, vnc_open, vnc); vnc->client->MallocFrameBuffer = vnc_resize; vnc->client->GotFrameBufferUpdate = vnc_update; vnc->client->GetPassword = vnc_passwd; #ifdef HAVE_VNC_TEXT vnc->client->canHandleNewFBSize = TRUE; /* was added before textchat */ vnc->client->HandleTextChat = vnc_textchat; #endif rfbClientLog = vnc_log; rc = rfbInitClient(vnc->client, &argc, argv); if (0 == rc) goto err; vnc->ch = g_io_channel_unix_new(vnc->client->sock); vnc->id = g_io_add_watch(vnc->ch, G_IO_IN, vnc_data_cb, vnc); /* gtk toplevel */ vnc->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(vnc->win), "destroy", G_CALLBACK(destroy_cb), vnc); vnc->on = gdk_cursor_new(GDK_LEFT_PTR); vnc->off = empty_cursor(); /* gtk drawing area */ vnc->draw = gtk_drawing_area_new(); GTK_WIDGET_SET_FLAGS(vnc->draw, GTK_CAN_FOCUS); gtk_widget_add_events(vnc->draw, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_EXPOSURE_MASK); gtk_widget_set_app_paintable(vnc->draw, TRUE); gtk_widget_set_double_buffered(vnc->draw, FALSE); g_signal_connect(G_OBJECT(vnc->draw), "expose-event", G_CALLBACK(expose_cb), vnc); g_signal_connect(G_OBJECT(vnc->draw), "button-press-event", G_CALLBACK(button_cb), vnc); g_signal_connect(G_OBJECT(vnc->draw), "button-release-event", G_CALLBACK(button_cb), vnc); g_signal_connect(G_OBJECT(vnc->draw), "motion-notify-event", G_CALLBACK(motion_cb), vnc); g_signal_connect(G_OBJECT(vnc->draw), "key-press-event", G_CALLBACK(key_cb), vnc); g_signal_connect(G_OBJECT(vnc->draw), "key-release-event", G_CALLBACK(key_cb), vnc); gdk_window_add_filter(NULL, event_filter, vnc); vnc->filter_installed = 1; /* labels for the status line */ vnc->line = gtk_label_new("status line"); vnc->res = gtk_label_new("vnc screen resolution"); vnc->kbd = gtk_button_new_with_label("keyboard mode"); vnc->ptr = gtk_button_new_with_label("mouse mode"); vnc->view = gtk_button_new_with_label("input mode"); g_signal_connect(G_OBJECT(vnc->kbd), "clicked", G_CALLBACK(kbd_btn), vnc); g_signal_connect(G_OBJECT(vnc->ptr), "clicked", G_CALLBACK(ptr_btn), vnc); g_signal_connect(G_OBJECT(vnc->view), "clicked", G_CALLBACK(view_btn), vnc); GTK_WIDGET_UNSET_FLAGS(vnc->kbd, GTK_CAN_FOCUS); GTK_WIDGET_UNSET_FLAGS(vnc->ptr, GTK_CAN_FOCUS); GTK_WIDGET_UNSET_FLAGS(vnc->view, 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_box_pack_start(GTK_BOX(vbox), vnc->draw, 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->kbd, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), vnc->ptr, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), vnc->view, FALSE, TRUE, 0); /* show window */ gtk_widget_show_all(vnc->win); vnc_window_conf(vnc); return vnc->win; err: vnc_release(vnc); return NULL; } #else /* HAVE_VNCCLIENT */ GtkWidget *vnc_open(char *hostname, int tcpport, unsigned long flags, int debug_level) { fprintf(stderr, "compiled without VNC support, sorry\n"); return NULL; } #endif /* HAVE_VNCCLIENT */