feat: add modern shell top bar

This commit is contained in:
Samuel Onoja committed 2026-09-25 07:47:56 +01:00
1 parent e511c0c31d
commit 4a6d91980c
9 files changed
+782 -20

No files matched your search

+50 -11
View File
@@ -17,7 +17,13 @@ mod state;
mod window_rules;
mod winit;
use std::{error::Error, io, process::Command};
use std::{
error::Error,
io,
process::{Child, Command},
thread,
time::Duration,
};
use calloop::signals::{Signal, Signals};
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
@@ -70,9 +76,14 @@ fn main() -> Result<(), Box<dyn Error>> {
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
spawn_client(&mut state, launch.client, launch.client_capabilities);
event_loop.run(None, &mut state, |_| {})?;
Ok(())
let mut child = spawn_client(&mut state, launch.client, launch.client_capabilities);
let result = event_loop.run(None, &mut state, |_| {});
if let Some(child) = &mut child {
terminate_child(child);
}
result.map_err(Into::into)
}
fn init_logging() {
@@ -85,10 +96,10 @@ fn spawn_client(
state: &mut Ferese,
mut args: Vec<std::ffi::OsString>,
capabilities: private_client::ClientCapabilities,
) {
) -> Option<Child> {
if args.is_empty() {
info!("no client requested; pass one after `--`, for example `-- foot`");
return;
return None;
}
let program = args.remove(0);
@@ -101,14 +112,42 @@ fn spawn_client(
Ok(connection) => Some(connection),
Err(error) => {
warn!(program = ?program, %error, "failed to create private Wayland connection");
return;
return None;
}
}
};
match command.spawn() {
Ok(child) => info!(program = ?program, pid = child.id(), "spawned Wayland client"),
Err(error) => warn!(program = ?program, %error, "failed to spawn Wayland client"),
}
let child = match command.spawn() {
Ok(child) => {
info!(program = ?program, pid = child.id(), "spawned Wayland client");
Some(child)
}
Err(error) => {
warn!(program = ?program, %error, "failed to spawn Wayland client");
None
}
};
drop(private_connection);
child
}
fn terminate_child(child: &mut Child) {
if child.try_wait().ok().flatten().is_some() {
return;
}
// SAFETY: the process ID comes from the live Child owned by Ferese.
unsafe {
libc::kill(child.id() as i32, libc::SIGTERM);
}
for _ in 0..20 {
if child.try_wait().ok().flatten().is_some() {
return;
}
thread::sleep(Duration::from_millis(10));
}
let _ = child.kill();
let _ = child.wait();
}
+68 -8
View File
@@ -1371,13 +1371,15 @@ fn framebuffer_clip_rect(
output_size: smithay::utils::Size<i32, Physical>,
transform: Transform,
) -> [f32; 4] {
let transformed = transform.transform_rect_in(geometry, &output_size);
let transformed_output_size = transform.transform_size(output_size);
let bottom = transformed_output_size.h - transformed.loc.y - transformed.size.h;
// Match GlesFrame's projection: Smithay already accounts for GL's Y axis.
// An extra bottom-left conversion here mirrors shader clips independently
// of the surface geometry (notably with the nested Flipped180 output).
let element_size = transform.transform_size(output_size);
let transformed = transform.transform_rect_in(geometry, &element_size);
[
transformed.loc.x as f32,
bottom as f32,
transformed.loc.y as f32,
transformed.size.w as f32,
transformed.size.h as f32,
]
@@ -1861,12 +1863,12 @@ mod tests {
}
#[test]
fn clip_rect_uses_opengl_bottom_left_origin() {
fn clip_rect_matches_smithay_gles_projection() {
let geometry = Rectangle::<i32, Physical>::new((10, 5).into(), (30, 40).into());
assert_eq!(
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::Normal),
[10.0, 35.0, 30.0, 40.0]
[10.0, 5.0, 30.0, 40.0]
);
}
@@ -1876,14 +1878,72 @@ mod tests {
assert_eq!(
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::Flipped180),
[10.0, 5.0, 30.0, 40.0]
[10.0, 35.0, 30.0, 40.0]
);
assert_eq!(
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::_180),
[60.0, 5.0, 30.0, 40.0]
[60.0, 35.0, 30.0, 40.0]
);
}
#[test]
fn panel_clearance_clip_matches_gles_projection_at_fractional_scales() {
for scale in [1.0, 1.25, 1.75, 2.0] {
for transform in [
Transform::Normal,
Transform::_90,
Transform::_180,
Transform::_270,
Transform::Flipped,
Transform::Flipped90,
Transform::Flipped180,
Transform::Flipped270,
] {
let output_size = smithay::utils::Size::<i32, Physical>::from((
(1280.0 * scale) as i32,
(800.0 * scale) as i32,
));
let element_size = transform.transform_size(output_size);
let logical_height = f64::from(element_size.h) / scale;
// Bar and exclusion end at 40; the 10px outer gap ends at 50.
let geometry = Rectangle::<f64, Logical>::new(
(10.0, 50.0).into(),
(400.0, logical_height - 60.0).into(),
)
.to_physical_precise_round(scale);
let clip = framebuffer_clip_rect(geometry, output_size, transform);
// Independently reproduce GlesFrame's orthographic projection,
// transform matrix, GL flip, and viewport mapping for each corner.
let matrix = transform.matrix();
let mut xs = Vec::new();
let mut ys = Vec::new();
for x in [geometry.loc.x, geometry.loc.x + geometry.size.w] {
for y in [geometry.loc.y, geometry.loc.y + geometry.size.h] {
let nx = 2.0 * x as f32 / element_size.w as f32 - 1.0;
let ny = 1.0 - 2.0 * y as f32 / element_size.h as f32;
let tx = matrix[0][0] * nx + matrix[1][0] * ny;
let ty = matrix[0][1] * nx + matrix[1][1] * ny;
xs.push((tx + 1.0) * output_size.w as f32 / 2.0);
ys.push((1.0 - ty) * output_size.h as f32 / 2.0);
}
}
let min_x = xs.iter().copied().fold(f32::INFINITY, f32::min);
let max_x = xs.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let min_y = ys.iter().copied().fold(f32::INFINITY, f32::min);
let max_y = ys.iter().copied().fold(f32::NEG_INFINITY, f32::max);
for (actual, expected) in
clip.into_iter()
.zip([min_x, min_y, max_x - min_x, max_y - min_y])
{
assert!(
(actual - expected).abs() < 0.001,
"{transform:?}, scale={scale}: {actual} != {expected}"
);
}
}
}
}
#[test]
fn blur_sampling_expands_around_the_visible_surface() {
let visible = Rectangle::<i32, Logical>::new((100, 80).into(), (400, 300).into());