use std::fs::File;
use std::os::fd::{AsFd, OwnedFd};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use color_eyre::eyre::{Result, WrapErr, eyre};
use memmap2::MmapMut;
use rustix::fs::{MemfdFlags, memfd_create};
use wayland_client::globals::{GlobalListContents, registry_queue_init};
use wayland_client::protocol::{
wl_buffer::WlBuffer,
wl_callback::{self, WlCallback},
wl_compositor::WlCompositor,
wl_keyboard::{self, KeyState, KeymapFormat, WlKeyboard},
wl_output::{self, WlOutput},
wl_pointer::{self, ButtonState, WlPointer},
wl_registry::WlRegistry,
wl_seat::{self, Capability, WlSeat},
wl_shm::{Format, WlShm},
wl_shm_pool::WlShmPool,
wl_surface::WlSurface,
wl_touch::{self, WlTouch},
};
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
use wayland_protocols::wp::cursor_shape::v1::client::{
wp_cursor_shape_device_v1::{Shape, WpCursorShapeDeviceV1},
wp_cursor_shape_manager_v1::WpCursorShapeManagerV1,
};
use wayland_protocols::xdg::xdg_output::zv1::client::{
zxdg_output_manager_v1::ZxdgOutputManagerV1,
zxdg_output_v1::{self, ZxdgOutputV1},
};
use wayland_protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1},
zwlr_layer_surface_v1::{self, Anchor, KeyboardInteractivity, ZwlrLayerSurfaceV1},
};
use xkbcommon::xkb;
use crate::geometry::Rect;
use crate::render::{FrozenFrame, Scene, render};
use crate::{ChoiceBox, SelectOptions, Selection};
const BTN_LEFT: u32 = 0x110;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Which {
Pointer,
Touch,
}
#[derive(Clone, Copy)]
struct OutputId(usize);
#[derive(Clone, Copy)]
struct SeatId(usize);
struct BufferData(Arc<AtomicBool>);
struct PoolBuffer {
buffer: WlBuffer,
mmap: MmapMut,
width: i32,
height: i32,
busy: Arc<AtomicBool>,
}
#[derive(Default)]
struct SelectionState {
current_output: Option<usize>,
x: i32,
y: i32,
anchor_x: i32,
anchor_y: i32,
selection: Rect,
selected_label: Option<String>,
has_selection: bool,
}
struct Output {
wl_output: WlOutput,
surface: Option<WlSurface>,
layer_surface: Option<ZwlrLayerSurfaceV1>,
xdg_output: Option<ZxdgOutputV1>,
geometry: Rect,
logical: Rect,
label: Option<String>,
scale: i32,
configured: bool,
dirty: bool,
frame_pending: bool,
width: i32,
height: i32,
buffers: [Option<PoolBuffer>; 2],
frozen: Option<FrozenFrame>,
}
struct Seat {
_wl_seat: WlSeat,
pointer: Option<WlPointer>,
keyboard: Option<WlKeyboard>,
touch: Option<WlTouch>,
button_pressed: bool,
touch_id: Option<i32>,
pointer_sel: SelectionState,
touch_sel: SelectionState,
xkb_state: Option<xkb::State>,
}
impl Seat {
fn current(&self) -> &SelectionState {
if self.touch_sel.has_selection {
&self.touch_sel
} else {
&self.pointer_sel
}
}
}
pub(crate) struct App {
compositor: WlCompositor,
shm: WlShm,
layer_shell: ZwlrLayerShellV1,
xdg_output_manager: Option<ZxdgOutputManagerV1>,
cursor_shape_manager: Option<WpCursorShapeManagerV1>,
outputs: Vec<Output>,
seats: Vec<Seat>,
boxes: Vec<ChoiceBox>,
box_rects: Vec<Rect>,
opts: SelectOptions,
font: Option<fontdue::Font>,
xkb_context: xkb::Context,
edit_anchor: bool,
resizing: bool,
aspect: Option<f64>,
fixed_aspect: bool,
running: bool,
result: Option<Selection>,
}
pub(crate) fn run(boxes: Vec<ChoiceBox>, opts: SelectOptions) -> Result<Selection> {
let _lock = crate::lock::Lock::acquire()?;
let conn = Connection::connect_to_env().wrap_err("connect to Wayland compositor")?;
let (globals, mut queue) =
registry_queue_init::<App>(&conn).wrap_err("initialize Wayland registry")?;
let qh = queue.handle();
let compositor: WlCompositor = globals
.bind(&qh, 4..=6, ())
.map_err(|_| eyre!("compositor is missing required global wl_compositor"))?;
let shm: WlShm = globals
.bind(&qh, 1..=1, ())
.map_err(|_| eyre!("compositor is missing required global wl_shm"))?;
let layer_shell: ZwlrLayerShellV1 = globals
.bind(&qh, 1..=4, ())
.map_err(|_| eyre!("compositor is missing required global zwlr_layer_shell_v1"))?;
let xdg_output_manager: Option<ZxdgOutputManagerV1> = globals.bind(&qh, 2..=3, ()).ok();
let cursor_shape_manager: Option<WpCursorShapeManagerV1> = globals.bind(&qh, 1..=1, ()).ok();
let fixed_aspect = opts.aspect_ratio.is_some();
let aspect = opts.aspect_ratio.map(|(w, h)| f64::from(h) / f64::from(w));
let font = if opts.display_dimensions {
crate::font::load(opts.font_family.as_deref())
} else {
None
};
let mut app = App {
compositor,
shm,
layer_shell,
xdg_output_manager,
cursor_shape_manager,
outputs: Vec::new(),
seats: Vec::new(),
boxes,
box_rects: Vec::new(),
opts,
font,
xkb_context: xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
edit_anchor: false,
resizing: false,
aspect,
fixed_aspect,
running: true,
result: None,
};
let registry = globals.registry();
for global in globals.contents().clone_list() {
if global.interface == WlOutput::interface().name {
let index = app.outputs.len();
let version = global.version.min(4);
let wl_output: WlOutput = registry.bind(global.name, version, &qh, OutputId(index));
app.outputs.push(Output {
wl_output,
surface: None,
layer_surface: None,
xdg_output: None,
geometry: Rect::default(),
logical: Rect::default(),
label: None,
scale: 1,
configured: false,
dirty: false,
frame_pending: false,
width: 0,
height: 0,
buffers: [None, None],
frozen: None,
});
} else if global.interface == WlSeat::interface().name {
let index = app.seats.len();
let version = global.version.min(7);
let wl_seat: WlSeat = registry.bind(global.name, version, &qh, SeatId(index));
app.seats.push(Seat {
_wl_seat: wl_seat,
pointer: None,
keyboard: None,
touch: None,
button_pressed: false,
touch_id: None,
pointer_sel: SelectionState::default(),
touch_sel: SelectionState::default(),
xkb_state: None,
});
}
}
queue
.roundtrip(&mut app)
.wrap_err("read Wayland output and seat state")?;
if app.outputs.is_empty() {
return Err(eyre!("compositor has no outputs"));
}
if app.opts.freeze {
let mut frames = crate::freeze::capture_outputs()?;
for output in &mut app.outputs {
let matching = output
.label
.as_ref()
.and_then(|name| {
frames
.iter()
.position(|frame| frame.name.as_ref() == Some(name))
})
.or_else(|| (!frames.is_empty()).then_some(0));
output.frozen = matching.map(|index| frames.remove(index).frame);
}
}
for oi in 0..app.outputs.len() {
let surface = app.compositor.create_surface(&qh, OutputId(oi));
let layer_surface = app.layer_shell.get_layer_surface(
&surface,
Some(&app.outputs[oi].wl_output),
Layer::Overlay,
"selection".to_string(),
&qh,
OutputId(oi),
);
layer_surface.set_anchor(Anchor::Top | Anchor::Left | Anchor::Right | Anchor::Bottom);
layer_surface.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
layer_surface.set_exclusive_zone(-1);
if let Some(manager) = &app.xdg_output_manager {
let xdg_output = manager.get_xdg_output(&app.outputs[oi].wl_output, &qh, OutputId(oi));
app.outputs[oi].xdg_output = Some(xdg_output);
} else {
let mut logical = app.outputs[oi].geometry;
logical.width /= app.outputs[oi].scale;
logical.height /= app.outputs[oi].scale;
app.outputs[oi].logical = logical;
}
surface.commit();
app.outputs[oi].surface = Some(surface);
app.outputs[oi].layer_surface = Some(layer_surface);
}
queue
.roundtrip(&mut app)
.wrap_err("configure selection overlays")?;
if app.opts.all_outputs {
for oi in 0..app.outputs.len() {
app.boxes.push(ChoiceBox {
rect: app.outputs[oi].logical,
label: app.outputs[oi].label.clone(),
id: None,
});
}
}
app.box_rects = app.boxes.iter().map(|b| b.rect).collect();
while app.running {
queue
.blocking_dispatch(&mut app)
.wrap_err("process Wayland event")?;
}
app.result
.take()
.ok_or_else(|| eyre!("selection cancelled"))
}
fn create_buffer(
shm: &WlShm,
qh: &QueueHandle<App>,
width: i32,
height: i32,
) -> Option<PoolBuffer> {
let stride = width * 4;
let size = (stride * height) as usize;
if size == 0 {
return None;
}
let fd: OwnedFd = memfd_create("sip-buffer", MemfdFlags::CLOEXEC).ok()?;
let file = File::from(fd);
file.set_len(size as u64).ok()?;
let mmap = unsafe { MmapMut::map_mut(&file).ok()? };
let pool = shm.create_pool(file.as_fd(), size as i32, qh, ());
let busy = Arc::new(AtomicBool::new(false));
let buffer = pool.create_buffer(
0,
width,
height,
stride,
Format::Argb8888,
qh,
BufferData(busy.clone()),
);
pool.destroy();
Some(PoolBuffer {
buffer,
mmap,
width,
height,
busy,
})
}
fn rgba_to_argb8888(src: &[u8], dst: &mut [u8]) {
for (s, d) in src.chunks_exact(4).zip(dst.chunks_exact_mut(4)) {
d[0] = s[2];
d[1] = s[1];
d[2] = s[0];
d[3] = s[3];
}
}
impl App {
fn sel(&self, si: usize, which: Which) -> &SelectionState {
match which {
Which::Pointer => &self.seats[si].pointer_sel,
Which::Touch => &self.seats[si].touch_sel,
}
}
fn sel_mut(&mut self, si: usize, which: Which) -> &mut SelectionState {
match which {
Which::Pointer => &mut self.seats[si].pointer_sel,
Which::Touch => &mut self.seats[si].touch_sel,
}
}
fn output_index_of_surface(&self, surface: &WlSurface) -> Option<usize> {
self.outputs
.iter()
.position(|o| o.surface.as_ref().is_some_and(|s| s.id() == surface.id()))
}
fn output_at(&self, x: i32, y: i32) -> (Option<String>, Option<Rect>) {
match self.outputs.iter().find(|o| o.logical.contains(x, y)) {
Some(o) => (o.label.clone(), Some(o.logical)),
None => (None, None),
}
}
fn move_seat(&mut self, si: usize, which: Which, sx: f64, sy: f64) {
let Some(oi) = self.sel(si, which).current_output else {
return;
};
let ox = self.outputs[oi].logical.x;
let oy = self.outputs[oi].logical.y;
let x = sx as i32 + ox;
let y = sy as i32 + oy;
let edit = self.edit_anchor;
let s = self.sel_mut(si, which);
if edit {
s.anchor_x += x - s.x;
s.anchor_y += y - s.y;
}
s.x = x;
s.y = y;
}
fn active_motion(&mut self, si: usize, which: Which) {
if self.opts.restrict {
return;
}
self.resizing = true;
let aspect = self.aspect;
let s = self.sel_mut(si, which);
let (ax, ay) = (s.anchor_x, s.anchor_y);
let dx = s.x - ax;
let dy = s.y - ay;
s.has_selection = true;
let mut width = dx.abs() + 1;
let mut height = dy.abs() + 1;
if let Some(ar) = aspect {
width = width.max((f64::from(height) / ar) as i32);
height = height.max((f64::from(width) * ar) as i32);
}
s.selection = Rect {
x: if dx > 0 { ax } else { ax - (width - 1) },
y: if dy > 0 { ay } else { ay - (height - 1) },
width,
height,
};
}
fn update_choice(&mut self, si: usize, which: Which) {
let s = self.sel(si, which);
let (x, y) = (s.x, s.y);
let mut best: Option<(Rect, Option<String>)> = None;
for b in &self.boxes {
if b.rect.contains(x, y) {
if let Some((cur, _)) = &best {
if cur.area() < b.rect.area() {
continue;
}
}
best = Some((b.rect, b.label.clone()));
}
}
let s = self.sel_mut(si, which);
match best {
Some((rect, label)) => {
s.selection = rect;
s.selected_label = label;
s.has_selection = true;
}
None => s.has_selection = false,
}
}
fn selection_start(&mut self, si: usize, which: Which) {
if self.opts.single_point {
let s = self.sel(si, which);
self.finalize(Rect::new(s.x, s.y, 1, 1), None);
} else if self.opts.restrict {
let s = self.sel(si, which);
if s.has_selection {
let (rect, label) = (s.selection, s.selected_label.clone());
self.finalize(rect, label);
}
} else {
let s = self.sel_mut(si, which);
s.anchor_x = s.x;
s.anchor_y = s.y;
}
}
fn selection_end(&mut self, si: usize, which: Which) {
if self.opts.single_point || self.opts.restrict {
return;
}
let s = self.sel(si, which);
if s.has_selection {
let (rect, label) = (s.selection, s.selected_label.clone());
self.finalize(rect, label);
} else {
self.finalize(Rect::new(s.x, s.y, 1, 1), None);
}
self.resizing = false;
}
fn selection_cancelled(&mut self, si: usize) {
self.seats[si].pointer_sel.has_selection = false;
self.seats[si].touch_sel.has_selection = false;
self.edit_anchor = false;
self.running = false;
}
fn finalize(&mut self, rect: Rect, label: Option<String>) {
if rect.width > 0 && rect.height > 0 {
let (output, output_geometry) = self.output_at(rect.x, rect.y);
self.result = Some(Selection {
rect,
label,
output,
output_geometry,
});
}
self.running = false;
}
fn recompute(&mut self, si: usize, qh: &QueueHandle<App>) {
let which = if self.seats[si].touch_sel.has_selection {
Which::Touch
} else {
Which::Pointer
};
if self.sel(si, which).has_selection {
self.active_motion(si, which);
self.mark_dirty(si, qh);
}
}
fn mark_dirty(&mut self, si: usize, qh: &QueueHandle<App>) {
let (psel, tsel, cross) = {
let s = &self.seats[si];
let psel = s
.pointer_sel
.has_selection
.then_some(s.pointer_sel.selection);
let tsel = s.touch_sel.has_selection.then_some(s.touch_sel.selection);
let cross = self.opts.crosshairs.then(|| (s.current().x, s.current().y));
(psel, tsel, cross)
};
for oi in 0..self.outputs.len() {
let lg = self.outputs[oi].logical;
let hit = psel.is_some_and(|r| lg.intersects(&r))
|| tsel.is_some_and(|r| lg.intersects(&r))
|| cross.is_some_and(|(cx, cy)| lg.contains(cx, cy));
if hit {
self.set_output_dirty(oi, qh);
}
}
}
fn set_output_dirty(&mut self, oi: usize, qh: &QueueHandle<App>) {
self.outputs[oi].dirty = true;
if self.outputs[oi].frame_pending {
return;
}
if let Some(surface) = self.outputs[oi].surface.clone() {
surface.frame(qh, OutputId(oi));
surface.commit();
self.outputs[oi].frame_pending = true;
}
}
fn send_frame(&mut self, oi: usize, qh: &QueueHandle<App>) {
if !self.outputs[oi].configured {
return;
}
let scale = self.outputs[oi].scale;
let bw = self.outputs[oi].width * scale;
let bh = self.outputs[oi].height * scale;
if bw <= 0 || bh <= 0 {
return;
}
let Some(bi) = self.acquire_buffer(oi, bw, bh, qh) else {
return;
};
let mut pixmap = match tiny_skia::Pixmap::new(bw as u32, bh as u32) {
Some(p) => p,
None => return,
};
{
let logical = self.outputs[oi].logical;
let mut selection_rect = None;
for s in &self.seats {
let cur = s.current();
if cur.has_selection {
selection_rect = Some(cur.selection);
break;
}
}
let mut crosshair = None;
if selection_rect.is_none() && self.opts.crosshairs {
for s in &self.seats {
let cur = s.current();
if logical.contains(cur.x, cur.y) {
crosshair = Some((cur.x, cur.y));
break;
}
}
}
let scene = Scene {
background: self.opts.background,
border: self.opts.border,
selection: self.opts.selection,
choice: self.opts.choice,
border_weight: self.opts.border_weight,
display_dimensions: self.opts.display_dimensions,
font: self.font.as_ref(),
logical,
scale: scale as f32,
choice_boxes: &self.box_rects,
selection_rect,
crosshair,
frozen: self.outputs[oi].frozen.as_ref(),
};
render(&mut pixmap.as_mut(), &scene);
}
let width = self.outputs[oi].width;
let height = self.outputs[oi].height;
let buffer = {
let buf = self.outputs[oi].buffers[bi]
.as_mut()
.expect("buffer exists");
rgba_to_argb8888(pixmap.data(), &mut buf.mmap);
buf.busy.store(true, Ordering::Relaxed);
buf.buffer.clone()
};
if let Some(surface) = self.outputs[oi].surface.clone() {
surface.attach(Some(&buffer), 0, 0);
surface.damage(0, 0, width, height);
surface.set_buffer_scale(scale);
surface.frame(qh, OutputId(oi));
self.outputs[oi].frame_pending = true;
surface.commit();
}
self.outputs[oi].dirty = false;
}
fn acquire_buffer(
&mut self,
oi: usize,
width: i32,
height: i32,
qh: &QueueHandle<App>,
) -> Option<usize> {
let index = {
let out = &self.outputs[oi];
let mut chosen = None;
for i in 0..2 {
if out.buffers[i]
.as_ref()
.is_none_or(|b| !b.busy.load(Ordering::Relaxed))
{
chosen = Some(i);
}
}
chosen?
};
let needs_new = match &self.outputs[oi].buffers[index] {
Some(b) => b.width != width || b.height != height,
None => true,
};
if needs_new {
self.outputs[oi].buffers[index] = None;
let buffer = create_buffer(&self.shm, qh, width, height)?;
self.outputs[oi].buffers[index] = Some(buffer);
}
Some(index)
}
fn set_crosshair_cursor(&self, si: usize, serial: u32, qh: &QueueHandle<App>) {
let Some(manager) = &self.cursor_shape_manager else {
return;
};
let Some(pointer) = &self.seats[si].pointer else {
return;
};
let device = manager.get_pointer(pointer, qh, ());
device.set_shape(serial, Shape::Crosshair);
device.destroy();
}
}
macro_rules! ignore {
($iface:ty, $udata:ty) => {
impl Dispatch<$iface, $udata> for App {
fn event(
_: &mut Self,
_: &$iface,
_: <$iface as Proxy>::Event,
_: &$udata,
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
};
}
ignore!(WlRegistry, GlobalListContents);
ignore!(WlCompositor, ());
ignore!(WlShm, ());
ignore!(WlShmPool, ());
ignore!(ZwlrLayerShellV1, ());
ignore!(ZxdgOutputManagerV1, ());
ignore!(WpCursorShapeManagerV1, ());
ignore!(WpCursorShapeDeviceV1, ());
ignore!(WlSurface, OutputId);
impl Dispatch<WlBuffer, BufferData> for App {
fn event(
_: &mut Self,
_: &WlBuffer,
event: <WlBuffer as Proxy>::Event,
data: &BufferData,
_: &Connection,
_: &QueueHandle<Self>,
) {
use wayland_client::protocol::wl_buffer::Event;
if let Event::Release = event {
data.0.store(false, Ordering::Relaxed);
}
}
}
impl Dispatch<WlCallback, OutputId> for App {
fn event(
state: &mut Self,
_: &WlCallback,
event: wl_callback::Event,
data: &OutputId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
if let wl_callback::Event::Done { .. } = event {
let oi = data.0;
state.outputs[oi].frame_pending = false;
if state.outputs[oi].dirty {
state.send_frame(oi, qh);
}
}
}
}
impl Dispatch<ZwlrLayerSurfaceV1, OutputId> for App {
fn event(
state: &mut Self,
surface: &ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
data: &OutputId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let oi = data.0;
match event {
zwlr_layer_surface_v1::Event::Configure {
serial,
width,
height,
} => {
state.outputs[oi].configured = true;
state.outputs[oi].width = width as i32;
state.outputs[oi].height = height as i32;
surface.ack_configure(serial);
state.send_frame(oi, qh);
}
zwlr_layer_surface_v1::Event::Closed => {
state.running = false;
}
_ => {}
}
}
}
impl Dispatch<WlOutput, OutputId> for App {
fn event(
state: &mut Self,
_: &WlOutput,
event: wl_output::Event,
data: &OutputId,
_: &Connection,
_: &QueueHandle<Self>,
) {
let out = &mut state.outputs[data.0];
match event {
wl_output::Event::Geometry { x, y, .. } => {
out.geometry.x = x;
out.geometry.y = y;
}
wl_output::Event::Mode {
flags,
width,
height,
..
} => {
if let WEnum::Value(flags) = flags {
if flags.contains(wl_output::Mode::Current) {
out.geometry.width = width;
out.geometry.height = height;
}
}
}
wl_output::Event::Scale { factor } => {
out.scale = factor;
}
wl_output::Event::Name { name } => {
if out.label.is_none() {
out.label = Some(name);
}
}
_ => {}
}
}
}
impl Dispatch<ZxdgOutputV1, OutputId> for App {
fn event(
state: &mut Self,
_: &ZxdgOutputV1,
event: zxdg_output_v1::Event,
data: &OutputId,
_: &Connection,
_: &QueueHandle<Self>,
) {
let out = &mut state.outputs[data.0];
match event {
zxdg_output_v1::Event::LogicalPosition { x, y } => {
out.logical.x = x;
out.logical.y = y;
}
zxdg_output_v1::Event::LogicalSize { width, height } => {
out.logical.width = width;
out.logical.height = height;
}
zxdg_output_v1::Event::Name { name } => {
out.label = Some(name);
}
_ => {}
}
}
}
impl Dispatch<WlSeat, SeatId> for App {
fn event(
state: &mut Self,
seat: &WlSeat,
event: wl_seat::Event,
data: &SeatId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let si = data.0;
if let wl_seat::Event::Capabilities {
capabilities: WEnum::Value(caps),
} = event
{
if caps.contains(Capability::Pointer) && state.seats[si].pointer.is_none() {
state.seats[si].pointer = Some(seat.get_pointer(qh, SeatId(si)));
}
if caps.contains(Capability::Keyboard) && state.seats[si].keyboard.is_none() {
state.seats[si].keyboard = Some(seat.get_keyboard(qh, SeatId(si)));
}
if caps.contains(Capability::Touch) && state.seats[si].touch.is_none() {
state.seats[si].touch = Some(seat.get_touch(qh, SeatId(si)));
}
}
}
}
impl Dispatch<WlPointer, SeatId> for App {
fn event(
state: &mut Self,
_: &WlPointer,
event: wl_pointer::Event,
data: &SeatId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let si = data.0;
match event {
wl_pointer::Event::Enter {
serial,
surface,
surface_x,
surface_y,
} => {
let Some(oi) = state.output_index_of_surface(&surface) else {
return;
};
if state.seats[si].pointer_sel.has_selection || state.opts.crosshairs {
state.mark_dirty(si, qh);
}
state.seats[si].pointer_sel.current_output = Some(oi);
state.move_seat(si, Which::Pointer, surface_x, surface_y);
if state.seats[si].button_pressed {
state.active_motion(si, Which::Pointer);
} else {
state.update_choice(si, Which::Pointer);
}
state.mark_dirty(si, qh);
state.set_crosshair_cursor(si, serial, qh);
}
wl_pointer::Event::Leave { .. } => {
state.seats[si].pointer_sel.current_output = None;
}
wl_pointer::Event::Motion {
surface_x,
surface_y,
..
} => {
if state.seats[si].pointer_sel.has_selection || state.opts.crosshairs {
state.mark_dirty(si, qh);
}
state.move_seat(si, Which::Pointer, surface_x, surface_y);
if state.seats[si].button_pressed {
state.active_motion(si, Which::Pointer);
} else {
state.update_choice(si, Which::Pointer);
}
if state.seats[si].pointer_sel.has_selection || state.opts.crosshairs {
state.mark_dirty(si, qh);
}
}
wl_pointer::Event::Button {
button,
state: WEnum::Value(button_state),
..
} => {
if state.seats[si].touch_sel.has_selection {
return;
}
state.seats[si].button_pressed = button_state == ButtonState::Pressed;
if button == BTN_LEFT {
match button_state {
ButtonState::Pressed => state.selection_start(si, Which::Pointer),
ButtonState::Released => state.selection_end(si, Which::Pointer),
_ => {}
}
} else {
state.selection_cancelled(si);
}
}
_ => {}
}
}
}
impl Dispatch<WlKeyboard, SeatId> for App {
fn event(
state: &mut Self,
_: &WlKeyboard,
event: wl_keyboard::Event,
data: &SeatId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let si = data.0;
match event {
wl_keyboard::Event::Keymap {
format: WEnum::Value(KeymapFormat::XkbV1),
fd,
size,
} => {
if let Some(keymap) = read_keymap(&state.xkb_context, fd, size) {
state.seats[si].xkb_state = Some(xkb::State::new(&keymap));
}
}
wl_keyboard::Event::Modifiers {
mods_depressed,
mods_latched,
mods_locked,
group,
..
} => {
if let Some(xkb_state) = &mut state.seats[si].xkb_state {
xkb_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
}
}
wl_keyboard::Event::Key {
key,
state: WEnum::Value(key_state),
..
} => {
let Some(xkb_state) = &state.seats[si].xkb_state else {
return;
};
let sym = xkb_state.key_get_one_sym(xkb::Keycode::new(key + 8));
state.handle_key(si, sym, key_state, qh);
}
_ => {}
}
}
}
impl App {
fn handle_key(
&mut self,
si: usize,
sym: xkb::Keysym,
key_state: KeyState,
qh: &QueueHandle<App>,
) {
use xkbcommon::xkb::keysyms;
let raw = sym.raw();
match key_state {
KeyState::Pressed => match raw {
keysyms::KEY_Escape => self.selection_cancelled(si),
keysyms::KEY_space => {
if self.seats[si].pointer_sel.has_selection
|| self.seats[si].touch_sel.has_selection
{
self.edit_anchor = true;
}
}
keysyms::KEY_Shift_L | keysyms::KEY_Shift_R => {
if !self.fixed_aspect {
self.aspect = Some(1.0);
if self.resizing {
self.recompute(si, qh);
}
}
}
_ => {}
},
KeyState::Released => match raw {
keysyms::KEY_space => self.edit_anchor = false,
keysyms::KEY_Shift_L | keysyms::KEY_Shift_R => {
if !self.fixed_aspect {
self.aspect = None;
if self.resizing {
self.recompute(si, qh);
}
}
}
_ => {}
},
_ => {}
}
}
}
fn read_keymap(context: &xkb::Context, fd: OwnedFd, size: u32) -> Option<xkb::Keymap> {
let file = File::from(fd);
let map = unsafe { memmap2::Mmap::map(&file).ok()? };
let len = (size as usize).min(map.len());
let text = std::str::from_utf8(&map[..len.saturating_sub(1)]).ok()?;
xkb::Keymap::new_from_string(
context,
text.to_string(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
}
impl Dispatch<WlTouch, SeatId> for App {
fn event(
state: &mut Self,
_: &WlTouch,
event: wl_touch::Event,
data: &SeatId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let si = data.0;
match event {
wl_touch::Event::Down {
id, surface, x, y, ..
} => {
if state.seats[si].pointer_sel.has_selection {
return;
}
if state.seats[si].touch_id.is_none() {
state.seats[si].touch_id = Some(id);
state.seats[si].touch_sel.current_output =
state.output_index_of_surface(&surface);
state.move_seat(si, Which::Touch, x, y);
state.selection_start(si, Which::Touch);
}
}
wl_touch::Event::Motion { id, x, y, .. } => {
if state.seats[si].touch_id == Some(id) {
state.move_seat(si, Which::Touch, x, y);
state.active_motion(si, Which::Touch);
state.mark_dirty(si, qh);
}
}
wl_touch::Event::Up { .. } => {
state.selection_end(si, Which::Touch);
state.seats[si].touch_id = None;
state.seats[si].touch_sel.current_output = None;
}
wl_touch::Event::Cancel => {
state.seats[si].touch_id = None;
state.seats[si].touch_sel.current_output = None;
}
_ => {}
}
}
}