@@ -23,13 +23,23 @@ inline int ReservedMargin() {
2323 return window_shadow::Margin (maximum);
2424}
2525
26+ // States in which the window manager fits the whole surface to the space it gives the
27+ // window - a tiling window manager's tiles, maximized, full screen. The gutter is then
28+ // no shadow, only a band of empty window around the content, so it is taken away
29+ // while the window is in one of them.
30+ constexpr int kFittedStates =
31+ GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN | GDK_WINDOW_STATE_TILED |
32+ GDK_WINDOW_STATE_TOP_TILED | GDK_WINDOW_STATE_RIGHT_TILED |
33+ GDK_WINDOW_STATE_BOTTOM_TILED | GDK_WINDOW_STATE_LEFT_TILED ;
34+
2635struct State {
2736 GtkWidget* target = nullptr ;
2837 GtkWidget* overlay = nullptr ;
2938 guint original_border = 0 ;
3039 int margin = ReservedMargin();
3140 std::shared_ptr<WindowShadow> custom;
3241 bool enabled = true ;
42+ bool fitted = false ;
3343 bool dirty = true ;
3444 int width = 0 , height = 0 ;
3545 WindowShape polygon;
@@ -42,6 +52,17 @@ struct State {
4252inline State* Get (GtkWidget* widget) {
4353 return widget ? static_cast <State*>(g_object_get_data (G_OBJECT (widget), kState )) : nullptr ;
4454}
55+ // The gutter the surface has around the content right now.
56+ inline int Gutter (GtkWidget* widget) {
57+ auto * state = Get (widget);
58+ if (!state)
59+ return 0 ;
60+ return state->fitted ? static_cast <int >(state->original_border ) : state->margin ;
61+ }
62+ inline bool IsFitted (GtkWidget* widget) {
63+ auto * window = gtk_widget_get_window (widget);
64+ return window && (gdk_window_get_state (window) & kFittedStates );
65+ }
4566inline void Path (cairo_t * cr, State* state, int width, int height) {
4667 if (!state->polygon .GetPointCount ()) {
4768 cairo_rectangle (cr, 0 , 0 , width, height);
@@ -59,7 +80,7 @@ inline void Path(cairo_t* cr, State* state, int width, int height) {
5980}
6081inline gboolean Paint (GtkWidget* widget, cairo_t * cr, gpointer data, bool inside) {
6182 auto * state = static_cast <State*>(data);
62- if (!state->enabled )
83+ if (!state->enabled || state-> fitted )
6384 return FALSE ;
6485 GtkWidget* child = gtk_bin_get_child (GTK_BIN (widget));
6586 if (!child)
@@ -118,8 +139,70 @@ inline gboolean DrawOverlay(GtkWidget*, cairo_t* cr, gpointer data) {
118139 auto * state = static_cast <State*>(data);
119140 return Paint (state->target , cr, data, true );
120141}
142+ // The gutter is shadow, not window. Tell the window manager, so that it places,
143+ // snaps and tiles the content rather than the whole surface (_GTK_FRAME_EXTENTS on
144+ // X11, the xdg_surface window geometry on Wayland). GTK declares its own
145+ // decoration's extents while allocating the window; this runs after it and
146+ // declares everything around the content, GTK's part included.
147+ inline void DeclareGutter (GtkWidget* widget) {
148+ auto * window = gtk_widget_get_window (widget);
149+ auto * child = gtk_bin_get_child (GTK_BIN (widget));
150+ int x = 0 , y = 0 ;
151+ if (!window || !child || !gtk_widget_get_visible (child) ||
152+ !gtk_widget_translate_coordinates (child, widget, 0 , 0 , &x, &y))
153+ return ;
154+ const int right = gtk_widget_get_allocated_width (widget) - x -
155+ gtk_widget_get_allocated_width (child);
156+ const int bottom = gtk_widget_get_allocated_height (widget) - y -
157+ gtk_widget_get_allocated_height (child);
158+ if (x < 0 || y < 0 || right < 0 || bottom < 0 )
159+ return ;
160+ gdk_window_set_shadow_width (window, x, right, y, bottom);
161+ }
121162inline void Allocated (GtkWidget* widget, GtkAllocation*, gpointer) {
122163 RefreshShadowInput (widget);
164+ DeclareGutter (widget);
165+ }
166+
167+ // GtkWindow paints its background under the whole allocation, gutter included,
168+ // which would hide the shadow behind an opaque band. A window that can be
169+ // translucent gets this class, and one rule for the screen clears its background;
170+ // the content paints its own.
171+ constexpr char kGutterStyleClass [] = " nativeapi-shadow-gutter" ;
172+ inline bool CanBeTranslucent (GtkWidget* widget) {
173+ auto * screen = gtk_widget_get_screen (widget);
174+ return gdk_screen_is_composited (screen) &&
175+ gtk_widget_get_visual (widget) == gdk_screen_get_rgba_visual (screen);
176+ }
177+ inline void ClearGutterBackground (GtkWidget* widget) {
178+ static bool installed = false ;
179+ if (!installed) {
180+ installed = true ;
181+ GtkCssProvider* provider = gtk_css_provider_new ();
182+ gtk_css_provider_load_from_data (provider,
183+ " window.nativeapi-shadow-gutter,"
184+ " window.nativeapi-shadow-gutter:backdrop {"
185+ " background: none; }" ,
186+ -1 , nullptr );
187+ gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (widget),
188+ GTK_STYLE_PROVIDER (provider),
189+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );
190+ g_object_unref (provider);
191+ }
192+ gtk_style_context_add_class (gtk_widget_get_style_context (widget), kGutterStyleClass );
193+ }
194+ // The window manager has already sized the surface for the new state; changing the
195+ // border only moves the content edge to the surface edge, or back in.
196+ inline gboolean StateChanged (GtkWidget* widget, GdkEventWindowState* event, gpointer data) {
197+ auto * state = static_cast <State*>(data);
198+ const bool fitted = (event->new_window_state & kFittedStates ) != 0 ;
199+ if (fitted != state->fitted ) {
200+ state->fitted = fitted;
201+ gtk_container_set_border_width (GTK_CONTAINER (widget),
202+ fitted ? state->original_border : state->margin );
203+ gtk_widget_queue_draw (widget);
204+ }
205+ return FALSE ;
123206}
124207inline void Remove (GtkWidget* widget) {
125208 auto * state = Get (widget);
@@ -130,6 +213,7 @@ inline void Remove(GtkWidget* widget) {
130213 const int width = child ? gtk_widget_get_allocated_width (child) : 0 ;
131214 const int height = child ? gtk_widget_get_allocated_height (child) : 0 ;
132215 g_signal_handlers_disconnect_by_data (widget, state);
216+ gtk_style_context_remove_class (gtk_widget_get_style_context (widget), kGutterStyleClass );
133217 if (state->overlay && child == state->overlay ) {
134218 auto * content = gtk_bin_get_child (GTK_BIN (state->overlay ));
135219 g_object_ref (content);
@@ -155,7 +239,13 @@ inline State* Ensure(GtkWidget* widget) {
155239 // Parent-window drawing is occluded by native GL child windows. A pass-through
156240 // GTK overlay paints the part of the core shadow inside the content rectangle;
157241 // the parent paints only the outer gutter, without double compositing.
158- if (auto * content = gtk_bin_get_child (GTK_BIN (widget))) {
242+ // That part exists only for a polygon shape, and moving the content into the
243+ // overlay unrealizes and realizes it again, which a realized host widget may
244+ // not survive: a Flutter FlView then never renders again - black on Hyprland,
245+ // and on GNOME its window is never shown at all. Such content stays where it
246+ // is; the parent then paints the whole shadow outside the content.
247+ auto * content = gtk_bin_get_child (GTK_BIN (widget));
248+ if (content && !gtk_widget_get_realized (content)) {
159249 g_object_ref (content);
160250 gtk_container_remove (GTK_CONTAINER (widget), content);
161251 state->overlay = gtk_overlay_new ();
@@ -176,10 +266,16 @@ inline State* Ensure(GtkWidget* widget) {
176266 state->custom = std::make_shared<WindowShadow>(*config);
177267 }
178268 state->original_border = gtk_container_get_border_width (GTK_CONTAINER (widget));
269+ state->fitted = IsFitted (widget);
179270 g_object_set_data_full (G_OBJECT (widget), kState , state,
180271 [](gpointer p) { delete static_cast <State*>(p); });
181272 g_signal_connect_after (widget, " draw" , G_CALLBACK (Draw), state);
182273 g_signal_connect_after (widget, " size-allocate" , G_CALLBACK (Allocated), state);
274+ g_signal_connect (widget, " window-state-event" , G_CALLBACK (StateChanged), state);
275+ if (CanBeTranslucent (widget))
276+ ClearGutterBackground (widget);
277+ if (state->fitted )
278+ return state;
183279 // The gutter belongs to GTK, not the application's render tree. Keeping it
184280 // reserved while disabled avoids geometry changes when toggling the shadow.
185281 int width = 0 , height = 0 ;
0 commit comments