multibg-wayland/src/main.rs

310 lines
9.7 KiB
Rust

mod cli;
mod compositors;
mod gpu;
mod image;
mod poll;
mod signal;
mod wayland;
use std::{
io,
os::fd::AsFd,
path::{Path, PathBuf},
sync::{
Arc,
mpsc::{channel, Receiver},
},
rc::Rc,
};
use clap::Parser;
use log::{debug, error, info, warn};
use rustix::{
event::{poll, PollFd, PollFlags},
io::retry_on_intr,
};
use smithay_client_toolkit::{
compositor::CompositorState,
dmabuf::DmabufState,
output::OutputState,
registry::RegistryState,
shell::wlr_layer::LayerShell,
shm::Shm,
};
use smithay_client_toolkit::reexports::client::{
Connection, EventQueue,
backend::{ReadEventsGuard, WaylandError},
globals::registry_queue_init,
protocol::wl_shm,
};
use smithay_client_toolkit::reexports::protocols
::wp::viewporter::client::wp_viewporter::WpViewporter;
use crate::{
cli::{Cli, PixelFormat},
compositors::{Compositor, ConnectionTask, WorkspaceVisible},
gpu::Gpu,
image::ColorTransform,
poll::{Poll, Waker},
signal::SignalPipe,
wayland::BackgroundLayer,
};
struct State {
connection: Rc<Connection>,
compositor_state: CompositorState,
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,
layer_shell: LayerShell,
viewporter: WpViewporter,
wallpaper_dir: PathBuf,
shm_format: Option<wl_shm::Format>,
background_layers: Vec<BackgroundLayer>,
compositor_connection_task: ConnectionTask,
color_transform: ColorTransform,
dmabuf_state: DmabufState,
gpu: Option<Gpu>,
}
impl State {
fn shm_format(&mut self) -> wl_shm::Format {
*self.shm_format.get_or_insert_with(|| {
let mut format = wl_shm::Format::Xrgb8888;
// Consume less gpu memory by using Bgr888 if available,
// fall back to the always supported Xrgb8888 otherwise
if self.shm.formats().contains(&wl_shm::Format::Bgr888) {
format = wl_shm::Format::Bgr888
}
debug!("Using shm format: {format:?}");
format
})
}
}
fn main() -> Result<(), ()> {
run().map_err(|e| { error!("{e:#}"); })
}
fn run() -> anyhow::Result<()> {
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or(
#[cfg(debug_assertions)]
"info,multibg_sway=trace",
#[cfg(not(debug_assertions))]
"info",
)
).init();
info!(concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION")));
let cli = Cli::parse();
let wallpaper_dir = Path::new(&cli.wallpaper_dir).canonicalize().unwrap();
let brightness = cli.brightness.unwrap_or(0);
let contrast = cli.contrast.unwrap_or(0.0);
let color_transform = if brightness == 0 && contrast == 0.0 {
ColorTransform::None
} else {
ColorTransform::Legacy { brightness, contrast }
};
// ********************************
// Initialize wayland client
// ********************************
let conn = Rc::new(Connection::connect_to_env().unwrap());
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle();
let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
let layer_shell = LayerShell::bind(&globals, &qh).unwrap();
let shm = Shm::bind(&globals, &qh).unwrap();
let shm_format = if cli.pixelformat == Some(PixelFormat::Baseline) {
debug!("Using shm format: {:?}", wl_shm::Format::Xrgb8888);
Some(wl_shm::Format::Xrgb8888)
} else {
None
};
let registry_state = RegistryState::new(&globals);
let viewporter: WpViewporter = registry_state
.bind_one(&qh, 1..=1, ()).expect("wp_viewporter not available");
let dmabuf_state = DmabufState::new(&globals, &qh);
let mut gpu = None;
if cli.gpu {
if let Some(version) = dmabuf_state.version() {
if version >= 4 {
debug!("Using Linux DMA-BUF version {version}");
} else {
warn!("Only legacy Linux DMA-BUF version {version} is \
available from the compositor where it gives no \
information about which GPU it uses.");
// TODO handle this better by providing cli options
// to choose DRM device by major:minor or /dev path
}
match Gpu::new() {
Ok(val) => gpu = Some(val),
Err(e) =>
error!("Failed to set up GPU, disabling GPU use: {e:#}"),
}
} else {
error!("Wayland protocol Linux DMA-BUF is unavailable \
from the compositor, disabling GPU use");
}
}
// Sync tools for sway ipc tasks
let (tx, rx) = channel();
let waker = Arc::new(Waker::new().unwrap());
let compositor = cli.compositor
.or_else(Compositor::from_env)
.unwrap_or(Compositor::Sway);
let mut state = State {
connection: Rc::clone(&conn),
compositor_state,
registry_state,
output_state: OutputState::new(&globals, &qh),
shm,
layer_shell,
viewporter,
wallpaper_dir,
shm_format,
background_layers: Vec::new(),
compositor_connection_task: ConnectionTask::new(
compositor, tx.clone(), Arc::clone(&waker)
),
color_transform,
dmabuf_state,
gpu,
};
event_queue.roundtrip(&mut state).unwrap();
debug!("Initial wayland roundtrip done. Starting main event loop.");
// ********************************
// Main event loop
// ********************************
let mut poll = Poll::with_capacity(3);
let token_wayland = poll.add_readable(&conn);
ConnectionTask::spawn_subscribe_event_loop(compositor, tx, waker.clone());
let token_compositor = poll.add_readable(&waker);
let signal_pipe = SignalPipe::new()
.map_err(|e| error!("Failed to set up signal handling: {e}"))
.ok();
let token_signal = signal_pipe.as_ref().map(|pipe| poll.add_readable(pipe));
loop {
flush_blocking(&conn);
let read_guard = ensure_prepare_read(&mut state, &mut event_queue);
poll.poll().expect("Main event loop poll failed");
if poll.ready(token_wayland) {
handle_wayland_event(&mut state, &mut event_queue, read_guard);
} else {
drop(read_guard);
}
if poll.ready(token_compositor) {
waker.read();
handle_sway_event(&mut state, &rx);
}
if let Some(token_signal) = token_signal {
if poll.ready(token_signal) {
match signal_pipe.as_ref().unwrap().read() {
Err(e) => error!("Failed to read the signal pipe: {e}"),
Ok(signal_flags) => {
if let Some(signal) = signal_flags.any_termination() {
info!("Received signal {signal}, exiting");
return Ok(());
} else if signal_flags.has_usr1()
|| signal_flags.has_usr2()
{
error!("Received signal USR1 or USR2 is \
reserved for future functionality");
}
},
}
}
}
}
}
fn flush_blocking(connection: &Connection) {
loop {
let result = connection.flush();
if result.is_ok() { return }
if let Err(WaylandError::Io(io_error)) = &result {
if io_error.kind() == io::ErrorKind::WouldBlock {
warn!("Wayland flush needs to block");
let mut poll_fds = [PollFd::from_borrowed_fd(
connection.as_fd(),
PollFlags::OUT,
)];
retry_on_intr(|| poll(&mut poll_fds, -1)).unwrap();
continue
}
}
result.expect("Failed to flush Wayland event queue");
}
}
fn ensure_prepare_read(
state: &mut State,
event_queue: &mut EventQueue<State>,
) -> ReadEventsGuard {
loop {
if let Some(guard) = event_queue.prepare_read() { return guard }
event_queue.dispatch_pending(state)
.expect("Failed to dispatch pending Wayland events");
}
}
fn handle_wayland_event(
state: &mut State,
event_queue: &mut EventQueue<State>,
read_guard: ReadEventsGuard,
) {
match read_guard.read() {
Ok(_) => {
event_queue.dispatch_pending(state)
.expect("Failed to dispatch pending Wayland events");
},
Err(error) => {
if let WaylandError::Io(io_error) = &error {
if io_error.kind() == io::ErrorKind::WouldBlock {
return
}
}
panic!("Failed to read Wayland events: {error}");
}
}
}
fn handle_sway_event(
state: &mut State,
rx: &Receiver<WorkspaceVisible>,
) {
while let Ok(workspace) = rx.try_recv() {
// Find the background layer that of the output where the workspace is
if let Some(affected_bg_layer) = state.background_layers.iter_mut()
.find(|bg_layer| bg_layer.output_name == workspace.output)
{
affected_bg_layer.draw_workspace_bg(&workspace.workspace_name);
} else {
error!(
"Workspace '{}' is on an unknown output '{}', \
known outputs were: {}",
workspace.workspace_name,
workspace.output,
state.background_layers.iter()
.map(|bg_layer| bg_layer.output_name.as_str())
.collect::<Vec<_>>().join(", ")
);
continue
};
}
}