From 613482d4510aa6ebd9b51e9208e64dd96699e9e0 Mon Sep 17 00:00:00 2001 From: John Pals <7024725+DigitalPals@users.noreply.github.com> Date: Sun, 27 Sep 2026 09:25:21 +0200 Subject: [PATCH] Reduce blur and enable Remote Server by default Use one blur pass in the shared desktop and ISO template. Enable Remote Server with an empty host, replace personal examples, and include the SSH client dependency. Preserve saved widget choices and verify fresh-install defaults. Validation: repository checks passed with 1177 unit tests; all 11 desktop image configuration tests passed. --- docs/operations.md | 2 +- docs/quickshell-notes.md | 12 +++++++---- docs/remote-server-widget.md | 18 +++++++++------- image/cybexos-desktop.spec | 2 +- image/test_desktop_payload.py | 11 +++++++--- roles/base/defaults/main.yml | 2 ++ .../files/quickshell/Common/Settings.qml | 2 +- .../quickshell/Common/SettingsHelpers.js | 2 +- .../quickshell/Settings/ModuleDetailView.qml | 4 ++-- roles/desktop/templates/looknfeel.lua.j2 | 10 ++++----- tests/quickshell/power-saver.test.cjs | 6 +++--- tests/quickshell/remote-server.test.cjs | 21 +++++++++++++------ .../quickshell/system-theme-hyprland.test.cjs | 2 +- tests/remote-server.py | 2 +- 14 files changed, 59 insertions(+), 37 deletions(-) diff --git a/docs/operations.md b/docs/operations.md index f583a269..6c3738c1 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -623,6 +623,6 @@ their isolated offscreen process. The power saver profile is a reduced-motion request too, for as long as it is on; it changes neither this variable nor **Settings → Appearance → Reduce -motion**. The compositor follows it as well: blur drops to one pass and +motion**. The compositor follows it as well: it uses one blur pass and Hyprland animations turn off, and the values it replaced return when power saver ends. diff --git a/docs/quickshell-notes.md b/docs/quickshell-notes.md index d61062b2..740ed244 100644 --- a/docs/quickshell-notes.md +++ b/docs/quickshell-notes.md @@ -646,10 +646,14 @@ glass and detached panels. What that added, and what it needs: artwork and functional shapes such as switch tracks and progress meters retain their own presentation. Update assets with `scripts/update-tabler-icons`; see `assets/tabler/README.md`. -- **Blur is the compositor's.** `roles/desktop/files/looknfeel.lua` exports the - named `quickshell_blur_rule` matching the `qs-*` namespaces. The Appearance - switch calls that handle through `hyprctl eval`; its initial `enabled` value - is read from the persisted JSON so compositor reloads retain the choice. +- **Blur is the compositor's.** `roles/desktop/templates/looknfeel.lua.j2` + defaults to one blur pass to reduce desktop GPU work while keeping + animations and display refresh rates unchanged. Both Ansible deployment + and the ISO desktop package render this template. + It exports the named `quickshell_blur_rule` matching the `qs-*` namespaces. + The Appearance switch calls that handle through `hyprctl eval`; its initial + `enabled` value is read from the persisted JSON so compositor reloads retain + the choice. Layer namespaces stay fixed because changing one after a Wayland surface is connected does not update the compositor rule safely. - **Nothing that floats over the desktop may draw a drop shadow.** Blur is diff --git a/docs/remote-server-widget.md b/docs/remote-server-widget.md index fa216ab2..3985f34c 100644 --- a/docs/remote-server-widget.md +++ b/docs/remote-server-widget.md @@ -1,10 +1,14 @@ # Remote Server widget -Add **Remote Server** from Settings → Menubar → Widgets, then open its options. -Set **SSH host** to an existing SSH alias or `user@hostname`, and optionally set -a display name. For example: `john@10.10.0.7`, **The Beast**. These are personal -settings, not distribution defaults; new installations leave the widget disabled -and its host empty, and its dashboard offers **Choose SSH host** until one is set. +**Remote Server** is included and enabled in the menubar on new installations, +including ISO installations. Its host and display name start empty: the bar +shows **Set up** and the dashboard offers **Choose SSH host**. No SSH connection +starts until a host is configured. Existing visibility and host preferences are +preserved when upgrading. + +Open its options in Settings → Menubar → Widgets. Set **SSH host** to an existing +SSH alias or `user@hostname`, and optionally set a display name. For example: +`user@server.example.com`, **My server**. These are personal settings. The default menubar statistic is CPU utilization. Options include load average, memory used percent/bytes or available bytes, filesystem used percent/free bytes, @@ -49,8 +53,8 @@ dashboard while its header and footer stay fixed. - OpenSSH and Python 3 on the desktop. GNU `df` provides local filesystem stats; `ip` provides optional addresses/default-route discovery on the server. - Working noninteractive SSH key/agent authentication. First connect in a - terminal, for example `ssh john@10.10.0.7`, to verify its host key. Unknown or - changed keys are rejected; the widget never accepts them automatically. + terminal, for example `ssh user@server.example.com`, to verify its host key. + Unknown or changed keys are rejected; the widget never accepts them automatically. - Configure ports, identities and jump hosts in `~/.ssh/config`. The host field accepts a destination, not SSH options or a shell command. diff --git a/image/cybexos-desktop.spec b/image/cybexos-desktop.spec index 87c22978..45aebeee 100644 --- a/image/cybexos-desktop.spec +++ b/image/cybexos-desktop.spec @@ -18,7 +18,7 @@ Obsoletes: fedora-config-desktop < %{epoch}:%{version}-%{release} # filesystem separately. Avoid spending minutes recompressing user toolchains. %global _binary_payload w3.zstdio %global _binary_filedigest_algorithm 8 -Requires: bash coreutils util-linux systemd python3 ansible-core gnupg2 +Requires: bash coreutils util-linux systemd python3 ansible-core gnupg2 openssh-clients Requires: sddm sddm-wayland-generic systemd-pam gnome-keyring-pam Requires: hyprland hyprland-guiutils quickshell hypridle hyprlock hyprsunset Requires: xdg-desktop-portal-hyprland xdg-desktop-portal-gtk xdg-utils diff --git a/image/test_desktop_payload.py b/image/test_desktop_payload.py index 15492aad..7d515d7d 100644 --- a/image/test_desktop_payload.py +++ b/image/test_desktop_payload.py @@ -46,7 +46,7 @@ def test_portable_defaults_and_boot_assets_share_workstation_sources(self): defaults = json.loads((vendor / "essential-seed/.config/cybexos/shell.json").read_text()) self.assertEqual(defaults, contract["shell"]) # Exercise first-login seeding followed by the real shell merger: - # an unversioned seed must not revive a historical font default. + # an unversioned seed keeps current defaults and no personal host. (vendor / "bin").mkdir() (vendor / "runtime").mkdir() home = payload / "installed-user" @@ -57,11 +57,16 @@ def test_portable_defaults_and_boot_assets_share_workstation_sources(self): "node", "-e", "const fs = require('fs'), H = require(process.argv[1]); " "const s = H.merge(JSON.parse(fs.readFileSync(process.argv[2], 'utf8'))); " - "console.log(H.FONT_CHOICES.find(f => f.id === s.font).family);", + "console.log(JSON.stringify({font: H.FONT_CHOICES.find(f => f.id === s.font).family, " + "remote: s.mods.right.find(m => m.id === 'remote'), options: s.modOpts.remote}));", str(ROOT / "roles/desktop/files/quickshell/Common/SettingsHelpers.js"), str(preferences), ], text=True, capture_output=True, check=True) - self.assertEqual(result.stdout.strip(), "JetBrainsMono Nerd Font") + settings = json.loads(result.stdout) + self.assertEqual(settings["font"], "JetBrainsMono Nerd Font") + self.assertTrue(settings["remote"]["on"]) + self.assertEqual(settings["options"]["host"], "") + self.assertEqual(settings["options"]["label"], "") wallpaper = vendor / "essential-seed/Pictures/Wallpapers" / defaults["wall"] self.assertEqual(wallpaper.read_bytes(), (ROOT / "assets/wallpapers" / defaults["wall"]).read_bytes()) self.assertTrue(app.is_file()) diff --git a/roles/base/defaults/main.yml b/roles/base/defaults/main.yml index 53eb5e34..c7d50eee 100644 --- a/roles/base/defaults/main.yml +++ b/roles/base/defaults/main.yml @@ -22,6 +22,8 @@ base_required_packages: - gvfs-fuse - mokutil - NetworkManager +# Remote Server's SSH transport is available on every installation. +- openssh-clients - pipewire - pipewire-alsa - pipewire-pulseaudio diff --git a/roles/desktop/files/quickshell/Common/Settings.qml b/roles/desktop/files/quickshell/Common/Settings.qml index a07b6d12..9240fc62 100644 --- a/roles/desktop/files/quickshell/Common/Settings.qml +++ b/roles/desktop/files/quickshell/Common/Settings.qml @@ -961,7 +961,7 @@ Singleton { } // Power saver takes the compositor's polish too: cybexos_power_saver() in - // looknfeel.lua drops blur to one pass and turns animations off, and + // looknfeel.lua uses one blur pass and turns animations off, and // holds that across a config reload. It is sent on startup as well, which // also lifts a saver left behind by a shell that exited meanwhile. property bool dispatchedPowerSaver: false diff --git a/roles/desktop/files/quickshell/Common/SettingsHelpers.js b/roles/desktop/files/quickshell/Common/SettingsHelpers.js index 54017ba6..22d6a0cc 100644 --- a/roles/desktop/files/quickshell/Common/SettingsHelpers.js +++ b/roles/desktop/files/quickshell/Common/SettingsHelpers.js @@ -217,7 +217,7 @@ function defaultMods() { mod("notes", true)], right: [ mod("modelusage", false), mod("updates", true), mod("gh", false), mod("t3", false), - mod("hermes", false), mod("remote", false), + mod("hermes", false), mod("remote", true), mod("tray", false), mod("notifications", true), mod("vol", true), mod("wifi", true), mod("bt", true), mod("batt", true), mod("control", true) ] diff --git a/roles/desktop/files/quickshell/Settings/ModuleDetailView.qml b/roles/desktop/files/quickshell/Settings/ModuleDetailView.qml index b14bd2ab..af48a57d 100644 --- a/roles/desktop/files/quickshell/Settings/ModuleDetailView.qml +++ b/roles/desktop/files/quickshell/Settings/ModuleDetailView.qml @@ -911,7 +911,7 @@ SettingsPage { SettingsTextRow { width: parent.width label: "SSH host" - placeholder: "john@10.10.0.7 or SSH alias" + placeholder: "user@hostname or SSH alias" hint: "Uses your SSH config and keys. Connect once in a terminal to verify the host key. Requires Linux and Python 3.9+." value: view.opts.host dirty: view.optDirty("host") @@ -921,7 +921,7 @@ SettingsPage { SettingsTextRow { width: parent.width label: "Display name" - placeholder: "The Beast" + placeholder: "My server" value: view.opts.label dirty: view.optDirty("label") onCommitted: text => view.setOpt("label", text) diff --git a/roles/desktop/templates/looknfeel.lua.j2 b/roles/desktop/templates/looknfeel.lua.j2 index f38725ba..fdad742c 100644 --- a/roles/desktop/templates/looknfeel.lua.j2 +++ b/roles/desktop/templates/looknfeel.lua.j2 @@ -38,13 +38,11 @@ hl.config({ -- Match Quickshell's Theme.hugCornerSize so tiled window corners continue -- the menubar's inverted edge curve at the reserved boundary. rounding = 16, - -- In glass mode every menubar and panel surface is a translucent tint over - -- whatever is behind it, and without a real blur they read as smeared - -- plastic. Three passes at this radius - -- is the point where the wallpaper stops being legible through the bar; + -- Blur separates translucent surfaces from the wallpaper. One pass keeps + -- compositing work down on high-resolution, high-refresh-rate displays; -- vibrancy stands in for the design's saturate(1.7). blur = { - enabled = true, size = 6, passes = 3, new_optimizations = true, + enabled = true, size = 6, passes = 1, new_optimizations = true, vibrancy = 0.25, noise = 0.012, }, shadow = { enabled = true, range = 4, render_power = 3, color = "rgba(1a1917ee)" }, @@ -246,7 +244,7 @@ quickshell_blur_rule = hl.layer_rule({ ignore_alpha = 0.1, }) --- Power saver trades compositor polish for battery: one blur pass and no +-- Power saver keeps blur to one pass and disables -- animations. Quickshell calls cybexos_power_saver() through `hyprctl eval` -- when it starts and whenever the power profile changes. What the saver -- replaced is read live and kept in _G, which outlives a config reload: a diff --git a/tests/quickshell/power-saver.test.cjs b/tests/quickshell/power-saver.test.cjs index ecb20283..982a9e82 100644 --- a/tests/quickshell/power-saver.test.cjs +++ b/tests/quickshell/power-saver.test.cjs @@ -1,6 +1,6 @@ // Power saver is the user's explicit request to trade polish for battery. // The shell follows it with its reduced-motion path; the compositor follows -// it with fewer blur passes and no animations. +// it with one blur pass and no animations. const test = require("node:test"); const assert = require("node:assert/strict"); const childProcess = require("node:child_process"); @@ -130,7 +130,7 @@ test("power saver drops compositor blur and animations, and restores what it rep { skip: luajit ? false : "luajit is not installed" }, () => { runLookAndFeel(` dofile(look) -assert(passes() == 3 and animations() == true, "the configured values") +assert(passes() == 1 and animations() == true, "the configured values") -- A user.lua override is what comes back afterwards. hl.config({ decoration = { blur = { passes = 2 } } }) @@ -153,7 +153,7 @@ assert(calls() == before, "the shell's startup request touches nothing when off" -- A compositor restart starts a fresh state; the shell re-sends its request. _G.__cybexos_power_saver = nil dofile(look) -assert(passes() == 3 and animations() == true) +assert(passes() == 1 and animations() == true) `); }); diff --git a/tests/quickshell/remote-server.test.cjs b/tests/quickshell/remote-server.test.cjs index 96807954..34a00e0d 100644 --- a/tests/quickshell/remote-server.test.cjs +++ b/tests/quickshell/remote-server.test.cjs @@ -5,28 +5,37 @@ const S = require("../../roles/desktop/files/quickshell/Common/SettingsHelpers.j function sample() { return { version: 1, boot: "a", uptime: 1200, cpu: 25, perCore: [20, 30], load: [1, 2, 3], - meta: { hostname: "beast", os: "Linux", defaultInterface: "eth0" }, + meta: { hostname: "server", os: "Linux", defaultInterface: "eth0" }, memory: { total: 16 * 1024 ** 3, available: 12 * 1024 ** 3, used: 4 * 1024 ** 3 }, storage: [{ mount: "/", total: 1000, free: 400, percent: 60 }], temperatures: [{ name: "CPU", celsius: 54 }], - network: [{ name: "eth0", addresses: ["10.10.0.7"], rx: 1024, tx: 2048 }, + network: [{ name: "eth0", addresses: ["192.0.2.10"], rx: 1024, tx: 2048 }, { name: "docker0", addresses: ["172.17.0.1"], rx: 999999, tx: 999999 }] }; } -test("remote widget migrates as disabled and preserves user options", () => { +test("remote widget starts enabled with an empty host and preserves user choices", () => { const old = S.defaults(); old.mods.right = old.mods.right.filter(m => m.id !== "remote"); delete old.modOpts.remote; const migrated = S.merge(old); - assert.equal(migrated.mods.right.find(m => m.id === "remote").on, false); + assert.equal(migrated.mods.right.find(m => m.id === "remote").on, true); assert.equal(migrated.modOpts.remote.host, ""); + assert.equal(migrated.modOpts.remote.label, ""); assert.equal(migrated.modOpts.remote.metric, "cpu"); - const normalized = S.normalizeModOpts({ remote: { host: " john@10.10.0.7 ", label: "The Beast", + const normalized = S.normalizeModOpts({ remote: { host: " user@192.0.2.10 ", label: "My server", metric: "diskFree", mount: "/data", interface: "eth0", pollSecs: 999 } }).remote; - assert.equal(normalized.host, "john@10.10.0.7"); + assert.equal(normalized.host, "user@192.0.2.10"); assert.equal(normalized.metric, "diskFree"); assert.equal(normalized.mount, "/data"); assert.equal(normalized.pollSecs, 60); assert.equal(S.normalizeModOpts({ remote: { metric: "bogus" } }).remote.metric, "cpu"); + + const saved = S.defaults(); + saved.v = S.VERSION; + saved.mods.right.find(m => m.id === "remote").on = false; + saved.modOpts.remote = normalized; + const restored = S.merge(saved); + assert.equal(restored.mods.right.find(m => m.id === "remote").on, false); + assert.deepEqual(restored.modOpts.remote, normalized); }); test("every menu metric produces the intended unit and missing readings stay unknown", () => { const s = sample(); diff --git a/tests/quickshell/system-theme-hyprland.test.cjs b/tests/quickshell/system-theme-hyprland.test.cjs index 7acb02a7..18677b10 100644 --- a/tests/quickshell/system-theme-hyprland.test.cjs +++ b/tests/quickshell/system-theme-hyprland.test.cjs @@ -293,7 +293,7 @@ assert(hl.get_config("decoration.blur.passes") == 1, "power saver survives a liv assert(hl.get_config("animations.enabled") == false) assert(layer_rules["quickshell-blur"].enabled == true) cybexos_power_saver(false) -assert(hl.get_config("decoration.blur.passes") == 3)`, { +assert(hl.get_config("decoration.blur.passes") == 1)`, { [THEME]: render(lightTokens()), ".config/cybexos/shell.json": '{"v": 23, "glassEnabled": true}', }); diff --git a/tests/remote-server.py b/tests/remote-server.py index 306552d7..8e42a34d 100644 --- a/tests/remote-server.py +++ b/tests/remote-server.py @@ -81,7 +81,7 @@ def test_probe_caches_slow_data_and_resets_on_new_boot(self): self.assertEqual(disks.call_count, 2) def test_ssh_destination_is_data_and_host_verification_is_required(self): - for host in ["john@10.10.0.7", "beast", "john@[2001:db8::1]"]: + for host in ["user@192.0.2.10", "server", "user@[2001:db8::1]"]: command = launcher.command(host) self.assertEqual(command[-2], host) for flag in ["BatchMode=yes", "StrictHostKeyChecking=yes", "ControlPath=none", "ClearAllForwardings=yes", "ForwardAgent=no"]: