gtk: Add suspended window state
authorJonas Ådahl <jadahl@gmail.com>
Wed, 24 May 2023 14:22:42 +0000 (16:22 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Sun, 30 Jul 2023 08:40:09 +0000 (11:40 +0300)
This is implemented using a new xdg_toplevel `suspended` state, and is
meant for allowing applications to know when they can stop doing
unnecessary work and thus save power.

In the other backends, the `suspended` state is set at the same time as
`minimized` as it's the closest there is to traditional windowing
systems.

12 files changed:
gdk/gdktoplevel.h
gdk/macos/GdkMacosWindow.c
gdk/wayland/gdktoplevel-wayland.c
gdk/win32/gdkevents-win32.c
gdk/x11/gdkdisplay-x11.c
gdk/x11/gdksurface-x11.c
gtk/gtkwindow.c
gtk/gtkwindow.h
meson.build
tests/meson.build
tests/testgtk.c
tests/testsuspended.c [new file with mode: 0644]

index 17b48802df18a7fc7b340bbd87d4893e9ece6d3d..3d220c4ba41e481e2221f807ce84d5f1329a0e9b 100644 (file)
@@ -85,6 +85,7 @@ typedef enum
  * @GDK_TOPLEVEL_STATE_BOTTOM_RESIZABLE: whether the bottom edge is resizable
  * @GDK_TOPLEVEL_STATE_LEFT_TILED: whether the left edge is tiled
  * @GDK_TOPLEVEL_STATE_LEFT_RESIZABLE: whether the left edge is resizable
+ * @GDK_TOPLEVEL_STATE_SUSPENDED: the surface is not visible to the user
  *
  * Specifies the state of a toplevel surface.
  *
@@ -111,7 +112,8 @@ typedef enum
   GDK_TOPLEVEL_STATE_BOTTOM_TILED     = 1 << 12,
   GDK_TOPLEVEL_STATE_BOTTOM_RESIZABLE = 1 << 13,
   GDK_TOPLEVEL_STATE_LEFT_TILED       = 1 << 14,
-  GDK_TOPLEVEL_STATE_LEFT_RESIZABLE   = 1 << 15
+  GDK_TOPLEVEL_STATE_LEFT_RESIZABLE   = 1 << 15,
+  GDK_TOPLEVEL_STATE_SUSPENDED        = 1 << 16
 } GdkToplevelState;
 
 /**
index 0636018c9388aa97624132ad7c2ad6e4690271ef..16da2503624e72bd8fb4d938c8368af07e9301ca 100644 (file)
@@ -73,7 +73,10 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)windowDidMiniaturize:(NSNotification *)aNotification
 {
-  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_TOPLEVEL_STATE_MINIMIZED);
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface),
+                                0,
+                                GDK_TOPLEVEL_STATE_MINIMIZED |
+                                GDK_TOPLEVEL_STATE_SUSPENDED);
 }
 
 -(void)windowDidDeminiaturize:(NSNotification *)aNotification
@@ -83,7 +86,10 @@ typedef NSString *CALayerContentsGravity;
   else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
     _gdk_macos_popup_surface_attach_to_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
 
-  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_TOPLEVEL_STATE_MINIMIZED, 0);
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface),
+                                GDK_TOPLEVEL_STATE_MINIMIZED |
+                                GDK_TOPLEVEL_STATE_SUSPENDED,
+                                0);
 }
 
 -(void)windowDidBecomeKey:(NSNotification *)aNotification
index efe9ef86cef79a8716e337b1ac0697a3be7497aa..e463b976865563e848463815bbe649fd5bfeec57 100644 (file)
@@ -620,6 +620,9 @@ xdg_toplevel_configure (void                *data,
           pending_state |= (GDK_TOPLEVEL_STATE_TILED |
                             GDK_TOPLEVEL_STATE_LEFT_TILED);
           break;
+        case XDG_TOPLEVEL_STATE_SUSPENDED:
+          pending_state |= GDK_TOPLEVEL_STATE_SUSPENDED;
+          break;
         default:
           /* Unknown state */
           break;
index 9562b664aec6ed78759ab05c1a553a7e1e1e9b26..392bf2335a24dfe96c34753fd6e77ad44172c874 100644 (file)
@@ -3014,9 +3014,11 @@ gdk_event_translate (MSG *msg,
          unset_bits = 0;
 
          if (IsIconic (msg->hwnd))
-           set_bits |= GDK_TOPLEVEL_STATE_MINIMIZED;
+           set_bits |= (GDK_TOPLEVEL_STATE_MINIMIZED |
+                         GDK_TOPLEVEL_STATE_SUSPENDED);
          else
-           unset_bits |= GDK_TOPLEVEL_STATE_MINIMIZED;
+           unset_bits |= (GDK_TOPLEVEL_STATE_MINIMIZED |
+                           GDK_TOPLEVEL_STATE_SUSPENDED);
 
          if (IsZoomed (msg->hwnd))
            set_bits |= GDK_TOPLEVEL_STATE_MAXIMIZED;
index 4dec10d1de4b087834db40f1d66c9fee4e60487c..a2afd1ac74bf2aa6c90abbe931c1bbbb042ba2c6 100644 (file)
@@ -418,12 +418,18 @@ do_net_wm_state_changes (GdkSurface *surface)
   if (old_state & GDK_TOPLEVEL_STATE_MINIMIZED)
     {
       if (!toplevel->have_hidden)
-        unset |= GDK_TOPLEVEL_STATE_MINIMIZED;
+        {
+          unset |= (GDK_TOPLEVEL_STATE_MINIMIZED |
+                    GDK_TOPLEVEL_STATE_SUSPENDED);
+        }
     }
   else
     {
       if (toplevel->have_hidden)
-        set |= GDK_TOPLEVEL_STATE_MINIMIZED;
+        {
+          set |= (GDK_TOPLEVEL_STATE_MINIMIZED |
+                  GDK_TOPLEVEL_STATE_SUSPENDED);
+        }
     }
 
   /* Update edge constraints and tiling */
@@ -810,9 +816,12 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
                * the minimized bit off.
                */
               if (GDK_SURFACE_IS_MAPPED (surface))
-                gdk_synthesize_surface_state (surface,
-                                              0,
-                                              GDK_TOPLEVEL_STATE_MINIMIZED);
+                {
+                  gdk_synthesize_surface_state (surface,
+                                                0,
+                                                GDK_TOPLEVEL_STATE_MINIMIZED |
+                                                GDK_TOPLEVEL_STATE_SUSPENDED);
+                }
             }
 
           if (surface_impl->toplevel &&
@@ -841,9 +850,12 @@ gdk_x11_display_translate_event (GdkEventTranslator *translator,
        {
          /* Unset minimized if it was set */
          if (surface->state & GDK_TOPLEVEL_STATE_MINIMIZED)
-           gdk_synthesize_surface_state (surface,
-                                         GDK_TOPLEVEL_STATE_MINIMIZED,
-                                         0);
+            {
+              gdk_synthesize_surface_state (surface,
+                                            GDK_TOPLEVEL_STATE_MINIMIZED |
+                                            GDK_TOPLEVEL_STATE_SUSPENDED,
+                                            0);
+            }
 
          if (toplevel)
            gdk_surface_thaw_updates (surface);
index 468ee941b68657f77baf742db9dd9a2ad2aaa124..08888a755b113373c8647d346b985cf243f1acc8 100644 (file)
@@ -3270,7 +3270,10 @@ gdk_x11_surface_minimize (GdkSurface *surface)
   else
     {
       /* Flip our client side flag, the real work happens on map. */
-      gdk_synthesize_surface_state (surface, 0, GDK_TOPLEVEL_STATE_MINIMIZED);
+      gdk_synthesize_surface_state (surface,
+                                    0,
+                                    GDK_TOPLEVEL_STATE_MINIMIZED |
+                                    GDK_TOPLEVEL_STATE_SUSPENDED);
       gdk_wmspec_change_state (TRUE, surface,
                                "_NET_WM_STATE_HIDDEN",
                                NULL);
@@ -3293,7 +3296,10 @@ gdk_x11_surface_unminimize (GdkSurface *surface)
   else
     {
       /* Flip our client side flag, the real work happens on map. */
-      gdk_synthesize_surface_state (surface, GDK_TOPLEVEL_STATE_MINIMIZED, 0);
+      gdk_synthesize_surface_state (surface,
+                                    GDK_TOPLEVEL_STATE_MINIMIZED |
+                                    GDK_TOPLEVEL_STATE_SUSPENDED,
+                                    0);
       gdk_wmspec_change_state (FALSE, surface,
                                "_NET_WM_STATE_HIDDEN",
                                NULL);
index a0607988f6f36cae230f3e81356781784c878d8c..e44859307f69bae9a92d196164b9c179371e603c 100644 (file)
@@ -238,6 +238,7 @@ typedef struct
   guint    client_decorated          : 1; /* Decorations drawn client-side */
   guint    use_client_shadow         : 1; /* Decorations use client-side shadows */
   guint    maximized                 : 1;
+  guint    suspended                 : 1;
   guint    fullscreen                : 1;
   guint    tiled                     : 1;
 
@@ -300,6 +301,7 @@ enum {
 
   /* Readonly properties */
   PROP_IS_ACTIVE,
+  PROP_SUSPENDED,
 
   /* Writeonly properties */
   PROP_STARTUP_ID,
@@ -973,6 +975,18 @@ gtk_window_class_init (GtkWindowClass *klass)
                             FALSE,
                             GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * GtkWindow:suspended: (attributes org.gtk.Property.get=gtk_window_is_suspended)
+   *
+   * Whether the window is suspended.
+   *
+   * See [method@Gtk.Window.is_suspended] for details about what suspended means.
+   */
+  window_props[PROP_SUSPENDED] =
+      g_param_spec_boolean ("suspended", NULL, NULL,
+                            FALSE,
+                            GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * GtkWindow:application: (attributes org.gtk.Property.get=gtk_window_get_application org.gtk.Property.set=gtk_window_set_application)
    *
@@ -1281,6 +1295,27 @@ gtk_window_is_fullscreen (GtkWindow *window)
   return priv->fullscreen;
 }
 
+/**
+ * gtk_window_is_suspended: (attributes org.gtk.Property.get=suspended)
+ * @window: a `GtkWindow`
+ *
+ * Retrieves the current suspended state of @window.
+ *
+ * A window being suspended means it's currently not visible to the user, for
+ * example by being on a inactive workspace, minimized, obstructed.
+ *
+ * Returns: whether the window is suspended.
+ */
+gboolean
+gtk_window_is_suspended (GtkWindow *window)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
+
+  return priv->suspended;
+}
+
 void
 _gtk_window_toggle_maximized (GtkWindow *window)
 {
@@ -1911,6 +1946,9 @@ gtk_window_get_property (GObject      *object,
     case PROP_FULLSCREENED:
       g_value_set_boolean (value, gtk_window_is_fullscreen (window));
       break;
+    case PROP_SUSPENDED:
+      g_value_set_boolean (value, gtk_window_is_suspended (window));
+      break;
     case PROP_FOCUS_WIDGET:
       g_value_set_object (value, gtk_window_get_focus (window));
       break;
@@ -4679,6 +4717,13 @@ surface_state_changed (GtkWidget *widget)
       g_object_notify_by_pspec (G_OBJECT (widget), window_props[PROP_MAXIMIZED]);
     }
 
+  if (changed_mask & GDK_TOPLEVEL_STATE_SUSPENDED)
+    {
+      priv->suspended = (new_surface_state & GDK_TOPLEVEL_STATE_SUSPENDED) ? TRUE : FALSE;
+
+      g_object_notify_by_pspec (G_OBJECT (widget), window_props[PROP_SUSPENDED]);
+    }
+
   update_edge_constraints (window, new_surface_state);
 
   if (changed_mask & (GDK_TOPLEVEL_STATE_FULLSCREEN |
index 01213337564360dbde79f60311eeb5f05fb66f2c..916a84f0d3db9837b90faba33df29f30a5c40f1e 100644 (file)
@@ -244,6 +244,9 @@ gboolean gtk_window_is_maximized           (GtkWindow    *window);
 GDK_AVAILABLE_IN_ALL
 gboolean gtk_window_is_fullscreen          (GtkWindow    *window);
 
+GDK_AVAILABLE_IN_4_12
+gboolean gtk_window_is_suspended           (GtkWindow    *window);
+
 GDK_AVAILABLE_IN_ALL
 void     gtk_window_destroy                (GtkWindow    *window);
 
index 4e9d7ca1fa81d8ea7fbf71dd59424e4ed9b36176..17681d1359c8c72ed7d547d7ea8c5e5a8a00d90d 100644 (file)
@@ -18,7 +18,7 @@ harfbuzz_req       = '>= 2.6.0'
 fribidi_req        = '>= 1.0.6'
 cairo_req          = '>= 1.14.0'
 gdk_pixbuf_req     = '>= 2.30.0'
-wayland_proto_req  = '>= 1.31'
+wayland_proto_req  = '>= 1.32'
 wayland_req        = '>= 1.21.0'
 graphene_req       = '>= 1.10.0'
 epoxy_req          = '>= 1.4'
index 3552c2199091233639e8ff9f3a104e56ee975f73..489015953ea8e621742e5a43e98995434a885a86 100644 (file)
@@ -103,6 +103,7 @@ gtk_tests = [
   ['teststack'],
   ['testrevealer'],
   ['testrevealer2'],
+  ['testsuspended'],
   ['testwindowsize'],
   ['testpopover'],
   ['listmodel'],
index 5ab0fee4e9c32c6d1b5e3fe784d32fb8ca8c2091..d00f3c43ef2c8a62a76790f402e32889d9cfe4e5 100644 (file)
@@ -4542,6 +4542,8 @@ surface_state_callback (GdkSurface  *window,
   msg = g_strconcat ((const char *)g_object_get_data (G_OBJECT (label), "title"), ": ",
                      (new_state & GDK_TOPLEVEL_STATE_MINIMIZED) ?
                      "minimized" : "not minimized", ", ",
+                     (new_state & GDK_TOPLEVEL_STATE_SUSPENDED) ?
+                     "suspended" : "not suspended", ", ",
                      (new_state & GDK_TOPLEVEL_STATE_STICKY) ?
                      "sticky" : "not sticky", ", ",
                      (new_state & GDK_TOPLEVEL_STATE_MAXIMIZED) ?
diff --git a/tests/testsuspended.c b/tests/testsuspended.c
new file mode 100644 (file)
index 0000000..b604afa
--- /dev/null
@@ -0,0 +1,46 @@
+#include <gtk/gtk.h>
+
+static void
+quit_cb (GtkWidget *widget,
+         gpointer   data)
+{
+  gboolean *done = data;
+
+  *done = TRUE;
+
+  g_main_context_wakeup (NULL);
+}
+
+static void
+report_suspended_state (GtkWindow *window)
+{
+  g_print ("Window is %s\n",
+           gtk_window_is_suspended (window) ? "suspended" : "active");
+}
+
+static void
+suspended_changed_cb (GtkWindow *window)
+{
+  report_suspended_state (window);
+}
+
+int main (int argc, char *argv[])
+{
+  GtkWidget *window;
+  gboolean done = FALSE;
+
+  gtk_init ();
+
+  window = gtk_window_new ();
+  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
+  g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
+  g_signal_connect (window, "notify::suspended",
+                    G_CALLBACK (suspended_changed_cb), &done);
+  gtk_window_present (GTK_WINDOW (window));
+  report_suspended_state (GTK_WINDOW (window));
+
+  while (!done)
+    g_main_context_iteration (NULL, TRUE);
+
+  return EXIT_SUCCESS;
+}