Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions src/manager/event_handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**

Check failure on line 1 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Lint the code

format

File content differs from formatting output
* @file Contains the implementation of handlers for various events that need
* to be processed by the extension. Those handlers are bound to event signals
* in effect_manager.ts.
Expand All @@ -18,17 +18,18 @@
import {logDebug} from '../utils/log.js';
import {getPref} from '../utils/settings.js';
import {hasMetaWindow, type RoundedWindowActor} from '../utils/types.js';
import {
isChromiumWindow,
computeBounds,
computeShadowActorOffset,
computeWindowContentsOffset,
getRoundedCornersCfg,
getRoundedCornersEffect,
shouldEnableEffect,
unwrapActor,
updateShadowActorStyle,
windowScaleFactor,
} from './utils.js';

Check failure on line 32 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Lint the code

assist/source/organizeImports

Sort the imported names.

/**
* Per-actor queue lock to run event handlers one after another and avoid
Expand Down Expand Up @@ -104,6 +105,24 @@

// Make sure the effect is applied correctly.
updateEffect(actor);

// For Chromium/Electron windows (QQ, WeChat, etc.) on fractional-scaled
// Wayland, force periodic shader refreshes to nudge Mutter/DRM toward the
// full-resolution buffer allocation, reducing initial blurry-window time.
const win = actor.metaWindow;
if (win && isChromiumWindow(win)) {
actor.rwcCustomData.refreshIds = [32, 80, 200, 500].map(ms =>

Check failure on line 114 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Build the extension

Property 'refreshIds' does not exist on type '{ shadow: Bin<Actor<LayoutManager, Content>>; unminimizedTimeoutId: number; propertyBindings: Binding[]; }'.
GLib.timeout_add(GLib.PRIORITY_DEFAULT, ms, () => {
if (actor.rwcCustomData) {
if (getRoundedCornersEffect(actor))
refreshRoundedCorners(actor);
else
actor.queue_repaint();

Check failure on line 120 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Build the extension

Property 'queue_repaint' does not exist on type 'RoundedWindowActor'.
}
return GLib.SOURCE_REMOVE;
})
);
}
}

export function onRemoveEffect(actor: RoundedWindowActor) {
Expand All @@ -126,6 +145,13 @@
shadow.destroy();
}

// Clean up Chromium timeout-based shader refreshes
const refreshIds = actor.rwcCustomData?.refreshIds;

Check failure on line 149 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Build the extension

Property 'refreshIds' does not exist on type '{ shadow: Bin<Actor<LayoutManager, Content>>; unminimizedTimeoutId: number; propertyBindings: Binding[]; }'.
if (refreshIds) {
for (const id of refreshIds)
GLib.source_remove(id);
}

// Remove all timeout handler
const timeoutId = actor.rwcCustomData?.unminimizedTimeoutId;
if (timeoutId) {
Expand Down Expand Up @@ -169,6 +195,33 @@
source.disconnect(id);
}
});
} else if (roundedCornersEffect) {
const win = actor.metaWindow;
if (win && isChromiumWindow(win)) {
// Chromium/Electron windows delivered from the dock (QQ, WeChat,
// etc.) appear blurry because Mutter's ClutterOffscreenEffect
// reuses the offscreen FBO populated during the unminimize
// animation at a mismatched resolution. Toggling the effect's
// enabled state destroys the stale FBO; the subsequent
// refreshRoundedCorners call triggers pre_paint which allocates
// a fresh FBO at the correct resolution.
//
// The 250ms delay allows Electron's Wayland renderer to deliver
// a settled frame before the FBO is recreated.
const id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => {
if (actor.rwcCustomData)
actor.rwcCustomData.unminimizedTimeoutId = 0;
const effect = getRoundedCornersEffect(actor);
if (actor.metaWindow && effect) {
effect.enabled = false;
effect.enabled = true;
refreshRoundedCorners(actor);
}
return GLib.SOURCE_REMOVE;
});
if (actor.rwcCustomData)
actor.rwcCustomData.unminimizedTimeoutId = id;
}
}
}

Expand All @@ -186,7 +239,7 @@

export const onSizeChanged = refreshRoundedCorners;

export const onFocusChanged = refreshShadow;
export const onFocusChanged = refreshRoundedCorners;

export const onSettingsChanged = refreshAllRoundedCorners;

Expand Down Expand Up @@ -281,10 +334,35 @@
// Don'd do anything when the window doesn't have the effect and shouldn't have it.
if (!hasEffect) return;

updateEffect(actor);
updateEffectWithFboInvalidate(actor, win);
});
}

/**
* Helper that forces FBO invalidation for Chromium/Electron windows before
* updating the effect. This destroys the stale offscreen framebuffer that
* Mutter populated during the unminimize animation at a mismatched resolution,
* ensuring the next pre_paint allocates a fresh FBO with the correct content.
*
* Non-Chromium windows are unaffected and go through the normal updateEffect.
*
* @param actor - The window actor.
* @param win - The Meta.Window the actor represents.
*/
function updateEffectWithFboInvalidate(
actor: RoundedWindowActor,
win: Meta.Window,

Check failure on line 354 in src/manager/event_handlers.ts

View workflow job for this annotation

GitHub Actions / Build the extension

Cannot find namespace 'Meta'.
) {
const effect = getRoundedCornersEffect(actor);
if (win && isChromiumWindow(win) && effect) {
effect.enabled = false;
effect.enabled = true;
}
updateEffect(actor);
}

/**

/**
* Update effect uniforms and constraints for a window.
*
Expand Down
19 changes: 19 additions & 0 deletions src/manager/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,25 @@ export function updateShadowActorStyle(
}
}

// Browser apps that may leave stale Wayland surfaces after minimize/restore,
// causing Mutter offscreen FBOs to render at mismatched resolutions.
const CHROMIUM_WM_CLASS_PATTERN =
/^(brave-(browser|origin)|chromium|electron|google-chrome|microsoft-edge|qq|wechat)$/;

/**
* Check whether a window belongs to a Chromium/Electron application.
* These apps can render blurry content through Mutter's ClutterOffscreenEffect
* when shown from the dock, because their Wayland buffers may not be at
* the expected resolution after unminimize transitions.
*
* @param win - The window to check.
* @returns Whether the window uses a Chromium-based renderer.
*/
export function isChromiumWindow(win: Meta.Window) {
const wmClass = win.get_wm_class_instance();
return wmClass !== null && CHROMIUM_WM_CLASS_PATTERN.test(wmClass);
}

/**
* Check whether a window should have rounded corners.
*
Expand Down
Loading