493 lines
14 KiB
Rust
493 lines
14 KiB
Rust
use std::time::Instant;
|
|
|
|
use anyhow::{Context, Result, bail};
|
|
use rustix::event::{PollFd, PollFlags, Timespec, poll};
|
|
use smithay_client_toolkit::{
|
|
compositor::{CompositorHandler, CompositorState},
|
|
delegate_registry,
|
|
output::{OutputHandler, OutputState},
|
|
registry::{ProvidesRegistryState, RegistryState},
|
|
registry_handlers,
|
|
seat::{
|
|
Capability, SeatHandler, SeatState,
|
|
keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
|
|
pointer::{PointerEvent, PointerHandler, cursor_shape::CursorShapeManager},
|
|
},
|
|
shell::wlr_layer::{LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure},
|
|
shm::{Shm, ShmHandler},
|
|
};
|
|
use wayland_client::{
|
|
Connection, EventQueue, QueueHandle,
|
|
globals::{GlobalList, registry_queue_init},
|
|
protocol::{wl_buffer::WlBuffer, wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface},
|
|
};
|
|
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
|
|
use wayland_protocols::wp::viewporter::client::{
|
|
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
|
|
};
|
|
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
|
|
|
|
use crate::capture::Pending;
|
|
use crate::error::HintExt;
|
|
use crate::frame::OutputGeom;
|
|
use crate::region::Overlay;
|
|
|
|
/// An output as valw sees it.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Output {
|
|
pub wl: wl_output::WlOutput,
|
|
pub geom: OutputGeom,
|
|
pub transform: wl_output::Transform,
|
|
/// Physical pixels per logical pixel, e.g. 1.25.
|
|
pub scale: f64,
|
|
}
|
|
|
|
pub struct Wayland {
|
|
pub conn: Connection,
|
|
pub queue: EventQueue<State>,
|
|
pub globals: GlobalList,
|
|
pub state: State,
|
|
}
|
|
|
|
pub struct State {
|
|
registry: RegistryState,
|
|
pub outputs: OutputState,
|
|
pub seat: SeatState,
|
|
pub shm: Shm,
|
|
pub compositor: CompositorState,
|
|
pub layer_shell: Option<LayerShell>,
|
|
pub screencopy: Option<ZwlrScreencopyManagerV1>,
|
|
pub viewporter: Option<WpViewporter>,
|
|
pub cursor_shape: Option<CursorShapeManager>,
|
|
pub cursor_device: Option<WpCursorShapeDeviceV1>,
|
|
pub keyboard: Option<wl_keyboard::WlKeyboard>,
|
|
pub pointer: Option<wl_pointer::WlPointer>,
|
|
/// Screencopy requests in flight.
|
|
pub captures: Vec<Pending>,
|
|
/// The region selection overlay, while it is open.
|
|
pub overlay: Option<Overlay>,
|
|
}
|
|
|
|
impl Wayland {
|
|
pub fn connect() -> Result<Wayland> {
|
|
let conn = Connection::connect_to_env()
|
|
.context("could not connect to the Wayland compositor")
|
|
.hint("valw needs a Wayland session; is WAYLAND_DISPLAY set?")?;
|
|
let (globals, mut queue) = registry_queue_init::<State>(&conn)
|
|
.context("could not read the compositor's globals")?;
|
|
let qh = queue.handle();
|
|
|
|
let mut state = State {
|
|
registry: RegistryState::new(&globals),
|
|
outputs: OutputState::new(&globals, &qh),
|
|
seat: SeatState::new(&globals, &qh),
|
|
shm: Shm::bind(&globals, &qh).context("compositor has no wl_shm")?,
|
|
compositor: CompositorState::bind(&globals, &qh)
|
|
.context("compositor has no wl_compositor")?,
|
|
layer_shell: LayerShell::bind(&globals, &qh).ok(),
|
|
screencopy: globals.bind(&qh, 1..=3, ()).ok(),
|
|
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
|
|
cursor_shape: CursorShapeManager::bind(&globals, &qh).ok(),
|
|
cursor_device: None,
|
|
keyboard: None,
|
|
pointer: None,
|
|
captures: Vec::new(),
|
|
overlay: None,
|
|
};
|
|
// Two round trips: one for wl_output, one for the xdg-output details.
|
|
queue
|
|
.roundtrip(&mut state)
|
|
.context("Wayland round trip failed")?;
|
|
queue
|
|
.roundtrip(&mut state)
|
|
.context("Wayland round trip failed")?;
|
|
|
|
let wl = Wayland {
|
|
conn,
|
|
queue,
|
|
globals,
|
|
state,
|
|
};
|
|
wl.log_environment();
|
|
Ok(wl)
|
|
}
|
|
|
|
/// All outputs with a known name and logical geometry.
|
|
pub fn outputs(&self) -> Vec<Output> {
|
|
self.state.output_list()
|
|
}
|
|
|
|
/// `(interface, version)` of every global, sorted.
|
|
pub fn global_list(&self) -> Vec<(String, u32)> {
|
|
let mut list: Vec<_> = self
|
|
.globals
|
|
.contents()
|
|
.clone_list()
|
|
.into_iter()
|
|
.map(|g| (g.interface, g.version))
|
|
.collect();
|
|
list.sort();
|
|
list.dedup();
|
|
list
|
|
}
|
|
|
|
fn log_environment(&self) {
|
|
let globals: Vec<String> = self
|
|
.global_list()
|
|
.into_iter()
|
|
.map(|(interface, version)| format!("{interface} v{version}"))
|
|
.collect();
|
|
tracing::info!("globals: {}", globals.join(", "));
|
|
for o in self.outputs() {
|
|
let g = &o.geom;
|
|
tracing::info!(
|
|
"output {} at {},{} size {}x{} (logical) transform {:?}",
|
|
g.name,
|
|
g.x,
|
|
g.y,
|
|
g.width,
|
|
g.height,
|
|
o.transform
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Dispatches events until `done` returns true or `deadline` passes.
|
|
pub fn dispatch_until(
|
|
&mut self,
|
|
deadline: Instant,
|
|
mut done: impl FnMut(&State) -> bool,
|
|
) -> Result<()> {
|
|
loop {
|
|
self.queue
|
|
.dispatch_pending(&mut self.state)
|
|
.context("Wayland dispatch failed")?;
|
|
if done(&self.state) {
|
|
return Ok(());
|
|
}
|
|
self.queue.flush().context("Wayland flush failed")?;
|
|
let Some(guard) = self.queue.prepare_read() else {
|
|
continue;
|
|
};
|
|
let left = deadline.saturating_duration_since(Instant::now());
|
|
if left.is_zero() {
|
|
bail!("the compositor did not answer in time");
|
|
}
|
|
let timeout = Timespec::try_from(left).expect("duration fits");
|
|
let fd = guard.connection_fd();
|
|
let mut fds = [PollFd::new(&fd, PollFlags::IN)];
|
|
match poll(&mut fds, Some(&timeout)) {
|
|
Ok(0) => continue,
|
|
Ok(_) => {
|
|
guard.read().context("Wayland read failed")?;
|
|
}
|
|
Err(rustix::io::Errno::INTR) => continue,
|
|
Err(e) => return Err(e).context("poll failed"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dispatches events until `done` returns true. Waits as long as it takes.
|
|
pub fn dispatch_blocking(&mut self, mut done: impl FnMut(&State) -> bool) -> Result<()> {
|
|
while !done(&self.state) {
|
|
self.queue
|
|
.blocking_dispatch(&mut self.state)
|
|
.context("Wayland dispatch failed")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl State {
|
|
/// All outputs with a known name and logical geometry.
|
|
pub fn output_list(&self) -> Vec<Output> {
|
|
self.outputs
|
|
.outputs()
|
|
.filter_map(|wl| {
|
|
let info = self.outputs.info(&wl)?;
|
|
let (x, y) = info.logical_position?;
|
|
let (width, height) = info.logical_size?;
|
|
Some(Output {
|
|
scale: output_scale(&info, width),
|
|
geom: OutputGeom {
|
|
name: info
|
|
.name
|
|
.clone()
|
|
.unwrap_or_else(|| format!("output-{}", info.id)),
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
},
|
|
transform: info.transform,
|
|
wl,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
/// Physical pixels per logical pixel: the current mode's width (turned to
|
|
/// match the logical orientation) over the logical width.
|
|
fn output_scale(info: &smithay_client_toolkit::output::OutputInfo, logical_width: i32) -> f64 {
|
|
let Some(mode) = info.modes.iter().find(|m| m.current) else {
|
|
return info.scale_factor as f64;
|
|
};
|
|
let (w, h) = mode.dimensions;
|
|
let rotated = matches!(
|
|
info.transform,
|
|
wl_output::Transform::_90
|
|
| wl_output::Transform::_270
|
|
| wl_output::Transform::Flipped90
|
|
| wl_output::Transform::Flipped270
|
|
);
|
|
let physical_width = if rotated { h } else { w };
|
|
if logical_width > 0 {
|
|
physical_width as f64 / logical_width as f64
|
|
} else {
|
|
1.0
|
|
}
|
|
}
|
|
|
|
impl CompositorHandler for State {
|
|
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,
|
|
qh: &QueueHandle<Self>,
|
|
surface: &wl_surface::WlSurface,
|
|
_: u32,
|
|
) {
|
|
if let Some(overlay) = &mut self.overlay {
|
|
overlay.frame_done(surface, qh);
|
|
}
|
|
}
|
|
|
|
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 State {
|
|
fn output_state(&mut self) -> &mut OutputState {
|
|
&mut self.outputs
|
|
}
|
|
|
|
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 State {
|
|
fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
|
|
if let Some(overlay) = &mut self.overlay {
|
|
overlay.cancel();
|
|
}
|
|
}
|
|
|
|
fn configure(
|
|
&mut self,
|
|
_: &Connection,
|
|
qh: &QueueHandle<Self>,
|
|
layer: &LayerSurface,
|
|
configure: LayerSurfaceConfigure,
|
|
_: u32,
|
|
) {
|
|
if let Some(overlay) = &mut self.overlay {
|
|
overlay.configure(layer, configure.new_size, qh);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SeatHandler for State {
|
|
fn seat_state(&mut self) -> &mut SeatState {
|
|
&mut self.seat
|
|
}
|
|
|
|
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
|
|
|
|
fn new_capability(
|
|
&mut self,
|
|
_: &Connection,
|
|
qh: &QueueHandle<Self>,
|
|
seat: wl_seat::WlSeat,
|
|
capability: Capability,
|
|
) {
|
|
if capability == Capability::Keyboard && self.keyboard.is_none() {
|
|
self.keyboard = self.seat.get_keyboard(qh, &seat, None).ok();
|
|
}
|
|
if capability == Capability::Pointer
|
|
&& self.pointer.is_none()
|
|
&& let Ok(pointer) = self.seat.get_pointer(qh, &seat)
|
|
{
|
|
self.cursor_device = self
|
|
.cursor_shape
|
|
.as_ref()
|
|
.map(|m| m.get_shape_device(&pointer, qh));
|
|
self.pointer = Some(pointer);
|
|
}
|
|
}
|
|
|
|
fn remove_capability(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: wl_seat::WlSeat,
|
|
capability: Capability,
|
|
) {
|
|
if capability == Capability::Keyboard
|
|
&& let Some(k) = self.keyboard.take()
|
|
{
|
|
k.release();
|
|
}
|
|
if capability == Capability::Pointer {
|
|
if let Some(d) = self.cursor_device.take() {
|
|
d.destroy();
|
|
}
|
|
if let Some(p) = self.pointer.take() {
|
|
p.release();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
|
|
}
|
|
|
|
impl KeyboardHandler for State {
|
|
fn enter(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: &wl_surface::WlSurface,
|
|
_: u32,
|
|
_: &[u32],
|
|
_: &[Keysym],
|
|
) {
|
|
}
|
|
|
|
fn leave(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: &wl_surface::WlSurface,
|
|
_: u32,
|
|
) {
|
|
}
|
|
|
|
fn press_key(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: u32,
|
|
event: KeyEvent,
|
|
) {
|
|
if event.keysym == Keysym::Escape
|
|
&& let Some(overlay) = &mut self.overlay
|
|
{
|
|
overlay.cancel();
|
|
}
|
|
}
|
|
|
|
fn repeat_key(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: u32,
|
|
_: KeyEvent,
|
|
) {
|
|
}
|
|
|
|
fn release_key(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: u32,
|
|
_: KeyEvent,
|
|
) {
|
|
}
|
|
|
|
fn update_modifiers(
|
|
&mut self,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
_: &wl_keyboard::WlKeyboard,
|
|
_: u32,
|
|
_: Modifiers,
|
|
_: RawModifiers,
|
|
_: u32,
|
|
) {
|
|
}
|
|
}
|
|
|
|
impl PointerHandler for State {
|
|
fn pointer_frame(
|
|
&mut self,
|
|
_: &Connection,
|
|
qh: &QueueHandle<Self>,
|
|
_: &wl_pointer::WlPointer,
|
|
events: &[PointerEvent],
|
|
) {
|
|
if let Some(overlay) = &mut self.overlay {
|
|
overlay.pointer(events, self.cursor_device.as_ref(), qh);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ShmHandler for State {
|
|
fn shm_state(&mut self) -> &mut Shm {
|
|
&mut self.shm
|
|
}
|
|
}
|
|
|
|
impl ProvidesRegistryState for State {
|
|
fn registry(&mut self) -> &mut RegistryState {
|
|
&mut self.registry
|
|
}
|
|
registry_handlers![OutputState, SeatState];
|
|
}
|
|
|
|
delegate_registry!(State);
|
|
smithay_client_toolkit::delegate_dispatch2!(State);
|
|
wayland_client::delegate_noop!(State: ZwlrScreencopyManagerV1);
|
|
wayland_client::delegate_noop!(State: WpViewporter);
|
|
wayland_client::delegate_noop!(State: WpViewport);
|
|
wayland_client::delegate_noop!(State: ignore WlBuffer);
|