Files
feresaria/compositor/src/main.rs
T
Samuel Onoja a6f83689f8 Add XWayland support (#16)
* save dev state

* fix xwayland-satellite supervisor lifecycle, readiness and cleanup

* remove wayland scanner protocol generated files

* default xwayland enabled with path

* remove jargons

* address x11 review: park unconfirmed cleanup, never signal a reaped group, fix storm test and notify dir mode

* fix resize tests: never build Size values from negative dimensions

* add min-width/min-height window rules that replace client minimum sizes

* rename effective_size_constraints to effective_minimum_size and minor cleanups
2026-10-06 19:40:22 +01:00

216 lines
6.9 KiB
Rust

mod backends;
mod config;
mod cursor;
mod daemon;
mod dimming;
mod display_presentation;
mod dmabuf_imports;
mod effects;
mod floating;
mod frame_scheduler;
mod gestures;
mod grabs;
mod handlers;
mod idle_inhibition;
mod input;
mod input_capture;
mod ipc;
mod metrics;
mod monitor_identity;
mod output_policy;
mod output_transaction;
mod overview;
mod portal_session;
mod portal_shortcuts;
mod presentation;
mod presentation_policy;
mod private_client;
mod process;
mod reload;
mod render;
#[cfg(feature = "resize-metrics")]
mod resize_metrics;
mod resize_transaction;
mod resume;
mod session_environment;
mod session_lock;
mod shell_control;
mod stacking;
#[cfg(test)]
mod startup_tests;
mod state;
mod theme;
mod wallpaper;
mod window_rules;
mod winit;
mod xwayland;
pub(crate) use session_environment::{SessionEnvironment, X11Environment};
use std::error::Error;
use std::io;
use calloop::signals::{Signal, Signals};
use process::{spawn_client, terminate_child, watch_client_exit};
use smithay::reexports::calloop::EventLoop;
use smithay::reexports::wayland_server::Display;
pub use state::{Ferese, RuntimeConfig};
use tracing::{info, warn};
use crate::backends::LaunchConfig;
fn main() -> Result<(), Box<dyn Error>> {
init_logging();
// Settings validates a candidate before atomically replacing the user's file.
// This mode never opens a display, input device, or compositor socket.
let args = std::env::args_os().skip(1).collect::<Vec<_>>();
if args.first().is_some_and(|arg| arg == "--check-config") {
if args.len() != 2 {
return Err("usage: ferese --check-config PATH".into());
}
check_config(std::path::Path::new(&args[1]))?;
println!("Configuration is valid");
return Ok(());
}
let launch =
LaunchConfig::from_environment().map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
let initial_source = config::config_path()
.filter(|path| path.exists())
.map(|path| theme::read_source(&path))
.transpose()?;
let directory = config::config_path()
.and_then(|path| path.parent().map(ToOwned::to_owned))
.unwrap_or_else(|| std::path::PathBuf::from("."));
let (_, runtime, candidate) = theme::prepare(initial_source.as_deref().unwrap_or(""), &directory)?;
let mut event_loop = EventLoop::try_new()?;
let signals = Signals::new(&[Signal::SIGINT, Signal::SIGTERM])?;
event_loop
.handle()
.insert_source(signals, |event, _, state: &mut Ferese| {
info!(signal = ?event.signal(), "stopping Ferese");
state.loop_signal.stop();
})?;
let display = Display::new()?;
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
state.config_source = initial_source.filter(|source| source.len() <= 60 * 1024);
state.config_sections = state
.config_source
.as_deref()
.and_then(|source| ferese_config::Document::parse(source).ok())
.map(|document| {
let mut value = document.value().clone();
if let Some(object) = value.as_object_mut() {
object.remove("theme");
}
value
});
theme::init(&mut event_loop, &mut state, candidate)?;
let _resume_monitor = resume::init(&mut event_loop)?;
idle_inhibition::media::init(&mut event_loop)?;
overview::init_font_loader(&mut event_loop, &mut state)?;
backends::init(launch.backend, &mut event_loop, &mut state)?;
xwayland::initialize(&mut state);
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
let child = spawn_client(&mut state, launch.client, launch.client_capabilities)
.map(|child| watch_client_exit(&state.loop_handle, child));
let runner = daemon::Runner::start(&mut state);
state.daemons = Some(runner.clone());
let result = event_loop.run(None, &mut state, after_dispatch);
if let Some(backend) = state.direct_backend.as_ref() {
backend.dump_scheduling_metrics();
}
#[cfg(feature = "resize-metrics")]
state.resize_metrics.dump();
runner.borrow_mut().stop();
if let Some(child) = child
&& let Some(mut child) = child.borrow_mut().take()
{
terminate_child(&mut child);
}
xwayland::shutdown_after_loop(&mut state);
result.map_err(Into::into)
}
fn init_logging() {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("ferese=info"));
tracing_subscriber::fmt().with_env_filter(filter).init();
}
fn check_config(path: &std::path::Path) -> Result<(), String> {
let source = theme::read_source(path)?;
theme::prepare(&source, path.parent().unwrap_or(std::path::Path::new(".")))?;
Ok(())
}
#[derive(Debug)]
enum RedrawRequest<'a> {
Global,
Cursor,
Outputs(&'a [smithay::output::Output]),
}
fn after_dispatch(state: &mut Ferese) {
after_dispatch_with_redraw(state, |state, request| match request {
RedrawRequest::Global => backends::direct::render_all(state),
RedrawRequest::Cursor => backends::direct::render_cursor(state),
RedrawRequest::Outputs(outputs) => backends::direct::render_on(state, outputs),
});
}
fn after_dispatch_with_redraw(state: &mut Ferese, mut redraw: impl FnMut(&mut Ferese, RedrawRequest<'_>)) {
state.process_pending_dmabuf_imports();
state.update_named_cursor(std::time::Instant::now());
state.poll_theme();
// The decoder wakes the loop after publishing its result, including
// when no output has a pending frame.
let wallpaper_changed = state.wallpaper.poll();
// All input/Wayland callbacks have returned, releasing seat locks.
if state.focus_cycle.is_some() && (state.session_lock.active() || state.input_capture.captures(1)) {
state.cancel_focus_cycle();
}
if state.input_capture.restore_focus {
state.restore_input_capture_focus();
}
state.apply_unlocked_pointer_hint();
let wallpaper_retry = state.wallpaper.take_retry_wakeup();
let cursor_changed = std::mem::take(&mut state.cursor_redraw_pending);
let output_redraws = std::mem::take(&mut state.output_redraw_pending);
if wallpaper_changed || wallpaper_retry {
redraw(state, RedrawRequest::Global);
} else {
if cursor_changed {
redraw(state, RedrawRequest::Cursor);
}
if !output_redraws.is_empty() {
redraw(state, RedrawRequest::Outputs(&output_redraws));
}
}
if let Err(error) = state
.wallpaper
.arm_retry_timer(&state.loop_handle, std::time::Instant::now())
{
warn!(%error, "could not schedule wallpaper upload retry");
}
// Clients need registry/sync/configure replies even when no frame is drawn.
if let Err(error) = state.display_handle.flush_clients() {
warn!(%error, "failed to flush Wayland clients");
}
}