/* * Remmina - The GTK Remote Desktop Client * Copyright (C) 2010 Jay Sorg * Copyright (C) 2010-2011 Vic Lee * Copyright (C) 2011 Marc-Andre Moreau * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. * If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. * If you * do not wish to do so, delete this exception statement from your * version. * If you delete this exception statement from all source * files in the program, then also delete it here. * */ #include "rdp_cliprdr.h" #include "rdp_event.h" #include "rdp_monitor.h" #include "rdp_settings.h" #include #ifdef GDK_WINDOWING_X11 #include #else #include #endif #include gboolean remmina_rdp_event_on_map(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); rdpGdi *gdi; if (rfi == NULL) return false; RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); int do_suppress = !remmina_plugin_service->file_get_int(remminafile, "no-suppress", FALSE); if (do_suppress) { gdi = ((rdpContext *)rfi)->gdi; REMMINA_PLUGIN_DEBUG("Map event received, disabling TS_SUPPRESS_OUTPUT_PDU "); gdi_send_suppress_output(gdi, FALSE); } return FALSE; } gboolean remmina_rdp_event_on_unmap(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); rdpGdi *gdi; if (rfi == NULL) return false; GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(gp)); GdkWindow *window = gtk_widget_get_window(toplevel); if (gdk_window_get_fullscreen_mode(window) == GDK_FULLSCREEN_ON_ALL_MONITORS) { REMMINA_PLUGIN_DEBUG("Unmap event received, but cannot enable TS_SUPPRESS_OUTPUT_PDU when in fullscreen"); return FALSE; } RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); int do_suppress = !remmina_plugin_service->file_get_int(remminafile, "no-suppress", FALSE); if (do_suppress) { gdi = ((rdpContext *)rfi)->gdi; REMMINA_PLUGIN_DEBUG("Unmap event received, enabling TS_SUPPRESS_OUTPUT_PDU "); gdi_send_suppress_output(gdi, TRUE); } return FALSE; } static gboolean remmina_rdp_event_on_focus_in(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); rdpInput *input; GdkModifierType state; #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *manager; #endif GdkDevice *keyboard = NULL; const gchar *wname = gtk_widget_get_name(gtk_widget_get_toplevel(widget)); REMMINA_PLUGIN_DEBUG("Top level name is: %s", wname); if (!rfi || !rfi->connected || rfi->is_reconnecting) return FALSE; input = rfi->clientContext.context.input; UINT32 toggle_keys_state = 0; #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat(gdk_display_get_default()); keyboard = gdk_seat_get_pointer(seat); #else manager = gdk_display_get_device_manager(gdk_display_get_default()); keyboard = gdk_device_manager_get_client_pointer(manager); #endif gdk_window_get_device_position(gdk_get_default_root_window(), keyboard, NULL, NULL, &state); 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); return FALSE; } void remmina_rdp_event_event_push(RemminaProtocolWidget *gp, const RemminaPluginRdpEvent *e) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpEvent *event; /* Called by the main GTK thread to send an event to the libfreerdp thread */ if (!rfi || !rfi->connected || rfi->is_reconnecting) return; if (rfi->event_queue) { #if GLIB_CHECK_VERSION(2,67,3) event = g_memdup2(e, sizeof(RemminaPluginRdpEvent)); #else event = g_memdup(e, sizeof(RemminaPluginRdpEvent)); #endif g_async_queue_push(rfi->event_queue, event); if (write(rfi->event_pipe[1], "\0", 1)) { } } } static void remmina_rdp_event_release_all_keys(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpEvent rdp_event = { 0 }; int i; /* Send all release key events for previously pressed keys */ for (i = 0; i < rfi->pressed_keys->len; i++) { rdp_event = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i); if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE || rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) && rdp_event.key_event.up == false) { rdp_event.key_event.up = true; remmina_rdp_event_event_push(gp, &rdp_event); } } g_array_set_size(rfi->pressed_keys, 0); } static void remmina_rdp_event_release_key(RemminaProtocolWidget *gp, RemminaPluginRdpEvent rdp_event) { TRACE_CALL(__func__); gint i; rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpEvent rdp_event_2 = { 0 }; rdp_event_2.type = REMMINA_RDP_EVENT_TYPE_SCANCODE; if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE || rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) && rdp_event.key_event.up) { /* Unregister the keycode only */ for (i = 0; i < rfi->pressed_keys->len; i++) { rdp_event_2 = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i); if (rdp_event_2.key_event.key_code == rdp_event.key_event.key_code && rdp_event_2.key_event.unicode_code == rdp_event.key_event.unicode_code && rdp_event_2.key_event.extended == rdp_event.key_event.extended && rdp_event_2.key_event.extended1 == rdp_event.key_event.extended1) { g_array_remove_index_fast(rfi->pressed_keys, i); break; } } } } static void keypress_list_add(RemminaProtocolWidget *gp, RemminaPluginRdpEvent rdp_event) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); if (!rdp_event.key_event.key_code) return; if (rdp_event.key_event.up) remmina_rdp_event_release_key(gp, rdp_event); else g_array_append_val(rfi->pressed_keys, rdp_event); } static void remmina_rdp_event_scale_area(RemminaProtocolWidget *gp, gint *x, gint *y, gint *w, gint *h) { TRACE_CALL(__func__); gint width, height; gint sx, sy, sw, sh; rfContext *rfi = GET_PLUGIN_DATA(gp); if (!rfi || !rfi->connected || rfi->is_reconnecting || !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_regions(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); gint x, y, w, h, i; for (i = 0; i < ui->reg.ninvalid; i++) { x = ui->reg.ureg[i].x; y = ui->reg.ureg[i].y; w = ui->reg.ureg[i].w; h = ui->reg.ureg[i].h; if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) remmina_rdp_event_scale_area(gp, &x, &y, &w, &h); gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h); } g_free(ui->reg.ureg); } void remmina_rdp_event_update_rect(RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) remmina_rdp_event_scale_area(gp, &x, &y, &w, &h); gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h); } static void remmina_rdp_event_update_scale_factor(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); GtkAllocation a; gint rdwidth, rdheight; gint gpwidth, gpheight; rfContext *rfi = GET_PLUGIN_DATA(gp); gtk_widget_get_allocation(GTK_WIDGET(gp), &a); gpwidth = a.width; gpheight = a.height; if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) { if ((gpwidth > 1) && (gpheight > 1)) { rdwidth = remmina_plugin_service->protocol_plugin_get_width(gp); rdheight = remmina_plugin_service->protocol_plugin_get_height(gp); rfi->scale_width = gpwidth; rfi->scale_height = gpheight; rfi->scale_x = (gdouble)rfi->scale_width / (gdouble)rdwidth; rfi->scale_y = (gdouble)rfi->scale_height / (gdouble)rdheight; } } else { rfi->scale_width = 0; rfi->scale_height = 0; rfi->scale_x = 0; rfi->scale_y = 0; } } static gboolean remmina_rdp_event_on_draw(GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); guint width, height; gchar *msg; cairo_text_extents_t extents; if (!rfi || !rfi->connected) return FALSE; if (rfi->is_reconnecting) { /* FreeRDP is reconnecting, just show a message to the user */ width = gtk_widget_get_allocated_width(widget); height = gtk_widget_get_allocated_height(widget); /* Draw text */ msg = g_strdup_printf(_("Reconnection attempt %d of %d…"), rfi->reconnect_nattempt, rfi->reconnect_maxattempts); cairo_select_font_face(context, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(context, 24); cairo_set_source_rgb(context, 0.9, 0.9, 0.9); cairo_text_extents(context, msg, &extents); cairo_move_to(context, (width - (extents.width + extents.x_bearing)) / 2, (height - (extents.height + extents.y_bearing)) / 2); cairo_show_text(context, msg); g_free(msg); } else { /* Standard drawing: We copy the surface from RDP */ if (!rfi->surface) return FALSE; if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) cairo_scale(context, rfi->scale_x, rfi->scale_y); cairo_surface_flush(rfi->surface); cairo_set_source_surface(context, rfi->surface, 0, 0); cairo_surface_mark_dirty(rfi->surface); cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); // Ignore alpha channel from FreeRDP cairo_paint(context); } return TRUE; } static gboolean remmina_rdp_event_delayed_monitor_layout(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpEvent rdp_event = { 0 }; GtkAllocation a; gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; RemminaFile *remminafile; if (!rfi || !rfi->connected || rfi->is_reconnecting) return FALSE; remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (rfi->scale != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) return FALSE; rfi->delayed_monitor_layout_handler = 0; gint gpwidth, gpheight, prevwidth, prevheight; gchar *monitorids = NULL; guint32 maxwidth = 0; guint32 maxheight = 0; remmina_rdp_monitor_get(rfi, &monitorids, &maxwidth, &maxheight); REMMINA_PLUGIN_DEBUG("Sending preconfigured monitor layout"); if (rfi->dispcontext && rfi->dispcontext->SendMonitorLayout) { remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); gtk_widget_get_allocation(GTK_WIDGET(gp), &a); gpwidth = a.width; gpheight = a.height; prevwidth = remmina_plugin_service->protocol_plugin_get_width(gp); prevheight = remmina_plugin_service->protocol_plugin_get_height(gp); if ((gpwidth != prevwidth || gpheight != prevheight) && gpwidth >= 200 && gpheight >= 200) { if (rfi->rdpgfxchan) { /* Workaround for FreeRDP issue #5417 */ if (gpwidth < AVC_MIN_DESKTOP_WIDTH) gpwidth = AVC_MIN_DESKTOP_WIDTH; if (gpheight < AVC_MIN_DESKTOP_HEIGHT) gpheight = AVC_MIN_DESKTOP_HEIGHT; } rdp_event.type = REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT; if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) { const rdpMonitor *base = freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray); for (gint i = 0; i < freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount); ++i) { const rdpMonitor *current = &base[i]; REMMINA_PLUGIN_DEBUG("Sending display layout n° %d", i); rdp_event.monitor_layout.Flags = current->is_primary; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Flags: %i", rdp_event.monitor_layout.Flags); rdp_event.monitor_layout.Left = current->x; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Left: %i", rdp_event.monitor_layout.Left); rdp_event.monitor_layout.Top = current->y; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Top: %i", rdp_event.monitor_layout.Top); rdp_event.monitor_layout.width = current->width; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - width: %i", rdp_event.monitor_layout.width); rdp_event.monitor_layout.height = current->height; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - height: %i", rdp_event.monitor_layout.height); rdp_event.monitor_layout.physicalWidth = current->attributes.physicalWidth; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - physicalWidth: %i", rdp_event.monitor_layout.physicalWidth); rdp_event.monitor_layout.physicalHeight = current->attributes.physicalHeight; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - PhysicalHeight: %i", rdp_event.monitor_layout.physicalHeight); if (current->attributes.orientation) rdp_event.monitor_layout.desktopOrientation = current->attributes.orientation; else rdp_event.monitor_layout.desktopOrientation = rdp_event.monitor_layout.desktopOrientation; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - desktopOrientation: %i", rdp_event.monitor_layout.desktopOrientation); rdp_event.monitor_layout.desktopScaleFactor = rdp_event.monitor_layout.desktopScaleFactor; REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - ScaleFactorflag: %i", rdp_event.monitor_layout.desktopScaleFactor); rdp_event.monitor_layout.deviceScaleFactor = rdp_event.monitor_layout.deviceScaleFactor; } remmina_rdp_event_event_push(gp, &rdp_event); } else { rdp_event.monitor_layout.width = gpwidth; rdp_event.monitor_layout.height = gpheight; rdp_event.monitor_layout.desktopOrientation = desktopOrientation; rdp_event.monitor_layout.desktopScaleFactor = desktopScaleFactor; rdp_event.monitor_layout.deviceScaleFactor = deviceScaleFactor; remmina_rdp_event_event_push(gp, &rdp_event); } } } g_free(monitorids); return FALSE; } void remmina_rdp_event_send_delayed_monitor_layout(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); if (!rfi || !rfi->connected || rfi->is_reconnecting) return; if (rfi->delayed_monitor_layout_handler) { g_source_remove(rfi->delayed_monitor_layout_handler); rfi->delayed_monitor_layout_handler = 0; } if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) rfi->delayed_monitor_layout_handler = g_timeout_add(500, (GSourceFunc)remmina_rdp_event_delayed_monitor_layout, gp); } static gboolean remmina_rdp_event_on_configure(GtkWidget *widget, GdkEventConfigure *event, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); /* Called when gp changes its size or position */ rfContext *rfi = GET_PLUGIN_DATA(gp); if (!rfi || !rfi->connected || rfi->is_reconnecting) return FALSE; remmina_rdp_event_update_scale_factor(gp); /* If the scaler is not active, schedule a delayed remote resolution change */ remmina_rdp_event_send_delayed_monitor_layout(gp); return FALSE; } static void remmina_rdp_event_translate_pos(RemminaProtocolWidget *gp, int ix, int iy, UINT16 *ox, UINT16 *oy) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); /* * Translate a position from local window coordinates (ix,iy) to * RDP coordinates and put result on (*ox,*uy) * */ if (!rfi || !rfi->connected || rfi->is_reconnecting) return; if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (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 void remmina_rdp_event_reverse_translate_pos_reverse(RemminaProtocolWidget *gp, int ix, int iy, int *ox, int *oy) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); /* * Translate a position from RDP coordinates (ix,iy) to * local window coordinates and put result on (*ox,*uy) * */ if (!rfi || !rfi->connected || rfi->is_reconnecting) return; if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1)) { *ox = (ix * rfi->scale_width) / remmina_plugin_service->protocol_plugin_get_width(gp); *oy = (iy * rfi->scale_height) / remmina_plugin_service->protocol_plugin_get_height(gp); } else { *ox = ix; *oy = iy; } } void remmina_rdp_mouse_jitter(RemminaProtocolWidget *gp){ TRACE_CALL(__func__); RemminaPluginRdpEvent rdp_event = { 0 }; RemminaFile *remminafile; rfContext *rfi = GET_PLUGIN_DATA(gp); remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE)) return; rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; rdp_event.mouse_event.flags = PTR_FLAGS_MOVE; rdp_event.mouse_event.extended = FALSE; rdp_event.mouse_event.x = rfi->last_x; rdp_event.mouse_event.y = rfi->last_y; remmina_rdp_event_event_push(gp, &rdp_event); } static gboolean remmina_rdp_event_on_motion(GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); RemminaPluginRdpEvent rdp_event = { 0 }; RemminaFile *remminafile; rfContext *rfi = GET_PLUGIN_DATA(gp); remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE)) return FALSE; rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; rdp_event.mouse_event.flags = PTR_FLAGS_MOVE; rdp_event.mouse_event.extended = FALSE; remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y); if (rfi != NULL){ rfi->last_x = rdp_event.mouse_event.x; rfi->last_y = 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) { TRACE_CALL(__func__); gint flag; gboolean extended = FALSE; RemminaPluginRdpEvent rdp_event = { 0 }; gint primary, secondary; RemminaFile *remminafile; remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE)) return FALSE; /* We bypass 2button-press and 3button-press events */ if ((event->type != GDK_BUTTON_PRESS) && (event->type != GDK_BUTTON_RELEASE)) return TRUE; flag = 0; if (remmina_plugin_service->file_get_int(remminafile, "left-handed", FALSE)) { primary = PTR_FLAGS_BUTTON2; secondary = PTR_FLAGS_BUTTON1; } else { primary = PTR_FLAGS_BUTTON1; secondary = PTR_FLAGS_BUTTON2; } switch (event->button) { case 1: flag |= primary; break; case 2: flag |= PTR_FLAGS_BUTTON3; break; case 3: flag |= secondary; break; case 8: /* back */ case 97: /* Xming */ extended = TRUE; flag |= PTR_XFLAGS_BUTTON1; break; case 9: /* forward */ case 112: /* Xming */ extended = TRUE; flag |= PTR_XFLAGS_BUTTON2; break; default: return FALSE; } if (event->type == GDK_BUTTON_PRESS) { if (extended) flag |= PTR_XFLAGS_DOWN; else flag |= PTR_FLAGS_DOWN; } 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); if (flag != 0) { rdp_event.mouse_event.flags = flag; rdp_event.mouse_event.extended = extended; remmina_rdp_event_event_push(gp, &rdp_event); } return TRUE; } static gboolean remmina_rdp_event_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); gint flag; RemminaPluginRdpEvent rdp_event = { 0 }; float windows_delta; RemminaFile *remminafile; remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE)) return FALSE; flag = 0; rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; /* See [MS-RDPBCGR] TS_POINTER_EVENT and WM_MOUSEWHEEL message */ switch (event->direction) { case GDK_SCROLL_UP: flag = PTR_FLAGS_WHEEL | 0x0078; // 120 is one scroll unit defined in WM_MOUSEWHEEL break; case GDK_SCROLL_DOWN: flag = PTR_FLAGS_WHEEL | 0x0188; // -120 (one scroll unit) in 9 bits two's complement break; #if GTK_CHECK_VERSION(3, 4, 0) case GDK_SCROLL_SMOOTH: if (event->delta_y == 0.0) return FALSE; windows_delta = event->delta_y * -120; if (windows_delta > 255) windows_delta = 255; if (windows_delta < -256) windows_delta = -256; flag = PTR_FLAGS_WHEEL | ((short)windows_delta & WheelRotationMask); break; #endif default: return FALSE; } rdp_event.mouse_event.flags = flag; rdp_event.mouse_event.extended = FALSE; 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 void remmina_rdp_event_init_keymap(rfContext *rfi, const gchar *strmap) { long int v1, v2; const char *s; char *endptr; RemminaPluginRdpKeymapEntry ke; if (strmap == NULL || strmap[0] == 0) { rfi->keymap = NULL; return; } s = strmap; rfi->keymap = g_array_new(FALSE, TRUE, sizeof(RemminaPluginRdpKeymapEntry)); while (1) { v1 = strtol(s, &endptr, 10); if (endptr == s) break; s = endptr; if (*s != ':') break; s++; v2 = strtol(s, &endptr, 10); if (endptr == s) break; s = endptr; ke.orig_keycode = v1 & 0x7fffffff; ke.translated_keycode = v2 & 0x7fffffff; g_array_append_val(rfi->keymap, ke); if (*s != ',') break; s++; } if (rfi->keymap->len == 0) { g_array_unref(rfi->keymap); rfi->keymap = NULL; } } static gboolean remmina_rdp_event_on_key(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp) { TRACE_CALL(__func__); guint32 unicode_keyval; guint16 hardware_keycode; rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpEvent rdp_event; RemminaPluginRdpKeymapEntry *kep; RemminaFile *remminafile; DWORD scancode = 0; int ik; if (!rfi || !rfi->connected || rfi->is_reconnecting) return FALSE; remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE)) return FALSE; #ifdef ENABLE_GTK_INSPECTOR_KEY /* GTK inspector key is propagated up. Disabled by default. * enable it by defining ENABLE_GTK_INSPECTOR_KEY */ if ((event->state & GDK_CONTROL_MASK) != 0 && (event->keyval == GDK_KEY_I || event->keyval == GDK_KEY_D)) return FALSE; #endif 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; rdp_event.key_event.extended1 = false; switch (event->keyval) { case GDK_KEY_Pause: /* * See https://msdn.microsoft.com/en-us/library/cc240584.aspx * 2.2.8.1.1.3.1.1.1 Keyboard Event (TS_KEYBOARD_EVENT) * for pause key management */ rdp_event.key_event.key_code = 0x1D; rdp_event.key_event.up = false; rdp_event.key_event.extended1 = TRUE; remmina_rdp_event_event_push(gp, &rdp_event); rdp_event.key_event.key_code = 0x45; rdp_event.key_event.up = false; rdp_event.key_event.extended1 = FALSE; remmina_rdp_event_event_push(gp, &rdp_event); rdp_event.key_event.key_code = 0x1D; rdp_event.key_event.up = true; rdp_event.key_event.extended1 = TRUE; remmina_rdp_event_event_push(gp, &rdp_event); rdp_event.key_event.key_code = 0x45; rdp_event.key_event.up = true; rdp_event.key_event.extended1 = FALSE; remmina_rdp_event_event_push(gp, &rdp_event); break; default: if (!rfi->use_client_keymap) { hardware_keycode = event->hardware_keycode; if (rfi->keymap) { for (ik = 0; ik < rfi->keymap->len; ik++) { kep = &g_array_index(rfi->keymap, RemminaPluginRdpKeymapEntry, ik); if (hardware_keycode == kep->orig_keycode) { hardware_keycode = kep->translated_keycode; break; } } } scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(hardware_keycode); if (scancode) { rdp_event.key_event.key_code = scancode & 0xFF; rdp_event.key_event.extended = scancode & 0x100; rdp_event.key_event.extended1 = FALSE; remmina_rdp_event_event_push(gp, &rdp_event); keypress_list_add(gp, rdp_event); } } else { unicode_keyval = gdk_keyval_to_unicode(event->keyval); /* Decide when whe should send a keycode or a Unicode character. * - All non char keys (Shift, Alt, Super) should be sent as keycode * - Space should be sent as keycode (see issue #1364) * - All special keys (F1-F10, numeric pad, Home/End/Arrows/PgUp/PgDn/Insert/Delete) keycode * - All key pressed while Ctrl or Alt or Super is down are not decoded by gdk_keyval_to_unicode(), so send it as keycode * - All keycodes not translatable to unicode chars, as keycode * - The rest as Unicode char */ if (event->keyval >= 0xfe00 || // Arrows, Shift, Alt, Fn, num keypad… event->hardware_keycode == 0x41 || // Spacebar unicode_keyval == 0 || // Impossible to translate (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK | GDK_SUPER_MASK)) != 0 // A modifier not recognized by gdk_keyval_to_unicode() ) { scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->hardware_keycode); rdp_event.key_event.key_code = scancode & 0xFF; rdp_event.key_event.extended = scancode & 0x100; rdp_event.key_event.extended1 = FALSE; if (rdp_event.key_event.key_code) { remmina_rdp_event_event_push(gp, &rdp_event); keypress_list_add(gp, rdp_event); } } else { rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE; rdp_event.key_event.unicode_code = unicode_keyval; rdp_event.key_event.extended = false; rdp_event.key_event.extended1 = FALSE; remmina_rdp_event_event_push(gp, &rdp_event); keypress_list_add(gp, rdp_event); } } break; } return TRUE; } gboolean remmina_rdp_event_on_clipboard(GtkClipboard *gtkClipboard, GdkEvent *event, RemminaProtocolWidget *gp) { /* Signal handler for GTK clipboard owner-change */ TRACE_CALL(__func__); RemminaPluginRdpEvent rdp_event = { 0 }; CLIPRDR_FORMAT_LIST *pFormatList; GObject *new_owner; /* Usually "owner-change" is fired when a user presses "COPY" on the client * OR when this plugin calls gtk_clipboard_set_with_owner() * after receiving a RDP server format list in remmina_rdp_cliprdr_server_format_list() * In the latter case, we must ignore owner change */ REMMINA_PLUGIN_DEBUG("gp=%p: owner-change event received", gp); rfContext *rfi = GET_PLUGIN_DATA(gp); if (rfi) remmina_rdp_clipboard_abort_client_format_data_request(rfi); new_owner = gtk_clipboard_get_owner(gtkClipboard); if (new_owner != (GObject *)gp) { /* To do: avoid this when the new owner is another remmina protocol widget of * the same remmina application */ REMMINA_PLUGIN_DEBUG("gp=%p owner-change: new owner is different than me: new=%p me=%p", gp, new_owner, gp); REMMINA_PLUGIN_DEBUG("gp=%p owner-change: new owner is not me: Sending local clipboard format list to server.", gp, new_owner, gp); pFormatList = remmina_rdp_cliprdr_get_client_format_list(gp); rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST; rdp_event.clipboard_formatlist.pFormatList = pFormatList; remmina_rdp_event_event_push(gp, &rdp_event); } else { REMMINA_PLUGIN_DEBUG(" ... but I'm the owner!"); } return TRUE; } void remmina_rdp_event_init(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); gchar *s; gint flags; rfContext *rfi = GET_PLUGIN_DATA(gp); GtkClipboard *clipboard; RemminaFile *remminafile; gboolean disable_smooth_scrolling = FALSE; if (!rfi) return; remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); /* we get first the global preferences */ s = remmina_plugin_service->pref_get_value("rdp_disable_smooth_scrolling"); disable_smooth_scrolling = (s && s[0] == '1' ? TRUE : FALSE); g_free(s), s = NULL; /* Otherwise we use the connection profile specific setting */ disable_smooth_scrolling = remmina_plugin_service->file_get_int(remminafile, "disable-smooth-scrolling", disable_smooth_scrolling); REMMINA_PLUGIN_DEBUG("Disable smooth scrolling is set to %d", disable_smooth_scrolling); 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); if (!disable_smooth_scrolling) { REMMINA_PLUGIN_DEBUG("Adding GDK_SMOOTH_SCROLL_MASK"); gtk_widget_add_events(rfi->drawing_area, GDK_SMOOTH_SCROLL_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), s = NULL; /* Read special keymap from profile file, if exists */ remmina_rdp_event_init_keymap(rfi, remmina_plugin_service->pref_get_value("rdp_map_keycode")); if (rfi->use_client_keymap && rfi->keymap) fprintf(stderr, "RDP profile error: you cannot define both rdp_map_hardware_keycode and have 'Use client keyboard mapping' enabled\n"); g_signal_connect(G_OBJECT(rfi->drawing_area), "draw", G_CALLBACK(remmina_rdp_event_on_draw), gp); 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); /** Fixme: This comment * needed for TS_SUPPRESS_OUTPUT_PDU * But it works only when we stay in the same window mode, if we switch to * fullscreen, for instance, the object refernce is lost, so we loose these * events. */ //g_signal_connect(G_OBJECT(gtk_widget_get_toplevel(rfi->drawing_area)), "map-event", // G_CALLBACK(remmina_rdp_event_on_map), gp); //g_signal_connect(G_OBJECT(gtk_widget_get_toplevel(rfi->drawing_area)), "unmap-event", // G_CALLBACK(remmina_rdp_event_on_unmap), gp); if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE)) { clipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); rfi->clipboard.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(RemminaPluginRdpEvent)); rfi->event_queue = g_async_queue_new_full(g_free); rfi->ui_queue = g_async_queue_new(); pthread_mutex_init(&rfi->ui_queue_mutex, NULL); if (pipe(rfi->event_pipe)) { g_print("Error creating pipes.\n"); rfi->event_pipe[0] = -1; rfi->event_pipe[1] = -1; rfi->event_handle = NULL; } else { flags = fcntl(rfi->event_pipe[0], F_GETFL, 0); fcntl(rfi->event_pipe[0], F_SETFL, flags | O_NONBLOCK); rfi->event_handle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, rfi->event_pipe[0], WINPR_FD_READ); if (!rfi->event_handle) g_print("CreateFileDescriptorEvent() failed\n"); } rfi->object_table = g_hash_table_new_full(NULL, NULL, NULL, g_free); rfi->display = gdk_display_get_default(); #if GTK_CHECK_VERSION(3, 22, 0) GdkVisual *visual = gdk_screen_get_system_visual( gdk_display_get_default_screen(rfi->display)); rfi->bpp = gdk_visual_get_depth(visual); #else rfi->bpp = gdk_visual_get_best_depth(); #endif } void remmina_rdp_event_free_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *obj) { TRACE_CALL(__func__); switch (obj->type) { case REMMINA_RDP_UI_NOCODEC: free(obj->nocodec.bitmap); break; default: break; } g_free(obj); } void remmina_rdp_event_uninit(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpUiObject *ui; if (!rfi) return; /* unregister the clipboard monitor */ if (rfi->clipboard.clipboard_handler) { g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD)), rfi->clipboard.clipboard_handler); rfi->clipboard.clipboard_handler = 0; } if (rfi->delayed_monitor_layout_handler) { g_source_remove(rfi->delayed_monitor_layout_handler); rfi->delayed_monitor_layout_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) remmina_rdp_event_free_event(gp, ui); if (rfi->surface) { cairo_surface_mark_dirty(rfi->surface); cairo_surface_destroy(rfi->surface); rfi->surface = NULL; } g_hash_table_destroy(rfi->object_table); g_array_free(rfi->pressed_keys, TRUE); if (rfi->keymap) { g_array_free(rfi->keymap, TRUE); rfi->keymap = NULL; } g_async_queue_unref(rfi->event_queue); rfi->event_queue = NULL; g_async_queue_unref(rfi->ui_queue); rfi->ui_queue = NULL; pthread_mutex_destroy(&rfi->ui_queue_mutex); if (rfi->event_handle) { CloseHandle(rfi->event_handle); rfi->event_handle = NULL; } close(rfi->event_pipe[0]); close(rfi->event_pipe[1]); } static void remmina_rdp_event_create_cairo_surface(rfContext *rfi) { int stride; rdpGdi *gdi; if (!rfi) return; gdi = ((rdpContext *)rfi)->gdi; if (!gdi) return; if (rfi->surface) { cairo_surface_mark_dirty(rfi->surface); cairo_surface_destroy(rfi->surface); rfi->surface = NULL; } stride = cairo_format_stride_for_width(rfi->cairo_format, gdi->width); rfi->surface = cairo_image_surface_create_for_data((unsigned char *)gdi->primary_buffer, rfi->cairo_format, gdi->width, gdi->height, stride); cairo_surface_flush(rfi->surface); } void remmina_rdp_event_update_scale(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); gint width, height; rdpGdi *gdi; rfContext *rfi = GET_PLUGIN_DATA(gp); width = remmina_plugin_service->protocol_plugin_get_width(gp); height = remmina_plugin_service->protocol_plugin_get_height(gp); gdi = ((rdpContext *)rfi)->gdi; rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); /* See if we also must rellocate rfi->surface with different width and height, * this usually happens after a DesktopResize RDP event*/ if (rfi->surface && (cairo_image_surface_get_width(rfi->surface) != gdi->width || cairo_image_surface_get_height(rfi->surface) != gdi->height)) { /* Destroys and recreate rfi->surface with new width and height */ cairo_surface_mark_dirty(rfi->surface); cairo_surface_destroy(rfi->surface); rfi->surface = NULL; remmina_rdp_event_create_cairo_surface(rfi); } else if (rfi->surface == NULL) { remmina_rdp_event_create_cairo_surface(rfi); } /* Send gdi->width and gdi->height obtained from remote server to gp plugin, * so they will be saved when closing connection */ if (width != gdi->width) remmina_plugin_service->protocol_plugin_set_width(gp, gdi->width); if (height != gdi->height) remmina_plugin_service->protocol_plugin_set_height(gp, gdi->height); remmina_rdp_event_update_scale_factor(gp); if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) /* In scaled mode and autores mode, drawing_area will get its dimensions from its parent */ gtk_widget_set_size_request(rfi->drawing_area, -1, -1); else /* In non scaled mode, the plugins forces dimensions of drawing area */ gtk_widget_set_size_request(rfi->drawing_area, width, height); remmina_plugin_service->protocol_plugin_update_align(gp); } static void remmina_rdp_event_connected(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); rdpGdi *gdi; gdi = ((rdpContext *)rfi)->gdi; gtk_widget_realize(rfi->drawing_area); remmina_rdp_event_create_cairo_surface(rfi); gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, gdi->width, gdi->height); remmina_rdp_event_update_scale(gp); remmina_plugin_service->protocol_plugin_signal_connection_opened(gp); const gchar *host = freerdp_settings_get_string (rfi->clientContext.context.settings, FreeRDP_ServerHostname); // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname REMMINA_PLUGIN_AUDIT(_("Connected to %s via RDP"), host); } static void remmina_rdp_event_reconnect_progress(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); gdk_window_invalidate_rect(gtk_widget_get_window(rfi->drawing_area), NULL, TRUE); } static BOOL remmina_rdp_event_create_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); GdkPixbuf *pixbuf; rfContext *rfi = GET_PLUGIN_DATA(gp); rdpPointer *pointer = (rdpPointer *)ui->cursor.pointer; cairo_surface_t *surface; UINT8 *data = malloc(pointer->width * pointer->height * 4); if (!freerdp_image_copy_from_pointer_data( (BYTE *)data, PIXEL_FORMAT_BGRA32, pointer->width * 4, 0, 0, pointer->width, pointer->height, pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, pointer->lengthAndMask, pointer->xorBpp, &(ui->cursor.context->gdi->palette))) { free(data); return FALSE; } surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, pointer->width, pointer->height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pointer->width)); cairo_surface_flush(surface); pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, pointer->width, pointer->height); cairo_surface_mark_dirty(surface); cairo_surface_destroy(surface); free(data); ((rfPointer *)ui->cursor.pointer)->cursor = gdk_cursor_new_from_pixbuf(rfi->display, pixbuf, pointer->xPos, pointer->yPos); g_object_unref(pixbuf); return TRUE; } static void remmina_rdp_event_free_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); g_object_unref(ui->cursor.pointer->cursor); ui->cursor.pointer->cursor = NULL; } static BOOL remmina_rdp_event_set_pointer_position(RemminaProtocolWidget *gp, gint x, gint y) { TRACE_CALL(__func__); GdkWindow *w, *nw; gint nx, ny, wx, wy; #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *manager; #endif GdkDevice *dev; rfContext *rfi = GET_PLUGIN_DATA(gp); if (rfi == NULL) return FALSE; w = gtk_widget_get_window(rfi->drawing_area); #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat(gdk_display_get_default()); dev = gdk_seat_get_pointer(seat); #else manager = gdk_display_get_device_manager(gdk_display_get_default()); dev = gdk_device_manager_get_client_pointer(manager); #endif nw = gdk_device_get_window_at_position(dev, NULL, NULL); if (nw == w) { nx = 0; ny = 0; remmina_rdp_event_reverse_translate_pos_reverse(gp, x, y, &nx, &ny); gdk_window_get_root_coords(w, nx, ny, &wx, &wy); gdk_device_warp(dev, gdk_window_get_screen(w), wx, wy); } return TRUE; } static void remmina_rdp_event_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); switch (ui->cursor.type) { case REMMINA_RDP_POINTER_NEW: ui->retval = remmina_rdp_event_create_cursor(gp, ui) ? 1 : 0; break; case REMMINA_RDP_POINTER_FREE: remmina_rdp_event_free_cursor(gp, ui); break; case REMMINA_RDP_POINTER_SET: gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), ui->cursor.pointer->cursor); ui->retval = 1; break; case REMMINA_RDP_POINTER_SETPOS: ui->retval = remmina_rdp_event_set_pointer_position(gp, ui->pos.x, ui->pos.y) ? 1 : 0; break; case REMMINA_RDP_POINTER_NULL: gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR)); ui->retval = 1; break; case REMMINA_RDP_POINTER_DEFAULT: gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), NULL); ui->retval = 1; break; } } static void remmina_rdp_ui_event_update_scale(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); remmina_rdp_event_update_scale(gp); } void remmina_rdp_event_unfocus(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); if (!rfi || !rfi->connected || rfi->is_reconnecting) return; remmina_rdp_event_release_all_keys(gp); } static void remmina_rdp_ui_event_destroy_cairo_surface(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); cairo_surface_mark_dirty(rfi->surface); cairo_surface_destroy(rfi->surface); rfi->surface = NULL; } static void remmina_rdp_event_process_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); switch (ui->event.type) { case REMMINA_RDP_UI_EVENT_UPDATE_SCALE: remmina_rdp_ui_event_update_scale(gp, ui); break; case REMMINA_RDP_UI_EVENT_DESTROY_CAIRO_SURFACE: remmina_rdp_ui_event_destroy_cairo_surface(gp, ui); break; } } static void remmina_rdp_event_process_ui_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); switch (ui->type) { case REMMINA_RDP_UI_UPDATE_REGIONS: remmina_rdp_event_update_regions(gp, ui); break; case REMMINA_RDP_UI_CONNECTED: remmina_rdp_event_connected(gp, ui); break; case REMMINA_RDP_UI_RECONNECT_PROGRESS: remmina_rdp_event_reconnect_progress(gp, ui); break; case REMMINA_RDP_UI_CURSOR: remmina_rdp_event_cursor(gp, ui); break; case REMMINA_RDP_UI_CLIPBOARD: remmina_rdp_event_process_clipboard(gp, ui); break; case REMMINA_RDP_UI_EVENT: remmina_rdp_event_process_event(gp, ui); break; default: break; } } static gboolean remmina_rdp_event_process_ui_queue(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); RemminaPluginRdpUiObject *ui; pthread_mutex_lock(&rfi->ui_queue_mutex); ui = (RemminaPluginRdpUiObject *)g_async_queue_try_pop(rfi->ui_queue); if (ui) { pthread_mutex_lock(&ui->sync_wait_mutex); if (!rfi->thread_cancelled) remmina_rdp_event_process_ui_event(gp, ui); // Should we signal the caller thread to unlock ? if (ui->sync) { ui->complete = TRUE; pthread_cond_signal(&ui->sync_wait_cond); pthread_mutex_unlock(&ui->sync_wait_mutex); } else { remmina_rdp_event_free_event(gp, ui); } pthread_mutex_unlock(&rfi->ui_queue_mutex); return TRUE; } else { rfi->ui_handler = 0; pthread_mutex_unlock(&rfi->ui_queue_mutex); return FALSE; } } static void remmina_rdp_event_queue_ui(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); rfContext *rfi = GET_PLUGIN_DATA(gp); gboolean ui_sync_save; int oldcanceltype; if (!rfi || rfi->thread_cancelled) return; if (remmina_plugin_service->is_main_thread()) { remmina_rdp_event_process_ui_event(gp, ui); return; } pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldcanceltype); pthread_mutex_lock(&rfi->ui_queue_mutex); ui_sync_save = ui->sync; ui->complete = FALSE; if (ui_sync_save) { pthread_mutex_init(&ui->sync_wait_mutex, NULL); pthread_cond_init(&ui->sync_wait_cond, NULL); } ui->complete = FALSE; g_async_queue_push(rfi->ui_queue, ui); if (!rfi->ui_handler) rfi->ui_handler = IDLE_ADD((GSourceFunc)remmina_rdp_event_process_ui_queue, gp); if (ui_sync_save) { /* Wait for main thread function completion before returning */ pthread_mutex_lock(&ui->sync_wait_mutex); pthread_mutex_unlock(&rfi->ui_queue_mutex); while (!ui->complete) pthread_cond_wait(&ui->sync_wait_cond, &ui->sync_wait_mutex); pthread_cond_destroy(&ui->sync_wait_cond); pthread_mutex_destroy(&ui->sync_wait_mutex); } else { pthread_mutex_unlock(&rfi->ui_queue_mutex); } pthread_setcanceltype(oldcanceltype, NULL); } void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { ui->sync = FALSE; remmina_rdp_event_queue_ui(gp, ui); } int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); int retval; ui->sync = TRUE; remmina_rdp_event_queue_ui(gp, ui); retval = ui->retval; remmina_rdp_event_free_event(gp, ui); return retval; } void *remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui) { TRACE_CALL(__func__); void *rp; ui->sync = TRUE; remmina_rdp_event_queue_ui(gp, ui); rp = ui->retptr; remmina_rdp_event_free_event(gp, ui); return rp; }