diff --git a/gdk/gdktoplevel.h b/gdk/gdktoplevel.h index 17b48802df..3d220c4ba4 100644 --- a/gdk/gdktoplevel.h +++ b/gdk/gdktoplevel.h @@ -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; /** diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 0636018c93..16da250362 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -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 diff --git a/gdk/wayland/gdktoplevel-wayland.c b/gdk/wayland/gdktoplevel-wayland.c index efe9ef86ce..e463b97686 100644 --- a/gdk/wayland/gdktoplevel-wayland.c +++ b/gdk/wayland/gdktoplevel-wayland.c @@ -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; diff --git a/gdk/win32/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c index 9562b664ae..392bf2335a 100644 --- a/gdk/win32/gdkevents-win32.c +++ b/gdk/win32/gdkevents-win32.c @@ -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; diff --git a/gdk/x11/gdkdisplay-x11.c b/gdk/x11/gdkdisplay-x11.c index 4dec10d1de..a2afd1ac74 100644 --- a/gdk/x11/gdkdisplay-x11.c +++ b/gdk/x11/gdkdisplay-x11.c @@ -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); diff --git a/gdk/x11/gdksurface-x11.c b/gdk/x11/gdksurface-x11.c index 468ee941b6..08888a755b 100644 --- a/gdk/x11/gdksurface-x11.c +++ b/gdk/x11/gdksurface-x11.c @@ -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); diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index a0607988f6..e44859307f 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -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 | diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h index 0121333756..916a84f0d3 100644 --- a/gtk/gtkwindow.h +++ b/gtk/gtkwindow.h @@ -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); diff --git a/meson.build b/meson.build index 4e9d7ca1fa..17681d1359 100644 --- a/meson.build +++ b/meson.build @@ -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' diff --git a/tests/meson.build b/tests/meson.build index 3552c21990..489015953e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -103,6 +103,7 @@ gtk_tests = [ ['teststack'], ['testrevealer'], ['testrevealer2'], + ['testsuspended'], ['testwindowsize'], ['testpopover'], ['listmodel'], diff --git a/tests/testgtk.c b/tests/testgtk.c index 5ab0fee4e9..d00f3c43ef 100644 --- a/tests/testgtk.c +++ b/tests/testgtk.c @@ -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 index 0000000000..b604afa472 --- /dev/null +++ b/tests/testsuspended.c @@ -0,0 +1,46 @@ +#include + +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; +}