Prepare 0.1.0-beta.2 feature freeze and release validation
This commit is contained in:
1 parent
a62f1367bd
commit
77fa25e5f7
14 files changed
+184
-127
No files matched your search
Generated
+18
-18
@@ -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
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()]),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user