#![allow(dead_code)]
use crate::config::Config;
use crate::hint::HintGrid;
use std::cell::RefCell;
use std::os::fd::AsRawFd;
use std::rc::Rc;
use tracing::{error, info};
use wayland_client::{
Connection, Dispatch, QueueHandle, delegate_noop,
protocol::{
wl_buffer, wl_compositor, wl_output, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface,
},
};
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use wayland_protocols_wlr::virtual_pointer::v1::client::{
zwlr_virtual_pointer_manager_v1, zwlr_virtual_pointer_v1,
};
pub struct AppState {
pub virtual_pointer_manager:
Option<zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1>,
pub running: bool,
pub configured: bool,
pub compositor: Option<wl_compositor::WlCompositor>,
pub shm: Option<wl_shm::WlShm>,
pub layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
pub outputs: Vec<(wl_output::WlOutput, Option<OutputInfo>)>,
pub layer_surfaces: Vec<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
pub width: i32,
pub height: i32,
pub seat: Option<wayland_client::protocol::wl_seat::WlSeat>,
pub keyboard: Option<wayland_client::protocol::wl_keyboard::WlKeyboard>,
pub xkb_context: Option<xkbcommon::xkb::Context>,
pub xkb_keymap: Option<xkbcommon::xkb::Keymap>,
pub xkb_state: Option<xkbcommon::xkb::State>,
pub input_buf: String,
pub selection_made: bool,
pub canceled: bool,
}
#[derive(Clone, Debug)]
pub struct OutputInfo {
pub name: String,
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
pub scale: i32,
}
impl AppState {
pub fn new() -> Self {
Self {
virtual_pointer_manager: None,
running: true,
configured: false,
compositor: None,
shm: None,
layer_shell: None,
outputs: Vec::new(),
layer_surfaces: Vec::new(),
width: 0,
height: 0,
seat: None,
keyboard: None,
xkb_context: Some(xkbcommon::xkb::Context::new(
xkbcommon::xkb::CONTEXT_NO_FLAGS,
)),
xkb_keymap: None,
xkb_state: None,
input_buf: String::new(),
selection_made: false,
canceled: false,
}
}
}
impl Dispatch<wl_registry::WlRegistry, ()> for AppState {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_data: &(),
_conn: &Connection,
qhandle: &QueueHandle<Self>,
) {
if let wl_registry::Event::Global {
name,
interface,
version: _,
} = event
{
match &interface[..] {
"wl_compositor" => {
state.compositor = Some(registry.bind::<wl_compositor::WlCompositor, _, _>(
name,
4,
qhandle,
(),
));
}
"wl_shm" => {
state.shm = Some(registry.bind::<wl_shm::WlShm, _, _>(name, 1, qhandle, ()));
}
"zwlr_layer_shell_v1" => {
state.layer_shell = Some(
registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>(
name,
1,
qhandle,
(),
),
);
}
"wl_output" => {
let output = registry.bind::<wl_output::WlOutput, _, _>(name, 4, qhandle, ());
state.outputs.push((output, None));
}
"wl_seat" => {
state.seat = Some(registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qhandle, ()));
}
"zwlr_virtual_pointer_manager_v1" => {
state.virtual_pointer_manager = Some(registry.bind::<zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1, _, _>(name, 2, qhandle, ()));
}
_ => {}
}
}
}
}
impl Dispatch<wl_output::WlOutput, ()> for AppState {
fn event(
state: &mut Self,
output: &wl_output::WlOutput,
event: wl_output::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let Some(idx) = state.outputs.iter().position(|(o, _)| o == output) {
match event {
wl_output::Event::Geometry {
x,
y,
physical_width: _,
physical_height: _,
subpixel: _,
make: _,
model: _,
transform: _,
} => {
if let Some(info) = &mut state.outputs[idx].1 {
info.x = x;
info.y = y;
} else {
state.outputs[idx].1 = Some(OutputInfo {
name: String::new(),
x,
y,
width: 0,
height: 0,
scale: 1,
});
}
}
wl_output::Event::Mode {
flags: _,
width,
height,
refresh: _,
} => {
if let Some(info) = &mut state.outputs[idx].1 {
info.width = width;
info.height = height;
} else {
state.outputs[idx].1 = Some(OutputInfo {
name: String::new(),
x: 0,
y: 0,
width,
height,
scale: 1,
});
}
}
wl_output::Event::Scale { factor } => {
if let Some(info) = &mut state.outputs[idx].1 {
info.scale = factor;
}
}
wl_output::Event::Name { name } => {
if let Some(info) = &mut state.outputs[idx].1 {
info.name = name;
}
}
_ => {}
}
}
}
}
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for AppState {
fn event(
state: &mut Self,
surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
match event {
zwlr_layer_surface_v1::Event::Configure {
serial,
width,
height,
} => {
surface.ack_configure(serial);
state.configured = true;
state.width = width as i32;
state.height = height as i32;
info!(
"Layer surface configured: width={}, height={}",
width, height
);
}
zwlr_layer_surface_v1::Event::Closed => {
state.running = false;
info!("Layer surface closed by compositor");
}
_ => {}
}
}
}
delegate_noop!(AppState: ignore wl_compositor::WlCompositor);
delegate_noop!(AppState: ignore wl_shm::WlShm);
delegate_noop!(AppState: ignore wl_shm_pool::WlShmPool);
delegate_noop!(AppState: ignore wl_buffer::WlBuffer);
delegate_noop!(AppState: ignore wl_surface::WlSurface);
delegate_noop!(AppState: ignore zwlr_layer_shell_v1::ZwlrLayerShellV1);
delegate_noop!(AppState: ignore zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1);
delegate_noop!(AppState: ignore zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1);
struct OverlayInstance {
output: wl_output::WlOutput,
info: OutputInfo,
wl_surface: wl_surface::WlSurface,
layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
buffer: wl_buffer::WlBuffer,
ptr: *mut u8,
size: usize,
cairo_surface: cairo::ImageSurface,
cairo_context: cairo::Context,
screen_index: u32,
monitor_char: char,
}
pub struct Renderer {
conn: Connection,
state: Rc<RefCell<AppState>>,
}
impl Renderer {
pub fn new() -> anyhow::Result<Self> {
info!("Connecting to Wayland compositor...");
let conn = Connection::connect_to_env().map_err(|e| {
error!("Could not connect to Wayland env: {:?}", e);
anyhow::anyhow!("Wayland connection failed")
})?;
let state = Rc::new(RefCell::new(AppState::new()));
Ok(Self { conn, state })
}
fn create_shm_buffer(
shm: &wl_shm::WlShm,
width: i32,
height: i32,
qhandle: &QueueHandle<AppState>,
) -> anyhow::Result<(wl_buffer::WlBuffer, *mut u8, usize)> {
let stride = width * 4;
let size = (stride * height) as usize;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = format!("/dev/shm/waywarp-shm-{}", timestamp);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let _ = std::fs::remove_file(&path); file.set_len(size as u64)?;
let fd = file.as_raw_fd();
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
)
};
if ptr == libc::MAP_FAILED {
return Err(anyhow::anyhow!("mmap shared memory buffer failed"));
}
use std::os::fd::AsFd;
let pool = shm.create_pool(file.as_fd(), size as i32, qhandle, ());
let buffer = pool.create_buffer(
0,
width,
height,
stride,
wl_shm::Format::Argb8888,
qhandle,
(),
);
Ok((buffer, ptr as *mut u8, size))
}
pub fn draw_overlay(&mut self, _grid: &HintGrid, config: &Config) -> anyhow::Result<()> {
let mut event_queue = self.conn.new_event_queue();
let qhandle = event_queue.handle();
let _registry = self.conn.display().get_registry(&qhandle, ());
info!("Retrieving active registry globals...");
event_queue.roundtrip(&mut *self.state.borrow_mut())?;
event_queue.roundtrip(&mut *self.state.borrow_mut())?;
let active_outputs: Vec<(wl_output::WlOutput, OutputInfo)> = {
let s = self.state.borrow();
s.outputs
.iter()
.filter_map(|(o, info)| info.clone().map(|i| (o.clone(), i)))
.filter(|(_, info)| info.width > 0 && info.height > 0)
.collect()
};
let active_outputs = if active_outputs.is_empty() {
info!("No active outputs resolved from compositor. Falling back to default.");
let fallback_output = {
let s = self.state.borrow();
s.outputs
.first()
.map(|(o, _)| o.clone())
.ok_or_else(|| anyhow::anyhow!("No outputs registered at all"))?
};
vec![(
fallback_output,
OutputInfo {
name: "default".to_string(),
x: 0,
y: 0,
width: 1920,
height: 1080,
scale: 1,
},
)]
} else {
active_outputs
};
let is_multi = active_outputs.len() > 1;
info!(
"Detected active outputs: {}. Multi-monitor mode: {}",
active_outputs.len(),
is_multi
);
let (compositor, shm, layer_shell) = {
let s = self.state.borrow();
(
s.compositor
.clone()
.ok_or_else(|| anyhow::anyhow!("wl_compositor binding missing"))?,
s.shm
.clone()
.ok_or_else(|| anyhow::anyhow!("wl_shm binding missing"))?,
s.layer_shell
.clone()
.ok_or_else(|| anyhow::anyhow!("zwlr_layer_shell_v1 missing"))?,
)
};
let chars = HintGrid::get_unique_chars(&config.hint_chars);
let mut instances = Vec::new();
for (i, (output, info)) in active_outputs.iter().enumerate() {
let screen_index = i as u32;
let monitor_char = chars[i % chars.len()];
info!(
"Spawning Layer Shell Overlay Window on monitor: {:?} ({:?}) with index {} char '{}'",
info.name, output, screen_index, monitor_char
);
let wl_surface = compositor.create_surface(&qhandle, ());
let layer_surface = layer_shell.get_layer_surface(
&wl_surface,
Some(output), zwlr_layer_shell_v1::Layer::Overlay,
"waywarp".to_string(),
&qhandle,
(),
);
layer_surface.set_size(info.width as u32, info.height as u32);
layer_surface.set_anchor(
zwlr_layer_surface_v1::Anchor::Top
| zwlr_layer_surface_v1::Anchor::Bottom
| zwlr_layer_surface_v1::Anchor::Left
| zwlr_layer_surface_v1::Anchor::Right,
);
if i == 0 {
layer_surface.set_keyboard_interactivity(
zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive,
);
} else {
layer_surface
.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::None);
}
layer_surface.set_exclusive_zone(-1);
wl_surface.commit();
let (buffer, ptr, size) =
Self::create_shm_buffer(&shm, info.width, info.height, &qhandle)?;
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, size) };
let cairo_surface = cairo::ImageSurface::create_for_data(
slice,
cairo::Format::ARgb32,
info.width,
info.height,
info.width * 4,
)?;
let cairo_context = cairo::Context::new(&cairo_surface)?;
instances.push(OverlayInstance {
output: output.clone(),
info: info.clone(),
wl_surface,
layer_surface,
buffer,
ptr,
size,
cairo_surface,
cairo_context,
screen_index,
monitor_char,
});
}
{
let mut s = self.state.borrow_mut();
for inst in &instances {
s.layer_surfaces.push(inst.layer_surface.clone());
}
}
event_queue.roundtrip(&mut *self.state.borrow_mut())?;
let mut active_grid = if is_multi {
let mut all_hints = Vec::new();
for inst in &instances {
let grid = HintGrid::generate_first_pass(
inst.info.width,
inst.info.height,
&config.hint_chars,
inst.screen_index,
true,
Some(inst.monitor_char),
);
all_hints.extend(grid.hints);
}
let mut g = HintGrid::new(0, 0, 0);
g.hints = all_hints;
g
} else {
let inst = &instances[0];
HintGrid::generate_first_pass(
inst.info.width,
inst.info.height,
&config.hint_chars,
0,
false,
None,
)
};
let draw_grid = |instances: &mut [OverlayInstance],
grid: &HintGrid,
prefix: &str|
-> anyhow::Result<()> {
for inst in instances {
let cr = &inst.cairo_context;
let info = &inst.info;
let has_matches = grid
.hints
.iter()
.any(|h| h.screen == inst.screen_index && h.label.starts_with(prefix));
if !prefix.is_empty() && !has_matches {
cr.set_source_rgba(0.0, 0.0, 0.0, 0.0);
cr.set_operator(cairo::Operator::Source);
cr.paint()?;
} else {
cr.set_source_rgba(0.0, 0.0, 0.0, 0.2);
cr.set_operator(cairo::Operator::Source);
cr.paint()?;
for hint in &grid.hints {
if hint.screen != inst.screen_index {
continue;
}
if !hint.label.starts_with(prefix) {
continue;
}
let label_w = hint.width as f64;
let label_h = hint.height as f64;
let x = hint.x as f64;
let y = hint.y as f64;
cr.set_operator(cairo::Operator::Over);
cr.set_source_rgba(
config.hint_bg[0],
config.hint_bg[1],
config.hint_bg[2],
config.hint_bg[3],
);
let r = config.hint_border_radius;
cr.new_sub_path();
cr.arc(
x - label_w / 2.0 + r,
y - label_h / 2.0 + r,
r,
180.0 * std::f64::consts::PI / 180.0,
270.0 * std::f64::consts::PI / 180.0,
);
cr.arc(
x + label_w / 2.0 - r,
y - label_h / 2.0 + r,
r,
270.0 * std::f64::consts::PI / 180.0,
360.0 * std::f64::consts::PI / 180.0,
);
cr.arc(
x + label_w / 2.0 - r,
y + label_h / 2.0 - r,
r,
0.0 * std::f64::consts::PI / 180.0,
90.0 * std::f64::consts::PI / 180.0,
);
cr.arc(
x - label_w / 2.0 + r,
y + label_h / 2.0 - r,
r,
90.0 * std::f64::consts::PI / 180.0,
180.0 * std::f64::consts::PI / 180.0,
);
cr.close_path();
cr.fill()?;
cr.set_source_rgba(
config.hint_fg[0],
config.hint_fg[1],
config.hint_fg[2],
config.hint_fg[3],
);
cr.select_font_face(
&config.hint_font,
cairo::FontSlant::Normal,
cairo::FontWeight::Normal,
);
cr.set_font_size(config.hint_size as f64);
let extents = cr.text_extents(&hint.label)?;
cr.move_to(
x - extents.width() / 2.0 - extents.x_bearing(),
y - extents.height() / 2.0 - extents.y_bearing(),
);
cr.show_text(&hint.label)?;
}
}
inst.cairo_surface.flush();
inst.wl_surface.attach(Some(&inst.buffer), 0, 0);
inst.wl_surface.damage(0, 0, info.width, info.height);
inst.wl_surface.commit();
}
Ok(())
};
draw_grid(&mut instances, &active_grid, "")?;
info!("Initial multi-monitor frames committed. Awaiting Seat keyboard events...");
let mut current_prefix = String::new();
let mut refinement_level = 1;
while self.state.borrow().running {
event_queue.roundtrip(&mut *self.state.borrow_mut())?;
let (prefix, selection_made, canceled) = {
let s = self.state.borrow();
(s.input_buf.clone(), s.selection_made, s.canceled)
};
if canceled {
break;
}
if selection_made {
let matched = active_grid
.hints
.iter()
.find(|h| h.label == prefix || h.label.starts_with(&prefix));
if let Some(h) = matched {
info!(
"Selection forced confirm: label='{}' at ({}, {}) on screen {}",
h.label, h.x, h.y, h.screen
);
let (target_output, target_info) = active_outputs
.iter()
.enumerate()
.find(|(idx, _)| *idx as u32 == h.screen)
.map(|(_, (o, info))| (Some(o), info.clone()))
.unwrap_or((
None,
OutputInfo {
name: "default".to_string(),
x: 0,
y: 0,
width: 1920,
height: 1080,
scale: 1,
},
));
if let Some(ref manager) = self.state.borrow().virtual_pointer_manager {
let pointer =
crate::pointer::VirtualPointer::new(manager, target_output, &qhandle);
pointer.move_to(h.x, h.y, target_info.width, target_info.height);
pointer.click(crate::pointer::MouseButton::Left);
}
let _ = Config::execute_callback(
&config.on_select_cmd,
h.x,
h.y,
target_info.width,
target_info.height,
);
}
break;
}
if prefix != current_prefix {
current_prefix = prefix.clone();
info!("Input buffer mutated: {:?}", current_prefix);
let matches: Vec<&crate::hint::Hint> = active_grid
.hints
.iter()
.filter(|h| h.label.starts_with(¤t_prefix))
.collect();
if matches.is_empty() {
self.state.borrow_mut().input_buf.clear();
current_prefix.clear();
draw_grid(&mut instances, &active_grid, "")?;
} else if matches.len() == 1 {
let matched_hint = matches[0].clone();
if refinement_level < config.refinement_passes {
info!(
"Triggering refinement pass {} for label='{}' on screen {}",
refinement_level + 1,
matched_hint.label,
matched_hint.screen
);
refinement_level += 1;
self.state.borrow_mut().input_buf.clear();
current_prefix.clear();
let target_info = active_outputs
.iter()
.enumerate()
.find(|(idx, _)| *idx as u32 == matched_hint.screen)
.map(|(_, (_, info))| info.clone())
.unwrap_or(OutputInfo {
name: "default".to_string(),
x: 0,
y: 0,
width: 1920,
height: 1080,
scale: 1,
});
let unique_len =
HintGrid::get_unique_chars(&config.hint_chars).len() as i32;
let cell_w = target_info.width / unique_len;
let cell_h = target_info.height / unique_len;
let x_min = matched_hint.x - cell_w / 2;
let y_min = matched_hint.y - cell_h / 2;
let x_max = matched_hint.x + cell_w / 2;
let y_max = matched_hint.y + cell_h / 2;
active_grid = HintGrid::generate_refinement(
x_min,
y_min,
x_max,
y_max,
&config.hint_chars,
matched_hint.screen,
);
draw_grid(&mut instances, &active_grid, "")?;
} else {
info!(
"Warp target successfully resolved: ({}, {}) on screen {}",
matched_hint.x, matched_hint.y, matched_hint.screen
);
let (target_output, target_info) = active_outputs
.iter()
.enumerate()
.find(|(idx, _)| *idx as u32 == matched_hint.screen)
.map(|(_, (o, info))| (Some(o), info.clone()))
.unwrap_or((
None,
OutputInfo {
name: "default".to_string(),
x: 0,
y: 0,
width: 1920,
height: 1080,
scale: 1,
},
));
if let Some(ref manager) = self.state.borrow().virtual_pointer_manager {
let pointer = crate::pointer::VirtualPointer::new(
manager,
target_output,
&qhandle,
);
pointer.move_to(
matched_hint.x,
matched_hint.y,
target_info.width,
target_info.height,
);
pointer.click(crate::pointer::MouseButton::Left);
}
let _ = Config::execute_callback(
&config.on_select_cmd,
matched_hint.x,
matched_hint.y,
target_info.width,
target_info.height,
);
self.state.borrow_mut().running = false;
}
} else {
draw_grid(&mut instances, &active_grid, ¤t_prefix)?;
}
}
std::thread::sleep(std::time::Duration::from_millis(16));
}
if let Some(inst) = instances.first() {
let _ = Config::execute_callback(
&config.on_exit_cmd,
0,
0,
inst.info.width,
inst.info.height,
);
} else {
let _ = Config::execute_callback(&config.on_exit_cmd, 0, 0, 1920, 1080);
}
for inst in instances {
unsafe {
libc::munmap(inst.ptr as *mut libc::c_void, inst.size);
}
}
Ok(())
}
}