Skip to content

fix: Chromium effect reapply after lock/unlock and minimize, misc fixes#144

Closed
GrzegorzKozub wants to merge 10 commits into
flexagoon:mainfrom
GrzegorzKozub:main
Closed

fix: Chromium effect reapply after lock/unlock and minimize, misc fixes#144
GrzegorzKozub wants to merge 10 commits into
flexagoon:mainfrom
GrzegorzKozub:main

Conversation

@GrzegorzKozub

@GrzegorzKozub GrzegorzKozub commented Apr 14, 2026

Copy link
Copy Markdown

Summary

Bundle of small fixes and performance improvements accumulated while chasing #124.

Changes

Fix: Chromium corner glitch on restore after app-initiated minimize (#124)

Chromium-based browsers implement their own title bar and context menu, using xdg_toplevel.set_minimized to request minimize from the compositor. This is distinct from compositor-initiated minimizes (Super+H, Super+D), which do not exhibit the issue. When Chromium sends xdg_toplevel.set_minimized, the compositor acknowledges the minimized state and Chromium stops updating its Wayland surface buffer — the last rendered frame is held. On restore, the stale surface causes the GLSL rounded corner shader to render with wrong bounds, producing a visible glitch until the window receives focus and self-corrects.

  • event_handlers.ts: In onUnminimize, detect Chromium windows outside the magic lamp path and schedule a 250ms refreshRoundedCorners call. This gives Chromium time to deliver a fresh post-restore frame before the shader uniforms are recomputed. The effect is never disabled during the minimize state, so there is no hover delay in the overview and intermediate focus/size change callbacks are not blocked.

Known limitation: The overview thumbnail for a minimized Chromium window shows square corners — the Shell.GLSLEffect shader does not apply through Clutter.Clone, so the raw window surface is rendered. This is a compositor-level constraint that cannot be addressed from the extension side.

Fix: Chromium effect reapply after screen lock/unlock (#124)

GNOME Shell disables and re-enables extensions during screen lock/unlock. When the extension re-enables, Chromium-based browsers (Brave, Chrome, Edge) may render stale surfaces for unfocused windows. The compositor skips repainting GLSL effects for these windows, resulting in scrambled borders and a doubled frame.

  • event_manager.ts: After applying effects to existing windows on re-enable, wait 250ms for surfaces to settle, then briefly focus each unfocused Chromium window (detected by wm_class) to force the compositor to repaint the GLSL effect, then restore focus to the original window.
  • event_handlers.ts: Promote onFocusChanged from refreshShadow to refreshRoundedCorners so that any focus change recomputes shader bounds, not just the shadow.
  • utils.ts: Add isChromiumWindow helper matching known Chromium-based browser wm_class values via regex, including brave-origin (Brave PWAs / site-specific browser instances).

Fix: Overview shadow allocation crash

  • add_shadow_in_overview.ts: Guard against zero frame width in vfunc_allocate on the overview shadow clone, which previously caused NaN values in the allocation box and triggered Clutter assertion failures.

Fix: Silence expected permission errors on /proc/<pid>/maps

getAppType reads /proc/<pid>/maps to detect LibHandy / LibAdwaita windows. For processes owned by another user (flatpak sandboxes, root-owned apps), the file is unreadable and GIO raises PERMISSION_DENIED. The existing fallback already handled the outcome, but the error was logged with a full stack trace on every such window.

  • manager/utils.ts: Treat PERMISSION_DENIED and NOT_FOUND from /proc/<pid>/maps as expected and log them at debug level; keep logError for everything else.

Fix: clutter_actor_node_new crash on new browser windows (GNOME 50.2)

On mutter 50.2 (Wayland-only), actor.metaWindow can be null during window actor lifecycle transitions. Previously, applyEffectTo connected notify::size and size-changed before checking actor.metaWindow. When metaWindow was null, the resulting TypeError aborted the function mid-way, leaving size signals connected but the effect never added. On the next notify::size (fired during Clutter layout), onAddEffect was called while the actor was mid-paint, corrupting the effect's actor pointer and causing clutter_actor_node_new(NULL) → SIGABRT.

  • event_manager.ts: Guard actor.metaWindow before connecting any signals in applyEffectTo so the function either completes fully or returns early with nothing connected. Also null-guard actor from get_compositor_private() in the window-created handler, and re-fetch a fresh actor in the wm-class deferred callback instead of using the captured (potentially stale) reference.
  • event_handlers.ts: Null-guard actor.metaWindow at the top of onAddEffect and refreshRoundedCorners.

Fix: effect add/remove mid-paint crash (GNOME 50.2, keepRoundedCorners.maximized = false)

A second crash path in the same assertion (clutter_actor_node_new(NULL)) was observed after the null-metaWindow fix above was deployed. Root cause: Clutter emits notify::size and size-changed during a layout pass that runs inside a paint frame — specifically during meta_window_actor_paint_to_content, which GNOME Shell calls to capture a window snapshot at the start of the maximize animation.

With keepRoundedCorners.maximized = false (the default), the notify::size callback triggered refreshRoundedCornersonRemoveEffect for the newly-maximized window. onRemoveEffect called actor.remove_effect_by_name() while CLUTTER_ACTOR_IN_PAINT was set, which corrupted the RoundedCornersEffect's actor pointer via clutter_actor_meta_set_actor. The subsequent clutter_actor_continue_paint call then hit clutter_actor_node_new(NULL) → SIGABRT.

  • event_manager.ts: Wrap all seven signal callbacks in applyEffectTo, and the two deferred-apply callbacks in enableEffect, with GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, ...). This pushes effect add/remove out of any active paint frame into the next GLib main loop iteration, where it is safe to modify the actor's effect list.

Performance improvements

While investigating the above, noticed several hot paths that re-read GSettings per-frame or per-event. Each getPref() call deserializes through D-Bus and GLib Variant unpacking, which adds up quickly during shader updates and resize/focus events.

  • settings.ts: Cache deserialized pref values, invalidated automatically via the GSettings changed signal. This eliminates repeated unpacks for debug-mode (hit from logDebug in every hot-path statement), blacklist/whitelist (read per shouldEnableEffect), keep-shadow-for-maximized-fullscreen, tweak-kitty-terminal, and the shadow dictionaries.
  • utils.tsupdateShadowActorStyle: Read global-rounded-corner-settings once and derive borderRadius, padding, and smoothing from it, instead of 4 separate getPref calls.
  • utils.tscomputeBounds: Reorder the kitty check to compare wm_class first, so non-kitty windows skip the getPref entirely.

Test plan

  • Open multiple Chromium-based browser windows (Brave, Chrome, Edge), lock and unlock — all windows should render correctly, no scrambled borders.
  • Right-click the Brave/Chrome title bar and select Minimize; restore the window by clicking it in the overview — corners should appear correct within ~250ms with no flash. Hovering over the minimized thumbnail in the overview should show the close button and title without delay. (Super+H / Super+D minimize is unaffected and needs no special handling.)
  • Non-Chromium windows should be unaffected.
  • Preferences changes still take effect immediately (pref cache invalidation).
  • Open flatpak / other-user-owned windows and tail journalctl --user -f -u gnome-shell — no Gio.IOErrorEnum: Error opening file /proc/<pid>/maps stack traces.
  • Open multiple new browser windows and tabs on GNOME 50.2 — no clutter_actor_node_new crash / gnome-shell SIGABRT.
  • Open a maximized Chromium-based browser window with keepRoundedCorners.maximized = false (default) — no crash on window creation or focus change.

Fixes #124

AI contribution disclosure

This pull request was developed with significant AI assistance (Claude). All changes were manually reviewed, tested on a live GNOME Shell session, and verified to be necessary and correct.


🤖 Generated with Claude Code

@GrzegorzKozub
GrzegorzKozub force-pushed the main branch 2 times, most recently from 00148c2 to 98559d1 Compare April 15, 2026 06:37
@GrzegorzKozub GrzegorzKozub changed the title fix: re-apply effects after screen lock/unlock fix: re-apply effects to Chromium windows after screen lock/unlock Apr 15, 2026
@GrzegorzKozub

Copy link
Copy Markdown
Author

Hi @flexagoon. I've been testing this for a couple of days now and find the fix working without any visible negative visual or usability impact. There's also no issues in journal. Take a look, please 😃

Comment thread src/manager/event_manager.ts Outdated
Comment on lines +45 to +78
deferredRefreshId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => {
const focusedWin = global.display.get_focus_window();
const chromiumWindows = global
.get_window_actors()
.map(a => a.metaWindow)
.filter(
(win): win is Meta.Window =>
win != null &&
win !== focusedWin &&
isChromiumWindow(win),
);

if (chromiumWindows.length > 0) {
const timestamp = global.get_current_time();
for (const [i, win] of chromiumWindows.entries()) {
GLib.timeout_add(GLib.PRIORITY_DEFAULT, i * 100, () => {
win.focus(timestamp);
return GLib.SOURCE_REMOVE;
});
}

// Restore focus to the originally focused window.
GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
chromiumWindows.length * 100,
() => {
focusedWin?.focus(global.get_current_time());
return GLib.SOURCE_REMOVE;
},
);
}

deferredRefreshId = 0;
return GLib.SOURCE_REMOVE;

@flexagoon flexagoon Apr 16, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the best way to redraw a window
There is an actor.queue_redraw() function, could you check if that works instead? (There's also .queue_relayout(), not sure which one of those would be correct here)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this suggests actor.queue_redraw() or queue_relayout() instead of focus cycling. I already tried actor.queue_redraw() earlier and it didn't work for background windows. The compositor skips repainting GLSL effects for occluded actors even with queue_redraw(). Only focusing the window forces the compositor to actually process the repaint.

@GrzegorzKozub

Copy link
Copy Markdown
Author

I also added some performance improvements now @flexagoon. Details in the description. Been using this version while keeping a look at the journal and it seems fine.

@GrzegorzKozub GrzegorzKozub changed the title fix: re-apply effects to Chromium windows after screen lock/unlock fix: Chromium effect reapply after lock, overview crash, and misc fixes Apr 18, 2026
@GrzegorzKozub

Copy link
Copy Markdown
Author

Hi @flexagoon 😃 I rebased and added AI disclosure. I've been running the version with my change for a month now on 3 different machines and seen no issues. Can we take a look into merging this?

@flexagoon

Copy link
Copy Markdown
Owner

@GrzegorzKozub yeah, I'll review it when I get time, sorry, Im somewhat busy with uni rn

@yuelinxin

Copy link
Copy Markdown

I tested this PR on my Gnome 50 + Fedora system, does not fix the minimize glitch mentioned in #124 . It seems to be a glitch from upstream, since when I disabled the extension, this glitch is still there just without the border issues.

@GrzegorzKozub
GrzegorzKozub force-pushed the main branch 2 times, most recently from c5db71f to ea996f7 Compare June 29, 2026 12:23
@GrzegorzKozub GrzegorzKozub changed the title fix: Chromium effect reapply after lock, overview crash, and misc fixes fix: Chromium effect reapply after lock/unlock and minimize, misc fixes Jun 29, 2026
@GrzegorzKozub
GrzegorzKozub force-pushed the main branch 2 times, most recently from 3e13f1f to 01c3821 Compare June 29, 2026 13:19
@GrzegorzKozub

Copy link
Copy Markdown
Author

I tested this PR on my Gnome 50 + Fedora system, does not fix the minimize glitch mentioned in #124 . It seems to be a glitch from upstream, since when I disabled the extension, this glitch is still there just without the border issues.

I was able to fix this partially, as described in the first paragraph of this PR description @yuelinxin

@GrzegorzKozub
GrzegorzKozub force-pushed the main branch 4 times, most recently from b7df411 to afb3d14 Compare June 30, 2026 06:35
GNOME Shell disables and re-enables extensions during screen lock/unlock.
When the extension re-enables, Chromium-based browsers (Brave, Chrome,
Edge) may render stale surfaces for unfocused windows. The compositor
skips repainting GLSL effects for these windows, resulting in scrambled
borders and a doubled frame.

Fix by briefly focusing each unfocused Chromium window after re-enable,
which forces the compositor to repaint the GLSL effect. Also promote
onFocusChanged from refreshShadow to refreshRoundedCorners so that any
subsequent focus change recomputes shader bounds, not just the shadow.

Fixes flexagoon#124
vfunc_allocate on the overview shadow clone can be called before the
window has a valid frame rect, causing division by zero which produces
NaN values in the allocation box and triggers Clutter assertion failures:

  Can't update stage views actor Shadow Actor (Overview) is on because it needs an allocation.
  clutter_actor_set_allocation_internal: assertion '!isnan (box->x1) && !isnan (box->x2) && !isnan (box->y1) && !isnan (box->y2)' failed
getPref() reads go through D-Bus and GLib Variant unpacking, which is
expensive when done in hot paths that run per-frame (shader uniform
updates) or per-event (focus/resize). This commit addresses the most
frequent offenders.

- Cache unpacked pref values in settings.ts. The cache is invalidated
  automatically by connecting to the GSettings 'changed' signal, so
  values stay correct across the preferences UI. This single change
  eliminates repeated unpacks for debug-mode (called from logDebug on
  every hot-path statement), blacklist/whitelist (read on every
  shouldEnableEffect call), keep-shadow-for-maximized-fullscreen,
  tweak-kitty-terminal, and the focused/unfocused shadow dictionaries.

- Simplify updateShadowActorStyle to read global-rounded-corner-settings
  once and derive borderRadius/padding/smoothing from it, instead of
  four separate getPref calls (three via default parameters, one inside
  the function body). Caller-provided overrides are still honored.

- Reorder the kitty check in computeBounds to compare wm_class first.
  The pref read is cheap now but the early wm_class comparison avoids
  it entirely for every non-kitty window, which is almost all of them.
GrzegorzKozub and others added 2 commits June 30, 2026 09:59
getAppType reads /proc/<pid>/maps to detect LibHandy/LibAdwaita windows.
For processes owned by another user (flatpak sandboxes, root-owned apps)
the file is unreadable, producing a benign PERMISSION_DENIED that was
logged with a full stack trace on every such window. Treat
PERMISSION_DENIED and NOT_FOUND as expected and log them at debug level;
keep logError for everything else.
…crash

On mutter 50.2 (Wayland-only), actor.metaWindow can be null during
window actor lifecycle transitions. Previously, applyEffectTo connected
notify::size and size-changed before reaching the actor.metaWindow
signal connections. When metaWindow was null, the TypeError aborted the
function mid-way, leaving size signals connected but the effect never
added. On the next notify::size (fired during Clutter layout), onAddEffect
was called while the actor was mid-paint, corrupting the effect's actor
pointer and causing clutter_actor_node_new(NULL) → SIGABRT.

Fix by guarding actor.metaWindow before connecting any signals in
applyEffectTo so the function either completes fully or returns early
with no signals connected. Add matching null guards to onAddEffect and
refreshRoundedCorners. Also guard actor from get_compositor_private() in
the window-created handler and re-fetch it fresh in the wm-class callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GrzegorzKozub and others added 2 commits June 30, 2026 09:59
On mutter 50.2, Clutter can emit notify::size and size-changed during a
layout pass that runs inside a paint frame — specifically during
meta_window_actor_paint_to_content, which GNOME Shell calls to capture a
window snapshot at the start of the maximize animation.

If keepRoundedCorners.maximized is false (the default), the notify::size
callback triggered refreshRoundedCorners → onRemoveEffect for the newly-
maximized window. onRemoveEffect called actor.remove_effect_by_name() while
CLUTTER_ACTOR_IN_PAINT was set, which corrupted the RoundedCornersEffect's
actor pointer via clutter_actor_meta_set_actor. The subsequent
clutter_actor_continue_paint call then hit clutter_actor_node_new(NULL) →
SIGABRT.

The same class of bug exists in the notify::first-child and notify::wm-class
deferred callbacks: both call applyEffectTo synchronously, which can invoke
onAddEffect (add_effect_with_name) while the actor is mid-paint if the
Wayland event or layout notification fires inside a paint frame.

Fix by wrapping all seven signal callbacks in applyEffectTo, and the two
deferred-apply callbacks in enableEffect, with GLib.idle_add at
PRIORITY_DEFAULT_IDLE. This pushes effect add/remove out of any active paint
frame into the next GLib main loop iteration, where it is safe to modify the
actor's effect list.

The previous fix (4af9a02) addressed the null-metaWindow path that led to
the same assertion. This commit addresses the remaining path where
metaWindow is valid but effect mutations still race with an active paint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nimize

Chromium-based browsers use xdg_toplevel.set_minimized for their own
title bar minimize, which causes Wayland surface staleness. On restore,
the stale surface makes the GLSL rounded corner shader render with wrong
bounds, producing a visible glitch until focus fires and self-corrects.

Fix by scheduling a refreshRoundedCorners call 250ms after unminimize
for Chromium windows, giving the Wayland surface time to deliver a fresh
frame before the shader bounds are recomputed.

Compositor-initiated minimizes (Super+H/D) do not cause surface staleness
and are unaffected. The overview thumbnail continues to show the stale
Chromium surface while minimized — this is a known Chromium/Wayland
limitation outside the extension's control.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	src/manager/utils.ts
#	src/patch/add_shadow_in_overview.ts
@flexagoon

Copy link
Copy Markdown
Owner

@GrzegorzKozub could you please try this build?

https://github.com/flexagoon/rounded-window-corners/actions/runs/29527132270

It fixes the minimize glitch for me, and I wasn't able to reproduce the screen lock one

@GrzegorzKozub

GrzegorzKozub commented Jul 17, 2026

Copy link
Copy Markdown
Author

Hi @flexagoon 😃

Please notice that I have been monitoring this extension not only by reproducing reported issues but also looking at crashes and journal issues as I've been using it across my machines. The result of this is a bundle of fixes and guards for different issues I came across.

These fixes are listed and documented in this PR body. I do believe the changes here have stabilized this great extension a lot. I've been running the code version from this PR since its creation and kept the code rebased on your changes for good measure.

I have also disclosed that AI has been used to analyse these bugs, find the relevant docs and e.g. GNOME code, and fix them.

Maybe we could leverage this work and allow other users to benefit instead of bisecting. If we get crashes or side effects we can always revert.

@flexagoon

flexagoon commented Jul 17, 2026

Copy link
Copy Markdown
Owner

@GrzegorzKozub I understand, I appreciate your work and I'm using your PR as reference. I just believe there are simpler ways to fix especially the Chromium issue, which is what I tried to do in the build I linked. Could you please try it and see if it works?

As for the other issues like crashes and optimizations, some of those have already been fixed (eg. in 25bcb35), and for the rest I will rebase your fixes as separate commits and add them to the code (you'll still be listed as the commit author). I just want to deal with the Chromium issue first.

@flexagoon flexagoon closed this Jul 17, 2026
@flexagoon flexagoon reopened this Jul 17, 2026
@GrzegorzKozub

Copy link
Copy Markdown
Author

Looked at the actual build (chromium-fix branch, commit 91b6e32, one commit on top of be872f4):

event_handlers.ts: +6 -1
utils.ts:          +25 -0

It adds an isChromium(win) helper that reads /proc/<pid>/maps looking for /dev/shm/.org.chromium.Chromium, then in onUnminimize schedules a 250ms deferred refreshRoundedCorners for detected Chromium/Electron windows — the same core mechanism as the minimize/restore fix in this PR, just with pid-maps-based detection instead of wm_class matching.

Two things that make me less confident it fully replaces that fix:

  • It runs unconditionally, without excluding the magic-lamp-effect path, so it could race with or double-schedule against that animation callback.
  • On a failed /proc/<pid>/maps read (flatpak/sandboxed/root-owned processes, or a vanished PID) it calls logError unconditionally and doesn't cache the failure, so it re-logs a full stack trace on every unminimize for those windows — the same class of log-noise issue the /proc//maps fix in this PR addresses, just reintroduced in this new code path.

Scope-wise, it only targets one of the six fixes bundled here:

  • Partially covers: Chromium corner glitch on restore after app-initiated minimize.
  • Not covered: Chromium effect reapply after screen lock/unlock (flexagoon noted he couldn't reproduce it), the overview shadow allocation crash, the /proc//maps permission-log fix, the clutter_actor_node_new null-metaWindow crash, the mid-paint effect add/remove crash (GLib.idle_add wrapping), and the GSettings-caching performance work.

Happy to test the build regardless — just flagging that a pass/fail result there only speaks to the first item, not the rest of this PR.


🤖 This comment was drafted with AI assistance (Claude Code).

@flexagoon

flexagoon commented Jul 17, 2026

Copy link
Copy Markdown
Owner

@GrzegorzKozub yes, I know what my commit does, please test it

@flexagoon

Copy link
Copy Markdown
Owner

@GrzegorzKozub please don't add more stuff to this pull requests, extra changes should be submitted as separate PRs

@GrzegorzKozub

GrzegorzKozub commented Jul 19, 2026

Copy link
Copy Markdown
Author

Sorry friend, but most likely I won't be contributing to this fork anymore. My fork remains active and I'll keep it up to date with yours for my own use. Feel free to integrate any of my changes at your preferred pace.

You can close this merge request or keep it to track the diff, up to you.

I've also switched the AUR package I maintain to my fork so you may want to remark this in the README here. The -git flavor of the AUR package remains attached to this fork.

flexagoon added a commit that referenced this pull request Jul 19, 2026
@flexagoon

Copy link
Copy Markdown
Owner

Fix: Chromium corner glitch on restore after app-initiated minimize

Fixed by f71ef3f

Fix: Chromium effect reapply after screen lock/unlock

Can't reproduce

Fix: Overview shadow allocation crash

Can't reproduce

Fix: Silence expected permission errors on /proc//maps

Fixed in 639bace

Fix: clutter_actor_node_new crash on new browser windows

Can't reproduce

Fix: effect add/remove mid-paint crash

Can't reproduce

Performance improvements

Probably hallucinated. GSettings are designed to be fast. The system itself reads those preferences many times a second with no performance impact, so this is not a real performance issue.

@flexagoon flexagoon closed this Jul 19, 2026
@flexagoon

Copy link
Copy Markdown
Owner

@GrzegorzKozub could you please also update the extension metadata in your fork (repository URL and my username) to avoid confusion?

https://github.com/GrzegorzKozub/rounded-window-corners/blob/main/resources/metadata.json

GrzegorzKozub added a commit to GrzegorzKozub/rounded-window-corners that referenced this pull request Jul 20, 2026
Upstream has not integrated these fixes (flexagoon#144),
so document them here for users of this fork and link from the README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GrzegorzKozub added a commit to GrzegorzKozub/rounded-window-corners that referenced this pull request Jul 20, 2026
Upstream has not integrated these fixes (flexagoon#144),
so document them here for users of this fork and link from the README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Glitching when minimizing

3 participants