Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
725 lines (587 sloc) 19.2 KB
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2010 Jay Sorg
* Copyright (C) 2010-2011 Vic Lee
* Copyright (C) 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* 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 "rdp_plugin.h"
#include "rdp_event.h"
#include "rdp_gdi.h"
#include <gdk/gdkkeysyms.h>
#include <cairo/cairo-xlib.h>
#include <freerdp/locale/keyboard.h>
static void remmina_rdp_event_on_focus_in(GtkWidget* widget, GdkEventKey* event, RemminaProtocolWidget* gp)
{
rfContext* rfi;
rdpInput* input;
GdkModifierType state;
#if GTK_VERSION == 3
GdkDeviceManager *manager;
GdkDevice *keyboard = NULL;
#endif
rfi = GET_DATA(gp);
input = rfi->instance->input;
uint32 toggle_keys_state = 0;
#if GTK_VERSION == 3
manager = gdk_display_get_device_manager(gdk_display_get_default());
keyboard = gdk_device_manager_get_client_pointer(manager);
gdk_window_get_device_position(gdk_get_default_root_window(), keyboard, NULL, NULL, &state);
#else
gdk_window_get_pointer(gdk_get_default_root_window(), NULL, NULL, &state);
#endif
if (state & GDK_LOCK_MASK)
{
toggle_keys_state |= KBD_SYNC_CAPS_LOCK;
}
if (state & GDK_MOD2_MASK)
{
toggle_keys_state |= KBD_SYNC_NUM_LOCK;
}
if (state & GDK_MOD5_MASK)
{
toggle_keys_state |= KBD_SYNC_SCROLL_LOCK;
}
input->SynchronizeEvent(input, toggle_keys_state);
input->KeyboardEvent(input, KBD_FLAGS_RELEASE, 0x0F);
}
static void remmina_rdp_event_event_push(RemminaProtocolWidget* gp, const RemminaPluginRdpEvent* e)
{
rfContext* rfi;
RemminaPluginRdpEvent* event;
rfi = GET_DATA(gp);
if (rfi->event_queue)
{
event = g_memdup(e, sizeof(RemminaPluginRdpEvent));
g_async_queue_push(rfi->event_queue, event);
if (write(rfi->event_pipe[1], "\0", 1))
{
}
}
}
static void remmina_rdp_event_release_key(RemminaProtocolWidget* gp, gint scancode)
{
gint i, k;
rfContext* rfi;
RemminaPluginRdpEvent rdp_event = { 0 };
rfi = GET_DATA(gp);
rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE;
if (scancode == 0)
{
/* Send all release key events for previously pressed keys */
rdp_event.key_event.up = True;
for (i = 0; i < rfi->pressed_keys->len; i++)
{
rdp_event.key_event.key_code = g_array_index(rfi->pressed_keys, gint, i);
remmina_rdp_event_event_push(gp, &rdp_event);
}
g_array_set_size(rfi->pressed_keys, 0);
}
else
{
/* Unregister the keycode only */
for (i = 0; i < rfi->pressed_keys->len; i++)
{
k = g_array_index(rfi->pressed_keys, gint, i);
if (k == scancode)
{
g_array_remove_index_fast(rfi->pressed_keys, i);
break;
}
}
}
}
static void remmina_rdp_event_scale_area(RemminaProtocolWidget* gp, gint* x, gint* y, gint* w, gint* h)
{
gint width, height;
gint sx, sy, sw, sh;
rfContext* rfi;
rfi = GET_DATA(gp);
if (!rfi->surface)
return;
width = remmina_plugin_service->protocol_plugin_get_width(gp);
height = remmina_plugin_service->protocol_plugin_get_height(gp);
if ((width == 0) || (height == 0))
return;
if ((rfi->scale_width == width) && (rfi->scale_height == height))
{
/* Same size, just copy the pixels */
*x = MIN(MAX(0, *x), width - 1);
*y = MIN(MAX(0, *y), height - 1);
*w = MIN(width - *x, *w);
*h = MIN(height - *y, *h);
return;
}
/* We have to extend the scaled region one scaled pixel, to avoid gaps */
sx = MIN(MAX(0, (*x) * rfi->scale_width / width
- rfi->scale_width / width - 2), rfi->scale_width - 1);
sy = MIN(MAX(0, (*y) * rfi->scale_height / height
- rfi->scale_height / height - 2), rfi->scale_height - 1);
sw = MIN(rfi->scale_width - sx, (*w) * rfi->scale_width / width
+ rfi->scale_width / width + 4);
sh = MIN(rfi->scale_height - sy, (*h) * rfi->scale_height / height
+ rfi->scale_height / height + 4);
*x = sx;
*y = sy;
*w = sw;
*h = sh;
}
void remmina_rdp_event_update_region(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui)
{
rfContext* rfi;
gint x, y, w, h;
x = ui->region.x;
y = ui->region.y;
w = ui->region.width;
h = ui->region.height;
rfi = GET_DATA(gp);
if (remmina_plugin_service->protocol_plugin_get_scale(gp))
remmina_rdp_event_scale_area(gp, &x, &y, &w, &h);
gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h);
}
void remmina_rdp_event_update_rect(RemminaProtocolWidget* gp, gint x, gint y, gint w, gint h)
{
rfContext* rfi;
rfi = GET_DATA(gp);
if (remmina_plugin_service->protocol_plugin_get_scale(gp))
remmina_rdp_event_scale_area(gp, &x, &y, &w, &h);
gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h);
}
static gboolean remmina_rdp_event_update_scale_factor(RemminaProtocolWidget* gp)
{
GtkAllocation a;
gboolean scale;
gint width, height;
gint hscale, vscale;
gint gpwidth, gpheight;
RemminaFile* remminafile;
rfContext* rfi;
rfi = GET_DATA(gp);
remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
gtk_widget_get_allocation(GTK_WIDGET(gp), &a);
width = a.width;
height = a.height;
scale = remmina_plugin_service->protocol_plugin_get_scale(gp);
if (scale)
{
if ((width > 1) && (height > 1))
{
gpwidth = remmina_plugin_service->protocol_plugin_get_width(gp);
gpheight = remmina_plugin_service->protocol_plugin_get_height(gp);
hscale = remmina_plugin_service->file_get_int(remminafile, "hscale", 0);
vscale = remmina_plugin_service->file_get_int(remminafile, "vscale", 0);
rfi->scale_width = (hscale > 0 ?
MAX(1, gpwidth * hscale / 100) : width);
rfi->scale_height = (vscale > 0 ?
MAX(1, gpheight * vscale / 100) : height);
rfi->scale_x = (gdouble) rfi->scale_width / (gdouble) gpwidth;
rfi->scale_y = (gdouble) rfi->scale_height / (gdouble) gpheight;
}
}
else
{
rfi->scale_width = 0;
rfi->scale_height = 0;
rfi->scale_x = 0;
rfi->scale_y = 0;
}
if ((width > 1) && (height > 1))
gtk_widget_queue_draw_area(GTK_WIDGET(gp), 0, 0, width, height);
rfi->scale_handler = 0;
return FALSE;
}
#if GTK_VERSION == 2
static gboolean remmina_rdp_event_on_expose(GtkWidget *widget, GdkEventExpose *event, RemminaProtocolWidget *gp)
#else
static gboolean remmina_rdp_event_on_draw(GtkWidget* widget, cairo_t* context, RemminaProtocolWidget* gp)
#endif
{
gboolean scale;
rfContext* rfi;
#if GTK_VERSION == 2
gint x, y;
cairo_t *context;
#endif
rfi = GET_DATA(gp);
if (!rfi->surface)
return FALSE;
scale = remmina_plugin_service->protocol_plugin_get_scale(gp);
#if GTK_VERSION == 2
x = event->area.x;
y = event->area.y;
context = gdk_cairo_create(gtk_widget_get_window (rfi->drawing_area));
cairo_rectangle(context, x, y, event->area.width, event->area.height);
#endif
if (scale)
cairo_scale(context, rfi->scale_x, rfi->scale_y);
cairo_set_source_surface(context, rfi->surface, 0, 0);
#if GTK_VERSION == 2
cairo_fill(context);
cairo_destroy(context);
#else
cairo_paint(context);
#endif
return TRUE;
}
static gboolean remmina_rdp_event_on_configure(GtkWidget* widget, GdkEventConfigure* event, RemminaProtocolWidget* gp)
{
rfContext* rfi;
rfi = GET_DATA(gp);
/* We do a delayed reallocating to improve performance */
if (rfi->scale_handler)
g_source_remove(rfi->scale_handler);
rfi->scale_handler = g_timeout_add(1000, (GSourceFunc) remmina_rdp_event_update_scale_factor, gp);
return FALSE;
}
static void remmina_rdp_event_translate_pos(RemminaProtocolWidget* gp, int ix, int iy, uint16* ox, uint16* oy)
{
rfContext* rfi;
rfi = GET_DATA(gp);
if ((rfi->scale) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1))
{
*ox = (uint16) (ix * remmina_plugin_service->protocol_plugin_get_width(gp) / rfi->scale_width);
*oy = (uint16) (iy * remmina_plugin_service->protocol_plugin_get_height(gp) / rfi->scale_height);
}
else
{
*ox = (uint16) ix;
*oy = (uint16) iy;
}
}
static gboolean remmina_rdp_event_on_motion(GtkWidget* widget, GdkEventMotion* event, RemminaProtocolWidget* gp)
{
RemminaPluginRdpEvent rdp_event = { 0 };
rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
rdp_event.mouse_event.flags = PTR_FLAGS_MOVE;
remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
remmina_rdp_event_event_push(gp, &rdp_event);
return TRUE;
}
static gboolean remmina_rdp_event_on_button(GtkWidget* widget, GdkEventButton* event, RemminaProtocolWidget* gp)
{
gint flag;
RemminaPluginRdpEvent rdp_event = { 0 };
/* We only accept 3 buttons */
if ((event->button < 1) || (event->button > 3))
return FALSE;
/* We bypass 2button-press and 3button-press events */
if ((event->type != GDK_BUTTON_PRESS) && (event->type != GDK_BUTTON_RELEASE))
return TRUE;
rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
flag = 0;
if (event->type == GDK_BUTTON_PRESS)
flag = PTR_FLAGS_DOWN;
switch (event->button)
{
case 1:
flag |= PTR_FLAGS_BUTTON1;
break;
case 2:
flag |= PTR_FLAGS_BUTTON3;
break;
case 3:
flag |= PTR_FLAGS_BUTTON2;
break;
}
if (flag != 0)
{
rdp_event.mouse_event.flags = flag;
remmina_rdp_event_event_push(gp, &rdp_event);
}
return TRUE;
}
static gboolean remmina_rdp_event_on_scroll(GtkWidget* widget, GdkEventScroll* event, RemminaProtocolWidget* gp)
{
gint flag;
RemminaPluginRdpEvent rdp_event = { 0 };
flag = 0;
rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
switch (event->direction)
{
case GDK_SCROLL_UP:
flag = PTR_FLAGS_WHEEL | 0x0078;
break;
case GDK_SCROLL_DOWN:
flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088;
break;
#ifdef GDK_SCROLL_SMOOTH
case GDK_SCROLL_SMOOTH:
if (event->delta_y < 0)
flag = PTR_FLAGS_WHEEL | 0x0078;
if (event->delta_y > 0)
flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088;
if (!flag)
return FALSE;
break;
#endif
default:
return FALSE;
}
rdp_event.mouse_event.flags = flag;
remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
remmina_rdp_event_event_push(gp, &rdp_event);
return TRUE;
}
static gboolean remmina_rdp_event_on_key(GtkWidget* widget, GdkEventKey* event, RemminaProtocolWidget* gp)
{
GdkDisplay* display;
guint16 cooked_keycode;
rfContext* rfi;
RemminaPluginRdpEvent rdp_event;
rfi = GET_DATA(gp);
rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE;
rdp_event.key_event.up = (event->type == GDK_KEY_PRESS ? False : True);
rdp_event.key_event.extended = False;
switch (event->keyval)
{
case GDK_KEY_Pause:
rdp_event.key_event.key_code = 0x1D;
rdp_event.key_event.up = False;
remmina_rdp_event_event_push(gp, &rdp_event);
rdp_event.key_event.key_code = 0x45;
rdp_event.key_event.up = False;
remmina_rdp_event_event_push(gp, &rdp_event);
rdp_event.key_event.key_code = 0x1D;
rdp_event.key_event.up = True;
remmina_rdp_event_event_push(gp, &rdp_event);
rdp_event.key_event.key_code = 0x45;
rdp_event.key_event.up = True;
remmina_rdp_event_event_push(gp, &rdp_event);
break;
default:
if (!rfi->use_client_keymap)
{
rdp_event.key_event.key_code = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->hardware_keycode);
remmina_plugin_service->log_printf("[RDP]keyval=%04X keycode=%i scancode=%i extended=%i\n",
event->keyval, event->hardware_keycode, rdp_event.key_event.key_code, &rdp_event.key_event.extended);
}
else
{
//TODO: Port to GDK functions
display = gdk_display_get_default();
cooked_keycode = XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(display), XKeycodeToKeysym(GDK_DISPLAY_XDISPLAY(display), event->hardware_keycode, 0));
rdp_event.key_event.key_code = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(cooked_keycode);
remmina_plugin_service->log_printf("[RDP]keyval=%04X raw_keycode=%i cooked_keycode=%i scancode=%i extended=%i\n",
event->keyval, event->hardware_keycode, cooked_keycode, rdp_event.key_event.key_code, &rdp_event.key_event.extended);
}
if (rdp_event.key_event.key_code)
remmina_rdp_event_event_push(gp, &rdp_event);
break;
}
/* Register/unregister the pressed key */
if (rdp_event.key_event.key_code)
{
if (event->type == GDK_KEY_PRESS)
g_array_append_val(rfi->pressed_keys, rdp_event.key_event.key_code);
else
remmina_rdp_event_release_key(gp, rdp_event.key_event.key_code);
}
return TRUE;
}
static gboolean remmina_rdp_event_on_clipboard(GtkClipboard *clipboard, GdkEvent *event, RemminaProtocolWidget *gp)
{
RemminaPluginRdpEvent rdp_event = { 0 };
rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD;
remmina_rdp_event_event_push(gp, &rdp_event);
return TRUE;
}
void remmina_rdp_event_init(RemminaProtocolWidget* gp)
{
gchar* s;
gint flags;
rfContext* rfi;
GtkClipboard* clipboard;
rfi = GET_DATA(gp);
rfi->drawing_area = gtk_drawing_area_new();
gtk_widget_show(rfi->drawing_area);
gtk_container_add(GTK_CONTAINER(gp), rfi->drawing_area);
gtk_widget_add_events(rfi->drawing_area, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_SCROLL_MASK | GDK_FOCUS_CHANGE_MASK);
gtk_widget_set_can_focus(rfi->drawing_area, TRUE);
remmina_plugin_service->protocol_plugin_register_hostkey(gp, rfi->drawing_area);
s = remmina_plugin_service->pref_get_value("rdp_use_client_keymap");
rfi->use_client_keymap = (s && s[0] == '1' ? TRUE : FALSE);
g_free(s);
#if GTK_VERSION == 3
g_signal_connect(G_OBJECT(rfi->drawing_area), "draw",
G_CALLBACK(remmina_rdp_event_on_draw), gp);
#elif GTK_VERSION == 2
g_signal_connect(G_OBJECT(rfi->drawing_area), "expose-event",
G_CALLBACK(remmina_rdp_event_on_expose), gp);
#endif
g_signal_connect(G_OBJECT(rfi->drawing_area), "configure-event",
G_CALLBACK(remmina_rdp_event_on_configure), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "motion-notify-event",
G_CALLBACK(remmina_rdp_event_on_motion), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "button-press-event",
G_CALLBACK(remmina_rdp_event_on_button), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "button-release-event",
G_CALLBACK(remmina_rdp_event_on_button), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "scroll-event",
G_CALLBACK(remmina_rdp_event_on_scroll), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "key-press-event",
G_CALLBACK(remmina_rdp_event_on_key), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "key-release-event",
G_CALLBACK(remmina_rdp_event_on_key), gp);
g_signal_connect(G_OBJECT(rfi->drawing_area), "focus-in-event",
G_CALLBACK(remmina_rdp_event_on_focus_in), gp);
RemminaFile* remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE))
{
clipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
rfi->clipboard_handler = g_signal_connect(clipboard, "owner-change", G_CALLBACK(remmina_rdp_event_on_clipboard), gp);
}
rfi->pressed_keys = g_array_new(FALSE, TRUE, sizeof (gint));
rfi->event_queue = g_async_queue_new_full(g_free);
rfi->ui_queue = g_async_queue_new();
if (pipe(rfi->event_pipe))
{
g_print("Error creating pipes.\n");
rfi->event_pipe[0] = -1;
rfi->event_pipe[1] = -1;
}
else
{
flags = fcntl(rfi->event_pipe[0], F_GETFL, 0);
fcntl(rfi->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
}
rfi->object_table = g_hash_table_new_full(NULL, NULL, NULL, g_free);
rfi->display = gdk_display_get_default();
rfi->bpp = gdk_visual_get_best_depth();
}
void remmina_rdp_event_uninit(RemminaProtocolWidget* gp)
{
rfContext* rfi;
RemminaPluginRdpUiObject* ui;
rfi = GET_DATA(gp);
/* unregister the clipboard monitor */
if (rfi->clipboard_handler)
{
g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD)), rfi->clipboard_handler);
rfi->clipboard_handler = 0;
}
if (rfi->scale_handler)
{
g_source_remove(rfi->scale_handler);
rfi->scale_handler = 0;
}
if (rfi->ui_handler)
{
g_source_remove(rfi->ui_handler);
rfi->ui_handler = 0;
}
while ((ui =(RemminaPluginRdpUiObject*) g_async_queue_try_pop(rfi->ui_queue)) != NULL)
{
rf_object_free(gp, ui);
}
if (rfi->surface)
{
cairo_surface_destroy(rfi->surface);
rfi->surface = NULL;
}
g_hash_table_destroy(rfi->object_table);
g_array_free(rfi->pressed_keys, TRUE);
g_async_queue_unref(rfi->event_queue);
rfi->event_queue = NULL;
g_async_queue_unref(rfi->ui_queue);
rfi->ui_queue = NULL;
close(rfi->event_pipe[0]);
close(rfi->event_pipe[1]);
}
void remmina_rdp_event_update_scale(RemminaProtocolWidget* gp)
{
gint width, height;
gint hscale, vscale;
RemminaFile* remminafile;
rfContext* rfi;
rfi = GET_DATA(gp);
remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
width = remmina_plugin_service->protocol_plugin_get_width(gp);
height = remmina_plugin_service->protocol_plugin_get_height(gp);
hscale = remmina_plugin_service->file_get_int(remminafile, "hscale", 0);
vscale = remmina_plugin_service->file_get_int(remminafile, "vscale", 0);
if (rfi->scale)
{
gtk_widget_set_size_request(rfi->drawing_area,
(hscale > 0 ? width * hscale / 100 : -1),
(vscale > 0 ? height * vscale / 100 : -1));
}
else
{
gtk_widget_set_size_request(rfi->drawing_area, width, height);
}
remmina_plugin_service->protocol_plugin_emit_signal(gp, "update-align");
}
static void remmina_rdp_event_connected(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui)
{
rfContext* rfi;
int stride;
rfi = GET_DATA(gp);
gtk_widget_realize(rfi->drawing_area);
stride = cairo_format_stride_for_width(rfi->cairo_format, rfi->width);
rfi->surface = cairo_image_surface_create_for_data((unsigned char*) rfi->primary_buffer, rfi->cairo_format, rfi->width, rfi->height, stride);
gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, rfi->width, rfi->height);
if (rfi->clipboard_handler)
{
remmina_rdp_event_on_clipboard(NULL, NULL, gp);
}
remmina_rdp_event_update_scale(gp);
}
static void remmina_rdp_event_update_cursor(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui)
{
rfContext* rfi;
rfi = GET_DATA(gp);
gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), ui->cursor.cursor);
}
gboolean remmina_rdp_event_queue_ui(RemminaProtocolWidget* gp)
{
rfContext* rfi;
RemminaPluginRdpUiObject* ui;
rfi = GET_DATA(gp);
ui = (RemminaPluginRdpUiObject*) g_async_queue_try_pop(rfi->ui_queue);
if (ui)
{
switch (ui->type)
{
case REMMINA_RDP_UI_UPDATE_REGION:
remmina_rdp_event_update_region(gp, ui);
break;
case REMMINA_RDP_UI_CONNECTED:
remmina_rdp_event_connected(gp, ui);
break;
case REMMINA_RDP_UI_UPDATE_CURSOR:
remmina_rdp_event_update_cursor(gp, ui);
break;
default:
break;
}
rf_object_free(gp, ui);
return TRUE;
}
else
{
LOCK_BUFFER(FALSE)
rfi->ui_handler = 0;
UNLOCK_BUFFER(FALSE)
return FALSE;
}
}
void remmina_rdp_event_unfocus(RemminaProtocolWidget* gp)
{
remmina_rdp_event_release_key(gp, 0);
}