[gtk-vnc] Add a button to rotate the screen



commit 251e0e8bbc8b2632253b9d55cacf53b7a15bfe79
Author: Thomas Loimer <thomas loimer tuwien ac at>
Date:   Sat May 22 21:55:08 2021 +0200

    Add a button to rotate the screen
    
    On each click on the new menu item, the screen is rotated by additional
    90 degrees clockwise.

 examples/gvncviewer.c      |  13 +++
 src/libgtk-vnc_sym.version |   3 +
 src/vncdisplay.c           | 207 +++++++++++++++++++++++++++++++++++++++------
 src/vncdisplay.h           |   3 +
 4 files changed, 202 insertions(+), 24 deletions(-)
---
diff --git a/examples/gvncviewer.c b/examples/gvncviewer.c
index e1794b5..4f0bfe2 100644
--- a/examples/gvncviewer.c
+++ b/examples/gvncviewer.c
@@ -423,6 +423,13 @@ static void do_keep_aspect_ratio(GtkWidget *menu, GtkWidget *vncdisplay)
         vnc_display_set_keep_aspect_ratio(VNC_DISPLAY(vncdisplay), FALSE);
 }
 
+static void do_rotate(GtkWidget *menu G_GNUC_UNUSED, GtkWidget *vncdisplay)
+{
+    guint rotation = vnc_display_get_rotation(VNC_DISPLAY(vncdisplay));
+    vnc_display_set_rotation(VNC_DISPLAY(vncdisplay), rotation + 90u);
+}
+
+
 static void do_power_control(VncDisplay *vncdisplay, VncConnectionPowerAction action)
 {
     VncConnection *conn = vnc_display_get_connection(vncdisplay);
@@ -721,6 +728,7 @@ int main(int argc, char **argv)
     GtkWidget *smoothing;
     GtkWidget *keep_aspect_ratio;
     GtkWidget *resize;
+    GtkWidget *rotate;
     GtkWidget *showgrabkeydlg;
     GtkWidget *shutdown;
     GtkWidget *reboot;
@@ -800,6 +808,7 @@ int main(int argc, char **argv)
     smoothing = gtk_check_menu_item_new_with_mnemonic("Smooth scaling");
     keep_aspect_ratio = gtk_check_menu_item_new_with_mnemonic("Keep aspect ratio");
     resize = gtk_check_menu_item_new_with_mnemonic("Allow resizing");
+    rotate = gtk_menu_item_new_with_mnemonic("Rotate ↷");
 
     gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(scaling), TRUE);
     gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(smoothing), TRUE);
@@ -809,6 +818,7 @@ int main(int argc, char **argv)
     gtk_menu_shell_append(GTK_MENU_SHELL(submenu), scaling);
     gtk_menu_shell_append(GTK_MENU_SHELL(submenu), smoothing);
     gtk_menu_shell_append(GTK_MENU_SHELL(submenu), keep_aspect_ratio);
+    gtk_menu_shell_append(GTK_MENU_SHELL(submenu), rotate);
     gtk_menu_shell_append(GTK_MENU_SHELL(submenu), resize);
 
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(view), submenu);
@@ -892,6 +902,7 @@ int main(int argc, char **argv)
 
     vnc_display_set_scaling(VNC_DISPLAY(vnc), TRUE);
     vnc_display_set_allow_resize(VNC_DISPLAY(vnc), TRUE);
+    vnc_display_set_rotation(VNC_DISPLAY(vnc), 0u);
     vnc_display_set_lossy_encoding(VNC_DISPLAY(vnc), TRUE);
     vnc_display_set_zoom_level(VNC_DISPLAY(vnc), opt_zoom);
 
@@ -963,6 +974,8 @@ int main(int argc, char **argv)
                      G_CALLBACK(do_smoothing), vnc);
     g_signal_connect(keep_aspect_ratio, "toggled",
                      G_CALLBACK(do_keep_aspect_ratio), vnc);
+    g_signal_connect(rotate, "activate",
+                     G_CALLBACK(do_rotate), vnc);
     g_signal_connect(shutdown, "activate",
                      G_CALLBACK(do_shutdown), vnc);
     g_signal_connect(reboot, "activate",
diff --git a/src/libgtk-vnc_sym.version b/src/libgtk-vnc_sym.version
index 4446b20..3adda9d 100644
--- a/src/libgtk-vnc_sym.version
+++ b/src/libgtk-vnc_sym.version
@@ -100,6 +100,9 @@
     vnc_display_get_zoom_level;
     vnc_display_set_zoom_level;
 
+    vnc_display_get_rotation;
+    vnc_display_set_rotation;
+
   local:
       *;
 };
diff --git a/src/vncdisplay.c b/src/vncdisplay.c
index f67448a..ba02708 100644
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -87,6 +87,7 @@ struct _VncDisplayPrivate
     gboolean allow_resize;
     gboolean smoothing;
     gboolean keep_aspect_ratio;
+    guint rotation;
     guint zoom_level;
 
     GSList *preferable_auths;
@@ -123,6 +124,7 @@ enum
     PROP_ALLOW_RESIZE,
     PROP_SMOOTHING,
     PROP_KEEP_ASPECT_RATIO,
+    PROP_ROTATE,
     PROP_DEPTH,
     PROP_ZOOM_LEVEL,
     PROP_GRAB_KEYS,
@@ -232,6 +234,9 @@ vnc_display_get_property (GObject    *object,
         case PROP_KEEP_ASPECT_RATIO:
             g_value_set_boolean (value, vnc->priv->keep_aspect_ratio);
             break;
+        case PROP_ROTATE:
+            g_value_set_uint (value, vnc->priv->rotation);
+            break;
         case PROP_DEPTH:
             g_value_set_enum (value, vnc->priv->depth);
             break;
@@ -293,6 +298,9 @@ vnc_display_set_property (GObject      *object,
         case PROP_KEEP_ASPECT_RATIO:
             vnc_display_set_keep_aspect_ratio (vnc, g_value_get_boolean (value));
             break;
+        case PROP_ROTATE:
+            vnc_display_set_rotation (vnc, g_value_get_uint (value));
+            break;
         case PROP_DEPTH:
             vnc_display_set_depth (vnc, g_value_get_enum (value));
             break;
@@ -405,6 +413,9 @@ get_render_region_info(GtkWidget *widget,
 {
     VncDisplay *obj = VNC_DISPLAY(widget);
     VncDisplayPrivate *priv = obj->priv;
+    /*
+     * Width and height of the unscaled, but possibly rotated remote desktop. */
+    int rotwidth, rotheight;
 
     *winwidth = gdk_window_get_width(gtk_widget_get_window(widget));
     *winheight = gdk_window_get_height(gtk_widget_get_window(widget));
@@ -424,36 +435,44 @@ get_render_region_info(GtkWidget *widget,
     *fbwidth = vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb));
     *fbheight = vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb));
 
+    if (priv->rotation == 0u || priv->rotation == 180u) {
+        rotwidth = *fbwidth;
+        rotheight = *fbheight;
+    } else {
+        rotwidth = *fbheight;
+        rotheight = *fbwidth;
+    }
+
     if (priv->allow_scaling) {
         *offsetx = 0;
         *offsety = 0;
         *width = *winwidth;
         *height = *winheight;
-        *scalex = (double)*winwidth / (double)*fbwidth;
-        *scaley = (double)*winheight / (double)*fbheight;
+        *scalex = (double)*winwidth / (double)rotwidth;
+        *scaley = (double)*winheight / (double)rotheight;
 
         if (priv->keep_aspect_ratio) {
             if (*scalex > *scaley) {
                 *scalex = *scaley;
-                *width = *fbwidth * *scalex;
+                *width = rotwidth * *scalex;
                 *offsetx = (*winwidth - *width) / 2;
             } else if (*scalex < *scaley) {
                 *scaley = *scalex;
-                *height = *fbheight * *scaley;
+                *height = rotheight * *scaley;
                 *offsety = (*winheight - *height) / 2;
             }
         }
     } else {
-        if (*winwidth > *fbwidth) {
-            *offsetx = (*winwidth - *fbwidth) / 2;
-            *width = *fbwidth;
+        if (*winwidth > rotwidth) {
+            *offsetx = (*winwidth - rotwidth) / 2;
+            *width = rotwidth;
         } else {
             *offsetx = 0;
             *width = *winwidth;
         }
-        if (*winheight > *fbheight) {
-            *offsety = (*winheight - *fbheight) / 2;
-            *height = *fbheight;
+        if (*winheight > rotheight) {
+            *offsety = (*winheight - rotheight) / 2;
+            *height = rotheight;
         } else {
             *offsety = 0;
             *height = *winheight;
@@ -506,11 +525,46 @@ static gboolean draw_event(GtkWidget *widget, cairo_t *cr)
     /* Now render the remote desktop, scaling/offsetting
      * as needed */
     if (priv->fb) {
-        cairo_scale(cr, scalex, scaley);
+        cairo_matrix_t mtx = {0., 0., 0., 0., 0., 0.};
+        double source_surface_offsetx, source_surface_offsety;
+
+        switch (priv->rotation) {
+        case 0u:
+        default:
+            mtx.xx = scalex;
+            mtx.yy = scaley;
+            source_surface_offsetx = offsetx / scalex;
+            source_surface_offsety = offsety / scaley;
+            break;
+        case 90u:
+            mtx.yx = scaley;
+            mtx.xy = -scalex;
+            mtx.x0 = (double)winwidth;
+            source_surface_offsetx = offsety / scaley;
+            source_surface_offsety = offsetx / scalex;
+            break;
+        case 180u:
+            mtx.xx = -scalex;
+            mtx.yy = -scaley;
+            mtx.x0 = (double)winwidth;
+            mtx.y0 = (double)winheight;
+            source_surface_offsetx = offsetx / scalex;
+            source_surface_offsety = offsety / scaley;
+            break;
+        case 270u:
+            mtx.yx = -scaley;
+            mtx.xy = scalex;
+            mtx.y0 = (double)winheight;
+            source_surface_offsetx = offsety / scaley;
+            source_surface_offsety = offsetx / scalex;
+            break;
+        }
+        cairo_transform(cr, &mtx);
         cairo_set_source_surface(cr,
-                                 priv->fbCache,
-                                 offsetx / scalex,
-                                 offsety / scaley);
+                priv->fbCache,
+                source_surface_offsetx,
+                source_surface_offsety);
+
         if (!priv->smoothing) {
             cairo_pattern_set_filter(cairo_get_source(cr),
                                      CAIRO_FILTER_NEAREST);
@@ -840,8 +894,28 @@ static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
     motion->x -= offsetx;
     motion->y -= offsety;
 
-    motion->x /= scalex;
-    motion->y /= scaley;
+    /* The inverse transform of that applied in draw_event(). */
+    switch (priv->rotation) {
+        int tmp;
+    case 0u:
+    default:
+        motion->x /= scalex;
+        motion->y /= scaley;
+        break;
+    case 90u:
+        tmp = motion->x;
+        motion->x = motion->y / scaley;
+        motion->y = (width - tmp) / scalex;
+        break;
+    case 180u:
+        motion->x = (width - motion->x) / scalex;
+        motion->y = (height - motion->y) / scaley;
+        break;
+    case 270u:
+        tmp = motion->x;
+        motion->x = (height - motion->y) / scaley;
+        motion->y = tmp / scalex;
+    }
 
     /* Next adjust the real client pointer */
     if (!priv->absolute) {
@@ -881,7 +955,7 @@ static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
              * them to the boundaries. We don't want to actually
              * drop the events though, because even if the X coord
              * is out of bounds we want the server to see Y coord
-             * changes, and vica-verca. */
+             * changes, and vice-versa. */
             if (dx < 0)
                 dx = 0;
             if (dy < 0)
@@ -1269,7 +1343,9 @@ static void get_preferred_width(GtkWidget *widget,
         vnc_connection_is_initialized(priv->conn) &&
         priv->fb &&
         priv->force_size)
-        *defwidth = vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb));
+        *defwidth = (priv->rotation == 0u || priv->rotation == 180u) ?
+                        vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb)) :
+                        vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb));
     else
         *defwidth = 0;
 
@@ -1290,7 +1366,9 @@ static void get_preferred_height(GtkWidget *widget,
         vnc_connection_is_initialized(priv->conn) &&
         priv->fb &&
         priv->force_size)
-        *defheight = vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb));
+        *defheight = (priv->rotation == 0u || priv->rotation == 180u) ?
+                        vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb)) :
+                        vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb));
     else
         *defheight = 0;
 
@@ -1336,10 +1414,39 @@ static void on_framebuffer_update(VncConnection *conn G_GNUC_UNUSED,
         cairo_destroy(cr);
     }
 
-    x *= scalex;
-    y *= scaley;
-    w *= scalex;
-    h *= scaley;
+    switch (priv->rotation) {
+        /* This repeates the same transformation as in draw_event() above. */
+        /* To put cairo_matrix_t mtx into struct priv, and use here? */
+        int tmp;
+    case 0u:
+    default:
+        x *= scalex;
+        y *= scaley;
+        w *= scalex;
+        h *= scaley;
+        break;
+    case 90u:
+        tmp = x;
+        x = (fbheight - y - h) * scalex;
+        y = tmp * scaley;
+        tmp = w;
+        w = h * scalex;
+        h = tmp * scaley;
+        break;
+    case 180u:
+        x = (fbwidth - x - w) * scalex;
+        y = (fbheight - y - h) * scaley;
+        w *= scalex;
+        h *= scaley;
+        break;
+    case 270u:
+        tmp = x;
+        x = y * scalex;
+        y = (fbwidth - tmp - w) * scaley;
+        tmp = w;
+        w = h * scalex;
+        h = tmp * scaley;
+    }
     x += offsetx;
     y += offsety;
 
@@ -2471,6 +2578,20 @@ static void vnc_display_class_init(VncDisplayClass *klass)
                                                             G_PARAM_STATIC_NICK |
                                                             G_PARAM_STATIC_BLURB));
 
+    g_object_class_install_property (object_class,
+                                     PROP_ROTATE,
+                                     g_param_spec_uint ( "rotation",
+                                                          "Rotate +90° clockwise",
+                                                          "Rotate the image of the remote desktop 90° 
clockwise",
+                                                          0,
+                                                          270,
+                                                          0,
+                                                          G_PARAM_READWRITE |
+                                                          G_PARAM_CONSTRUCT |
+                                                          G_PARAM_STATIC_NAME |
+                                                          G_PARAM_STATIC_NICK |
+                                                          G_PARAM_STATIC_BLURB));
+
     g_object_class_install_property (object_class,
                                      PROP_DEPTH,
                                      g_param_spec_enum    ( "depth",
@@ -2753,6 +2874,7 @@ static void vnc_display_init(VncDisplay *display)
     priv->allow_resize = FALSE;
     priv->smoothing = TRUE;
     priv->keep_aspect_ratio = FALSE;
+    priv->rotation = 0u;
     priv->zoom_level = 100;
     priv->vncgrabseq = vnc_grab_sequence_new_from_string("Control_L+Alt_L");
     priv->vncactiveseq = g_new0(gboolean, priv->vncgrabseq->nkeysyms);
@@ -3218,7 +3340,7 @@ void vnc_display_set_force_size(VncDisplay *obj, gboolean enable)
  * Set whether changes in the widget size will be translated
  * into requests to resize the remote desktop. Resizing of
  * the remote desktop is not guaranteed to be honoured by
- * servers, but when it is, it will avoid the need todo
+ * servers, but when it is, it will avoid the need to do
  * scaling.
  */
 void vnc_display_set_allow_resize(VncDisplay *obj, gboolean enable)
@@ -3286,6 +3408,27 @@ void vnc_display_set_keep_aspect_ratio(VncDisplay *obj, gboolean enable)
 }
 
 
+/**
+ * vnc_display_set_rotation:
+ * @obj: (transfer none): the VNC display widget
+ * @rotation: The angle of rotation, degrees clockwise.
+ *
+ * Set the rotation angle to view the display of the remote desktop, in
+ * clockwise direction.
+ */
+void vnc_display_set_rotation(VncDisplay *obj, guint rotation)
+{
+    int ww, wh;
+
+    g_return_if_fail (VNC_IS_DISPLAY (obj));
+    obj->priv->rotation = rotation % 360u;
+
+    if (obj->priv->fb != NULL) {
+        gtk_widget_queue_resize(GTK_WIDGET(obj));
+    }
+}
+
+
 /**
  * vnc_display_set_depth:
  * @obj: (transfer none): the VNC display widget
@@ -3395,6 +3538,22 @@ gboolean vnc_display_get_keep_aspect_ratio(VncDisplay *obj)
 }
 
 
+/**
+ * vnc_display_get_rotation:
+ * @obj: (transfer none): the VNC display widget
+ *
+ * Determine the current rotation angle of the remote desktop.
+ *
+ * Returns: the rotation angle in clockwise direction
+ */
+guint vnc_display_get_rotation(VncDisplay *obj)
+{
+    g_return_val_if_fail (VNC_IS_DISPLAY (obj), 0u);
+
+    return obj->priv->rotation;
+}
+
+
 /**
  * vnc_display_get_scaling:
  * @obj: (transfer none): the VNC display widget
diff --git a/src/vncdisplay.h b/src/vncdisplay.h
index 0f1120e..7d607e9 100644
--- a/src/vncdisplay.h
+++ b/src/vncdisplay.h
@@ -143,6 +143,9 @@ gboolean vnc_display_get_smoothing(VncDisplay *obj);
 void vnc_display_set_keep_aspect_ratio(VncDisplay *obj, gboolean enable);
 gboolean vnc_display_get_keep_aspect_ratio(VncDisplay *obj);
 
+void vnc_display_set_rotation(VncDisplay *obj, guint rotation);
+guint vnc_display_get_rotation(VncDisplay *obj);
+
 void vnc_display_set_shared_flag(VncDisplay *obj, gboolean shared);
 gboolean vnc_display_get_shared_flag(VncDisplay *obj);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]