use std::{mem, time::Duration};
use anyhow::Context;
use smithay_client_toolkit::reexports::client::{EventQueue, protocol::wl_shm::Format};
use tokio::{
io::unix::AsyncFd,
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
};
use tracing::{debug, trace};
use crate::{
config_worker::{ConfigEvent, ConfigHandle, RenderBackend},
geometry_worker::{GeometryWorker, GeometryWorkerEvent},
gui::{Gui, GuiEvent},
image_resizer::ImageResizer,
ipc::{AlttabwayIpc, Direction, IpcCommand, Modifier},
renderer::{Renderer, SoftwareRenderer, WgpuRenderer},
timer::Timer,
wayland_client::WaylandClient,
wayland_client_event::WaylandClientEvent,
};
pub struct Daemon {
height: u32,
width: u32,
renderer: Box<dyn Renderer>,
wayland_client: WaylandClient,
wayland_client_q: EventQueue<WaylandClient>,
wayland_client_rx: UnboundedReceiver<WaylandClientEvent>,
renderer_tx: UnboundedSender<()>,
renderer_rx: UnboundedReceiver<()>,
preview_resizer: ImageResizer<u32>,
gui: Gui,
pending_repaint: bool,
geometry_worker: GeometryWorker<u32>,
ipc_listener: UnboundedReceiver<IpcCommand>,
visible: bool,
screenshot_timer: Timer,
required_modifiers: Vec<Modifier>,
config_handle: ConfigHandle,
}
impl Daemon {
pub const DEFAULT_REQ_MODIFIER: [Modifier; 1] = [Modifier::Alt];
pub async fn start() -> anyhow::Result<()> {
let ipc_listener = AlttabwayIpc::start_server().await?;
let config_handle = ConfigHandle::new();
let geometry_worker = GeometryWorker::new()?;
let (wayland_client, wayland_client_q, wayland_client_rx) = WaylandClient::init()?;
let (renderer_tx, renderer_rx) = mpsc::unbounded_channel();
let preview_resizer = ImageResizer::new();
let renderer: Box<dyn Renderer> = match config_handle.get_config().render_backend {
RenderBackend::Software => Box::new(SoftwareRenderer::new()),
backends => Box::new(WgpuRenderer::new(backends).await?),
};
debug!("Initialized wayland layer client");
let mut daemon = Self {
height: 400,
width: 800,
renderer,
wayland_client,
wayland_client_q,
wayland_client_rx,
renderer_tx,
renderer_rx,
preview_resizer,
gui: Gui::new(config_handle.get_config()),
pending_repaint: false,
geometry_worker,
ipc_listener,
visible: false,
screenshot_timer: Timer::new(Duration::from_secs(5)),
required_modifiers: Self::DEFAULT_REQ_MODIFIER.to_vec(),
config_handle,
};
Daemon::run_loop(&mut daemon).await
}
async fn run_loop(&mut self) -> anyhow::Result<()> {
loop {
self.wayland_client_q.flush()?;
let Some(read_guard) = self.wayland_client_q.prepare_read() else {
self.wayland_client_q
.dispatch_pending(&mut self.wayland_client)?;
continue;
};
let async_fd = AsyncFd::new(read_guard.connection_fd())?;
tokio::select! {
_ = async_fd.readable() => {
drop(async_fd);
read_guard.read()?;
self.wayland_client_q
.dispatch_pending(&mut self.wayland_client)?;
},
result = self.wayland_client_rx.recv() => {
let event = result.context("wayland client has crashed")?;
trace!("received wayland client event {:?}", event);
match event {
WaylandClientEvent::LayerShellConfigure(configure) => {
let (width, height) = configure.new_size;
self.width = if width == 0 { self.width } else { width };
self.height = if height == 0 { self.height } else { height };
if !self.visible || !self.wayland_client.has_surfaces() {
continue;
}
self.renderer.init_surface(&mut self.wayland_client, self.width, self.height, self.renderer_tx.clone())?;
}
WaylandClientEvent::Egui(events) => {
self.gui.handle_events(events);
if self.gui.needs_repaint() {
self.request_repaint()?
}
}
WaylandClientEvent::PaintRequest => self.paint()?,
WaylandClientEvent::ModifierChange => {
let wl_modifiers = self.wayland_client.get_modifiers();
if !wl_modifiers.ctrl && self.required_modifiers.contains(&Modifier::Ctrl) ||
!wl_modifiers.alt && self.required_modifiers.contains(&Modifier::Alt) ||
!wl_modifiers.shift && self.required_modifiers.contains(&Modifier::Shift) ||
!wl_modifiers.logo && self.required_modifiers.contains(&Modifier::Super)
{
if self.visible {
self.update_visibility(false)?;
if let Some(window_id) = self.gui.get_selected_item_id() {
self.wayland_client.activate_window(window_id);
}
}
}
}
WaylandClientEvent::TopLevelAdded(id) => self.gui.add_item(id),
WaylandClientEvent::TopLevelActivated(id) => {
self.gui.signal_item_activation(id);
if self.visible && self.wayland_client.has_surfaces() {
continue
}
self.screenshot_timer.ping_after(Duration::from_secs(1)).await?;
}
WaylandClientEvent::TopLevelTitleUpdate(id, new_title) => self.gui.update_item_title(id, new_title),
WaylandClientEvent::TopLevelAppIdUpdate(id, new_app_id) => self.gui.update_item_app_id(id, new_app_id),
WaylandClientEvent::TopLevelRemoved(id) => self.gui.remove_item(id),
WaylandClientEvent::ScreencopyDone(id, buffer, format) => {
let _span = tracing::trace_span!("Resize", id=id).entered();
tracing::trace!("start");
let pixels = self.wayland_client.get_buffer_mut(&buffer, |slice| {
match format {
Format::Argb8888 | Format::Xrgb8888 =>
slice.chunks(4).flat_map(|p| [p[2], p[1], p[0]]).collect(),
Format::Bgr888 => slice.to_vec(),
_ => panic!("unknown format")
}
});
let (width, height) = (pixels.len() as u32 / buffer.height() as u32 / 3, buffer.height() as u32);
self.preview_resizer.resize_rgb_pixels(id, (pixels, width), self.gui.calculate_preview_size((width, height)));
}
}
},
Some(()) = self.renderer_rx.recv() => {
self.paint()?
}
Some((id, preview_image)) = self.preview_resizer.recv() => {
if !self.visible {
self.gui.update_item_preview(id, preview_image.buffer(), preview_image.width());
}
}
result = self.geometry_worker.recv() => {
let event = result.context("geometry worker has crashed")?;
tracing::debug!("geometry worker event: {:?}", event);
match event {
GeometryWorkerEvent::ActiveWindow(window_id, geometry) => {
let Some(active_window_id) = self.get_active_window_id() else {
continue
};
if active_window_id != window_id {
continue
}
if self.visible {
continue
}
let (x, y, width, height) = geometry;
if width <= 0 || height <= 0 {
continue
}
let _ = self.wayland_client.capture_window_region(window_id, x, y, width, height, &self.wayland_client_q.handle());
}
}
}
result = self.ipc_listener.recv() => {
let event = result.context("ipc server has crashed")?;
tracing::debug!("ipc event: {:?}", event);
match event {
IpcCommand::Ping => (),
IpcCommand::Show { direction, mut modifiers } => {
mem::swap(&mut self.required_modifiers, &mut modifiers);
if self.visible {
if let Some(direction) = direction {
match direction {
Direction::Previous => self.gui.select_previous_item(),
Direction::Next => self.gui.select_next_item(),
}
self.request_repaint()?;
}
} else {
self.update_visibility(true)?;
}
}
IpcCommand::Hide => self.update_visibility(false)?,
}
}
result = self.screenshot_timer.wait() => {
result.context("screenshot timer has unexpectedly crashed")?;
let Some(active_window_id) = self.get_active_window_id() else { continue };
self.geometry_worker.request_active_window_geometry(active_window_id)?;
}
Some(event) = self.gui.recv() => {
match event {
GuiEvent::ItemClicked(window_id) => {
if self.visible {
self.update_visibility(false)?;
self.wayland_client.activate_window(window_id);
}
}
}
}
Some(event) = self.config_handle.recv() => {
match event {
ConfigEvent::Updated => {
self.gui.update_from_config(self.config_handle.get_config());
}
}
}
}
}
}
fn get_active_window_id(&self) -> Option<u32> {
self.gui.get_first_item_id()
}
fn request_repaint(&mut self) -> anyhow::Result<()> {
if self.pending_repaint {
return Ok(());
}
self.pending_repaint = true;
trace!("repaint requested");
self.wayland_client
.request_paint(&self.wayland_client_q.handle());
Ok(())
}
fn update_visibility(&mut self, visible: bool) -> anyhow::Result<()> {
self.visible = visible;
if visible {
if self.wayland_client.has_surfaces() {
return Ok(());
}
tracing::trace!("VISIBILITY CALLED");
self.gui.reset_selected_item();
if self.config_handle.requires_monitor_width()
&& let Some(monitor_width) = self.wayland_client.get_monitor_width()
{
tracing::trace!("Monitor width: {}", monitor_width);
self.gui.set_monitor_width(monitor_width);
}
(self.width, self.height) = self.gui.get_window_dimensions();
self.wayland_client.create_surfaces(
&self.wayland_client_q.handle(),
self.width,
self.height,
)?;
tracing::trace!("SURFACES CREATED");
} else {
self.renderer.destroy_surface(&mut self.wayland_client)?;
self.wayland_client.destroy_surfaces();
}
Ok(())
}
fn paint(&mut self) -> anyhow::Result<()> {
self.pending_repaint = false;
tracing::trace!("PAINT COMPLETE");
self.renderer
.render(&mut self.wayland_client, &mut self.gui)?;
use smithay_client_toolkit::seat::pointer::CursorIcon;
match self.gui.get_cursor_icon() {
egui::CursorIcon::Default => self.wayland_client.request_cursor(CursorIcon::Default),
egui::CursorIcon::PointingHand => {
self.wayland_client.request_cursor(CursorIcon::Pointer)
}
_ => (),
}
return Ok(());
}
}