diff options
Diffstat (limited to 'vnc-old.c')
-rw-r--r-- | vnc-old.c | 1379 |
1 files changed, 1379 insertions, 0 deletions
diff --git a/vnc-old.c b/vnc-old.c new file mode 100644 index 0000000..3fc3d65 --- /dev/null +++ b/vnc-old.c @@ -0,0 +1,1379 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <signal.h> + +#include <X11/X.h> +#include <X11/Xlib.h> + +#include <GL/gl.h> +#include <GL/glx.h> + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "x11.h" +#include "vnc.h" + +#ifdef HAVE_VNCCLIENT + +#include <rfb/rfbclient.h> + +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 <kraxel@redhat.com>", 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 = "<control>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[] = +"<ui>" +" <popup action='ConfMenu'>" +" <menuitem action='FullScreen'/>" +" <menuitem action='USKbd'/>" +" <menuitem action='ShowPointer'/>" +" <menuitem action='ViewOnly'/>" +" <separator/>" +" <menuitem action='GL_OFF'/>" +" <menuitem action='GL_FSonly'/>" +" <menuitem action='GL_ON'/>" +" <separator/>" +" <menuitem action='About'/>" +" <menuitem action='Close'/>" +" </popup>" +"</ui>"; + +/* ------------------------------------------------------------------ */ +/* 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 */ |