From 72da5fc3a24d531cc4de31238737ff75db06a215 Mon Sep 17 00:00:00 2001 From: Marek Kasik Date: Fri, 4 Sep 2015 16:25:54 +0200 Subject: [PATCH] Allow scaling of RDP sessions Add ability to scale RDP sessions using scaling functionality of cairo library. Session is placed in the center of the window as in other plugins. https://bugzilla.gnome.org/show_bug.cgi?id=753766 --- Makefile.am | 2 + plugins/rdp/vinagre-rdp-connection.c | 118 +++++++++++++++++- plugins/rdp/vinagre-rdp-connection.h | 6 +- plugins/rdp/vinagre-rdp-plugin.c | 16 ++- plugins/rdp/vinagre-rdp-tab.c | 224 +++++++++++++++++++++++++++++++++-- 5 files changed, 355 insertions(+), 11 deletions(-) diff --git a/Makefile.am b/Makefile.am index 09ff61e..6c1ba2d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,8 @@ vinagre_vinagre_SOURCES += \ plugins/rdp/vinagre-rdp-plugin.c \ plugins/rdp/vinagre-rdp-connection.c \ plugins/rdp/vinagre-rdp-tab.c + +vinagre_vinagre_LDADD += -lm endif if VINAGRE_ENABLE_SPICE diff --git a/plugins/rdp/vinagre-rdp-connection.c b/plugins/rdp/vinagre-rdp-connection.c index 66cf01f..f0ff02b 100644 --- a/plugins/rdp/vinagre-rdp-connection.c +++ b/plugins/rdp/vinagre-rdp-connection.c @@ -23,9 +23,17 @@ #include #include "vinagre-rdp-connection.h" +#include "vinagre-vala.h" + struct _VinagreRdpConnectionPrivate { - gint dummy; + gboolean scaling; +}; + +enum +{ + PROP_0, + PROP_SCALING, }; #define VINAGRE_RDP_CONNECTION_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), VINAGRE_TYPE_RDP_CONNECTION, VinagreRdpConnectionPrivate)) @@ -44,15 +52,76 @@ vinagre_rdp_connection_constructed (GObject *object) } static void +vinagre_rdp_connection_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + VinagreRdpConnection *conn; + + g_return_if_fail (VINAGRE_IS_RDP_CONNECTION (object)); + + conn = VINAGRE_RDP_CONNECTION (object); + + switch (prop_id) + { + case PROP_SCALING: + vinagre_rdp_connection_set_scaling (conn, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +vinagre_rdp_connection_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + VinagreRdpConnection *conn; + + g_return_if_fail (VINAGRE_IS_RDP_CONNECTION (object)); + + conn = VINAGRE_RDP_CONNECTION (object); + + switch (prop_id) + { + case PROP_SCALING: + g_value_set_boolean (value, conn->priv->scaling); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void rdp_fill_writer (VinagreConnection *conn, xmlTextWriter *writer) { + VinagreRdpConnection *rdp_conn = VINAGRE_RDP_CONNECTION (conn); VINAGRE_CONNECTION_CLASS (vinagre_rdp_connection_parent_class)->impl_fill_writer (conn, writer); + + xmlTextWriterWriteFormatElement (writer, BAD_CAST "scaling", "%d", rdp_conn->priv->scaling); } static void rdp_parse_item (VinagreConnection *conn, xmlNode *root) { + xmlNode *curr; + xmlChar *s_value; + VinagreRdpConnection *rdp_conn = VINAGRE_RDP_CONNECTION (conn); + VINAGRE_CONNECTION_CLASS (vinagre_rdp_connection_parent_class)->impl_parse_item (conn, root); + + for (curr = root->children; curr; curr = curr->next) + { + s_value = xmlNodeGetContent (curr); + + if (!xmlStrcmp(curr->name, BAD_CAST "scaling")) + { + vinagre_rdp_connection_set_scaling (rdp_conn, vinagre_utils_parse_boolean ((const gchar *) s_value)); + } + + xmlFree (s_value); + } } static void @@ -118,6 +187,22 @@ rdp_parse_options_widget (VinagreConnection *conn, GtkWidget *widget) vinagre_cache_prefs_set_integer ("rdp-connection", "height", height); vinagre_connection_set_height (conn, height); + + + scaling_button = g_object_get_data (G_OBJECT (widget), "scaling"); + if (!scaling_button) + { + g_warning ("Wrong widget passed to rdp_parse_options_widget()"); + return; + } + + scaling = (gboolean) gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scaling_button)); + + vinagre_cache_prefs_set_boolean ("rdp-connection", "scaling", scaling); + + g_object_set (conn, + "scaling", scaling, + NULL); } static void @@ -128,11 +213,24 @@ vinagre_rdp_connection_class_init (VinagreRdpConnectionClass *klass) g_type_class_add_private (klass, sizeof (VinagreRdpConnectionPrivate)); + object_class->set_property = vinagre_rdp_connection_set_property; + object_class->get_property = vinagre_rdp_connection_get_property; object_class->constructed = vinagre_rdp_connection_constructed; parent_class->impl_fill_writer = rdp_fill_writer; parent_class->impl_parse_item = rdp_parse_item; parent_class->impl_parse_options_widget = rdp_parse_options_widget; + + g_object_class_install_property (object_class, + PROP_SCALING, + g_param_spec_boolean ("scaling", + "Use scaling", + "Whether to use scaling on this connection", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + } VinagreConnection * @@ -141,4 +239,22 @@ vinagre_rdp_connection_new (void) return VINAGRE_CONNECTION (g_object_new (VINAGRE_TYPE_RDP_CONNECTION, NULL)); } +void +vinagre_rdp_connection_set_scaling (VinagreRdpConnection *conn, + gboolean scaling) +{ + g_return_if_fail (VINAGRE_IS_RDP_CONNECTION (conn)); + + conn->priv->scaling = scaling; +} + +gboolean +vinagre_rdp_connection_get_scaling (VinagreRdpConnection *conn) +{ + g_return_val_if_fail (VINAGRE_IS_RDP_CONNECTION (conn), FALSE); + + return conn->priv->scaling; +} + + /* vim: set ts=8: */ diff --git a/plugins/rdp/vinagre-rdp-connection.h b/plugins/rdp/vinagre-rdp-connection.h index b9e48be..b96fb1b 100644 --- a/plugins/rdp/vinagre-rdp-connection.h +++ b/plugins/rdp/vinagre-rdp-connection.h @@ -51,7 +51,11 @@ struct _VinagreRdpConnection GType vinagre_rdp_connection_get_type (void) G_GNUC_CONST; -VinagreConnection* vinagre_rdp_connection_new (void); +VinagreConnection* vinagre_rdp_connection_new (void); + +gboolean vinagre_rdp_connection_get_scaling (VinagreRdpConnection *conn); +void vinagre_rdp_connection_set_scaling (VinagreRdpConnection *conn, + gboolean scaling); G_END_DECLS diff --git a/plugins/rdp/vinagre-rdp-plugin.c b/plugins/rdp/vinagre-rdp-plugin.c index dae9ed3..4751102 100644 --- a/plugins/rdp/vinagre-rdp-plugin.c +++ b/plugins/rdp/vinagre-rdp-plugin.c @@ -115,16 +115,28 @@ impl_get_connect_widget (VinagreProtocol *plugin, VinagreConnection *conn) gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + + /* Scaling check button */ + check = gtk_check_button_new_with_mnemonic (_("_Scaling")); + g_object_set_data (G_OBJECT (grid), "scaling", check); + gtk_widget_set_margin_left (check, 12); + gtk_grid_attach (GTK_GRID (grid), check, 0, 1, 1, 1); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), + VINAGRE_IS_CONNECTION (conn) ? + vinagre_rdp_connection_get_scaling (VINAGRE_RDP_CONNECTION (conn)) : + vinagre_cache_prefs_get_boolean ("rdp-connection", "scaling", FALSE)); + + label = gtk_label_new_with_mnemonic (_("_Username:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); + gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1); gtk_widget_set_margin_left (label, 12); u_entry = gtk_entry_new (); /* Translators: This is the tooltip for the username field in a RDP connection */ gtk_widget_set_tooltip_text (u_entry, _("Optional. If blank, your username will be used. Also, it can be supplied in the Host field above, in the form username@hostname.")); g_object_set_data (G_OBJECT (grid), "username_entry", u_entry); - gtk_grid_attach (GTK_GRID (grid), u_entry, 1, 1, 1, 1); + gtk_grid_attach (GTK_GRID (grid), u_entry, 1, 2, 1, 1); gtk_label_set_mnemonic_widget (GTK_LABEL (label), u_entry); str = g_strdup (VINAGRE_IS_CONNECTION (conn) ? vinagre_connection_get_username (conn) : diff --git a/plugins/rdp/vinagre-rdp-tab.c b/plugins/rdp/vinagre-rdp-tab.c index 90eea1c..e9a4623 100644 --- a/plugins/rdp/vinagre-rdp-tab.c +++ b/plugins/rdp/vinagre-rdp-tab.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,9 @@ struct _VinagreRdpTabPrivate GSList *connected_actions; double scale; + gboolean scaling; + GtkWidget *scaling_button; + GtkAction *scaling_action; double offset_x, offset_y; guint authentication_attempts; @@ -74,6 +78,11 @@ struct _VinagreRdpTabPrivate G_DEFINE_TYPE (VinagreRdpTab, vinagre_rdp_tab, VINAGRE_TYPE_TAB) static void open_freerdp (VinagreRdpTab *rdp_tab); +static void setup_toolbar (VinagreRdpTab *rdp_tab); +static void vinagre_rdp_tab_set_scaling (VinagreRdpTab *tab, + gboolean scaling); +static void scaling_button_clicked (GtkToggleToolButton *button, + VinagreRdpTab *rdp_tab); struct frdp_context { @@ -135,12 +144,42 @@ free_frdpEvent (gpointer event, } static void +view_scaling_cb (GtkAction *action, + VinagreRdpTab *rdp_tab) +{ + VinagreRdpTabPrivate *priv = rdp_tab->priv; + gboolean scaling; + + scaling = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + + vinagre_rdp_tab_set_scaling (rdp_tab, scaling); + + g_signal_handlers_block_by_func (priv->scaling_button, scaling_button_clicked, rdp_tab); + gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (priv->scaling_button), scaling); + g_signal_handlers_unblock_by_func (priv->scaling_button, scaling_button_clicked, rdp_tab); +} + +const static GSList * +rdp_get_connected_actions (VinagreTab *tab) +{ + VinagreRdpTab *rdp_tab = VINAGRE_RDP_TAB (tab); + + return rdp_tab->priv->connected_actions; +} + +static void vinagre_rdp_tab_dispose (GObject *object) { VinagreRdpTab *rdp_tab = VINAGRE_RDP_TAB (object); VinagreRdpTabPrivate *priv = rdp_tab->priv; GtkWindow *window = GTK_WINDOW (vinagre_tab_get_window (VINAGRE_TAB (rdp_tab))); + if (priv->connected_actions) + { + vinagre_tab_free_actions (priv->connected_actions); + priv->connected_actions = NULL; + } + if (priv->freerdp_session) { gdi_free (priv->freerdp_session); @@ -202,6 +241,7 @@ vinagre_rdp_tab_constructed (GObject *object) if (G_OBJECT_CLASS (vinagre_rdp_tab_parent_class)->constructed) G_OBJECT_CLASS (vinagre_rdp_tab_parent_class)->constructed (object); + setup_toolbar (rdp_tab); open_freerdp (rdp_tab); } @@ -215,6 +255,7 @@ vinagre_rdp_tab_class_init (VinagreRdpTabClass *klass) object_class->dispose = vinagre_rdp_tab_dispose; tab_class->impl_get_tooltip = rdp_tab_get_tooltip; + tab_class->impl_get_connected_actions = rdp_get_connected_actions; g_type_class_add_private (object_class, sizeof (VinagreRdpTabPrivate)); } @@ -227,12 +268,110 @@ idle_close (VinagreTab *tab) return FALSE; } +static GSList * +create_connected_actions (VinagreRdpTab *tab) +{ + GSList *list = NULL; + VinagreTabUiAction *action; + + /* View->Scaling */ + action = g_slice_new (VinagreTabUiAction); + action->paths = g_new (gchar *, 3); + action->paths[0] = g_strdup ("/MenuBar/ViewMenu"); + action->paths[1] = g_strdup ("/ToolBar"); + action->paths[2] = NULL; + action->action = GTK_ACTION (gtk_toggle_action_new ("RDPViewScaling", + _("S_caling"), + _("Fit the remote screen into the current window size"), + "zoom-fit-best")); + gtk_action_set_icon_name (action->action, "zoom-fit-best"); + g_signal_connect (action->action, "activate", G_CALLBACK (view_scaling_cb), tab); + list = g_slist_append (list, action); + tab->priv->scaling_action = action->action; + + return list; +} + +static void +scaling_button_clicked (GtkToggleToolButton *button, + VinagreRdpTab *rdp_tab) +{ + vinagre_rdp_tab_set_scaling (rdp_tab, + gtk_toggle_tool_button_get_active (button)); +} + +static void +vinagre_rdp_tab_set_scaling (VinagreRdpTab *tab, + gboolean scaling) +{ + VinagreRdpTabPrivate *priv = tab->priv; + VinagreConnection *conn = vinagre_tab_get_conn (VINAGRE_TAB (tab)); + GtkWidget *scrolled; + gint window_width, window_height; + + priv->scaling = scaling; + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->scaling_action), + priv->scaling); + + if (scaling) + { + scrolled = gtk_widget_get_ancestor (priv->display, GTK_TYPE_SCROLLED_WINDOW); + window_width = gtk_widget_get_allocated_width (scrolled); + window_height = gtk_widget_get_allocated_height (scrolled); + + gtk_widget_set_size_request (priv->display, + window_width, + window_height); + + gtk_widget_set_halign (priv->display, GTK_ALIGN_FILL); + gtk_widget_set_valign (priv->display, GTK_ALIGN_FILL); + } + else + { + gtk_widget_set_size_request (priv->display, + vinagre_connection_get_width (VINAGRE_CONNECTION (conn)), + vinagre_connection_get_height (VINAGRE_CONNECTION (conn))); + gtk_widget_set_halign (priv->display, GTK_ALIGN_CENTER); + gtk_widget_set_valign (priv->display, GTK_ALIGN_CENTER); + } + + gtk_widget_queue_draw_area (priv->display, 0, 0, + gtk_widget_get_allocated_width (priv->display), + gtk_widget_get_allocated_height (priv->display)); +} + +static void +setup_toolbar (VinagreRdpTab *rdp_tab) +{ + GtkWidget *toolbar = vinagre_tab_get_toolbar (VINAGRE_TAB (rdp_tab)); + GtkWidget *button; + + /* Space */ + button = GTK_WIDGET (gtk_separator_tool_item_new ()); + gtk_tool_item_set_expand (GTK_TOOL_ITEM (button), TRUE); + gtk_widget_show (GTK_WIDGET (button)); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1); + + /* Scaling */ + button = GTK_WIDGET (gtk_toggle_tool_button_new ()); + gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), _("Scaling")); + gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Scaling")); + gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "zoom-fit-best"); + gtk_widget_show (GTK_WIDGET (button)); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1); + g_signal_connect (button, "toggled", G_CALLBACK (scaling_button_clicked), rdp_tab); + rdp_tab->priv->scaling_button = button; +} static void frdp_process_events (freerdp *instance, GQueue *events) { - frdpEvent *event; + VinagreRdpTab *rdp_tab = ((frdpContext *) instance->context)->rdp_tab; + VinagreRdpTabPrivate *priv = rdp_tab->priv; + frdpEvent *event; + gint x, y; while (!g_queue_is_empty (events)) { @@ -247,10 +386,27 @@ frdp_process_events (freerdp *instance, ((frdpEventKey *) event)->code); break; case FRDP_EVENT_TYPE_BUTTON: + if (priv->scaling) + { + x = (((frdpEventButton *) event)->x - priv->offset_x) / priv->scale; + y = (((frdpEventButton *) event)->y - priv->offset_y) / priv->scale; + } + else + { + x = ((frdpEventButton *) event)->x; + y = ((frdpEventButton *) event)->y; + } + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + instance->input->MouseEvent (instance->input, ((frdpEventButton *) event)->flags, - ((frdpEventButton *) event)->x, - ((frdpEventButton *) event)->y); + x, + y); break; default: break; @@ -268,10 +424,44 @@ frdp_drawing_area_draw (GtkWidget *area, { VinagreRdpTab *rdp_tab = (VinagreRdpTab *) user_data; VinagreRdpTabPrivate *priv = rdp_tab->priv; + VinagreRdpConnection *conn = VINAGRE_RDP_CONNECTION (vinagre_tab_get_conn (VINAGRE_TAB (rdp_tab))); + GtkWidget *scrolled; + double scale_x, scale_y; + gint window_width, window_height; if (priv->surface == NULL) return FALSE; + if (priv->scaling) + { + scrolled = gtk_widget_get_ancestor (area, GTK_TYPE_SCROLLED_WINDOW); + window_width = gtk_widget_get_allocated_width (scrolled); + window_height = gtk_widget_get_allocated_height (scrolled); + + scale_x = (double) window_width / vinagre_connection_get_width (VINAGRE_CONNECTION (conn)); + scale_y = (double) window_height / vinagre_connection_get_height (VINAGRE_CONNECTION (conn)); + + priv->scale = scale_x < scale_y ? scale_x : scale_y; + + priv->offset_x = (window_width - vinagre_connection_get_width (VINAGRE_CONNECTION (conn)) * priv->scale) / 2.0; + priv->offset_y = (window_height - vinagre_connection_get_height (VINAGRE_CONNECTION (conn)) * priv->scale) / 2.0; + + if (priv->offset_x < 0) + priv->offset_x = 0; + + if (priv->offset_y < 0) + priv->offset_y = 0; + + cairo_translate (cr, priv->offset_x, priv->offset_y); + cairo_scale (cr, priv->scale, priv->scale); + + if (window_width != gtk_widget_get_allocated_width (area) || + window_height != gtk_widget_get_allocated_height (area)) + gtk_widget_set_size_request (area, + window_width, + window_height); + } + cairo_set_source_surface (cr, priv->surface, 0, 0); cairo_paint (cr); @@ -293,6 +483,7 @@ frdp_end_paint (rdpContext *context) VinagreRdpTab *rdp_tab = ((frdpContext *) context)->rdp_tab; VinagreRdpTabPrivate *priv = rdp_tab->priv; rdpGdi *gdi = context->gdi; + double pos_x, pos_y; gint x, y, w, h; if (gdi->primary->hdc->hwnd->invalid->null) @@ -303,7 +494,21 @@ frdp_end_paint (rdpContext *context) w = gdi->primary->hdc->hwnd->invalid->w; h = gdi->primary->hdc->hwnd->invalid->h; - gtk_widget_queue_draw_area (priv->display, x, y, w, h); + if (priv->scaling) + { + pos_x = priv->offset_x + x * priv->scale; + pos_y = priv->offset_y + y * priv->scale; + + gtk_widget_queue_draw_area (priv->display, + floor (pos_x), + floor (pos_y), + ceil (pos_x + w * priv->scale) - floor (pos_x), + ceil (pos_y + h * priv->scale) - floor (pos_y)); + } + else + { + gtk_widget_queue_draw_area (priv->display, x, y, w, h); + } } static BOOL @@ -817,12 +1022,14 @@ init_freerdp (VinagreRdpTab *rdp_tab) gchar *hostname; gint width, height; gint port; + gboolean scaling; g_object_get (conn, "port", &port, "host", &hostname, "width", &width, "height", &height, + "scaling", &scaling, NULL); /* Setup FreeRDP session */ @@ -899,21 +1106,20 @@ init_display (VinagreRdpTab *rdp_tab) VinagreTab *tab = VINAGRE_TAB (rdp_tab); VinagreConnection *conn = vinagre_tab_get_conn (tab); GtkWindow *window = GTK_WINDOW (vinagre_tab_get_window (tab)); - gboolean fullscreen; + gboolean fullscreen, scaling; gint width, height; g_object_get (conn, "width", &width, "height", &height, "fullscreen", &fullscreen, + "scaling", &scaling, NULL); /* Setup display for FreeRDP session */ priv->display = gtk_drawing_area_new (); if (priv->display) { - gtk_widget_set_size_request (priv->display, width, height); - g_signal_connect (priv->display, "draw", G_CALLBACK (frdp_drawing_area_draw), rdp_tab); @@ -946,6 +1152,8 @@ init_display (VinagreRdpTab *rdp_tab) if (fullscreen) gtk_window_fullscreen (window); + + vinagre_rdp_tab_set_scaling (rdp_tab, scaling); } priv->key_press_handler_id = g_signal_connect (window, "key-press-event", @@ -1007,6 +1215,8 @@ static void vinagre_rdp_tab_init (VinagreRdpTab *rdp_tab) { rdp_tab->priv = VINAGRE_RDP_TAB_GET_PRIVATE (rdp_tab); + + rdp_tab->priv->connected_actions = create_connected_actions (rdp_tab); } GtkWidget * -- 2.5.5