2 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5.1 bf16d427b8 release: cargo-release config for tag-driven releases
`cargo release patch --execute` now does what the last three releases
did by hand: bump Cargo.toml/Cargo.lock, rewrite PKGBUILD's pkgver,
stamp the "## Unreleased" changelog section with version and date,
commit as "release X.Y.Z", tag vX.Y.Z and push commit and tag to the
gitea remote (not `origin`, which is the GitHub upstream). The tag then
triggers .gitea/workflows/release.yml as before. No crates.io publish.

CHANGELOG.md gets a standing "## Unreleased" section to collect entries
between releases; README documents the flow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 15:48:45 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 4f055935d4 backlight: run without a backlight device instead of panicking (0.4.3)
Release / package (x86_64, /var/local/cargo-target/iris-cargo-home, bare, /usr/bin/sccache, /var/local/rustup, /var/local/cargo-target) (push) Successful in 43s
Release / package (aarch64, , aarch64, , , ) (push) Successful in 2m20s
A config carrying a !backlight widget on a machine with nothing under
/sys/class/backlight (a laptop config on a desktop) hit
expect("did not find backlight device") in Backlight::new, which with
panic = "abort" killed wldash before a window appeared.

The device path is now Option<PathBuf>. A missing device, or a
configured `device` name that does not exist, prints one line on stderr
and leaves the widget in place so layout indices stay aligned: it
renders empty, skips sysfs reads and writes, and ignores clicks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 13:54:13 +02:00
7 changed files with 89 additions and 14 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## Unreleased
## 0.4.3 (2026-10-05)
- Backlight: a config with a `!backlight` widget on a machine without `/sys/class/backlight` entries (or with a `device` name that does not exist) panicked at start ("did not find backlight device"), fatal with `panic = "abort"`. The widget now warns once on stderr, renders empty and ignores clicks, so one config can be shared between a laptop and a desktop.
## 0.4.2 (2026-10-03) ## 0.4.2 (2026-10-03)
- Fonts: a `font_paths` entry pointing at a file that does not exist on this machine made the font-loader thread panic and abort wldash ("Font file not found"). Pinned paths that are missing now fall back to fontconfig by name, with one line on stderr; a font that cannot be resolved at all exits with a clear message instead of a backtrace. - Fonts: a `font_paths` entry pointing at a file that does not exist on this machine made the font-loader thread panic and abort wldash ("Font file not found"). Pinned paths that are missing now fall back to fontconfig by name, with one line on stderr; a font that cannot be resolved at all exits with a clear message instead of a backtrace.
Generated
+1 -1
View File
@@ -1071,7 +1071,7 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "wldash" name = "wldash"
version = "0.4.2" version = "0.4.3"
dependencies = [ dependencies = [
"calloop", "calloop",
"chrono", "chrono",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "wldash" name = "wldash"
version = "0.4.2" version = "0.4.3"
edition = "2024" edition = "2024"
[profile.release] [profile.release]
+1 -1
View File
@@ -4,7 +4,7 @@
# project.uhhm.no (namespace bl). pkgver is the version; the tag has # project.uhhm.no (namespace bl). pkgver is the version; the tag has
# to agree with it and with Cargo.toml. # to agree with it and with Cargo.toml.
pkgname=wldash pkgname=wldash
pkgver=0.4.2 pkgver=0.4.3
pkgrel=1 pkgrel=1
pkgdesc="Dashboard/launcher/control-panel for wlroots-style Wayland compositors (layer-shell)" pkgdesc="Dashboard/launcher/control-panel for wlroots-style Wayland compositors (layer-shell)"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
+11
View File
@@ -79,6 +79,17 @@ The launcher also accepts prefix operators to change its mode:
- Scaling of any kind - all sizes are currently hardcoded in pixels - Scaling of any kind - all sizes are currently hardcoded in pixels
## Releasing
Releases are cut with [cargo-release](https://github.com/crate-ci/cargo-release) (`cargo install cargo-release`), configured in `release.toml`. Put a line for each change under `## Unreleased` in `CHANGELOG.md` as you go, then on a clean `master`:
```
cargo release patch # dry run: shows the bump, replacements and tag
cargo release patch --execute # do it (minor/major or an explicit version also work)
```
This bumps `Cargo.toml`, `Cargo.lock` and `PKGBUILD` to the same version, stamps the changelog section with the version and date, commits as `release X.Y.Z`, tags `vX.Y.Z` and pushes commit and tag to the `gitea` remote. The tag triggers `.gitea/workflows/release.yml`, which builds the package per architecture and uploads it to the `[uhhm]` registry. The workflow refuses a tag whose version disagrees with `PKGBUILD` or `Cargo.toml`, which cannot happen when the tag comes from cargo-release.
## How to discuss ## How to discuss
Go to #kennylevinsen @ irc.libera.chat to discuss, or use [~kennylevinsen/public-inbox@lists.sr.ht](https://lists.sr.ht/~kennylevinsen/public-inbox). Go to #kennylevinsen @ irc.libera.chat to discuss, or use [~kennylevinsen/public-inbox@lists.sr.ht](https://lists.sr.ht/~kennylevinsen/public-inbox).
+26
View File
@@ -0,0 +1,26 @@
# cargo-release settings. Cut a release with
#
# cargo release patch # dry run, shows what would change
# cargo release patch --execute
#
# (or `minor`, `major`, or an explicit version). It bumps Cargo.toml and
# Cargo.lock, rewrites PKGBUILD's pkgver, turns the "## Unreleased"
# section of CHANGELOG.md into "## X.Y.Z (date)", commits, tags
# vX.Y.Z and pushes commit + tag to the gitea remote. That tag is what
# .gitea/workflows/release.yml builds and uploads to the [uhhm] registry.
# Not on crates.io; the package goes to the Arch registry via the tag.
publish = false
# `origin` is the upstream kennylevinsen fork on GitHub; releases go to
# project.uhhm.no, and the workflow only runs there.
push-remote = "gitea"
allow-branch = ["master"]
tag-name = "v{{version}}"
tag-message = "wldash {{version}}"
pre-release-commit-message = "release {{version}}"
pre-release-replacements = [
{ file = "PKGBUILD", search = "^pkgver=.*", replace = "pkgver={{version}}", exactly = 1 },
{ file = "PKGBUILD", search = "^pkgrel=.*", replace = "pkgrel=1", exactly = 1 },
{ file = "CHANGELOG.md", search = "^## Unreleased$", replace = "## {{version}} ({{date}})", exactly = 1 },
]
+43 -11
View File
@@ -26,7 +26,11 @@ fn write_file_as_u64(path: &Path, value: u64) -> Result<(), Error> {
} }
pub struct Backlight { pub struct Backlight {
device_path: PathBuf, /// `None` when no backlight exists on this machine: the widget then
/// renders empty and ignores clicks instead of taking wldash down.
/// Configs are shared between hosts, so a laptop config running on a
/// desktop must not panic here.
device_path: Option<PathBuf>,
cur: u64, cur: u64,
max: u64, max: u64,
dirty: bool, dirty: bool,
@@ -46,10 +50,13 @@ fn find_device() -> Option<PathBuf> {
impl Backlight { impl Backlight {
fn update(&mut self) { fn update(&mut self) {
if let Ok(cur) = read_file_as_u64(self.device_path.join("brightness").as_path()) { let Some(dev) = &self.device_path else {
return;
};
if let Ok(cur) = read_file_as_u64(dev.join("brightness").as_path()) {
self.cur = cur; self.cur = cur;
} }
if let Ok(max) = read_file_as_u64(self.device_path.join("max_brightness").as_path()) { if let Ok(max) = read_file_as_u64(dev.join("max_brightness").as_path()) {
self.max = max; self.max = max;
} }
} }
@@ -58,11 +65,30 @@ impl Backlight {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub fn new(path: Option<&str>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { pub fn new(path: Option<&str>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
let device_path = match path { let device_path = match path {
Some(path) => Some(Path::new("/sys/class/backlight").join(path)), Some(path) => {
None => find_device(), let p = Path::new("/sys/class/backlight").join(path);
if p.is_dir() {
Some(p)
} else {
eprintln!(
"wldash: backlight device {:?} not found, widget disabled",
p
);
None
}
}
None => {
let p = find_device();
if p.is_none() {
eprintln!(
"wldash: no backlight device under /sys/class/backlight, widget disabled"
);
}
p
}
}; };
let mut dev = Backlight { let mut dev = Backlight {
device_path: device_path.expect("did not find backlight device"), device_path,
cur: 0, cur: 0,
max: 0, max: 0,
dirty: true, dirty: true,
@@ -77,9 +103,13 @@ impl Backlight {
} }
pub fn set(&mut self, brightness: f32) { pub fn set(&mut self, brightness: f32) {
let Some(dev) = &self.device_path else {
return;
};
let val = (self.max as f32 * brightness.clamp(0.0, 1.0)) as u64; let val = (self.max as f32 * brightness.clamp(0.0, 1.0)) as u64;
let _ = write_file_as_u64(self.device_path.join("brightness").as_path(), val); let brightness_path = dev.join("brightness");
if let Ok(cur) = read_file_as_u64(self.device_path.join("brightness").as_path()) { let _ = write_file_as_u64(&brightness_path, val);
if let Ok(cur) = read_file_as_u64(&brightness_path) {
self.cur = cur; self.cur = cur;
} }
} }
@@ -93,12 +123,14 @@ impl BarWidgetImpl for Backlight {
self.dirty self.dirty
} }
fn value(&mut self) -> f32 { fn value(&mut self) -> f32 {
self.dirty = false;
if self.device_path.is_none() {
return 0.0;
}
if self.max == 0 || self.cur > self.max { if self.max == 0 || self.cur > self.max {
// what. // Sysfs handed us nonsense; show full rather than a bogus ratio.
return 1.0; return 1.0;
} }
self.dirty = false;
self.cur as f32 / self.max as f32 self.cur as f32 / self.max as f32
} }
fn color(&self) -> Color { fn color(&self) -> Color {