gtk: Add suspended window state

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.
This commit is contained in:
Jonas Ådahl 2023-05-24 16:22:42 +02:00 committed by Matthias Clasen
parent 03a8b9cf17
commit 7f946eff01
12 changed files with 144 additions and 16 deletions

View 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;
/**

View 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

View 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;

View 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;

View 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);

View 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);

View 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 |

View 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);

View 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'

View File

@ -103,6 +103,7 @@ gtk_tests = [
['teststack'],
['testrevealer'],
['testrevealer2'],
['testsuspended'],
['testwindowsize'],
['testpopover'],
['listmodel'],

View 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) ?

46
tests/testsuspended.c Normal file
View File

@ -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;
}