feat: wayland connection, niri IPC and valw doctor
This commit is contained in:
1 parent
c6094307f5
commit
272f2d30ab
4 files changed
+799
-4
No files matched your search
+263
@@ -0,0 +1,263 @@
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::config;
|
||||
use crate::niri;
|
||||
use crate::wayland::Wayland;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Level {
|
||||
Ok,
|
||||
Warn,
|
||||
Fail,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Check {
|
||||
pub level: Level,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Check {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let tag = match self.level {
|
||||
Level::Ok => "ok",
|
||||
Level::Warn => "warn",
|
||||
Level::Fail => "fail",
|
||||
};
|
||||
write!(f, "{tag:<5} {}", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
fn check(level: Level, text: impl Into<String>) -> Check {
|
||||
Check {
|
||||
level,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Globals valw can't work without: (interface, minimum version, used for).
|
||||
const REQUIRED: [(&str, u32, &str); 7] = [
|
||||
("wl_compositor", 1, "surfaces"),
|
||||
("wl_shm", 1, "pixel buffers"),
|
||||
("wl_seat", 1, "mouse and keyboard"),
|
||||
("zxdg_output_manager_v1", 2, "output names and positions"),
|
||||
("zwlr_layer_shell_v1", 1, "the selection overlay"),
|
||||
("zwlr_screencopy_manager_v1", 1, "screen capture"),
|
||||
("wp_viewporter", 1, "sharp overlays at fractional scale"),
|
||||
];
|
||||
|
||||
/// Globals that enable a feature: (any of these interfaces, feature).
|
||||
const OPTIONAL: [(&[&str], &str); 2] = [
|
||||
(
|
||||
&["wp_cursor_shape_manager_v1"],
|
||||
"crosshair cursor in region mode",
|
||||
),
|
||||
(
|
||||
&[
|
||||
"ext_data_control_manager_v1",
|
||||
"zwlr_data_control_manager_v1",
|
||||
],
|
||||
"clipboard copy",
|
||||
),
|
||||
];
|
||||
|
||||
pub fn check_globals(globals: &[(String, u32)]) -> Vec<Check> {
|
||||
let version = |name: &str| globals.iter().find(|(i, _)| i == name).map(|&(_, v)| v);
|
||||
let mut checks = Vec::new();
|
||||
for (name, min, purpose) in REQUIRED {
|
||||
checks.push(match version(name) {
|
||||
Some(v) if v >= min => check(Level::Ok, format!("{name} v{v} ({purpose})")),
|
||||
Some(v) => check(
|
||||
Level::Fail,
|
||||
format!("{name} v{v} is too old, need v{min} ({purpose})"),
|
||||
),
|
||||
None => check(Level::Fail, format!("{name} missing ({purpose})")),
|
||||
});
|
||||
}
|
||||
for (names, feature) in OPTIONAL {
|
||||
checks.push(
|
||||
match names.iter().find_map(|&n| version(n).map(|v| (n, v))) {
|
||||
Some((n, v)) => check(Level::Ok, format!("{n} v{v} ({feature})")),
|
||||
None => check(
|
||||
Level::Warn,
|
||||
format!("{} missing ({feature} disabled)", names.join(" / ")),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
pub fn check_niri(version: Result<String>) -> Check {
|
||||
let (y, m) = niri::IPC_VERSION;
|
||||
match version {
|
||||
Err(e) => check(
|
||||
Level::Warn,
|
||||
format!("niri IPC unavailable ({e:#}); using the first output for `screen`"),
|
||||
),
|
||||
Ok(v) => match niri::parse_version(&v) {
|
||||
Some(found) if found == niri::IPC_VERSION => check(Level::Ok, format!("niri {v}")),
|
||||
_ => check(
|
||||
Level::Warn,
|
||||
format!(
|
||||
"niri {v}, but valw was built against niri-ipc {y}.{m}; IPC replies may not parse"
|
||||
),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_config(path: &Path) -> Check {
|
||||
match config::load(path) {
|
||||
Ok(_) if path.exists() => check(Level::Ok, format!("config {}", path.display())),
|
||||
Ok(_) => check(
|
||||
Level::Ok,
|
||||
format!("config {} not found, using defaults", path.display()),
|
||||
),
|
||||
Err(e) => check(Level::Fail, format!("config: {e:#}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_save_dir(dir: &Path) -> Check {
|
||||
let probe = dir.join(".valw-doctor");
|
||||
let result = std::fs::create_dir_all(dir)
|
||||
.and_then(|()| std::fs::write(&probe, b""))
|
||||
.and_then(|()| std::fs::remove_file(&probe));
|
||||
match result {
|
||||
Ok(()) => check(
|
||||
Level::Ok,
|
||||
format!("save directory {} is writable", dir.display()),
|
||||
),
|
||||
Err(e) => check(
|
||||
Level::Fail,
|
||||
format!("save directory {}: {e}", dir.display()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs every check and prints one line each. Fails if any check failed.
|
||||
pub fn run() -> Result<()> {
|
||||
let mut checks = Vec::new();
|
||||
match Wayland::connect() {
|
||||
Ok(wl) => {
|
||||
checks.extend(check_globals(&wl.global_list()));
|
||||
for o in wl.outputs() {
|
||||
let g = &o.geom;
|
||||
checks.push(check(
|
||||
Level::Ok,
|
||||
format!(
|
||||
"output {}: {}x{} logical at {},{}, transform {:?}",
|
||||
g.name, g.width, g.height, g.x, g.y, o.transform
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => checks.push(check(Level::Fail, format!("wayland: {e:#}"))),
|
||||
}
|
||||
checks.push(check_niri(niri::version()));
|
||||
let config_path = config::default_path();
|
||||
checks.push(check_config(&config_path));
|
||||
if let Ok(config) = config::load(&config_path) {
|
||||
checks.push(check_save_dir(&config.save_dir()));
|
||||
}
|
||||
|
||||
for c in &checks {
|
||||
println!("{c}");
|
||||
}
|
||||
let failed = checks.iter().filter(|c| c.level == Level::Fail).count();
|
||||
if failed > 0 {
|
||||
bail!("{failed} check(s) failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn globals(list: &[(&str, u32)]) -> Vec<(String, u32)> {
|
||||
list.iter().map(|&(n, v)| (n.to_string(), v)).collect()
|
||||
}
|
||||
|
||||
fn niri_26_04() -> Vec<(String, u32)> {
|
||||
globals(&[
|
||||
("wl_compositor", 6),
|
||||
("wl_shm", 2),
|
||||
("wl_seat", 9),
|
||||
("zxdg_output_manager_v1", 3),
|
||||
("zwlr_layer_shell_v1", 5),
|
||||
("zwlr_screencopy_manager_v1", 3),
|
||||
("wp_viewporter", 1),
|
||||
("wp_cursor_shape_manager_v1", 2),
|
||||
("zwlr_data_control_manager_v1", 2),
|
||||
("ext_data_control_manager_v1", 1),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_present_is_all_ok() {
|
||||
let checks = check_globals(&niri_26_04());
|
||||
assert!(checks.iter().all(|c| c.level == Level::Ok), "{checks:#?}");
|
||||
assert!(
|
||||
checks
|
||||
.iter()
|
||||
.any(|c| c.text == "ext_data_control_manager_v1 v1 (clipboard copy)")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_required_fails_missing_optional_warns() {
|
||||
let list: Vec<_> = niri_26_04()
|
||||
.into_iter()
|
||||
.filter(|(n, _)| n != "zwlr_screencopy_manager_v1" && n != "wp_cursor_shape_manager_v1")
|
||||
.collect();
|
||||
let checks = check_globals(&list);
|
||||
assert!(checks.contains(&check(
|
||||
Level::Fail,
|
||||
"zwlr_screencopy_manager_v1 missing (screen capture)"
|
||||
)));
|
||||
assert!(checks.contains(&check(
|
||||
Level::Warn,
|
||||
"wp_cursor_shape_manager_v1 missing (crosshair cursor in region mode disabled)"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_version_fails() {
|
||||
let mut list = niri_26_04();
|
||||
list.retain(|(n, _)| n != "zxdg_output_manager_v1");
|
||||
list.push(("zxdg_output_manager_v1".into(), 1));
|
||||
assert!(check_globals(&list).contains(&check(
|
||||
Level::Fail,
|
||||
"zxdg_output_manager_v1 v1 is too old, need v2 (output names and positions)"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn niri_versions() {
|
||||
assert_eq!(check_niri(Ok("26.04 (Nixpkgs)".into())).level, Level::Ok);
|
||||
assert_eq!(check_niri(Ok("25.11".into())).level, Level::Warn);
|
||||
assert_eq!(
|
||||
check_niri(Err(anyhow::anyhow!("no socket"))).level,
|
||||
Level::Warn
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_dir_checks() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
assert_eq!(check_save_dir(&dir.path().join("new")).level, Level::Ok);
|
||||
let file = dir.path().join("file");
|
||||
std::fs::write(&file, b"").unwrap();
|
||||
assert_eq!(check_save_dir(&file).level, Level::Fail);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_format() {
|
||||
assert_eq!(check(Level::Warn, "x").to_string(), "warn x");
|
||||
assert_eq!(check(Level::Ok, "x").to_string(), "ok x");
|
||||
}
|
||||
}
|
||||
+48
-4
@@ -3,20 +3,64 @@
|
||||
|
||||
mod config;
|
||||
mod detach;
|
||||
mod doctor;
|
||||
mod error;
|
||||
mod frame;
|
||||
mod lock;
|
||||
mod log;
|
||||
mod niri;
|
||||
mod output;
|
||||
mod render;
|
||||
mod selection;
|
||||
mod wayland;
|
||||
|
||||
use clap::Parser;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::Local;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::error::Cancelled;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about = "macOS-style screenshots for niri")]
|
||||
struct Cli {}
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Cli::parse();
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Report what the compositor and system support.
|
||||
Doctor,
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
let log = log::init(&log::default_dir(), Local::now());
|
||||
log::install_panic_hook(log.clone());
|
||||
tracing::info!(
|
||||
"valw {} {:?}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
std::env::args().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
match run(cli) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) if e.downcast_ref::<Cancelled>().is_some() => {
|
||||
tracing::info!("cancelled by the user");
|
||||
ExitCode::from(3)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{e:#}");
|
||||
eprint!("{}", error::render(&e, log.as_deref()));
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Command::Doctor => doctor::run(),
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use niri_ipc::{Request, Response, socket::Socket};
|
||||
|
||||
/// The niri release the pinned `niri-ipc` crate belongs to. Keep in sync
|
||||
/// with the `niri-ipc = "=X.Y.0"` line in Cargo.toml (a test checks this).
|
||||
pub const IPC_VERSION: (u32, u32) = (26, 4);
|
||||
|
||||
fn request(request: Request) -> Result<Response> {
|
||||
let mut socket =
|
||||
Socket::connect().context("could not connect to niri (is NIRI_SOCKET set?)")?;
|
||||
socket
|
||||
.send(request)
|
||||
.context("niri IPC request failed")?
|
||||
.map_err(|e| anyhow!("niri replied with an error: {e}"))
|
||||
}
|
||||
|
||||
/// Name of the output niri has focused, if any.
|
||||
pub fn focused_output() -> Result<Option<String>> {
|
||||
match request(Request::FocusedOutput)? {
|
||||
Response::FocusedOutput(output) => Ok(output.map(|o| o.name)),
|
||||
other => Err(anyhow!("unexpected niri reply: {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// niri's version string, e.g. "26.04 (Nixpkgs)".
|
||||
pub fn version() -> Result<String> {
|
||||
match request(Request::Version)? {
|
||||
Response::Version(v) => Ok(v),
|
||||
other => Err(anyhow!("unexpected niri reply: {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// `(year, month)` from a niri version string like "26.04 (Nixpkgs)".
|
||||
pub fn parse_version(v: &str) -> Option<(u32, u32)> {
|
||||
let (year, rest) = v.split_once('.')?;
|
||||
let month: String = rest.chars().take_while(char::is_ascii_digit).collect();
|
||||
Some((year.parse().ok()?, month.parse().ok()?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parses_versions() {
|
||||
assert_eq!(parse_version("26.04 (Nixpkgs)"), Some((26, 4)));
|
||||
assert_eq!(parse_version("25.11"), Some((25, 11)));
|
||||
assert_eq!(parse_version("26.04.1-dev"), Some((26, 4)));
|
||||
assert_eq!(parse_version("unknown"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipc_version_matches_cargo_toml() {
|
||||
let (y, m) = IPC_VERSION;
|
||||
let pin = format!("niri-ipc = \"={y}.{m}.0\"");
|
||||
assert!(
|
||||
include_str!("../Cargo.toml").contains(&pin),
|
||||
"Cargo.toml should pin {pin}"
|
||||
);
|
||||
}
|
||||
}
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
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_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::error::HintExt;
|
||||
use crate::frame::OutputGeom;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
// 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
|
||||
.outputs
|
||||
.outputs()
|
||||
.filter_map(|wl| {
|
||||
let info = self.state.outputs.info(&wl)?;
|
||||
let (x, y) = info.logical_position?;
|
||||
let (width, height) = info.logical_size?;
|
||||
Some(Output {
|
||||
geom: OutputGeom {
|
||||
name: info
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("output-{}", info.id)),
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
transform: info.transform,
|
||||
wl,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `(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 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, _: &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 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) {}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &LayerSurface,
|
||||
_: LayerSurfaceConfigure,
|
||||
_: u32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
_: KeyEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
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,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &wl_pointer::WlPointer,
|
||||
_: &[PointerEvent],
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
Reference in new issue
Block a user