2.25 is the desktop widgets release. They finally work — edit mode, custom widgets, resize, persistence, the whole thing. The color pipeline got its biggest architectural change in a while with app-palette, and the shell now actually reads the wallpaper to pick text colors instead of guessing. Also, Steam theming moved to Millennium because Adwaita for Steam is dead.
Added
- Desktop widgets v2: complete rewrite of the widget system. Edit mode (shown above) with a manager panel, IPC-driven config persistence, resize handles, zone placement, custom widget pipeline with a real SDK (
setSourcefor required props, schema key declaration), and popover chips redesigned withSelectionGroupButton+GridLayout. Fixed approximately 15 critical bugs along the way — VME segfaults, stale-text config overwrites, loader feedback loops, edit controls eating their own clicks, widgets spawning at top-left, zone placement breaking on resize. The kind of PR that needs therapy afterward. - Audio visualizer desktop widget: waveform/spectrum visualizer as a background desktop widget. Configurable type and position across all presets.
- App palette (
app-palette.json): semantic intermediate color layer between raw Material You tokens and external apps. Provides contrast-safe tokens (app_background,app_surface,app_headerbar_bg,app_selection,app_border_subtle, etc.) derived from M3 surface/primary with readable-contrast enforcement (WCAG 4.5:1 minimum). All shell/python theming modules (GTK, Chrome, Spicetify, editors, Zed, Pear Desktop, SDDM, terminals, system24) and matugen templates now prefer app-palette with graceful fallback. - Brightness-aware widget text: desktop widgets analyze the actual wallpaper region behind them (grayscale average) to pick light/dark text with accent tinting, instead of relying on the global dark/light mode. New
--color-onlymode in the image analysis script does position-aware color without the full region search. Widgets re-analyze on drag end and wallpaper change. - Recording OSD redesign: redesigned Recorder widget with a real timer, status bar, and game mode section. Auto-hide behavior with
Mod+Shift+Rkeybind. Multi-indicator support withmediaEnabledtoggle. MediaIndicator and MediaOSD layout polished. - Clipboard navigate mode: keyboard navigation in the clipboard panel. HTML display cleanup, copy operation preserves cursor position. Also added HTML and Unicode sanitization helpers so pasted content doesn't inject garbage.
- Sidebar requested-widget navigation: any component can now request a specific sidebar tab by type (e.g.
"notepad") throughGlobalStates.sidebarRightRequestedWidgetwithout reaching into Persistent state. Both sidebar layouts listen and switch accordingly. Bar notepad button uses this instead of hardcoded tab indices. - Notepad context menu: right-click context menu in the notepad widget, themed selection colors, and persistent selection so you don't lose your highlight when the sidebar loses focus.
- Cava configuration: Settings UI for cava process parameters (sensitivity, bars, framerate, stereo) with live apply. Both ii and waffle settings families. Cava colors now generated from the theme palette.
- Configurable wave visualizer opacity: global wave opacity in Advanced Settings (cava section) + per-widget override in the desktop visualizer edit popover. Media player wave visualizers inherit the global value automatically.
- Settings UI for file paths: screenshot/recording filename formats, wallpapers directory, and booru download paths now editable from Settings.
- Lock screen widget customization: configurable widgets on the lock screen.
- Notepad tabs in sidebar: right sidebar notepad now supports multiple tabs.
- Background widget auto-fade: desktop widgets fade when compositor windows overlap them, keeping them unobtrusive during work.
- Widget edit mode polish: scrim overlay, repainted grid canvas, zone occupancy indicators with widget icons and direction arrows, placement strategy badges on widget labels. Drag behavior fixed — release guard, zone snap removed (zones cover entire screen so auto-snap was always wrong).
- Custom AI provider management (#134): add, edit, and delete custom AI providers from Settings → Services. Auto-detects API format from endpoint URL. New Anthropic Messages API strategy (SSE streaming, x-api-key auth) and OpenAI Responses API strategy. Extra models moved from hardcoded to
config.jsonentries with cross-process sync viaconfigChangedsignal. Settings UI reworked withConfigSelectionArrayformat selector, format icons and badge pills on provider list items, and improved empty state. Sidebar AI indicators (model, tool) are now clickable — prefill the corresponding/commandto trigger autocomplete. Settings gear shortcut and dynamic placeholder text. - Setup recipes framework (#138): auto-discovered
/setup-*launcher actions backed by self-contained, idempotent shell scripts inscripts/setup/. Metadata scanner (_scan.sh) emits JSON for QML — adding a.shfile is enough, no QML changes or restart needed. Shared_lib.shhelper with distro detection, progress notifications, and package helpers. First recipe:spotify.sh(AUR/Flatpak install, Spicetify + Marketplace). Gated behindenableSetupconfig toggle.
Changed
- SDDM pixel theme migrated to Qt6 (#137):
QtGraphicalEffects→Qt5Compat.GraphicalEffects, metadata rewritten from[Desktop Entry]to[SddmGreeterTheme]format withQtVersion=6. - Steam theming moved to Millennium: Adwaita for Steam is deprecated; theming now goes through Millennium's Material-Theme plugin. Updated translations across all locales.
- Aurora configurable glass transparency: style editor now exposes glass opacity. Fixed live reactivity that was broken because Config.revision wasn't being used as a dependency.
- Doctor TUI overhaul: animated steps with palette-aware badges, dot threshold, tagline centering. All deps now required (wlsunset added). Font list cleanup. Quickconfig buttons theme-adaptive. Compact output.
- switchwall performance: batched 24 individual jq config reads into a single mapfile call (62ms → 3ms). Integrated scheme auto-detection into
generate_colors_material.py(eliminates separate process spawn, saves ~585ms). Net: ~904ms → ~760ms per wallpaper change. - Wallpaper selector performance: eliminated per-item
magick identifyspawn (thundering herd — N concurrent magick processes on directory open). Lowered thumbnail size from 512px to 256px (4x less memory). Increased batch workers from 1 to 4. Removed per-item OpacityMask FBO. - applycolor module runner: replaced unbounded fork-all with a sliding window (nproc/2, capped at 4) using
ionice -c 3+nice -n 10. New manifest-based enablement skips disabled targets entirely instead of spawning them to self-exit. - Dock shadow opacity: per dark/light mode tuning (0.18/0.35) with spread reset for cleaner appearance.
- Quick Settings wallpaper section: Transparency and Colors-only toggles now side-by-side, reducing vertical footprint so the wallpaper thumbnails are more prominent.
- Debug logging gated behind QS_DEBUG: ~72 informational
console.logcalls across 29 files converted to a_log()helper that only prints whenQS_DEBUG=1. Error and failure logs remain unconditional. - Bar top-left icon defaults to distro logo: the icon button now shows the detected distro logo instead of a generic symbol.
- Aurora and Angel default opacity/blur increased: raised defaults for a more polished out-of-the-box look.
- Control panel weather: location toggle replaced with a minimal clickable eye icon to match the adjacent refresh button aesthetic.
Fixed
- Dock stale toplevel ghost entries: NiriService window matching could produce false matches on zero-score entries, and
sortedToplevelscould contain stale compositor entries not present in live ToplevelManager. Both now guarded. - Preset theme colors not propagating to external apps (#144):
FileView.setText()dropped async writes whenapplyPreset()fired 2-3x rapidly on startup. Replaced 6 FileView instances with a single debounced bash script that writes all generated files atomically. Also addedkde-cli-toolsas a dependency and doctor check — required for Dolphin's "Open With" dialog whenQT_QPA_PLATFORMTHEME=kde. - VSCode theme pipeline: switched to local extension with
_watch:truefor hot reload — no more toggle hack, nosettings.jsoninjection on every wallpaper change. Cursor / Windsurf / Antigravity / VSCodium now actually load the theme: forks only register extensions listed in theirextensions.json, not just present on disk. Strip restores the user's previous theme and removes the registry entry. Legacyinir.inir-theme-1.0.0directories cleaned up. - Wallhaven panel completely broken: missing
import Quickshellin the service. Every code path that called_log()aborted withReferenceError: Quickshell is not defined— searches, tag suggestions, tag counts, detail fetches. Same bug found and fixed incontrolPanel/WallpaperSection.qml(scheme variant chip), then a codebase-wide audit verified no other QML files use theQuickshellglobal without importing the module. - Desktop widgets growing off-screen: media widget gaining an extra MPRIS player (and any other dynamic-height widget) used to push past the screen edge in
freeplacement, forcing the user to reposition by hand. Position is now re-clamped automatically when (pos + size) > screen size, without overwriting the saved config — the widget snaps back to its original spot once the content shrinks again. - App grids not reflowing on resize: waffle start menu's pinned + all-apps grids had hard-coded
columns: 6plus fixed-width buttons, so resizing the menu either clipped buttons (too narrow) or left empty space on the right (too wide). Replaced withFlowinside a centered wrapper — columns adapt to width, row stays centered. - Overview search row left-clumped when results widen the panel:
ToolbarTextFieldhad only animplicitWidthso when result cards stretched the container, the input + icons stayed at 360px and everything clumped left.Layout.fillWidthlets the input absorb the extra space and keeps the row balanced. Also fixed a copy-paste bug in the result list'sOpacityMask(height: x.width→.height) that clipped results vertically. got operation finished from dropped FileView operationboot spam: three independent races where async FileView ops were being cancelled by overlapping reads/writes.Persistent.qml'swriteAdapter()raced its ownonFileChangedreload (now gated by_writeInFlightlikeConfig.qml, withsaveFailedhandling so a failed write doesn't leave the gate stuck). Multiple panel consumers callingResourceUsage.ensureRunning()in theirComponent.onCompletedeach triggered a poll, racing 5reload()ops every call (now primes once).MaterialThemeLoader'sreapplyTheme/_applyVariant/onFileChangedall calledthemeFileView.reload()directly during boot when the theme pipeline rewrites the JSON several times (now routed through a single 50ms debounce). Drops down from 5+ warnings to 3 residual benign ones.- Media artwork sync across shell: shared resolver now correctly drives all surfaces.
- Sidebar dynamic padding and angel border: restored when the border-disable flag was set.
- Audio mic mute/volume: backed with
wpctlinstead of unreliable QML PipeWire bindings for the mic path. - Booru providers: waifu.im tag search (case sensitivity), t.alcy.cc fixes,
/toggle-tagsbehavior, zerochan rating filter. - Recording OSD HoverHandler crash: undefined
hoveredaccess. - OSD hotZone behavior: removed autoHideHotZone, keep mask on pill at all times.
- Notification popup card shadows: removed shadow that was visually incorrect on transparent backgrounds.
- Overview dashboard weather layout: Material 3 chips with Flow layout instead of broken grid.
- Screenshot clipboard pollution: previews no longer pollute the clipboard with intermediate screenshot data.
- Opus audio codec options: clarified labels in Settings UI.
- Translation generation hitting ARG_MAX (#140):
gemini-translate.shpassed the fullen_US.json(~228 KB) as a shell argument, exceeding the kernel's 128 KB per-argument limit. Refactored to use jq--rawfileand pipe payload to curl via stdin. - Weather location leaked in logs: city name and coordinates were logged in plaintext when
hideLocationwas off. Now always redacted regardless of the UI toggle — logs are persistent and can be shared accidentally. - Cava settings UI showing non-functional options: removed colorSource, gradientCount, barWidth, barSpacing, foreground, and background controls from both ii and waffle settings. These had no consumers — CavaProcess only passes sensitivity/bars/framerate/stereo to the cava process; visual parameters are per-widget by design.
- Cava config restart race:
stop()now waits for process exit before regenerating config and restarting — was the root cause of needing to toggle multiple times for changes to take effect. - Lock screen battery percentage: now shows correctly. Intel CPU temp sensor priority fixed.
- Notification popup lifetime: fixed urgency comparison and added configurable
maxPopupLifetime. - Weather widget shape tooltips: all tooltip variants were triggering at once instead of only the active shape.
- ConfigSelectionArray undefined assignment:
onYChangedcould fire before children were populated, assigningundefinedto abool. - Spicetify theme not applying on wallpaper change (#138):
apply-spicetify-theme.shnow always callsspicetify applywhen the watch process is not running, fixing stale theme bundles after wallpaper switch. - Spicetify setup recipe failing on existing installs (#138): stale backup deadlock (version mismatch preventing both restore and backup), wrong
spotify_pathpointing to spotify-launcher expanded dir instead of/opt/spotify(AUR). Recipe now auto-detects the correct path and does progressive recovery (backup → restore backup → nuke stale state → retry). - Setup recipes not executable via CLI:
globalActions runIPC handler required 2 arguments but CLI passed 1. Split intorun(actionId)andrunWithArgs(actionId, args). - SDDM Qt VirtualKeyboard hijacking input: Qt6 greeter loads
qtvirtualkeyboardby default, stealing all keyboard events from the theme's own input handling and "press any key" transition. SetInputMethod=(empty) in the SDDM drop-in config to disable it — our customVirtualKeyboard.qmlhandles on-screen input. - SDDM config not updating on
inir update: the installer'selevate teefor/etc/sddm.conf.d/inir-theme.confcould fail silently during updates, swallowing sudo errors as "non-fatal". Reworked to compare desired vs existing config (skip write if identical), always update settings when ii-pixel is already the active theme, and surface failures with a manual fix command instead of hiding them. - Update indicator popup overflowing the bar panel: monospace commit hashes and branch names pushed the popup past the panel edge. Capped popup content width at 280px with an
Itemwrapper that reports constrainedimplicitWidthtoStyledPopup, and addedelideto branch/progress text fields.
Issues / PRs
Contributors
Thanks to @EldoDvae for the SDDM Qt6 migration (#137), @Aurorainic for custom AI provider support (#134), and @yukazakiri for the setup recipes framework and Spicetify fix (#138).