quickshell: DIAGNOSTIC hide chrome SDF to measure its GPU cost

This commit is contained in:
rope 2026-08-19 13:40:20 +01:00
parent 389d7a5d65
commit 9d4b1f8c93

View file

@ -1,10 +1,56 @@
# settings/quickshell.nix — Quickshell desktop shell (bar, notifications, QML), # settings/quickshell.nix — Quickshell desktop shell (bar, notifications, QML),
# split out of settings/hyprland.nix. The hyprland-side blur layer rule for # split out of settings/hyprland.nix. The hyprland-side blur layer rule for
# the "quickshell-bar" namespace still lives there. # the "quickshell-bar" namespace still lives there.
{ config, pkgs, lib, ... }: { config, pkgs, lib, inputs, ... }:
let let
isMacbook = config.networking.hostName == "FredOS-Macbook"; isMacbook = config.networking.hostName == "FredOS-Macbook";
ui = import ./ui.nix; # shared corner-rounding knob ui = import ./ui.nix; # shared corner-rounding knob
# ── Wallpaper set ──────────────────────────────────────────────────────
# Every image in walls/ gets its own dark base16 palette, generated at
# build time by stylix's own palette-generator. The shell picks one at
# RUNTIME (state file + FileView in Theme.qml), so switching wallpaper
# re-tints the whole shell without a rebuild.
#
# One derivation per image, not one for the set: palette generation is a
# genetic search that takes seconds, and per-image derivations mean adding
# a wallpaper only regenerates the new one. builtins.path hashes the image
# content alone, so unrelated repo edits don't invalidate any of them.
paletteGenerator =
inputs.stylix.packages.${pkgs.stdenv.hostPlatform.system}.palette-generator;
wallFiles = builtins.attrNames (lib.filterAttrs
(n: t: t == "regular"
&& lib.any (e: lib.hasSuffix e (lib.toLower n)) [ ".png" ".jpg" ".jpeg" ])
(builtins.readDir (inputs.self + "/walls")));
# Store names allow a narrow charset, filenames don't. Keep wallpaper
# names plain — a space survives, anything more exotic may not.
wallStem = f: lib.head (lib.splitString "." f);
wallSlug = f: lib.replaceStrings [ " " ] [ "-" ] (wallStem f);
wallImage = f: builtins.path {
name = "wall-" + lib.replaceStrings [ " " ] [ "-" ] f;
path = inputs.self + "/walls/${f}";
};
wallPalette = f: pkgs.runCommand "palette-${wallSlug f}" { } ''
mkdir -p $out
${paletteGenerator}/bin/palette-generator dark ${wallImage f} $out/palette.json
'';
# The list Theme.qml carries verbatim. Naming the store paths inside a
# home-manager file keeps both image and palette alive as generation
# references — no extra gcroot needed.
wallsJson = builtins.toJSON (map (f: {
name = wallStem f;
image = "${wallImage f}";
palette = "${wallPalette f}/palette.json";
}) wallFiles);
# Falls back to walls/wallpaper.png — the one stylix themes everything
# else from — so a missing state file lands on the build-time scheme.
defaultWall = if lib.elem "wallpaper" (map wallStem wallFiles)
then "wallpaper"
else wallStem (lib.head wallFiles);
in in
{ {
config = lib.mkIf (lib.elem config.networking.hostName [ "FredOS-Gaming" "FredOS-Macbook" ]) { config = lib.mkIf (lib.elem config.networking.hostName [ "FredOS-Gaming" "FredOS-Macbook" ]) {
@ -383,25 +429,74 @@ in
onChange = qsRestart; onChange = qsRestart;
text = '' text = ''
pragma Singleton pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick import QtQuick
QtObject { QtObject {
id: theme
// Wallpapers
// Built in settings/quickshell.nix: one entry per image in
// walls/, each with a dark base16 palette generated from it.
readonly property var walls: ${wallsJson}
// Which one is live. Persisted so the choice survives a
// reboot; every colour below is a binding on `pal`, so
// writing this file re-tints the entire shell in place.
property FileView wallFile: FileView {
path: Quickshell.env("HOME") + "/.local/state/quickshell-wallpaper.json"
printErrors: false
// Synchronous: the palette load chains off this, and an
// async hop here means one frame painted in the wrong
// scheme on every startup.
blockLoading: true
adapter: JsonAdapter {
id: wallState
property string name: "${defaultWall}"
}
onLoadFailed: writeAdapter()
}
// Unknown name (wallpaper deleted from the repo since it was
// picked) falls back to the first entry rather than blanking.
readonly property var currentWall:
walls.find(w => w.name === wallState.name) || walls[0] || null
readonly property string wallImage: currentWall ? currentWall.image : ""
function setWall(name) {
wallState.name = name;
wallFile.writeAdapter();
}
property var pal: ({})
property FileView palFile: FileView {
path: theme.currentWall ? theme.currentWall.palette : ""
printErrors: false
blockLoading: true
onLoaded: theme.pal = JSON.parse(text())
}
// Surfaces: one hue, three steps. base01/base02 are tints of // Surfaces: one hue, three steps. base01/base02 are tints of
// base00 rather than stylix's own base01/base02, which come // base00 rather than stylix's own base01/base02, which come
// straight off the wallpaper and routinely land off-hue a // straight off the wallpaper and routinely land off-hue a
// purple card floating over a slate bar. Tinting keeps // purple card floating over a slate bar. Tinting keeps
// bar card hover reading as one stack. Two numbers, and // bar card hover reading as one stack. Two numbers, and
// they are the whole surface ladder. // they are the whole surface ladder.
readonly property color base00: "#${c.base00}" //
// The "#${c.base00}" fallbacks are stylix's build-time scheme
// (walls/wallpaper.png): what shows for the frame or two
// before palFile lands, and if a palette file ever goes bad.
readonly property color base00: "#" + (pal.base00 || "${c.base00}")
readonly property color base01: Qt.lighter(base00, 1.7) // card readonly property color base01: Qt.lighter(base00, 1.7) // card
readonly property color base02: Qt.lighter(base00, 2.4) // hover / chip / track readonly property color base02: Qt.lighter(base00, 2.4) // hover / chip / track
// Ink, wallpaper-derived: base05 primary, base04 muted, // Ink, wallpaper-derived: base05 primary, base04 muted,
// base03 disabled (also the border colour). // base03 disabled (also the border colour).
readonly property color base03: "#${c.base03}" readonly property color base03: "#" + (pal.base03 || "${c.base03}")
readonly property color base04: "#${c.base04}" readonly property color base04: "#" + (pal.base04 || "${c.base04}")
readonly property color base05: "#${c.base05}" readonly property color base05: "#" + (pal.base05 || "${c.base05}")
// The one themed accent: active, focused, selected, filled. // The one themed accent: active, focused, selected, filled.
readonly property color base0D: "#${c.base0D}" readonly property color base0D: "#" + (pal.base0D || "${c.base0D}")
// Status colours: fixed hues, pulled toward the theme. // Status colours: fixed hues, pulled toward the theme.
// Sourcing these from stylix doesn't work base08-base0F all // Sourcing these from stylix doesn't work base08-base0F all
@ -538,7 +633,7 @@ in
Image { Image {
id: wall id: wall
anchors.fill: parent anchors.fill: parent
source: "file://" + Quickshell.env("HOME") + "/.local/share/backgrounds/wallpaper.png" source: "file://" + Theme.wallImage
// cache:false soft reload (qs ipc reload) keeps the process // cache:false soft reload (qs ipc reload) keeps the process
// alive, and the QML pixmap cache is keyed by URL, so a new // alive, and the QML pixmap cache is keyed by URL, so a new
// wallpaper behind the same path never shows without this. // wallpaper behind the same path never shows without this.
@ -1485,7 +1580,7 @@ in
Image { Image {
anchors.fill: parent anchors.fill: parent
source: "file://" + Quickshell.env("HOME") + "/.local/share/backgrounds/wallpaper.png" source: "file://" + Theme.wallImage
cache: false // same-URL pixmap cache survives soft reload cache: false // same-URL pixmap cache survives soft reload
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
} }
@ -1947,6 +2042,9 @@ in
// ears / border-gaps / melt geometry lives in the math now. // ears / border-gaps / melt geometry lives in the math now.
ShaderEffect { ShaderEffect {
id: chromeSdf id: chromeSdf
// DIAGNOSTIC ONLY revert. Measuring how much of the
// shell's idle GPU cost is this full-screen SDF pass.
visible: false
anchors.fill: parent anchors.fill: parent
// Panels grow rightward out of the bar column, so the // Panels grow rightward out of the bar column, so the
// cross-axis is now vertical: the 8px inset that used to // cross-axis is now vertical: the 8px inset that used to
@ -3833,6 +3931,36 @@ in
} }
} }
BarSep {}
// Wallpaper picker. Sits under the workspaces because it
// grows downward with them the mid/bottom clusters
// anchor from the other end and never move.
Item {
id: wallBtn
width: Theme.barWidth
height: 24
SIcon {
anchors.centerIn: parent
text: "image"
font.pixelSize: 18
color: bar.activeDropdown === wallDropdown ? Theme.base0D
: wallMa.containsMouse ? Theme.base05 : Theme.base04
Behavior on color { ColorAnimation { duration: Theme.animFade } }
}
MouseArea {
id: wallMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: bar.toggleDropdown(wallDropdown, function() {
let pos = wallBtn.mapToItem(bar.contentItem, 0, wallBtn.height / 2);
wallDropdown.dropdownY = pos.y;
})
}
}
} }
// Mid cluster: the server glance, pinned just above the // Mid cluster: the server glance, pinned just above the
@ -4992,6 +5120,78 @@ in
} }
} }
// Wallpaper picker thumbnail grid. Clicking a tile writes
// the state file, which re-binds Theme's whole palette: the
// wallpaper, the bar and every open panel re-tint together,
// no rebuild and no restart.
BarDropdown {
id: wallDropdown
fullWidth: wallCard.width + 2 * Theme.panelGap
fullHeight: wallCard.height + 2 * Theme.panelGap
Rectangle {
id: wallCard
anchors.centerIn: parent
width: wallGrid.width + 2 * Theme.cardPad
height: wallGrid.height + 2 * Theme.cardPad
radius: Theme.radiusCard
color: Theme.cardBg
Grid {
id: wallGrid
anchors.centerIn: parent
// Two up: a 16:9 thumb wide enough to recognise
// still leaves the panel narrower than the
// launcher, and the column stays short.
columns: 2
spacing: 6
Repeater {
model: Theme.walls
ClippingRectangle {
id: wallTile
required property var modelData
readonly property bool active:
Theme.currentWall && Theme.currentWall.name === modelData.name
width: 124
height: 70
radius: Theme.radiusTiny
color: Theme.base01
// Accent ring on the live one, base04 on
// hover same selection language as the
// workspace pill above it.
border.width: Theme.borderWidth
border.color: active ? Theme.base0D
: wallTileMa.containsMouse ? Theme.base04
: "transparent"
Behavior on border.color { ColorAnimation { duration: Theme.animFade } }
Image {
anchors.fill: parent
source: "file://" + wallTile.modelData.image
// Decode at thumbnail size: the full
// 4K texture per tile is what makes a
// picker like this cost real VRAM.
sourceSize.width: 248
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
MouseArea {
id: wallTileMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: Theme.setWall(wallTile.modelData.name)
}
}
}
}
}
}
// Volume dropdown // Volume dropdown
// Server monitor dropdown the btop-at-a-distance view. // Server monitor dropdown the btop-at-a-distance view.
BarDropdown { BarDropdown {