summaryrefslogtreecommitdiffstats
path: root/plugins/rdp/rdp_event.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/rdp/rdp_event.c1494
1 files changed, 1494 insertions, 0 deletions
diff --git a/plugins/rdp/rdp_event.c b/plugins/rdp/rdp_event.c
new file mode 100644
index 0000000..19b349c
--- /dev/null
+++ b/plugins/rdp/rdp_event.c
@@ -0,0 +1,1494 @@
+/*
+ * 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>
+ * 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 <gdk/gdkkeysyms.h>
+#ifdef GDK_WINDOWING_X11
+#include <cairo/cairo-xlib.h>
+#else
+#include <cairo/cairo.h>
+#endif
+#include <freerdp/locale/keyboard.h>
+
+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;
+}