Prepare 0.1.0-beta.2 feature freeze and release validation

This commit is contained in:
Samuel Onoja committed 2026-10-14 18:16:55 +01:00
1 parent a62f1367bd
commit 77fa25e5f7
14 files changed
+184 -127

No files matched your search

Generated
+18 -18
View File
@@ -1848,7 +1848,7 @@ dependencies = [
[[package]]
name = "ferese"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ab_glyph",
"async-channel",
@@ -1881,14 +1881,14 @@ dependencies = [
[[package]]
name = "ferese-activities"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"serde",
]
[[package]]
name = "ferese-animation"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-layout",
"serde",
@@ -1896,7 +1896,7 @@ dependencies = [
[[package]]
name = "ferese-config"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-activities",
"ferese-panel",
@@ -1911,7 +1911,7 @@ dependencies = [
[[package]]
name = "ferese-core"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-layout",
"serde",
@@ -1919,7 +1919,7 @@ dependencies = [
[[package]]
name = "ferese-ipc"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-activities",
"ferese-theme-model",
@@ -1930,11 +1930,11 @@ dependencies = [
[[package]]
name = "ferese-layout"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
[[package]]
name = "ferese-lock"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-config",
"ferese-ipc",
@@ -1954,7 +1954,7 @@ dependencies = [
[[package]]
name = "ferese-panel"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-animation",
"ferese-layout",
@@ -1964,7 +1964,7 @@ dependencies = [
[[package]]
name = "ferese-polkit-agent"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-config",
"ferese-ipc",
@@ -1983,7 +1983,7 @@ dependencies = [
[[package]]
name = "ferese-protocols"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"bitflags 2.13.2",
"wayland-backend",
@@ -1995,7 +1995,7 @@ dependencies = [
[[package]]
name = "ferese-settings"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-config",
"ferese-ipc",
@@ -2021,7 +2021,7 @@ source = "git+https://github.com/ferese-wm/ferese-shape?tag=v0.1.0#63d7b6dfd520d
[[package]]
name = "ferese-shell"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"base64",
"ferese-activities",
@@ -2055,7 +2055,7 @@ dependencies = [
[[package]]
name = "ferese-theme"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-config",
"ferese-layout",
@@ -2069,7 +2069,7 @@ dependencies = [
[[package]]
name = "ferese-theme-client"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"ferese-config",
"ferese-ipc",
@@ -2083,14 +2083,14 @@ dependencies = [
[[package]]
name = "ferese-theme-model"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"serde",
]
[[package]]
name = "feresectl"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"clap",
"ferese-activities",
@@ -8249,7 +8249,7 @@ checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
[[package]]
name = "xdg-desktop-portal-ferese"
version = "0.1.0-beta"
version = "0.1.0-beta.2"
dependencies = [
"calloop 0.14.4",
"dirs 6.0.0",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
resolver = "3"
[workspace.package]
version = "0.1.0-beta"
version = "0.1.0-beta.2"
edition = "2024"
license = "MIT"
rust-version = "1.95"
+2 -2
View File
@@ -886,10 +886,10 @@ impl Ferese {
let Some(surface) = self.seat.get_keyboard().and_then(|keyboard| keyboard.current_focus()) else {
return false;
};
if !self
if self
.space
.layer_for_surface(&surface, smithay::desktop::WindowSurfaceType::ALL)
.is_some_and(|layer| layer.namespace() == "ferese-launcher")
.is_none_or(|layer| layer.namespace() != "ferese-launcher")
|| self
.layer_under(location)
.is_some_and(|(layer, _, _)| layer.wl_surface() == &surface)
+73 -73
View File
@@ -703,6 +703,79 @@ fn send_request_failed(shell: &FereseShellV1, request: ferese_shell_v1::FailedRe
);
}
impl Ferese {
pub(crate) fn dismiss_launcher(&self) {
for shell in self
.shell_resources
.iter()
.filter_map(|s| s.upgrade().ok())
.filter(|s| s.version() >= 10)
{
shell.dismiss_launcher();
}
}
fn shell_thumbnail(&mut self, id: WindowId) -> Option<(u32, u32, std::fs::File)> {
use std::io::{Seek, Write};
if self.session_lock.active() {
return None;
}
let window = self
.windows
.ids()
.iter()
.find(|(_, candidate)| **candidate == id)?
.0
.clone();
if self.capture_protected(&window) {
return None;
}
let workspace = self.workspaces.workspace_for_window(id)?;
let owner = self.output_workspaces.output_for_workspace(workspace)?;
let output = self
.output_ids
.iter()
.find(|(_, candidate)| **candidate == owner)?
.0
.clone();
let geometry = window.geometry();
if geometry.size.w <= 0 || geometry.size.h <= 0 {
return None;
}
let scale = (320. / f64::from(geometry.size.w))
.min(168. / f64::from(geometry.size.h))
.min(1.);
let result = if let Some(backend) = self.nested_backend.clone() {
let mut backend = backend.try_borrow_mut().ok()?;
crate::render::capture_window_frame(backend.renderer(), &window, geometry, scale, None)
} else {
let mut backend = self.direct_backend.take()?;
let result = backend.capture_window_frame(self, &window, &output, None, scale);
self.direct_backend = Some(backend);
result
};
let mut buffer = result.ok()?;
if buffer.width <= 0 || buffer.width > 320 || buffer.height <= 0 || buffer.height > 168 {
return None;
}
// GL ABGR readback is RGBA bytes on the supported little-endian targets.
// Iced expects straight rather than premultiplied channels.
for pixel in buffer.pixels.chunks_exact_mut(4) {
let alpha = u16::from(pixel[3]);
for channel in &mut pixel[..3] {
*channel = (u16::from(*channel) * 255 + alpha / 2)
.checked_div(alpha)
.unwrap_or(0)
.min(255) as u8;
}
}
let mut file = tempfile::tempfile().ok()?;
file.write_all(&buffer.pixels).ok()?;
file.rewind().ok()?;
Some((buffer.width as u32, buffer.height as u32, file))
}
}
#[cfg(test)]
mod tests {
#[test]
@@ -916,76 +989,3 @@ mod tests {
}
}
}
impl Ferese {
pub(crate) fn dismiss_launcher(&self) {
for shell in self
.shell_resources
.iter()
.filter_map(|s| s.upgrade().ok())
.filter(|s| s.version() >= 10)
{
shell.dismiss_launcher();
}
}
fn shell_thumbnail(&mut self, id: WindowId) -> Option<(u32, u32, std::fs::File)> {
use std::io::{Seek, Write};
if self.session_lock.active() {
return None;
}
let window = self
.windows
.ids()
.iter()
.find(|(_, candidate)| **candidate == id)?
.0
.clone();
if self.capture_protected(&window) {
return None;
}
let workspace = self.workspaces.workspace_for_window(id)?;
let owner = self.output_workspaces.output_for_workspace(workspace)?;
let output = self
.output_ids
.iter()
.find(|(_, candidate)| **candidate == owner)?
.0
.clone();
let geometry = window.geometry();
if geometry.size.w <= 0 || geometry.size.h <= 0 {
return None;
}
let scale = (320. / f64::from(geometry.size.w))
.min(168. / f64::from(geometry.size.h))
.min(1.);
let result = if let Some(backend) = self.nested_backend.clone() {
let mut backend = backend.try_borrow_mut().ok()?;
crate::render::capture_window_frame(backend.renderer(), &window, geometry, scale, None)
} else {
let mut backend = self.direct_backend.take()?;
let result = backend.capture_window_frame(self, &window, &output, None, scale);
self.direct_backend = Some(backend);
result
};
let mut buffer = result.ok()?;
if buffer.width <= 0 || buffer.width > 320 || buffer.height <= 0 || buffer.height > 168 {
return None;
}
// GL ABGR readback is RGBA bytes on the supported little-endian targets.
// Iced expects straight rather than premultiplied channels.
for pixel in buffer.pixels.chunks_exact_mut(4) {
let alpha = u16::from(pixel[3]);
for channel in &mut pixel[..3] {
*channel = (u16::from(*channel) * 255 + alpha / 2)
.checked_div(alpha)
.unwrap_or(0)
.min(255) as u8;
}
}
let mut file = tempfile::tempfile().ok()?;
file.write_all(&buffer.pixels).ok()?;
file.rewind().ok()?;
Some((buffer.width as u32, buffer.height as u32, file))
}
}
+1
View File
@@ -40,6 +40,7 @@ and [guides at that tag](https://github.com/ferese-wm/ferese/tree/v0.1.0-beta/do
## Development
- [0.1.0-beta.2 preparation](releases/0.1.0-beta.2.md): feature freeze, changes and validation gates.
- [Packages and releases](packaging.md): build Fedora and Arch packages and publish a tagged release.
- [Development](development.md): build Ferese, run tests and check portal contracts.
- [Publishing activities](development/activities.md): IPC schema, ownership, limits and templates.
+55
View File
@@ -0,0 +1,55 @@
# 0.1.0-beta.2 preparation
The 0.1.0 feature set is frozen as of October 14, 2026. Changes after the freeze
are limited to regression fixes, release preparation and documentation.
Beta 2 is being validated. It has not been released.
## Changes since 0.1.0-beta
- Native launcher with app, file and window search, category browsing and theme support.
- Panel styles, the Morph hub and configurable Live Activities.
- Tabbed window groups and named keyboard binding modes.
- Night light, animated temperature changes and brightness shortcuts on the lock screen.
- Window blur controls and reorganized Settings pages.
- Region recording with audio, fractional-scale capture fixes and idle capture-cache cleanup.
- Complete CRTC matching, bounded resize waits and output-specific DMA-BUF feedback.
- Layer and popup focus fixes, managed-window activation and outside-launcher touch dismissal.
- Security-context filtering for both privileged clipboard protocols. Ordinary clipboard
access remains available, and host clipboard tools retain data-control access.
## Release gates
Record the full candidate commit with each log, artifact and hardware report.
If a fix changes the candidate, rerun validation on the new commit before tagging.
- [x] Freeze the 0.1.0 feature set.
- [x] Set the workspace and lockfile version to `0.1.0-beta.2` and prepare release notes.
- [ ] Pass formatting and Clippy with warnings denied.
- [ ] Pass workspace tests, capture and recorder checks, Python tests and headless integration tests.
- [ ] Build Fedora 44 RPMs and Arch packages from the same candidate.
- [ ] Validate direct DRM: login/logout, locking, suspend/resume, lid and monitor changes,
fullscreen, screenshots and screen sharing.
- [ ] Test fresh Fedora 44 and Arch installs and upgrades from `0.1.0-beta`.
Check existing configurations, PAM, services and display-manager session selection.
- [ ] Pass native soak runs with crash, RSS, file-descriptor and client-exit recovery checks.
- [ ] Resolve release blockers, attach validation evidence and publish the tested candidate.
## Validation procedure
Use the **Release packages** workflow's manual dispatch on the candidate branch.
It runs release checks and builds both package formats. Confirm that the run's
`headSha` matches the candidate. Manual dispatch does not publish a release.
For local checks, follow [Development](../development.md). Use the
[native soak runner](../native-soak-testing.md) in a disposable session and retain
its declared duration and resource budgets with the results. A short smoke run
does not satisfy the full soak gate.
Package builds alone do not establish install or upgrade compatibility.
Record the distribution, package version, configuration changes and session results
for each fresh install and upgrade. Hardware reports also need the GPU, driver,
kernel and monitor setup.
The reported login flash still needs reproduction on the candidate. The bar's
initial material publication was changed, but the separate info-modal report
has not been confirmed. DMA-BUF refresh cost also remains unprofiled.
+8 -8
View File
@@ -26,8 +26,12 @@ impl FereseShell {
} else {
foreground
};
let fill = container(Space::new().width((BODY_WIDTH - 4.) * f32::from(percent) / 100.).height(10)).class(
theme::Container::custom(move |_| container::Style {
let fill = container(
Space::new()
.width((BODY_WIDTH - 4.) * f32::from(percent) / 100.)
.height(10),
)
.class(theme::Container::custom(move |_| container::Style {
background: Some(Background::Color(outline.scale_alpha(0.16))),
border: Border {
shape: cosmic::iced::border::Shape::Continuous,
@@ -35,8 +39,7 @@ impl FereseShell {
..Default::default()
},
..Default::default()
}),
);
}));
let number = self
.render
.text(if show_percentage {
@@ -72,9 +75,6 @@ impl FereseShell {
},
..Default::default()
}));
row![body, terminal]
.spacing(1)
.align_y(Alignment::Center)
.into()
row![body, terminal].spacing(1).align_y(Alignment::Center).into()
}
}
+3 -3
View File
@@ -18,11 +18,11 @@ pub(super) fn run(target: Target, terminal: &[String]) -> Result<(), String> {
} else {
open("gio", &[std::ffi::OsStr::new("launch"), desktop.as_os_str()])
};
if result.is_ok() {
if let Err(error) = apps::record(&id) {
if result.is_ok()
&& let Err(error) = apps::record(&id)
{
eprintln!("Could not save launcher history: {error}");
}
}
return result;
}
Target::File(path) => return open("gio", &[std::ffi::OsStr::new("open"), path.as_os_str()]),
+5 -3
View File
@@ -464,13 +464,15 @@ impl FereseShell {
if launcher.closing && !launcher.progress.is_animating() {
return self.finish_launcher();
}
if !launcher.closing && launcher.query.is_empty() && launcher.category == Some(Category::Windows) {
if let Some(control) = &self.control {
if !launcher.closing
&& launcher.query.is_empty()
&& launcher.category == Some(Category::Windows)
&& let Some(control) = &self.control
{
launcher
.thumbnails
.refresh(&launcher.rows, launcher.selected, control, now);
}
}
if !launcher.closing
&& now.duration_since(launcher.refresh) >= Duration::from_secs(1)
&& matches!(launcher.file_status, files::Status::Indexing)
+4 -3
View File
@@ -177,9 +177,10 @@ impl FereseShell {
..Default::default()
};
strip = strip.push(
button::custom(
crate::bar_content(self.battery_indicator(self.config.theme, opacity, true), 28.)
)
button::custom(crate::bar_content(
self.battery_indicator(self.config.theme, opacity, true),
28.,
))
.name(format!("Low battery, {percent} percent. Open battery controls"))
.padding(0)
.height(28)
+4 -1
View File
@@ -15,7 +15,10 @@ impl FereseShell {
let foreground = color_with_opacity(self.config.theme.text_primary, opacity);
Some(motion::button(
self.render,
button::custom(crate::bar_content(self.battery_indicator(self.config.theme, opacity, true), 28.))
button::custom(crate::bar_content(
self.battery_indicator(self.config.theme, opacity, true),
28.,
))
.name(format!(
"Battery, {percent} percent, {}. Open battery controls",
battery.status
+1 -6
View File
@@ -377,12 +377,7 @@ impl FereseShell {
.width(Length::Fixed(22.))
.into()
};
let badge = ferese_theme::menus::badge(
heading_icon,
color(theme.accent),
p,
self.render.radius(20.),
);
let badge = ferese_theme::menus::badge(heading_icon, color(theme.accent), p, self.render.radius(20.));
let mut heading = ferese_theme::menus::heading(badge, kind.title(), self.render.font);
let style = MenuStyle {
theme,
+6 -2
View File
@@ -118,8 +118,12 @@ pub(super) fn view<'a>(
container(shell.battery_indicator(theme, p, true))
.center_x(Length::Fill)
.center_y(28),
shell.render.text("Battery").size(11)
.width(Length::Fill).height(16)
shell
.render
.text("Battery")
.size(11)
.width(Length::Fill)
.height(16)
.align_x(alignment::Horizontal::Center)
.class(theme::Text::Color(muted)),
]
+3 -7
View File
@@ -461,7 +461,7 @@ impl FereseShell {
Event::Window(window::Event::Closed) => {
self.closing_menu_effects.remove(&id);
Task::none()
},
}
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::Frame(_, surface, frame_id)))
if self.menu.as_ref().is_some_and(|menu| menu.id == frame_id) =>
{
@@ -523,12 +523,8 @@ impl FereseShell {
// The first panel presentation supplies the role and measured shape
// together with its buffer. Enabling the role here would paint the
// whole surface before those bounds arrive.
let binding = EffectsBinding::attach_role(
&mut self.effects_context,
surface,
None,
if hidden { 0.0 } else { 1.0 },
);
let binding =
EffectsBinding::attach_role(&mut self.effects_context, surface, None, if hidden { 0.0 } else { 1.0 });
match binding {
Ok(binding) => entry.effects = Some(binding),