feat: initialize direct session backend
This commit is contained in:
1 parent
1b8ba7570c
commit
c50dd98685
4 files changed
+226
-8
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
use std::{error::Error, io};
|
||||||
|
|
||||||
|
use smithay::{
|
||||||
|
backend::{
|
||||||
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
|
session::{Event as SessionEvent, Session, libseat::LibSeatSession},
|
||||||
|
udev::{UdevBackend, UdevEvent},
|
||||||
|
},
|
||||||
|
reexports::{calloop::EventLoop, input::Libinput},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Ferese;
|
||||||
|
|
||||||
|
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
|
||||||
|
let (session, notifier) = LibSeatSession::new()?;
|
||||||
|
let seat_name = session.seat();
|
||||||
|
let udev_backend = UdevBackend::new(&seat_name)?;
|
||||||
|
let mut libinput_context =
|
||||||
|
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
|
||||||
|
libinput_context
|
||||||
|
.udev_assign_seat(&seat_name)
|
||||||
|
.map_err(|()| io::Error::other(format!("failed to assign libinput seat {seat_name}")))?;
|
||||||
|
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
|
||||||
|
|
||||||
|
state.direct_session = Some(session);
|
||||||
|
state.session_active = true;
|
||||||
|
|
||||||
|
event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(libinput_backend, |event, _, state| {
|
||||||
|
state.process_input_event(event);
|
||||||
|
})?;
|
||||||
|
event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(notifier, move |event, _, state| match event {
|
||||||
|
SessionEvent::PauseSession => {
|
||||||
|
state.session_active = false;
|
||||||
|
libinput_context.suspend();
|
||||||
|
tracing::info!("direct session paused");
|
||||||
|
}
|
||||||
|
SessionEvent::ActivateSession => {
|
||||||
|
if libinput_context.resume().is_err() {
|
||||||
|
tracing::error!("failed to resume libinput");
|
||||||
|
}
|
||||||
|
state.session_active = true;
|
||||||
|
tracing::info!("direct session activated");
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(udev_backend, |event, _, _state| match event {
|
||||||
|
UdevEvent::Added { device_id, path } => {
|
||||||
|
tracing::info!(?device_id, ?path, "DRM device added");
|
||||||
|
}
|
||||||
|
UdevEvent::Changed { device_id } => {
|
||||||
|
tracing::debug!(?device_id, "DRM device changed");
|
||||||
|
}
|
||||||
|
UdevEvent::Removed { device_id } => {
|
||||||
|
tracing::info!(?device_id, "DRM device removed");
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tracing::info!(seat = %seat_name, "initialized direct session input and device discovery");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
mod direct;
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use smithay::reexports::calloop::EventLoop;
|
||||||
|
|
||||||
|
use crate::Ferese;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum BackendKind {
|
||||||
|
Nested,
|
||||||
|
Drm,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub struct LaunchConfig {
|
||||||
|
pub backend: BackendKind,
|
||||||
|
pub client: Vec<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LaunchConfig {
|
||||||
|
pub fn from_environment() -> Result<Self, String> {
|
||||||
|
let graphical_session =
|
||||||
|
std::env::var_os("WAYLAND_DISPLAY").is_some() || std::env::var_os("DISPLAY").is_some();
|
||||||
|
|
||||||
|
Self::parse(std::env::args_os().skip(1), graphical_session)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(
|
||||||
|
args: impl IntoIterator<Item = OsString>,
|
||||||
|
graphical_session: bool,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let mut args = args.into_iter().peekable();
|
||||||
|
let mut requested_backend = None;
|
||||||
|
let mut client = Vec::new();
|
||||||
|
|
||||||
|
while let Some(argument) = args.next() {
|
||||||
|
if argument == "--" {
|
||||||
|
client.extend(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if argument == "--backend" {
|
||||||
|
let value = args
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "--backend requires auto, nested, or drm".to_owned())?;
|
||||||
|
requested_backend = Some(parse_backend(&value)?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(value) = argument
|
||||||
|
.to_str()
|
||||||
|
.and_then(|argument| argument.strip_prefix("--backend="))
|
||||||
|
{
|
||||||
|
requested_backend = Some(parse_backend(&OsString::from(value))?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.push(argument);
|
||||||
|
client.extend(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let backend = requested_backend.flatten().unwrap_or(if graphical_session {
|
||||||
|
BackendKind::Nested
|
||||||
|
} else {
|
||||||
|
BackendKind::Drm
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { backend, client })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
backend: BackendKind,
|
||||||
|
event_loop: &mut EventLoop<Ferese>,
|
||||||
|
state: &mut Ferese,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
match backend {
|
||||||
|
BackendKind::Nested => crate::winit::init(event_loop, state),
|
||||||
|
BackendKind::Drm => direct::init(event_loop, state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_backend(value: &OsString) -> Result<Option<BackendKind>, String> {
|
||||||
|
match value.to_str() {
|
||||||
|
Some("auto") => Ok(None),
|
||||||
|
Some("nested") => Ok(Some(BackendKind::Nested)),
|
||||||
|
Some("drm") => Ok(Some(BackendKind::Drm)),
|
||||||
|
_ => Err(format!(
|
||||||
|
"unknown backend {:?}; expected auto, nested, or drm",
|
||||||
|
value
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_selects_nested_inside_a_graphical_session() {
|
||||||
|
let config = LaunchConfig::parse([OsString::from("foot")], true).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.backend, BackendKind::Nested);
|
||||||
|
assert_eq!(config.client, [OsString::from("foot")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_selects_drm_without_a_graphical_session() {
|
||||||
|
let config = LaunchConfig::parse(Vec::<OsString>::new(), false).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.backend, BackendKind::Drm);
|
||||||
|
assert!(config.client.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_backend_and_separator_are_consumed() {
|
||||||
|
let config = LaunchConfig::parse(
|
||||||
|
[
|
||||||
|
OsString::from("--backend=drm"),
|
||||||
|
OsString::from("--"),
|
||||||
|
OsString::from("foot"),
|
||||||
|
OsString::from("--title=Ferese"),
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.backend, BackendKind::Drm);
|
||||||
|
assert_eq!(
|
||||||
|
config.client,
|
||||||
|
[OsString::from("foot"), OsString::from("--title=Ferese")]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_an_unknown_backend() {
|
||||||
|
let error =
|
||||||
|
LaunchConfig::parse([OsString::from("--backend"), OsString::from("other")], true)
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(error.contains("unknown backend"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-8
@@ -1,31 +1,36 @@
|
|||||||
#![allow(irrefutable_let_patterns)]
|
#![allow(irrefutable_let_patterns)]
|
||||||
|
|
||||||
|
mod backends;
|
||||||
mod grabs;
|
mod grabs;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod input;
|
mod input;
|
||||||
mod state;
|
mod state;
|
||||||
mod winit;
|
mod winit;
|
||||||
|
|
||||||
use std::{error::Error, process::Command};
|
use std::{error::Error, io, process::Command};
|
||||||
|
|
||||||
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
||||||
pub use state::Ferese;
|
pub use state::Ferese;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::backends::LaunchConfig;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
init_logging();
|
init_logging();
|
||||||
|
|
||||||
|
let launch = LaunchConfig::from_environment()
|
||||||
|
.map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
|
||||||
let mut event_loop = EventLoop::try_new()?;
|
let mut event_loop = EventLoop::try_new()?;
|
||||||
let display = Display::new()?;
|
let display = Display::new()?;
|
||||||
let mut state = Ferese::new(&mut event_loop, display)?;
|
let mut state = Ferese::new(&mut event_loop, display)?;
|
||||||
winit::init(&mut event_loop, &mut state)?;
|
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
||||||
|
|
||||||
// SAFETY: the backend has already read the host display and Ferese is
|
// SAFETY: the backend has already read the host display and Ferese is
|
||||||
// single-threaded before the event loop and optional child start.
|
// single-threaded before the event loop and optional child start.
|
||||||
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
|
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
|
||||||
|
|
||||||
info!(socket = ?state.socket_name, "Ferese is accepting Wayland clients");
|
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
||||||
spawn_client_from_args();
|
spawn_client(launch.client);
|
||||||
event_loop.run(None, &mut state, |_| {})?;
|
event_loop.run(None, &mut state, |_| {})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -36,12 +41,12 @@ fn init_logging() {
|
|||||||
tracing_subscriber::fmt().with_env_filter(filter).init();
|
tracing_subscriber::fmt().with_env_filter(filter).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_client_from_args() {
|
fn spawn_client(mut args: Vec<std::ffi::OsString>) {
|
||||||
let mut args = std::env::args_os().skip(1);
|
if args.is_empty() {
|
||||||
let Some(program) = args.next() else {
|
|
||||||
info!("no client requested; pass one after `--`, for example `-- foot`");
|
info!("no client requested; pass one after `--`, for example `-- foot`");
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
let program = args.remove(0);
|
||||||
|
|
||||||
match Command::new(&program).args(args).spawn() {
|
match Command::new(&program).args(args).spawn() {
|
||||||
Ok(child) => info!(program = ?program, pid = child.id(), "spawned nested client"),
|
Ok(child) => info!(program = ?program, pid = child.id(), "spawned nested client"),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use ferese_core::{WindowPlacement, WorkspaceSet};
|
|||||||
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::session::libseat::LibSeatSession,
|
||||||
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
||||||
input::{Seat, SeatState},
|
input::{Seat, SeatState},
|
||||||
reexports::{
|
reexports::{
|
||||||
@@ -51,6 +52,8 @@ pub struct Ferese {
|
|||||||
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
||||||
pub focused_window: Option<WindowId>,
|
pub focused_window: Option<WindowId>,
|
||||||
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
||||||
|
pub direct_session: Option<LibSeatSession>,
|
||||||
|
pub session_active: bool,
|
||||||
next_window_id: u64,
|
next_window_id: u64,
|
||||||
last_animation_tick: Instant,
|
last_animation_tick: Instant,
|
||||||
pub popups: PopupManager,
|
pub popups: PopupManager,
|
||||||
@@ -110,6 +113,8 @@ impl Ferese {
|
|||||||
window_geometry: HashMap::new(),
|
window_geometry: HashMap::new(),
|
||||||
focused_window: None,
|
focused_window: None,
|
||||||
intercepted_keys: HashSet::new(),
|
intercepted_keys: HashSet::new(),
|
||||||
|
direct_session: None,
|
||||||
|
session_active: true,
|
||||||
next_window_id: 1,
|
next_window_id: 1,
|
||||||
last_animation_tick: start_time,
|
last_animation_tick: start_time,
|
||||||
popups: PopupManager::default(),
|
popups: PopupManager::default(),
|
||||||
|
|||||||
Reference in new issue
Block a user