From 77fa25e5f7945cc05fc518fd24c3e7e18dbcc538 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 14 Oct 2026 18:16:55 +0100 Subject: [PATCH] Prepare 0.1.0-beta.2 feature freeze and release validation --- Cargo.lock | 36 ++--- Cargo.toml | 2 +- compositor/src/input.rs | 4 +- compositor/src/shell_control.rs | 146 +++++++++---------- docs/README.md | 1 + docs/releases/0.1.0-beta.2.md | 55 +++++++ shell/ferese-shell/src/battery_indicator.rs | 28 ++-- shell/ferese-shell/src/launcher/launch.rs | 8 +- shell/ferese-shell/src/launcher/mod.rs | 14 +- shell/ferese-shell/src/panel_hub/activity.rs | 7 +- shell/ferese-shell/src/panel_hub/battery.rs | 21 +-- shell/ferese-shell/src/status_ui/mod.rs | 7 +- shell/ferese-shell/src/status_ui/system.rs | 8 +- shell/ferese-shell/src/surfaces.rs | 10 +- 14 files changed, 202 insertions(+), 145 deletions(-) create mode 100644 docs/releases/0.1.0-beta.2.md diff --git a/Cargo.lock b/Cargo.lock index 7c6ee1e..ee07fca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index ce4ee26..3ee9ad9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/compositor/src/input.rs b/compositor/src/input.rs index f2b8b47..4c3fbe5 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -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) diff --git a/compositor/src/shell_control.rs b/compositor/src/shell_control.rs index 5979af0..98060a5 100644 --- a/compositor/src/shell_control.rs +++ b/compositor/src/shell_control.rs @@ -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)) - } -} diff --git a/docs/README.md b/docs/README.md index be6b5c4..8b756a3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. diff --git a/docs/releases/0.1.0-beta.2.md b/docs/releases/0.1.0-beta.2.md new file mode 100644 index 0000000..2fb8ca1 --- /dev/null +++ b/docs/releases/0.1.0-beta.2.md @@ -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. diff --git a/shell/ferese-shell/src/battery_indicator.rs b/shell/ferese-shell/src/battery_indicator.rs index 79f7e2b..2fd19b7 100644 --- a/shell/ferese-shell/src/battery_indicator.rs +++ b/shell/ferese-shell/src/battery_indicator.rs @@ -26,17 +26,20 @@ 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 { - background: Some(Background::Color(outline.scale_alpha(0.16))), - border: Border { - shape: cosmic::iced::border::Shape::Continuous, - radius: 4.0.into(), - ..Default::default() - }, + 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, + radius: 4.0.into(), ..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() } } diff --git a/shell/ferese-shell/src/launcher/launch.rs b/shell/ferese-shell/src/launcher/launch.rs index 88fac4c..b2e4b09 100644 --- a/shell/ferese-shell/src/launcher/launch.rs +++ b/shell/ferese-shell/src/launcher/launch.rs @@ -18,10 +18,10 @@ 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) { - eprintln!("Could not save launcher history: {error}"); - } + if result.is_ok() + && let Err(error) = apps::record(&id) + { + eprintln!("Could not save launcher history: {error}"); } return result; } diff --git a/shell/ferese-shell/src/launcher/mod.rs b/shell/ferese-shell/src/launcher/mod.rs index 6601ae4..84b1930 100644 --- a/shell/ferese-shell/src/launcher/mod.rs +++ b/shell/ferese-shell/src/launcher/mod.rs @@ -464,12 +464,14 @@ 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 { - launcher - .thumbnails - .refresh(&launcher.rows, launcher.selected, control, now); - } + 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) diff --git a/shell/ferese-shell/src/panel_hub/activity.rs b/shell/ferese-shell/src/panel_hub/activity.rs index 26dc61e..4d71cf3 100644 --- a/shell/ferese-shell/src/panel_hub/activity.rs +++ b/shell/ferese-shell/src/panel_hub/activity.rs @@ -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) diff --git a/shell/ferese-shell/src/panel_hub/battery.rs b/shell/ferese-shell/src/panel_hub/battery.rs index 176735a..3b6a5aa 100644 --- a/shell/ferese-shell/src/panel_hub/battery.rs +++ b/shell/ferese-shell/src/panel_hub/battery.rs @@ -15,15 +15,18 @@ 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.)) - .name(format!( - "Battery, {percent} percent, {}. Open battery controls", - battery.status - )) - .padding([0, 3]) - .width(WIDTH as f32) - .height(28) - .on_press(cosmic::Action::App(Message::HubOpen(id, status_ui::Menu::Battery))), + button::custom(crate::bar_content( + self.battery_indicator(self.config.theme, opacity, true), + 28., + )) + .name(format!( + "Battery, {percent} percent, {}. Open battery controls", + battery.status + )) + .padding([0, 3]) + .width(WIDTH as f32) + .height(28) + .on_press(cosmic::Action::App(Message::HubOpen(id, status_ui::Menu::Battery))), foreground, active, opacity, diff --git a/shell/ferese-shell/src/status_ui/mod.rs b/shell/ferese-shell/src/status_ui/mod.rs index 157ce65..e4c3641 100644 --- a/shell/ferese-shell/src/status_ui/mod.rs +++ b/shell/ferese-shell/src/status_ui/mod.rs @@ -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, diff --git a/shell/ferese-shell/src/status_ui/system.rs b/shell/ferese-shell/src/status_ui/system.rs index 71ce74d..976325a 100644 --- a/shell/ferese-shell/src/status_ui/system.rs +++ b/shell/ferese-shell/src/status_ui/system.rs @@ -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)), ] diff --git a/shell/ferese-shell/src/surfaces.rs b/shell/ferese-shell/src/surfaces.rs index 33d76ee..c62c69f 100644 --- a/shell/ferese-shell/src/surfaces.rs +++ b/shell/ferese-shell/src/surfaces.rs @@ -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),