Skip to content

gnome-bg: don't serve a smaller cached pixbuf to a larger request - #277

Merged
mtwebster merged 1 commit into
linuxmint:masterfrom
regisfso:fix-wallpaper-cache-size-blind
Sep 18, 2026
Merged

mtwebster merged 1 commit into
linuxmint:masterfrom
regisfso:fix-wallpaper-cache-size-blind

Conversation

@regisfso

Copy link
Copy Markdown
Contributor

Symptom

On a dual-monitor setup where both displays share a similar aspect ratio (here: an eDP-1 1366x768 internal panel and an HDMI-1 2560x1440 external monitor, both 16:9), the wallpaper on the external monitor renders visibly soft/blurry right after a cold boot, even though the wallpaper file's pixel dimensions exactly match the external monitor's native resolution and picture-options is zoom. Re-setting org.cinnamon.desktop.background picture-uri to itself (a known user-level workaround, e.g. toggling it to '' and back) forces a fresh render and fixes the sharpness for the rest of the session — until the next full reboot.

Mechanism

Both the per-file pixbuf cache in get_as_pixbuf_for_size() and the single-slot bg->pixbuf_cache in get_pixbuf_for_size() can serve a pixbuf that is smaller than what is currently being requested, and the caller then has to upscale it to fill the target monitor:

  • file_cache_lookup(bg, PIXBUF, filename) at gnome-bg.c:2121 is keyed only by filename — not by monitor or requested size.
  • load_from_cache_file() at gnome-bg.c:2133 can return a pixbuf already scaled down for a smaller monitor, read from the on-disk per-monitor cache.
  • That downscaled pixbuf gets stored under the bare filename at gnome-bg.c:2161 (file_cache_add_pixbuf), with no record of the size/monitor it was scaled for.
  • get_pixbuf_for_size()'s own cache check at gnome-bg.c:2549 then matches by aspect ratio alone (0.2 > fabs(...)), so a pixbuf cached for the smaller monitor is also handed straight back for the larger one whenever the two aspect ratios are close — as they are for a 1366x768 and a 2560x1440 display (|1.7786 − 1.7778| ≈ 0.0008, far under the 0.2 tolerance).

In a fresh csd-background process (right after login, before the on-disk cache for the larger monitor has even been consulted), the smaller monitor gets rendered first in draw_each_monitor()'s per-monitor loop, its already-downscaled 1366x768 pixbuf is cached under the wallpaper's filename, and the very next call for the 2560x1440 monitor reuses it — upscaled — instead of loading the correctly-sized pixbuf.

Why the on-disk cache looks fine when you go investigate this

This is the part most likely to mislead anyone chasing a report like this: the on-disk per-monitor cache (~/.cache/wallpaper/<monitor>_<placement>_<width>_<height>_<md5(path)>) for the external monitor stays untouched and correctly sized even while the bug is actively rendering blurry. refresh_cache_file() at gnome-bg.c:1085 only (re)writes the cache file when cache_file_is_valid() at gnome-bg.c:832 says the existing one is stale — and mtime-based validity has nothing to do with whether the content that's currently on screen is correct. So the file you'd naturally go check is sharp; the pixels actually on the display are not.

Evidence

Measured with an edge-detection energy metric (PIL.ImageFilter.FIND_EDGES mean, summed over channels — higher = sharper) on the same region of the external monitor, comparing against the original wallpaper file:

Sample Edge energy
Original wallpaper file (2560x1440, native) 7.70 – 8.06 (two crops)
Simulated bug: downscale to 1366x768, upscale back to 2560x1440 5.28 – 5.66
On-disk 1_5_2560_1440_* cache file (misleadingly sharp, per above) 7.73
Screen content after a patched cold boot 8.08

The patched cold-boot number matches the original file; the simulated-bug number is what an affected boot's on-screen content would measure like (confirmed visually on the affected machine — see Testing).

Reproduction conditions

  • Reproduces reliably on a full boot/login, when the external monitor's final geometry settles shortly after the session starts and a valid on-disk cache entry for the smaller monitor already exists from an earlier session.
  • Does not reproduce by merely restarting the csd-background process within an already-running session (tested) — the trigger appears to be specific to how monitor geometry becomes available during actual session startup, not just "fresh process." I have not tracked down the exact RandR/GDK timing involved, and this PR's fix does not depend on it: regardless of when a too-small pixbuf ends up cached, serving it for a larger request is wrong.

Related

Likely the same defect class as #238 ("After plugging my laptop to my TV via HDMI and unplugging it, the wallpaper becomes blurry"), auto-closed as stale without investigation. That report involves very different aspect ratios (1024x768 TV vs. 1366x768 laptop panel), which the aspect-ratio check in get_pixbuf_for_size() would correctly not match — but the filename-only file_cache_lookup() in get_as_pixbuf_for_size() has no such guard and would still serve the wrong-sized cached pixbuf. Both call sites are fixed here.

The fix

Both cache checks now require the cached pixbuf to be at least as large as what's being requested, before falling back to the aspect-ratio check (in get_pixbuf_for_size()) or returning it outright (in get_as_pixbuf_for_size()). To avoid punishing the common case of a wallpaper file smaller than the monitor — where the cached pixbuf being "too small" is simply the most the source image can offer, and forcing a reload would just re-fetch the same pixels — the required size is clamped to the source image's native resolution via gdk_pixbuf_get_file_info().

Testing

  • Built this patch against the currently shipping 6.6.2 (Linux Mint 22.3 "Zena": cinnamon-settings-daemon 6.6.4+zena, libcinnamon-desktop4 6.6.2+zena) and confirmed it applies and compiles cleanly against master as well (only the affected functions changed since 6.6.2).
  • Installed the patched library in place of the packaged one on the affected machine (Intel TigerLake-LP GT2 / i915, eDP-1 1366x768 + HDMI-1 2560x1440 via a dock/adapter).
  • With the reporter's user-level autostart workaround disabled, did a full cold boot: external monitor wallpaper was sharp from login, matching the original file (edge energy 8.08 vs. 8.06 for the original, see table above).

🤖 Generated with Claude Code

file_cache_lookup() in get_as_pixbuf_for_size() is keyed only by
filename, and get_pixbuf_for_size()'s bg->pixbuf_cache short-circuit
matches by aspect ratio alone. Neither checks that the cached pixbuf
is actually big enough for what is being requested.

On a multi-monitor setup this causes a real, visible bug: if the
smaller monitor is rendered first in a fresh GnomeBG (e.g. right
after login, before the on-disk per-monitor wallpaper cache for the
larger monitor is consulted), its downscaled pixbuf gets cached under
the bare filename. The next monitor then reuses that undersized
pixbuf -- exact match in get_as_pixbuf_for_size, or aspect-ratio
match in get_pixbuf_for_size when the monitors share a similar aspect
ratio (e.g. two 16:9 displays) -- and it gets upscaled to fill the
larger monitor, producing a visibly blurry wallpaper even though the
source file has plenty of resolution.

Both call sites now require the cached pixbuf to be at least as big
as the request, clamped to the source image's native resolution so
wallpapers smaller than the monitor (which can never be upscaled from
more detail than they have) don't lose their cache on every draw.

Reproduced and fixed on a Linux Mint 22.3 (Cinnamon 6.6.9) laptop with
an eDP-1 1366x768 internal panel and an HDMI-1 2560x1440 external
monitor: after a cold boot the external monitor's wallpaper rendered
visibly soft (edge-detection energy ~5.7 vs. ~7.7-8.1 for the
original file, matching a simulated 1366x768-then-upscale reference),
while re-setting org.cinnamon.desktop.background picture-uri (the
known user workaround) forced a fresh render and fixed it for the
rest of the session. The on-disk per-monitor cache
(~/.cache/wallpaper/<monitor>_<placement>_<w>_<h>_<md5>) is not a
reliable signal when investigating this class of report: it stays
untouched and correctly sized because refresh_cache_file() skips
rewriting a file that is already valid by mtime, even while the
*displayed* image is the stale, upscaled one.

This is likely the same defect reported in linuxmint#238 (blurry wallpaper
after an HDMI TV, at a different aspect ratio, is unplugged and the
internal panel's geometry is restored) -- a case the aspect-ratio
check alone would miss, but the filename-only file_cache_lookup()
explains.

Verified the fix on the affected machine: built this patch against
the currently shipping 6.6.2, installed it in place of the packaged
libcinnamon-desktop4, and confirmed a cold boot (with the user-level
workaround disabled) renders the external monitor sharp from login
(edge-detection energy ~8.08, matching the original file's ~8.06).
@mtwebster

Copy link
Copy Markdown
Member

Thanks

One thing to note, Cinnamon will no longer be using this library in 6.8 - all background handling has been moved Cinnamon, and it has a new internal CinnamonBg library for dealing with this (and supports individual wallpapers/slideshows on each monitor).

@mtwebster
mtwebster merged commit daaf901 into linuxmint:master Sep 18, 2026
3 checks passed
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.

2 participants