use std::sync::Arc;
use std::time::Duration;
use smithay_client_toolkit::compositor::{CompositorHandler, CompositorState};
use smithay_client_toolkit::output::{OutputHandler, OutputState};
use smithay_client_toolkit::reexports::calloop::{EventLoop as CalloopLoop, LoopHandle};
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
use smithay_client_toolkit::seat::keyboard::{
KeyEvent as WlKeyEvent, KeyboardHandler, Keysym, Modifiers as WlModifiers,
};
use smithay_client_toolkit::seat::pointer::{
AxisScroll, PointerEvent, PointerEventKind, PointerHandler,
};
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
use smithay_client_toolkit::shell::WaylandSurface;
use smithay_client_toolkit::shell::xdg::popup::{Popup, PopupConfigure, PopupHandler};
use smithay_client_toolkit::shell::xdg::window::{
Window as XdgWindow, WindowConfigure, WindowDecorations, WindowHandler,
};
use smithay_client_toolkit::shell::xdg::{XdgShell, XdgSurface};
use smithay_client_toolkit::shm::slot::{Buffer, SlotPool};
use smithay_client_toolkit::shm::{Shm, ShmHandler};
use smithay_client_toolkit::{
delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry,
delegate_seat, delegate_shm, delegate_xdg_popup, delegate_xdg_shell, delegate_xdg_window,
registry_handlers,
};
use wayland_client::globals::registry_queue_init;
use wayland_client::protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface};
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::{
self, WpFractionalScaleV1,
};
use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;
use wayland_protocols::xdg::dialog::v1::client::xdg_wm_dialog_v1::XdgWmDialogV1;
use wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity, XdgPositioner};
use wayland_protocols::xdg::shell::client::xdg_surface::XdgSurface as XdgSurfaceObj;
use crate::app::App;
use crate::background::BackgroundState;
use crate::event::{
Event, EventCtx, Key, Modifiers, MouseButton, NamedKey, SCROLL_PIXELS_PER_LINE,
WHEEL_LINES_PER_DETENT,
};
use crate::font::Font;
use crate::geometry::{Point, Rect, Size};
use crate::painter::Painter;
use crate::theme::Theme;
use crate::widget::{PopupKind, PopupRequest, Widget};
pub(crate) fn run(app: App) {
let (window_cfg, theme, root) = app.into_parts();
let conn = Connection::connect_to_env().expect("saudade: Wayland connect failed");
let (globals, event_queue) =
registry_queue_init::<State>(&conn).expect("saudade: registry init failed");
let qh: QueueHandle<State> = event_queue.handle();
let mut event_loop: CalloopLoop<State> =
CalloopLoop::try_new().expect("saudade: calloop init failed");
let loop_handle = event_loop.handle();
WaylandSource::new(conn.clone(), event_queue)
.insert(loop_handle)
.expect("saudade: WaylandSource insert failed");
let compositor =
CompositorState::bind(&globals, &qh).expect("saudade: wl_compositor not available");
let xdg_shell = XdgShell::bind(&globals, &qh).expect("saudade: xdg_shell not available");
let shm = Shm::bind(&globals, &qh).expect("saudade: wl_shm not available");
let xdg_dialog_mgr: Option<XdgWmDialogV1> =
globals.bind::<XdgWmDialogV1, _, _>(&qh, 1..=1, ()).ok();
let fractional_mgr: Option<WpFractionalScaleManagerV1> = globals
.bind::<WpFractionalScaleManagerV1, _, _>(&qh, 1..=1, ())
.ok();
let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
window.set_title(&window_cfg.title);
window.set_app_id(format!("saudade.{}", sanitize(&window_cfg.title)));
let fractional_scale_obj = fractional_mgr
.as_ref()
.map(|mgr| mgr.get_fractional_scale(window.wl_surface(), &qh, ()));
let initial_w = window_cfg.size.w.max(1) as u32;
let initial_h = window_cfg.size.h.max(1) as u32;
if window_cfg.resizable {
window.set_min_size(Some((100, 60)));
} else {
window.set_min_size(Some((initial_w, initial_h)));
window.set_max_size(Some((initial_w, initial_h)));
}
window.commit();
let pool = SlotPool::new((initial_w * initial_h * 4) as usize * 4, &shm)
.expect("saudade: slot pool init failed");
let mut state = State {
registry_state: RegistryState::new(&globals),
seat_state: SeatState::new(&globals, &qh),
output_state: OutputState::new(&globals, &qh),
compositor,
shm,
xdg_shell,
xdg_dialog_mgr,
window,
root,
theme,
font: Font::load_system(),
mono_font: Font::load_monospace(),
pool,
surface_w: initial_w,
surface_h: initial_h,
scale: 1,
fractional_scale_obj,
fractional_scale: None,
resizable: window_cfg.resizable,
configured: false,
needs_redraw: true,
exit: false,
keyboard: None,
pointer: None,
modifiers: Modifiers::default(),
bg: BackgroundState::from_env(),
cursor: None,
popups: Vec::new(),
qh: qh.clone(),
loop_handle: event_loop.handle(),
};
drop(conn);
while !state.exit {
event_loop
.dispatch(Duration::from_millis(16), &mut state)
.expect("saudade: dispatch failed");
state.tick();
}
}
struct State {
registry_state: RegistryState,
seat_state: SeatState,
output_state: OutputState,
compositor: CompositorState,
shm: Shm,
xdg_shell: XdgShell,
xdg_dialog_mgr: Option<XdgWmDialogV1>,
window: XdgWindow,
root: Box<dyn Widget>,
theme: Theme,
font: Option<Font>,
mono_font: Option<Font>,
pool: SlotPool,
surface_w: u32,
surface_h: u32,
scale: i32,
#[allow(dead_code)]
fractional_scale_obj: Option<WpFractionalScaleV1>,
fractional_scale: Option<f32>,
resizable: bool,
configured: bool,
needs_redraw: bool,
exit: bool,
keyboard: Option<wl_keyboard::WlKeyboard>,
pointer: Option<wl_pointer::WlPointer>,
modifiers: Modifiers,
bg: BackgroundState,
cursor: Option<Point>,
popups: Vec<PopupState>,
qh: QueueHandle<State>,
loop_handle: LoopHandle<'static, State>,
}
enum ChildSurface {
Popup(Popup),
Dialog {
window: XdgWindow,
dialog_v1: Option<XdgDialogV1>,
},
}
impl ChildSurface {
fn wl_surface(&self) -> &wl_surface::WlSurface {
match self {
ChildSurface::Popup(p) => p.wl_surface(),
ChildSurface::Dialog { window, .. } => window.wl_surface(),
}
}
fn kind(&self) -> PopupKind {
match self {
ChildSurface::Popup(_) => PopupKind::Popup,
ChildSurface::Dialog { .. } => PopupKind::Dialog,
}
}
fn xdg_surface(&self) -> &XdgSurfaceObj {
match self {
ChildSurface::Popup(p) => p.xdg_surface(),
ChildSurface::Dialog { window, .. } => window.xdg_surface(),
}
}
}
impl Drop for ChildSurface {
fn drop(&mut self) {
if let ChildSurface::Dialog {
dialog_v1: Some(d), ..
} = self
{
d.destroy();
}
}
}
struct PopupState {
surface: ChildSurface,
pool: SlotPool,
anchor: Rect,
surface_w: u32,
surface_h: u32,
configured: bool,
needs_redraw: bool,
cursor: Option<Point>,
}
impl State {
fn tick(&mut self) {
self.sync_popup();
if self.root.wants_ticks() {
self.dispatch(Event::Tick);
}
if self.configured && self.needs_redraw {
self.draw_main();
self.needs_redraw = false;
}
for idx in 0..self.popups.len() {
let should_draw = self.popups[idx].configured && self.popups[idx].needs_redraw;
if should_draw && self.draw_popup(idx) {
self.popups[idx].needs_redraw = false;
}
}
}
fn mark_popups_dirty(&mut self) {
for p in &mut self.popups {
p.needs_redraw = true;
}
}
fn relayout(&mut self) {
self.root.layout(Rect::new(
0,
0,
self.surface_w.max(1) as i32,
self.surface_h.max(1) as i32,
));
}
fn dispatch(&mut self, event: Event) {
let mut ctx = EventCtx::new();
self.root.event(&event, &mut ctx);
if ctx.paint_requested {
self.needs_redraw = true;
self.mark_popups_dirty();
}
if ctx.close_requested {
self.exit = true;
}
if let Some(size) = ctx.resize_request {
self.apply_resize(size);
}
}
fn apply_resize(&mut self, size: Size) {
let w = size.w.max(1) as u32;
let h = size.h.max(1) as u32;
if w == self.surface_w && h == self.surface_h {
return;
}
if !self.resizable {
self.window.set_min_size(Some((w, h)));
self.window.set_max_size(Some((w, h)));
}
self.surface_w = w;
self.surface_h = h;
self.window.commit();
self.relayout();
self.needs_redraw = true;
self.mark_popups_dirty();
}
fn draw_main(&mut self) {
let scale = self.scale.max(1);
let buf_w = (self.surface_w.max(1) * scale as u32) as i32;
let buf_h = (self.surface_h.max(1) * scale as u32) as i32;
let stride = buf_w * 4;
let buffer = match self
.pool
.create_buffer(buf_w, buf_h, stride, wl_shm::Format::Argb8888)
{
Ok((b, _)) => b,
Err(_) => return,
};
let canvas = match self.pool.canvas(&buffer) {
Some(c) => c,
None => return,
};
let pixels = bytes_as_u32_mut(canvas);
let mut painter = Painter::with_popup_anchor(
pixels,
buf_w,
buf_h,
scale as f32,
0,
0,
self.font.as_ref(),
self.mono_font.as_ref(),
None,
);
painter.set_system_scale(self.fractional_scale.unwrap_or(scale as f32));
painter.fill_pattern(self.theme.background, self.bg.pattern, self.bg.color);
self.root.paint(&mut painter, &self.theme);
let surface = self.window.wl_surface();
let _ = buffer.attach_to(surface);
surface.damage_buffer(0, 0, buf_w, buf_h);
surface.set_buffer_scale(scale);
surface.frame(&self.qh, surface.clone());
surface.commit();
}
fn draw_popup(&mut self, idx: usize) -> bool {
let scale = self.scale.max(1);
let Some(p) = self.popups.get_mut(idx) else {
return false;
};
let buf_w = (p.surface_w.max(1) * scale as u32) as i32;
let buf_h = (p.surface_h.max(1) * scale as u32) as i32;
let stride = buf_w * 4;
let buffer = match p
.pool
.create_buffer(buf_w, buf_h, stride, wl_shm::Format::Argb8888)
{
Ok((b, _)) => b,
Err(_) => return false,
};
let canvas = match p.pool.canvas(&buffer) {
Some(c) => c,
None => return false,
};
let pixels = bytes_as_u32_mut(canvas);
let scale_f = scale as f32;
let anchor = p.anchor;
let origin_x = -((anchor.x as f32 * scale_f).round() as i32);
let origin_y = -((anchor.y as f32 * scale_f).round() as i32);
let clip_w = (anchor.w as f32 * scale_f).round() as i32;
let clip_h = (anchor.h as f32 * scale_f).round() as i32;
let mut painter = Painter::with_popup_anchor(
pixels,
buf_w,
buf_h,
scale_f,
origin_x,
origin_y,
self.font.as_ref(),
self.mono_font.as_ref(),
Some(anchor),
);
painter.set_system_scale(self.fractional_scale.unwrap_or(scale_f));
painter.fill(self.theme.background);
painter.set_clip_phys(0, 0, clip_w, clip_h);
self.root.paint(&mut painter, &self.theme);
painter.clear_clip();
let surface = p.surface.wl_surface();
let _ = buffer.attach_to(surface);
surface.damage_buffer(0, 0, buf_w, buf_h);
surface.set_buffer_scale(scale);
surface.frame(&self.qh, surface.clone());
surface.commit();
true
}
fn sync_popup(&mut self) {
let mut requests = Vec::new();
self.root.collect_popups(&mut requests);
let keep = self
.popups
.iter()
.zip(requests.iter())
.take_while(|(p, req)| p.anchor == req.rect && p.surface.kind() == req.kind)
.count();
self.popups.truncate(keep);
for req in requests.into_iter().skip(keep) {
let made = match self.popups.last() {
Some(parent) => {
let parent_anchor = parent.anchor;
let parent_xdg = parent.surface.xdg_surface();
self.create_popup(&req, parent_anchor, Some(parent_xdg))
}
None => self.create_popup(&req, Rect::new(0, 0, 0, 0), None),
};
match made {
Some(p) => self.popups.push(p),
None => break,
}
}
}
fn create_popup(
&self,
request: &PopupRequest,
parent_anchor: Rect,
parent_xdg: Option<&XdgSurfaceObj>,
) -> Option<PopupState> {
let anchor = request.rect;
let phys_w = anchor.w.max(1) as u32;
let phys_h = anchor.h.max(1) as u32;
let surface = match request.kind {
PopupKind::Popup => {
let rel_x = anchor.x - parent_anchor.x;
let rel_y = anchor.y - parent_anchor.y;
let positioner: XdgPositioner =
self.xdg_shell.xdg_wm_base().create_positioner(&self.qh, ());
positioner.set_size(anchor.w.max(1), anchor.h.max(1));
positioner.set_anchor_rect(rel_x, rel_y, 1, 1);
positioner.set_anchor(Anchor::BottomLeft);
positioner.set_gravity(Gravity::BottomRight);
let parent = parent_xdg.unwrap_or_else(|| self.window.xdg_surface());
let popup = match Popup::new(
parent,
&positioner,
&self.qh,
&self.compositor,
&self.xdg_shell,
) {
Ok(p) => p,
Err(_) => return None,
};
positioner.destroy();
ChildSurface::Popup(popup)
}
PopupKind::Dialog => {
let dialog_surface = self.compositor.create_surface(&self.qh);
let dialog = self.xdg_shell.create_window(
dialog_surface,
WindowDecorations::RequestServer,
&self.qh,
);
let title = request.title.as_deref().unwrap_or("Dialog");
dialog.set_title(title);
dialog.set_parent(Some(&self.window));
dialog.set_min_size(Some((phys_w, phys_h)));
dialog.set_max_size(Some((phys_w, phys_h)));
let dialog_v1 = self.xdg_dialog_mgr.as_ref().map(|mgr| {
let d = mgr.get_xdg_dialog(dialog.xdg_toplevel(), &self.qh, ());
d.set_modal();
d
});
dialog.commit();
ChildSurface::Dialog {
window: dialog,
dialog_v1,
}
}
};
let max_scale = self.scale.max(1) as u32;
let pool_bytes = (phys_w * phys_h * max_scale * max_scale * 4) as usize * 2;
let pool = match SlotPool::new(pool_bytes, &self.shm) {
Ok(p) => p,
Err(_) => return None,
};
Some(PopupState {
surface,
pool,
anchor,
surface_w: phys_w,
surface_h: phys_h,
configured: false,
needs_redraw: true,
cursor: None,
})
}
fn physical_to_logical(&self, surface_x: f64, surface_y: f64) -> Point {
let s = self.scale.max(1) as f64;
let _ = s;
Point::new(surface_x.floor() as i32, surface_y.floor() as i32)
}
}
impl CompositorHandler for State {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
new_factor: i32,
) {
let new = new_factor.max(1);
if new == self.scale {
return;
}
self.scale = new;
self.needs_redraw = true;
self.mark_popups_dirty();
self.relayout();
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_transform: wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
}
impl OutputHandler for State {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
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 WindowHandler for State {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, window: &XdgWindow) {
if window.xdg_toplevel() == self.window.xdg_toplevel() {
let mut ctx = EventCtx::new();
self.root.on_cancel(&mut ctx);
self.exit = true;
return;
}
if self.popups.iter().any(|p| {
matches!(&p.surface, ChildSurface::Dialog { window: dialog, .. }
if dialog.xdg_toplevel() == window.xdg_toplevel())
}) {
let mods = self.modifiers;
self.dispatch(Event::KeyDown {
key: Key::Named(NamedKey::Escape),
modifiers: mods,
});
}
}
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
window: &XdgWindow,
configure: WindowConfigure,
_serial: u32,
) {
if window.xdg_toplevel() == self.window.xdg_toplevel() {
let w = configure
.new_size
.0
.map(|v| v.get())
.unwrap_or(self.surface_w.max(1));
let h = configure
.new_size
.1
.map(|v| v.get())
.unwrap_or(self.surface_h.max(1));
self.surface_w = w;
self.surface_h = h;
let first_configure = !self.configured;
self.configured = true;
self.needs_redraw = true;
self.relayout();
if first_configure {
self.root.focus_first();
}
return;
}
if let Some(p) = self.popups.iter_mut().find(|p| {
matches!(&p.surface, ChildSurface::Dialog { window: dialog, .. }
if dialog.xdg_toplevel() == window.xdg_toplevel())
}) {
p.configured = true;
p.needs_redraw = true;
}
}
}
impl PopupHandler for State {
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
popup: &Popup,
configure: PopupConfigure,
) {
if let Some(p) = self.popups.iter_mut().find(|p| {
matches!(&p.surface, ChildSurface::Popup(existing)
if existing.xdg_popup() == popup.xdg_popup())
}) {
p.surface_w = configure.width.max(1) as u32;
p.surface_h = configure.height.max(1) as u32;
p.configured = true;
p.needs_redraw = true;
}
}
fn done(&mut self, _: &Connection, _: &QueueHandle<Self>, _popup: &Popup) {
let mods = self.modifiers;
self.dispatch(Event::KeyDown {
key: Key::Named(NamedKey::Escape),
modifiers: mods,
});
}
}
impl SeatHandler for State {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Keyboard && self.keyboard.is_none() {
self.keyboard = self
.seat_state
.get_keyboard_with_repeat(
qh,
&seat,
None,
self.loop_handle.clone(),
Box::new(|state: &mut State, _kbd, event| {
state.handle_key(event, true);
}),
)
.ok();
}
if capability == Capability::Pointer && self.pointer.is_none() {
self.pointer = self.seat_state.get_pointer(qh, &seat).ok();
}
}
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
&& 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>,
_keyboard: &wl_keyboard::WlKeyboard,
_surface: &wl_surface::WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[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,
event: WlKeyEvent,
) {
self.handle_key(event, true);
}
fn release_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
event: WlKeyEvent,
) {
self.handle_key(event, false);
}
fn update_modifiers(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
modifiers: WlModifiers,
_layout: u32,
) {
self.modifiers = Modifiers {
shift: modifiers.shift,
control: modifiers.ctrl,
alt: modifiers.alt,
alt_graph: false,
logo: modifiers.logo,
};
}
}
impl State {
fn handle_key(&mut self, event: WlKeyEvent, pressed: bool) {
let modifiers = self.modifiers;
let mapped = map_keysym(event.keysym);
if pressed {
if let Some(mapped) = mapped {
self.dispatch(Event::KeyDown {
key: mapped,
modifiers,
});
}
if !modifiers.has_command()
&& let Some(utf8) = event.utf8.as_deref()
{
for ch in utf8.chars() {
if (ch.is_control() && ch != '\t' && ch != '\n') || ch == '\r' {
continue;
}
self.dispatch(Event::Char { ch, modifiers });
}
}
} else if let Some(mapped) = mapped {
self.dispatch(Event::KeyUp {
key: mapped,
modifiers,
});
}
}
}
impl PointerHandler for State {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &wl_pointer::WlPointer,
events: &[PointerEvent],
) {
for event in events {
let popup_idx = self
.popups
.iter()
.position(|p| p.surface.wl_surface().id() == event.surface.id());
let pos = match popup_idx {
Some(i) => {
let anchor = self.popups[i].anchor;
Point::new(
event.position.0.floor() as i32 + anchor.x,
event.position.1.floor() as i32 + anchor.y,
)
}
None => self.physical_to_logical(event.position.0, event.position.1),
};
match event.kind {
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } => {
match popup_idx {
Some(i) => self.popups[i].cursor = Some(pos),
None => self.cursor = Some(pos),
}
self.dispatch(Event::PointerMove { pos });
self.mark_popups_dirty();
}
PointerEventKind::Leave { .. } => {
match popup_idx {
Some(i) => self.popups[i].cursor = None,
None => self.cursor = None,
}
self.dispatch(Event::PointerLeave);
}
PointerEventKind::Press { button, .. } => {
let Some(b) = map_button(button) else {
continue;
};
self.dispatch(Event::PointerDown { pos, button: b });
self.mark_popups_dirty();
}
PointerEventKind::Release { button, .. } => {
let Some(b) = map_button(button) else {
continue;
};
self.dispatch(Event::PointerUp { pos, button: b });
self.mark_popups_dirty();
}
PointerEventKind::Axis {
horizontal,
vertical,
..
} => {
let lines = |axis: AxisScroll| {
if axis.discrete != 0 {
axis.discrete as f32 * WHEEL_LINES_PER_DETENT
} else {
axis.absolute as f32 / SCROLL_PIXELS_PER_LINE
}
};
let delta_x = lines(horizontal);
let delta_y = lines(vertical);
if delta_x != 0.0 || delta_y != 0.0 {
self.dispatch(Event::Scroll {
pos,
delta_x,
delta_y,
});
self.mark_popups_dirty();
}
}
}
}
}
}
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_state
}
registry_handlers![OutputState, SeatState];
}
delegate_compositor!(State);
delegate_output!(State);
delegate_shm!(State);
delegate_seat!(State);
delegate_keyboard!(State);
delegate_pointer!(State);
delegate_xdg_shell!(State);
delegate_xdg_window!(State);
delegate_xdg_popup!(State);
delegate_registry!(State);
fn sanitize(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_lowercase()
} else {
'-'
}
})
.collect()
}
fn map_button(button: u32) -> Option<MouseButton> {
match button {
0x110 => Some(MouseButton::Left),
0x111 => Some(MouseButton::Right),
0x112 => Some(MouseButton::Middle),
_ => None,
}
}
fn map_keysym(keysym: Keysym) -> Option<Key> {
use Keysym as K;
let named = match keysym {
K::Return | K::KP_Enter => NamedKey::Enter,
K::BackSpace => NamedKey::Backspace,
K::Delete | K::KP_Delete => NamedKey::Delete,
K::Tab | K::ISO_Left_Tab => NamedKey::Tab,
K::Escape => NamedKey::Escape,
K::space => NamedKey::Space,
K::Left | K::KP_Left => NamedKey::Left,
K::Right | K::KP_Right => NamedKey::Right,
K::Up | K::KP_Up => NamedKey::Up,
K::Down | K::KP_Down => NamedKey::Down,
K::Home | K::KP_Home => NamedKey::Home,
K::End | K::KP_End => NamedKey::End,
K::Page_Up | K::KP_Page_Up => NamedKey::PageUp,
K::Page_Down | K::KP_Page_Down => NamedKey::PageDown,
_ => {
let ch = keysym.key_char()?;
return Some(Key::Char(ch));
}
};
Some(Key::Named(named))
}
fn bytes_as_u32_mut(bytes: &mut [u8]) -> &mut [u32] {
let len = bytes.len() / 4;
unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut u32, len) }
}
impl Dispatch<XdgPositioner, ()> for State {
fn event(
_state: &mut Self,
_proxy: &XdgPositioner,
_event: <XdgPositioner as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<XdgWmDialogV1, ()> for State {
fn event(
_state: &mut Self,
_proxy: &XdgWmDialogV1,
_event: <XdgWmDialogV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<XdgDialogV1, ()> for State {
fn event(
_state: &mut Self,
_proxy: &XdgDialogV1,
_event: <XdgDialogV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<WpFractionalScaleManagerV1, ()> for State {
fn event(
_state: &mut Self,
_proxy: &WpFractionalScaleManagerV1,
_event: <WpFractionalScaleManagerV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<WpFractionalScaleV1, ()> for State {
fn event(
state: &mut Self,
_proxy: &WpFractionalScaleV1,
event: wp_fractional_scale_v1::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event {
let f = scale as f32 / 120.0;
if state.fractional_scale != Some(f) {
state.fractional_scale = Some(f);
state.needs_redraw = true;
state.mark_popups_dirty();
}
}
}
}
#[allow(dead_code)]
fn _unused(_b: Buffer, _arc: Arc<()>) {}