286 lines
8.3 KiB
Rust
286 lines
8.3 KiB
Rust
|
|
//! Wayland state: output enumeration, layer surface creation, event dispatch.
|
||
|
|
|
||
|
|
use std::time::{Duration, Instant};
|
||
|
|
|
||
|
|
use anyhow::{bail, Context, Result};
|
||
|
|
use smithay_client_toolkit::{
|
||
|
|
compositor::{CompositorHandler, CompositorState},
|
||
|
|
delegate_compositor, delegate_layer, delegate_output, delegate_registry,
|
||
|
|
output::{OutputHandler, OutputState},
|
||
|
|
registry::{ProvidesRegistryState, RegistryState},
|
||
|
|
registry_handlers,
|
||
|
|
shell::{
|
||
|
|
wlr_layer::{
|
||
|
|
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
|
||
|
|
LayerSurfaceConfigure,
|
||
|
|
},
|
||
|
|
WaylandSurface,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
use wayland_client::{
|
||
|
|
globals::registry_queue_init,
|
||
|
|
protocol::{wl_output, wl_surface},
|
||
|
|
Connection, Proxy, QueueHandle,
|
||
|
|
};
|
||
|
|
|
||
|
|
use crate::renderer::Renderer;
|
||
|
|
use crate::shader::ShaderSource;
|
||
|
|
|
||
|
|
/// Application state held across the Wayland event loop.
|
||
|
|
pub struct AppState {
|
||
|
|
// SCTK required fields
|
||
|
|
registry_state: RegistryState,
|
||
|
|
output_state: OutputState,
|
||
|
|
|
||
|
|
// Layer shell
|
||
|
|
layer: Option<LayerSurface>,
|
||
|
|
|
||
|
|
// Rendering
|
||
|
|
renderer: Option<Renderer>,
|
||
|
|
|
||
|
|
// Configuration that renderer needs at configure time
|
||
|
|
display_ptr: *mut std::ffi::c_void,
|
||
|
|
surface_ptr: *mut std::ffi::c_void,
|
||
|
|
shader: ShaderSource,
|
||
|
|
fps: u32,
|
||
|
|
|
||
|
|
// Bookkeeping
|
||
|
|
configured: bool,
|
||
|
|
width: u32,
|
||
|
|
height: u32,
|
||
|
|
pub exit: bool,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Safety: we only use the raw pointers in single-threaded context
|
||
|
|
unsafe impl Send for AppState {}
|
||
|
|
unsafe impl Sync for AppState {}
|
||
|
|
|
||
|
|
/// Parse the layer CLI string into an SCTK Layer.
|
||
|
|
pub fn parse_layer(s: &str) -> Result<Layer> {
|
||
|
|
match s.to_lowercase().as_str() {
|
||
|
|
"background" => Ok(Layer::Background),
|
||
|
|
"bottom" => Ok(Layer::Bottom),
|
||
|
|
"top" => Ok(Layer::Top),
|
||
|
|
"overlay" => Ok(Layer::Overlay),
|
||
|
|
other => bail!("unknown layer: {}", other),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Set up a Wayland connection and run the render loop for one output.
|
||
|
|
pub fn run(
|
||
|
|
target_output: String,
|
||
|
|
shader: ShaderSource,
|
||
|
|
fps: u32,
|
||
|
|
layer_kind: Layer,
|
||
|
|
) -> Result<()> {
|
||
|
|
let conn = Connection::connect_to_env().context("failed to connect to Wayland display")?;
|
||
|
|
let display_ptr = conn.backend().display_ptr() as *mut std::ffi::c_void;
|
||
|
|
|
||
|
|
let (globals, mut event_queue) =
|
||
|
|
registry_queue_init::<AppState>(&conn).context("registry_queue_init")?;
|
||
|
|
let qh = event_queue.handle();
|
||
|
|
|
||
|
|
let compositor =
|
||
|
|
CompositorState::bind(&globals, &qh).context("wl_compositor not available")?;
|
||
|
|
let layer_shell =
|
||
|
|
LayerShell::bind(&globals, &qh).context("zwlr_layer_shell_v1 not available")?;
|
||
|
|
|
||
|
|
let mut state = AppState {
|
||
|
|
registry_state: RegistryState::new(&globals),
|
||
|
|
output_state: OutputState::new(&globals, &qh),
|
||
|
|
layer: None,
|
||
|
|
renderer: None,
|
||
|
|
display_ptr,
|
||
|
|
surface_ptr: std::ptr::null_mut(),
|
||
|
|
shader,
|
||
|
|
fps,
|
||
|
|
configured: false,
|
||
|
|
width: 0,
|
||
|
|
height: 0,
|
||
|
|
exit: false,
|
||
|
|
};
|
||
|
|
|
||
|
|
// One roundtrip to discover outputs.
|
||
|
|
event_queue.roundtrip(&mut state).context("initial roundtrip")?;
|
||
|
|
|
||
|
|
// Find the matching wl_output.
|
||
|
|
let target_wl_output = find_output(&state.output_state, &target_output);
|
||
|
|
|
||
|
|
// Create the wl_surface and layer surface.
|
||
|
|
let surface = compositor.create_surface(&qh);
|
||
|
|
state.surface_ptr = surface.id().as_ptr() as *mut std::ffi::c_void;
|
||
|
|
|
||
|
|
let layer = layer_shell.create_layer_surface(
|
||
|
|
&qh,
|
||
|
|
surface,
|
||
|
|
layer_kind,
|
||
|
|
Some("gpupaper"),
|
||
|
|
target_wl_output.as_ref(),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Stretch to fill the entire output.
|
||
|
|
layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
|
||
|
|
layer.set_exclusive_zone(-1);
|
||
|
|
layer.set_size(0, 0);
|
||
|
|
layer.set_keyboard_interactivity(KeyboardInteractivity::None);
|
||
|
|
layer.commit();
|
||
|
|
state.layer = Some(layer);
|
||
|
|
|
||
|
|
// Wait for the configure event which gives us the actual dimensions.
|
||
|
|
log::info!("waiting for configure…");
|
||
|
|
while !state.configured {
|
||
|
|
event_queue.blocking_dispatch(&mut state).context("event dispatch failed")?;
|
||
|
|
}
|
||
|
|
|
||
|
|
log::info!("configured: {}x{}", state.width, state.height);
|
||
|
|
|
||
|
|
// Create renderer now that we have confirmed dimensions.
|
||
|
|
state.renderer = Some(unsafe {
|
||
|
|
Renderer::new(
|
||
|
|
state.display_ptr,
|
||
|
|
state.surface_ptr,
|
||
|
|
state.width,
|
||
|
|
state.height,
|
||
|
|
&state.shader,
|
||
|
|
state.fps,
|
||
|
|
)
|
||
|
|
.context("failed to create renderer")?
|
||
|
|
});
|
||
|
|
|
||
|
|
// Render loop.
|
||
|
|
let frame_duration = if fps > 0 {
|
||
|
|
Some(Duration::from_nanos(1_000_000_000 / fps as u64))
|
||
|
|
} else {
|
||
|
|
None
|
||
|
|
};
|
||
|
|
|
||
|
|
loop {
|
||
|
|
// Non-blocking Wayland event dispatch.
|
||
|
|
event_queue.dispatch_pending(&mut state).context("event dispatch")?;
|
||
|
|
// Flush outgoing messages.
|
||
|
|
conn.flush().ok();
|
||
|
|
|
||
|
|
if state.exit {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
let frame_start = Instant::now();
|
||
|
|
|
||
|
|
if let Some(r) = state.renderer.as_mut() {
|
||
|
|
r.render();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cap frame rate if requested.
|
||
|
|
if let Some(dur) = frame_duration {
|
||
|
|
let elapsed = frame_start.elapsed();
|
||
|
|
if elapsed < dur {
|
||
|
|
std::thread::sleep(dur - elapsed);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Try to find the wl_output whose name matches `target`.
|
||
|
|
/// If `target` is `"all"` or `"*"`, returns `None` (compositor picks).
|
||
|
|
fn find_output(output_state: &OutputState, target: &str) -> Option<wl_output::WlOutput> {
|
||
|
|
if target == "all" || target == "*" {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
for output in output_state.outputs() {
|
||
|
|
if let Some(info) = output_state.info(&output) {
|
||
|
|
if let Some(name) = &info.name {
|
||
|
|
if name == target {
|
||
|
|
log::info!("matched output: {}", name);
|
||
|
|
return Some(output);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
log::warn!("output '{}' not found; using default", target);
|
||
|
|
None
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Trait implementations for AppState
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
impl CompositorHandler for AppState {
|
||
|
|
fn scale_factor_changed(
|
||
|
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||
|
|
_: &wl_surface::WlSurface, _: i32,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
fn transform_changed(
|
||
|
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||
|
|
_: &wl_surface::WlSurface, _: wl_output::Transform,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
fn frame(
|
||
|
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||
|
|
_: &wl_surface::WlSurface, _: u32,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
fn surface_enter(
|
||
|
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||
|
|
_: &wl_surface::WlSurface, _: &wl_output::WlOutput,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
fn surface_leave(
|
||
|
|
&mut self, _: &Connection, _: &QueueHandle<Self>,
|
||
|
|
_: &wl_surface::WlSurface, _: &wl_output::WlOutput,
|
||
|
|
) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl OutputHandler for AppState {
|
||
|
|
fn output_state(&mut self) -> &mut OutputState {
|
||
|
|
&mut self.output_state
|
||
|
|
}
|
||
|
|
|
||
|
|
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
|
||
|
|
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
|
||
|
|
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl LayerShellHandler for AppState {
|
||
|
|
fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
|
||
|
|
log::info!("layer surface closed");
|
||
|
|
self.exit = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
fn configure(
|
||
|
|
&mut self,
|
||
|
|
_conn: &Connection,
|
||
|
|
_qh: &QueueHandle<Self>,
|
||
|
|
_layer: &LayerSurface,
|
||
|
|
configure: LayerSurfaceConfigure,
|
||
|
|
_serial: u32,
|
||
|
|
) {
|
||
|
|
let (w, h) = configure.new_size;
|
||
|
|
self.width = if w == 0 { 1920 } else { w };
|
||
|
|
self.height = if h == 0 { 1080 } else { h };
|
||
|
|
|
||
|
|
if !self.configured {
|
||
|
|
self.configured = true;
|
||
|
|
} else if let Some(r) = self.renderer.as_mut() {
|
||
|
|
r.resize(self.width, self.height);
|
||
|
|
}
|
||
|
|
|
||
|
|
log::debug!("configure: {}x{}", self.width, self.height);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Delegation macros
|
||
|
|
delegate_compositor!(AppState);
|
||
|
|
delegate_output!(AppState);
|
||
|
|
delegate_layer!(AppState);
|
||
|
|
delegate_registry!(AppState);
|
||
|
|
|
||
|
|
impl ProvidesRegistryState for AppState {
|
||
|
|
fn registry(&mut self) -> &mut RegistryState {
|
||
|
|
&mut self.registry_state
|
||
|
|
}
|
||
|
|
registry_handlers![OutputState];
|
||
|
|
}
|