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