2.24 is a practical one: screen recording got real controls, keyboard indicators stopped being noisy, media players behave across ii and waffle, and the update flow is finally visible instead of doing spooky background theater.
Added
- Settings UI Easy Mode: optional curated mode that hides 7 advanced pages from the nav rail (Background, Tools, Services, Advanced, Modules, Waffle Style, Compositor) and trims expert-only sections inside the remaining pages. New
settingsUi.easyModeconfig key, opt-in via the title-bar toggle (school/tune icon) in both overlay and window settings, also surfaced in Modules → Settings UI. Search results filter to essentials too. Fresh users pick Easy or Advanced on the welcome wizard. Default Advanced — nobody's existing layout shifts on update. - Welcome wizard refresh: 5 steps got a polish pass. Step 1 gains a "What you'll dial in" preview card (Theme & wallpaper / Layout / Features / Tips) so users see value before they decide to skip. Skip button moved to a top-right ghost link with a tooltip pointing at
inir welcomeso it's recoverable. Step indicator gets per-circle labels and a "Step X of N" counter, past circles are clickable to jump back. Features step expanded with weather widget, bar auto-hide, time-format selector (system/24h/12h) and show-seconds toggle. Ready step replaces the CLI tip card with a "Try it now" action card (pick wallpaper, test notification, show shortcuts cheatsheet, open quick settings — all one-click via existing IPC), and the troubleshooting callout is now a clickable card linking to the wiki guide. Wizard surface usescolLayer1Baseso Material/Cards styles stay solid even with content transparency on. inir welcomeCLI: re-runs the welcome wizard. For users who skipped on first run and now want to flip Easy/Advanced or replay the Try-it-now actions. Reuses the same launch lock pattern asinir settings-window.- Terminal-visible shell updates: clicking Update on the iNiR update overlay now launches
setupin the user's configured terminal (resolved viaAppLauncher.commandFor("terminal"), falls back to kitty) so the full TUI is visible — progress lines, success/warn/error banners, the snapshot ID, dependency checks, the lot. Output is also tee'd toupdate.logfor the diagnostics flow. Auto-closes on success, pauses with "Press Enter to close" on failure so the error doesn't disappear in a flash. Toggle in Settings → Services → "Open terminal during update" orshellUpdates.openTerminalOnUpdatein config.json. Default on, off restores the previous silent-background behavior. - Animated update phase indicator: every step in
setup updatenow shows a dot row (●●●◉○○○), the step counter[N/7], and either a braille spinner (atomic ops like the git pull, migrations, and shell restart) or a clean header followed by the existing verbose output (file sync, dependency check, python venv). Each spinner step ends with✓ msg (Xs)so the elapsed time is visible. Falls back to plain static lines when the output isn't a TTY (e.g. piped to a log file). New helperstui_step_start/tui_step_done/tui_step_fail/tui_step_warn/tui_step_skipinsdata/lib/tui.shand_step_phase_start/_step_phase_done/_step_phase_headerwrappers insetupthat write the sameupdate-statusmarkers as before. setup update --simulate: walks the seven update phases with the new TUI visuals, sleeps in place of the real ops, writes the same status markers as the real flow. No git pull, no rsync, no migrations, no shell restart — useful for previewing the visuals or exercising the resume-from-status-file logic without touching the install. Shows a "Simulation mode" banner and a final elapsed-time summary. Aliased as--dry-run.- OSK keep-on-top toggle (#135): new toolbar button on the on-screen keyboard. When enabled, the OSK re-stacks above launcher / sidebars / overview / settings / etc. as those overlays open, via a brief wlr-layer-shell remap. Default off. Requested by ImDarkos.
- Bluetooth device-aware icons: bar, verticalBar and sidebar quick toggles now show a Material Symbol that matches the connected device — headphones, keyboard, mouse, smartphone, watch, gamepad, printer, and friends. Falls back to the generic glyph for unknown devices. Same treatment in the waffle family via the existing
bluetoothDeviceIconhelper. - YAMIS monochrome iconset as an extra: dirn-typo's
yet-another-monochrome-icon-set(GPL-3, ~23 MiB). Cloned in user scope, never overrides the active icon theme — pick it from Settings → Appearance → Icon theme. Default-on for fresh installs (auto on-y), available via./setup extrasfor existing users, ff-pulled on update if already installed. inir colorpickerCLI: top-level command for the wallpaper color picker, documented alongside the rest of the CLI.- Keyboard status indicators across ii and waffle: Caps Lock, Num Lock, and layout changes now have native shell popups plus compact bar/taskbar indicators, backed by the shared layout service and kernel LED state instead of compositor-specific hacks. Settings live in Settings → General → Time and Waffle Settings → General → Time & Language. Num Lock is available but defaults off now, because keyboards love lying about it on startup.
- Date format controls finally exposed in settings: long and short date formats are now editable in both settings families, so clocks can use locale-friendly patterns like
dddd, MMMM ddwithout hand-editing config files. The vertical bar keeps its compact numeric day/month display instead of exploding on free-form short-date formats. - Screen recording presets and encoder controls: Settings → Tools → Screen recording and Waffle Settings → Interface → Screen recording now expose quality presets, acceleration mode, fallback behavior, codec, FPS, bitrate, CRF, encoder speed, pixel format, audio codec/source/backend/sample rate, and detected encoder capability. The recorder probes
ffmpeg, audio sources, render devices, VAAPI/NVENC availability, and picks safer defaults instead of assuming one GPU path fits every machine. Bold strategy avoided. - Discord-ready recording compression: optional post-recording compression creates a separate shareable copy while keeping the original untouched. Target size, max dimension, encoder speed, audio bitrate, and "only if needed" controls live under the same recording settings in both settings families. The compressor uses two-pass H.264 budgeting with a safety margin for Discord's 10/25/50 MB limits, so long clips stop becoming accidental file-transfer boss fights.
Changed
- Audio mic via PipeWire native: removed the 2s
wpctlpolling for mic state and now reads/writes through QS PipeWire bindings directly. Sink-sidewpctlpaths kept as a defensive fallback for USB / device-route edge cases. - Standalone-window environment isolation: settings, waffleSettings, welcome and killDialog now use
INIR_STANDALONE_WINDOW=1instead of piggybacking onQS_NO_RELOAD_POPUP. Fixes the main shell being incorrectly identified as a settings process — which suppressed reload toasts and skipped external theme application. Standalone windows also disable file watching now (single-shot UI doesn't need hot-reload). - Keyboard layout handling on Niri: layout availability now comes from
niri msg -j keyboard-layouts, so the switcher and panel indicator only show when there is actually more than one layout. The bar spacing also collapses when no keyboard indicators are visible.
Fixed
- Media artwork updates across all players: the shared resolver now drives ii, waffle, overview, sidebars, lock screens, OSDs, control panel, and media popup presets. It keys cache files by artwork URL plus title/artist/album, keeps the current art visible while the next image resolves, cache-busts local
file://display sources so Qt actually reloads them, and refuses empty cache files. Yes, Qt still needed convincing that the same path can contain new pixels. - Plasma Browser Integration / YouTube artwork flashing: browser-provided temp art like
/tmp/plasma-browser-integration_artwork_*.jpgis copied into iNiR's cover-art cache before being shown. When Plasma deletes the temp file, the player no longer flashes back to the fallback icon like it got jump-scared. - Media controls using one real control path: bar clicks, vertical bar clicks, sidebars, overview, control panel, lock media, waffle widgets, waffle action center, waffle lock, OSDs, and media popup presets now route previous/next/toggle through
MprisController. That gives normal MPRIS, YtMusic, and filtered active-player selection the same behavior everywhere, instead of each widget inventing its own tiny chaos engine. - YouTube previous/next when Plasma says "nah": on Niri, browser YouTube players can fall back to focusing the matching browser window, sending YouTube's Shift+P / Shift+N shortcuts through
wtype, then restoring focus. It is guarded behind Niri,wtype, a matching browser window, and an unlocked session; lock screens won't start steering random browser windows because that would be unhinged. - Manual media player selection in the compact sidebar: selecting another player now survives playback-state churn until that player disappears. The player switcher also uses the default context-menu delegate again, so switching does not depend on a broken
type: "item"model entry. - Media buttons pretending unavailable actions exist: previous/next buttons now bind to the same capability checks as the controller, including the browser fallback. If a player cannot go next, the UI stops putting on a little theater performance.
- Media popup startup crash:
PlayerBaseowns itsConnectionsobjects as properties now, avoiding theCannot assign to non-existent default propertycrash from looseConnectionsinside aQtObject. - Non-waffle media players picking the wrong browser/MPRIS duplicate for cover art: sidebar, media popup presets, control panel, overview, lock screen, and volume mixer now follow the same deduped active-player selection as the bar popup, so they use the art-capable entry instead of the empty sibling.
- Shared media cover art flashing back to the fallback icon during track changes: ii/shared players now keep valid art visible while rechecking/downloading, skip empty-path retries, and ignore aborted checker/downloader exits.
- Bar resources stuck at "100% memory, 0% rest" until the sidebar opened:
ResourceUsage.qmldefaultedmemoryTotal=1with no zero-guard, so the percentage binding evaluated to 100% before the first poll. The poll waited a fullupdateInterval(3s) andFileView.text()returned empty on that first call anyway. NowensureRunning()primes_pollSensors()synchronously and initial totals start at0with a percentage guard. - Quickshell grabbing the NVIDIA dGPU on hybrid laptops (#136, discussion #133): even with
resources.monitorGpu=falseskipping the polling (#106), the Vulkan loader stilldlopen'dlibnvidia-*during device enumeration, opening the/dev/nvidia*fds visible in the issue's lsof. Newapply_gpu_policy()in the launcher detects hybrid via DRMboot_vgaand, when the toggle is off, setsVK_LOADER_DRIVERS_DISABLE=*nvidia*,MESA_VK_DEVICE_SELECT=pci-<iGPU>,__GLX_VENDOR_LIBRARY_NAME=mesa,__VK_LAYER_NV_optimus=non_NVIDIA_only,VDPAU_DRIVER=none, and a few related vars beforeQGuiApplicationinitialises. One Settings toggle controls both halves. Hard opt-out:INIR_GPU_FORCE_DEFAULT=1. - Hot-reload SIGSEGV: shipped Quickshell upstream patch (
patches/quickshell/fix-extension-uaf.patch) moves extension deletion inEngineGeneration::destroy()to after root destruction, fixing the use-after-free inIpcHandlerRegistry. Also addedQS_DISABLE_CRASH_HANDLER=1to the systemd unit so failed reloads stop dumping ~1 MB crash reports into~/.cache/quickshell/crashes/on every iteration. - Duplicate settings / welcome instances on rapid keypress:
flockguard inopen_detached_qml_window(). - Token compliance across settings UI: hardcoded white / black / orange replaced with the matching token (
colOnLayer0,Looks.colors.fg,colWarning). Spinbox schedule never saving (wrong signal name) and disabled toggles rendering as checked are also fixed in the same pass. - Material lock screen red-screen artifact on Niri: material lock kept three legacy
FastBlurpaths (Image, AnimatedImage, Video) alive in the QML tree even when the safeMultiEffectpipeline was the actual renderer. Some GPU drivers fail to compile the FastBlur shader and leak a red buffer through, even on invisible items. Switched material lock to the same source →MultiEffectshape waffle already used, then gatedsourceandlayer.enabledof the FastBlur paths on!useSafeBlurPipelineso they go fully inert on Niri. Hyprland behavior unchanged. - Session sleep crashing the shell: the lock-before-sleep flow had a broken sleep path that took down
inir.servicewhenever the system tried to suspend with the lockscreen on. Suspend requests now go through the sharedSessionflow; hibernate visibility kept honest. - Color generation firing 2-3× per action, apps coming out washed: three overlapping bugs.
ThemeService.onReadyChangedreset the live-regen signature on every Quickshell instance — including the standalone settings window — firing a phantomregenerateAutoTheme()on open. ExplicitregenerateAutoTheme()calls left the debounce primed to fire again ~700 ms later because the signature wasn't synced. Andswitchwall.shranapplycolor.shinternally while the shell'sMaterialThemeLoaderwatcher ran it again on the samecolors.jsonchange — two parallel module waves racing each other through GTK / chromium / spicetify / pear-desktop. Now the signature primes up front,setAutoRegenTimerroutes throughregenerateAutoTheme, and the script-sideapplycolor.shis gone —theming_modules.logshows one run per user action instead of 8-12+ per second. - Settings overlay → window mode toggle was a no-op: clicking "Window" in the material settings overlay flipped
overlayModethen started a 500 ms timer to launchinir settings-window. TheLazyLoaderinshell.qmlunloads the overlay component the instantoverlayModeflips — including that timer. Spawn never fired. Now mirrors the window→overlay shape: spawn first (process survives the QML scope), then close. - Updater treating diverged branches like routine pulls: prerelease VMs and local-dev checkouts could be reset to the remote without warning. The flow now classifies behind / up-to-date / ahead / diverged separately and refuses destructive recovery on diverged branches.
setupanddoctorreporting vague maintenance guesses: both commands and the launcher now show the actual repo checkout, launcher path, and service state. The repair help path stops dumping users into generic usage text.screen-offrespecting idle inhibitors: any app keeping the session awake (browsers playing audio, mpv, video calls) prevented the timeout from firing. Screen-off now keys off user input only; lock and suspend keep the inhibitor-respecting default. Power on/off routes throughCompositorServiceIPC for free Hyprland support and surfaced failures.- Duplicate
hideWhenFullscreenkey in waffles.background defaults: copy-paste leak after the backdrop block. Both occurrences weretrueso behavior was unchanged, but JSON parsers warned and any future divergence would have silently lost one assignment. - Update progress display vanishing after the shell restart mid-update: clicking the bar's update indicator opened the overlay, clicking Update kicked off
setup updatedetached, and once the script reached step 7 (systemctl --user restart inir.service) the new shell instance came up withisUpdating=false. The bar X/7 indicator stopped, the overlay didn't reopen, and the user was left wondering whether the update was running or wedged.ShellUpdates.qmlnow readsupdate-status1s after init: in-flightprogress:N:M:msgmarkers (N < M) restoreisUpdating, step/total/message and resume the existing poller + watchdog so the bar pill resumes counting; final-step markers (N >= M) are treated as completed and cleared;successis cleared as stale state;failed:NsurfaceslastErrorand clears so it doesn't replay every restart. - Bar update popup content looking shifted/off-center:
ShellUpdateIndicatorhad the popupColumnLayoutattached directly asStyledPopupcontent but not centered in the popup background, so rows hugged the top-left and looked offset relative to the card. It now uses the sameanchors.centerIn: parentpattern as the other bar popups (battery/resources), so content lands where it's supposed to. - Settings Easy/Advanced mode tooltip being both too wide and backwards: the title-bar mode toggle tooltip near the top-right controls could spill off-screen, and the later copy tweak still described the current state instead of the action, which was just rude. The tooltip now opens to the left and says what the click actually does: switch to Easy mode or switch to Advanced mode. Also removed the redundant Easy pill badge from the overlay header since the top-right toggle already does the job.
- Update terminal closing immediately on success: the terminal launched for
setup updatewould vanish the instant the update finished, giving nobody a chance to read what happened. Now stays open with a short summary and waits for the user to close it manually. - Dock hover-reveal trigger grabbing the whole screen edge: when the dock was hidden in hover-reveal mode, the reveal
MouseAreacould stretch into a giant invisible edge strip instead of matching the dock footprint. The hitbox now uses the real dock width/height and only the anchors needed for the current edge, so reveal tracks the dock area instead of some random side of the monitor.
Issues / PRs
Contributors
Thanks to ImDarkos (#135), ST-SARAVANAPRIYAN (#136) and standwlkdljea (#106).
Update: https://github.com/snowarch/iNiR?tab=readme-ov-file#update
Fresh install: https://github.com/snowarch/iNiR?tab=readme-ov-file#install
Full changelog: https://github.com/snowarch/iNiR/blob/main/CHANGELOG.md