#include #include #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 pos { int x; int y; }; struct rect { int w; int h; }; struct vnc_window { /* vnc connection */ char display[128]; rfbClient *client; GIOChannel *ch; guint id, connected; /* gtk */ GtkAccelGroup *ac; GtkActionGroup *ag; GtkUIManager *ui; GtkWidget *win; GtkWidget *draw; GtkWidget *line, *res, *mbutton, *popup; GdkCursor *on,*off; int filter_installed; int input_grabbed; /* x11 */ XImage *ximage; void *shm; GC gc; Display *dpy; unsigned char keydown[32]; /* opengl */ int have_gl; GLuint tex; int tex_max; unsigned char *tex_data; unsigned int dirty_y1, dirty_y2; /* window / vnc display config */ struct rect window; struct pos vncoff; struct rect vncdpy; struct rect texture; int updates, redraw; /* config */ int fullscreen; int viewonly; int standalone; int showpointer; int gl_allways; int gl_fullscreen; 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); static void vnc_window_conf(struct vnc_window *vnc); static void vnc_window_texts(struct vnc_window *vnc); /* ------------------------------------------------------------------ */ /* opengl bits */ static int gl_error; static int gl_attrib[] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER, None }; static int catch_gl_error(Display * dpy, XErrorEvent * event) { fprintf(stderr,"WARNING: Your OpenGL setup is broken.\n"); gl_error++; return 0; } static int gl_init(struct vnc_window *vnc) { Window win = gdk_x11_drawable_get_xid(vnc->draw->window); Screen *scr = DefaultScreenOfDisplay(vnc->dpy); void *old_handler; XVisualInfo *visinfo; GLXContext ctx; if (vnc->debug) fprintf(stderr, "gl: init [window=0x%lx]\n", win); if (!win) return -1; visinfo = glXChooseVisual(vnc->dpy, XScreenNumberOfScreen(scr), gl_attrib); if (!visinfo) { if (vnc->debug) fprintf(stderr,"gl: can't get visual (rgb,db)\n"); return -1; } ctx = glXCreateContext(vnc->dpy, visinfo, NULL, True); if (!ctx) { if (vnc->debug) fprintf(stderr,"gl: can't create context\n"); return -1; } /* there is no point in using OpenGL for image scaling if it * isn't hardware accelerated ... */ if (vnc->debug) fprintf(stderr, "gl: DRI=%s\n", glXIsDirect(vnc->dpy, ctx) ? "Yes" : "No"); if (!glXIsDirect(vnc->dpy, ctx)) return -1; old_handler = XSetErrorHandler(catch_gl_error); glXMakeCurrent(vnc->dpy, win, ctx); XSync(vnc->dpy, False); XSetErrorHandler(old_handler); if (gl_error) return -1; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &vnc->tex_max); if (vnc->debug) fprintf(stderr, "gl: texture max size: %d\n", vnc->tex_max); return 0; } static void gl_resize_window(struct vnc_window *vnc) { if (!vnc->tex) return; glClearColor (0.0, 0.0, 0.0, 0.0); glShadeModel(GL_FLAT); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glViewport(0, 0, vnc->window.w, vnc->window.h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, vnc->window.w, 0.0, vnc->window.h, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); vnc->redraw++; } static void gl_blit(struct vnc_window *vnc, int y1, int y2) { Window win = gdk_x11_drawable_get_xid(vnc->draw->window); float x,y; unsigned int ww = vnc->window.w, wh = vnc->window.h; int wx = 0, wy = 0; if (!vnc->tex) return; if (y1 > vnc->vncdpy.h) y1 = vnc->vncdpy.h; if (y2 > vnc->vncdpy.h) y2 = vnc->vncdpy.h; glBindTexture(GL_TEXTURE_2D, vnc->tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0,y1, vnc->vncdpy.w, y2 - y1, GL_BGRA_EXT /* GL_RGB */, GL_UNSIGNED_BYTE, vnc->tex_data + y1 * 4 * vnc->vncdpy.w); x = (float)vnc->vncdpy.w / vnc->texture.w; y = (float)vnc->vncdpy.h / vnc->texture.h; glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glBegin(GL_QUADS); glTexCoord2f(0,y); glVertex3f(wx, wy, 0); glTexCoord2f(0,0); glVertex3f(wx, wy+wh, 0); glTexCoord2f(x,0); glVertex3f(wx+ww, wy+wh, 0); glTexCoord2f(x,y); glVertex3f(wx+ww, wy, 0); glEnd(); glXSwapBuffers(vnc->dpy, win); glDisable(GL_TEXTURE_2D); } static void gl_update_vnc(struct vnc_window *vnc, int x, int y, int w, int h) { if (vnc->dirty_y1 > y) vnc->dirty_y1 = y; if (vnc->dirty_y2 < y+h) vnc->dirty_y2 = y+h; vnc->updates++; } static void gl_update_win(struct vnc_window *vnc, int x, int y, int w, int h) { vnc->dirty_y1 = 0; vnc->dirty_y2 = vnc->vncdpy.h; vnc->updates++; } static void gl_flush(struct vnc_window *vnc) { if (vnc->redraw) { vnc->dirty_y1 = 0; vnc->dirty_y2 = vnc->vncdpy.h; } gl_blit(vnc, vnc->dirty_y1, vnc->dirty_y2); vnc->dirty_y1 = 99999; vnc->dirty_y2 = 0; vnc->updates = 0; vnc->redraw = 0; } /* ------------------------------------------------------------------ */ /* x11 draw bits */ static void x11_update_win(struct vnc_window *vnc, int x, int y, int w, int h) { Window win = gdk_x11_drawable_get_xid(vnc->draw->window); if (!vnc->gc) { XGCValues values; XColor color, dummy; Colormap cmap; cmap = gdk_x11_colormap_get_xcolormap(gtk_widget_get_colormap(vnc->win)); XAllocNamedColor(vnc->dpy, cmap, "#404040", &color, &dummy); values.function = GXcopy; values.foreground = color.pixel; vnc->gc = XCreateGC(vnc->dpy, win, GCForeground|GCFunction, &values); } if (x < vnc->vncoff.x) vnc->redraw++; if (y < vnc->vncoff.y) vnc->redraw++; if (x+w > vnc->vncoff.x + vnc->vncdpy.w) vnc->redraw++; if (y+h > vnc->vncoff.y + vnc->vncdpy.h) vnc->redraw++; if (vnc->redraw) { XFillRectangle(vnc->dpy, win, vnc->gc, 0,0, vnc->window.w, vnc->window.h); XPUTIMAGE(vnc->dpy, win, vnc->gc, vnc->ximage, 0,0, vnc->vncoff.x, vnc->vncoff.y, vnc->vncdpy.w, vnc->vncdpy.h); vnc->redraw = 0; } else { XPUTIMAGE(vnc->dpy, win, vnc->gc, vnc->ximage, x-vnc->vncoff.x, y-vnc->vncoff.y, x,y, w,h); } } static void x11_update_vnc(struct vnc_window *vnc, int x, int y, int w, int h) { x11_update_win(vnc, x + vnc->vncoff.x, y + vnc->vncoff.y, w, h); } static void x11_resize_window(struct vnc_window *vnc) { vnc->vncoff.x = (vnc->window.w - vnc->vncdpy.w) / 2; vnc->vncoff.y = (vnc->window.h - vnc->vncdpy.h) / 2; } static void x11_flush(struct vnc_window *vnc) { if (vnc->redraw) x11_update_win(vnc, 0, 0, vnc->vncdpy.w, vnc->vncdpy.w); vnc->updates = 0; vnc->redraw = 0; } /* ------------------------------------------------------------------ */ /* x11/gl wrappers */ /* vnc display coordinates */ static void dpy_update_vnc(struct vnc_window *vnc, int x, int y, int w, int h) { if (vnc->debug) fprintf(stderr, "%s: mode%s%s, %dx%d+%d+%d\n", __FUNCTION__, vnc->tex ? " GL" : "", vnc->ximage ? " X11" : "", w, h, x, y); if (vnc->ximage) x11_update_vnc(vnc, x, y, w, h); if (vnc->tex) gl_update_vnc(vnc, x, y, w, h); } /* app window coordinates */ static void dpy_update_win(struct vnc_window *vnc, int x, int y, int w, int h) { if (vnc->debug) fprintf(stderr, "%s: mode%s%s, %dx%d+%d+%d\n", __FUNCTION__, vnc->tex ? " GL" : "", vnc->ximage ? " X11" : "", w, h, x, y); if (vnc->ximage) x11_update_win(vnc, x, y, w, h); if (vnc->tex) gl_update_win(vnc, x, y, w, h); } static void dpy_redraw(struct vnc_window *vnc) { vnc->redraw++; } static void dpy_flush(struct vnc_window *vnc, const char *caller) { if (vnc->debug) fprintf(stderr, "%s: from %s, mode%s%s, updates %d, redraw %d\n", __FUNCTION__, caller, vnc->tex ? " GL" : "", vnc->ximage ? " X11" : "", vnc->updates, vnc->redraw); if (!vnc->updates && !vnc->redraw) return; if (vnc->ximage) x11_flush(vnc); if (vnc->tex) gl_flush(vnc); } static int dpy_gl_check(struct vnc_window *vnc) { int using_gl = 0; if (vnc->have_gl && vnc->vncdpy.w < vnc->tex_max && vnc->vncdpy.h < vnc->tex_max) { if (vnc->gl_allways) using_gl = 1; if (vnc->gl_fullscreen && vnc->fullscreen) using_gl = 1; } return using_gl; } static void dpy_setup(struct vnc_window *vnc, int width, int height, int using_gl) { /* cleanup */ if (vnc->ximage) { x11_destroy_ximage(vnc->dpy, vnc->ximage, vnc->shm); vnc->ximage = NULL; } if (vnc->tex) { /* FIXME: release texture */ vnc->tex = 0; vnc->vncdpy.w = 0; vnc->vncdpy.h = 0; free(vnc->tex_data); vnc->tex_data = NULL; } vnc->vncdpy.w = width; vnc->vncdpy.h = height; if (!using_gl) { /* init X11 */ vnc->ximage = x11_create_ximage(vnc->dpy, vnc->vncdpy.w, vnc->vncdpy.h, &vnc->shm); if (NULL == vnc->ximage) { fprintf(stderr, "Oops: creating ximage failed\n"); return; } vnc->client->width = vnc->ximage->bytes_per_line / (vnc->ximage->bits_per_pixel / 8); vnc->client->frameBuffer = (void*)vnc->ximage->data; vnc->client->format.bitsPerPixel = vnc->ximage->bits_per_pixel; vnc->client->format.redShift = x11_red_shift; vnc->client->format.greenShift = x11_green_shift; vnc->client->format.blueShift = x11_blue_shift; vnc->client->format.redMax = (1 << x11_red_bits) - 1; vnc->client->format.greenMax = (1 << x11_green_bits) - 1; vnc->client->format.blueMax = (1 << x11_blue_bits) - 1; } else { /* init OpenGL */ void *dummy; int i; /* figure texture size (power of two) */ for (i = 0; vnc->vncdpy.w >= (1 << i); i++) ; vnc->texture.w = (1 << i); for (i = 0; vnc->vncdpy.h >= (1 << i); i++) ; vnc->texture.h = (1 << i); if (vnc->debug) fprintf(stderr,"%s: client %dx%d, tex %dx%d\n", __FUNCTION__, vnc->vncdpy.w, vnc->vncdpy.h, vnc->texture.w, vnc->texture.h); /* create texture */ glGenTextures(1, &vnc->tex); glBindTexture(GL_TEXTURE_2D, vnc->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); dummy = malloc(vnc->texture.w * vnc->texture.h * 4); memset(dummy, 128, vnc->texture.w * vnc->texture.h * 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, vnc->texture.w, vnc->texture.h, 0, GL_RGB, GL_UNSIGNED_BYTE, dummy); free(dummy); /* image buffer */ vnc->tex_data = malloc(vnc->vncdpy.w * vnc->vncdpy.h * 4); vnc->client->frameBuffer = vnc->tex_data; vnc->client->format.bitsPerPixel = 32; vnc->client->format.redShift = 16; vnc->client->format.greenShift = 8; vnc->client->format.blueShift = 0; vnc->client->format.redMax = 255; vnc->client->format.greenMax = 255; vnc->client->format.blueMax = 255; } if (vnc->debug) fprintf(stderr, "%s: SetFormatAndEncodings: %s\n", __FUNCTION__, using_gl ? "GL" : "X11" ); SetFormatAndEncodings(vnc->client); SendFramebufferUpdateRequest(vnc->client, 0, 0, vnc->vncdpy.w, vnc->vncdpy.h, False); vnc_window_conf(vnc); } static void dpy_resize_window(struct vnc_window *vnc) { int using_gl; vnc->vncoff.x = 0; vnc->vncoff.y = 0; using_gl = dpy_gl_check(vnc); if ((vnc->tex && !using_gl) || (vnc->ximage && using_gl)) { /* switching display mode */ dpy_setup(vnc, vnc->client->width, vnc->client->height, using_gl); } else { if (vnc->ximage) x11_resize_window(vnc); if (vnc->tex) gl_resize_window(vnc); dpy_redraw(vnc); } vnc_window_texts(vnc); } /* ------------------------------------------------------------------ */ /* 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_window_texts(struct vnc_window *vnc) { char textline[256]; int pos; if (vnc->client && vnc->client->desktopName && strlen(vnc->client->desktopName)) { gtk_window_set_title(GTK_WINDOW(vnc->win), vnc->client->desktopName); } else { snprintf(textline, sizeof(textline), "connecting to %s ...", vnc->display); gtk_window_set_title(GTK_WINDOW(vnc->win), textline); } if (vnc->input_grabbed) { gtk_label_set_text(GTK_LABEL(vnc->line), "Press Ctrl-Alt to release input grab."); } else if (vnc->client) { snprintf(textline, sizeof(textline), "VNC: \"%s\" at %s", vnc->client->desktopName ?: "", vnc->display); gtk_label_set_text(GTK_LABEL(vnc->line), textline); } if (vnc->client) { pos = 0; pos += snprintf(textline+pos, sizeof(textline)-pos, "%dx%d", vnc->vncdpy.w, vnc->vncdpy.h); if (vnc->vncdpy.w != vnc->window.w || vnc->vncdpy.h != vnc->window.h) pos += snprintf(textline+pos, sizeof(textline)-pos, " @ %dx%d", vnc->window.w, vnc->window.h); pos += snprintf(textline+pos, sizeof(textline)-pos, " (%s)", vnc->ximage ? "X11" : "GL"); gtk_label_set_text(GTK_LABEL(vnc->res), textline); } } static void vnc_window_conf(struct vnc_window *vnc) { if (!vnc->draw) return; if (vnc->client) gtk_widget_set_size_request(vnc->draw, vnc->vncdpy.w, vnc->vncdpy.h); if (vnc->draw->window) { gdk_window_set_cursor(vnc->draw->window, vnc->showpointer ? vnc->on : vnc->off); /* FIXME */ XAddInput(vnc->dpy, gdk_x11_drawable_get_xid(vnc->draw->window), KeymapStateMask); } dpy_resize_window(vnc); } static void vnc_release(struct vnc_window *vnc) { int sock = -1; if (NULL == vnc) return; if (vnc->connected && vnc->client) { /* * library bugs? * - calling rfbClientCleanup() unconnected segfaults * - rfbClientCleanup() doesn't close the socket */ sock = vnc->client->sock; rfbClientCleanup(vnc->client); vnc->client = NULL; } 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); if (-1 != sock) close(sock); 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->vncdpy.w == client->width && vnc->vncdpy.h == client->height) { if (vnc->debug) fprintf(stderr, "%s: no size change, early exit\n", __FUNCTION__); return TRUE; } dpy_setup(vnc, client->width, client->height, dpy_gl_check(vnc)); 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; dpy_update_vnc(vnc, 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_entry_set_activates_default(GTK_ENTRY(entry), TRUE); 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 (!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); } dpy_flush(vnc, __FUNCTION__); 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; dpy_update_win(vnc, event->area.x, event->area.y, event->area.width, event->area.height); if (0 == event->count) dpy_flush(vnc, __FUNCTION__); return TRUE; } static gboolean configure_cb(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { struct vnc_window *vnc = data; if (vnc->debug) fprintf(stderr,"%s: %dx%d\n", __FUNCTION__, event->width, event->height); vnc->window.w = event->width; vnc->window.h = event->height; dpy_resize_window(vnc); dpy_flush(vnc, __FUNCTION__); return TRUE; } 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 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; } /* fixup mouse coordinates */ x -= vnc->vncoff.x; y -= vnc->vncoff.y; if (vnc->tex) { x = x * vnc->window.w / vnc->vncdpy.w; y = y * vnc->window.h / vnc->vncdpy.h; } if (x < 0) x = 0; if (y < 0) y = 0; if (x >= vnc->vncdpy.w) x = vnc->vncdpy.w -1; if (y >= vnc->vncdpy.h) y = vnc->vncdpy.h -1; 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 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 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; if (!GTK_WIDGET_HAS_FOCUS(vnc->draw)) 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; } } /* ------------------------------------------------------------------ */ static void menu_cb_full_screen(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; vnc->fullscreen = gtk_toggle_action_get_active(action); if (vnc->fullscreen) gtk_window_fullscreen(GTK_WINDOW(vnc->win)); else gtk_window_unfullscreen(GTK_WINDOW(vnc->win)); } static void menu_cb_us_keyboard(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; vnc->uskbd = gtk_toggle_action_get_active(action); } static void menu_cb_show_pointer(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; vnc->showpointer = gtk_toggle_action_get_active(action); if (vnc->draw->window) gdk_window_set_cursor(vnc->draw->window, vnc->showpointer ? vnc->on : vnc->off); } static void menu_cb_view_only(GtkToggleAction *action, gpointer user_data) { struct vnc_window *vnc = user_data; vnc->viewonly = gtk_toggle_action_get_active(action); } static void menu_cb_gl(GtkRadioAction *action, GtkRadioAction *current, gpointer user_data) { struct vnc_window *vnc = user_data; int value = gtk_radio_action_get_current_value(action); switch (value) { case 1: /* OFF */ vnc->gl_fullscreen = 0; vnc->gl_allways = 0; break; case 2: /* fullscreen ON */ vnc->gl_fullscreen = 1; vnc->gl_allways = 0; break; case 3: /* allways ON */ vnc->gl_fullscreen = 1; vnc->gl_allways = 1; break; } dpy_resize_window(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 = "About", .stock_id = GTK_STOCK_ABOUT, .label = "_About ...", .callback = G_CALLBACK(menu_cb_about), },{ .name = "Close", .stock_id = GTK_STOCK_QUIT, .label = "_Close", // .accelerator = "Q", .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 = "USKbd", .label = "US _Keyboard", .callback = G_CALLBACK(menu_cb_us_keyboard), },{ .name = "ShowPointer", .label = "Show _Pointer", .callback = G_CALLBACK(menu_cb_show_pointer), },{ .name = "ViewOnly", .label = "_View only", .callback = G_CALLBACK(menu_cb_view_only), } }; static const GtkRadioActionEntry rentries[] = { { .name = "GL_OFF", .label = "_Disable OpenGL scaling", .value = 1, },{ .name = "GL_FSonly", .label = "OpenGL scaling for fullscreen", .value = 2, },{ .name = "GL_ON", .label = "Allways scale using _OpenGL", .value = 3, } }; static char ui_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " ""; /* ------------------------------------------------------------------ */ /* public API functions */ GtkWidget *vnc_open(char *hostname, int tcpport, unsigned long flags, int debug_level) { GtkWidget *vbox, *hbox, *frame, *item; GtkAction *action; GError *err; 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->gl_allways = (flags & VNC_FLAG_GL_ALLWAYS); vnc->gl_fullscreen = (flags & VNC_FLAG_GL_FULLSCR); vnc->debug = debug_level; debug_libvnc = debug_level; snprintf(vnc->display, sizeof(vnc->display), "%s:%d", hostname, tcpport - 5900); vnc->dirty_y1 = 99999; vnc->dirty_y2 = 0; /* 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; /* 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); g_signal_connect(G_OBJECT(vnc->draw), "configure-event", G_CALLBACK(configure_cb), vnc); g_signal_connect(G_OBJECT(vnc->win), "window-state-event", G_CALLBACK(window_state_cb), vnc); gdk_window_add_filter(NULL, event_filter, vnc); vnc->filter_installed = 1; /* 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_action_group_add_radio_actions(vnc->ag, rentries, G_N_ELEMENTS(rentries), 0, G_CALLBACK(menu_cb_gl), 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), "Config"); /* popup menu: initial state */ item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/USKbd"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->uskbd); item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/ShowPointer"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->showpointer); item = gtk_ui_manager_get_widget(vnc->ui, "/ConfMenu/ViewOnly"); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), vnc->viewonly); action = gtk_ui_manager_get_action(vnc->ui, "/ConfMenu/GL_ON"); if (vnc->gl_allways) gtk_radio_action_set_current_value(GTK_RADIO_ACTION(action), 3); else if (vnc->gl_fullscreen) gtk_radio_action_set_current_value(GTK_RADIO_ACTION(action), 2); else gtk_radio_action_set_current_value(GTK_RADIO_ACTION(action), 1); /* 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("config"); 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_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->mbutton, FALSE, TRUE, 0); /* show window */ gtk_widget_show_all(vnc->win); vnc_window_conf(vnc); if (flags & VNC_FLAG_FULLSCREEN) gtk_window_fullscreen(GTK_WINDOW(vnc->win)); /* opengl */ if (0 == gl_init(vnc)) vnc->have_gl = 1; /* rfb client */ 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); vnc->connected = 1; 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 */