pub mod keys;
use std::collections::{HashMap, HashSet};
use std::time::Duration;
use smithay_client_toolkit::activation::RequestData;
use smithay_client_toolkit::reexports::calloop::EventLoop;
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::{
activation::{ActivationHandler, ActivationState},
compositor::{CompositorHandler, CompositorState},
delegate_activation, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer,
delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{
Capability, SeatHandler, SeatState,
keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
pointer::PointerHandler,
},
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
shm::{
Shm, ShmHandler,
slot::{Buffer, SlotPool},
},
};
use wayland_client::{
Connection, QueueHandle,
globals::registry_queue_init,
protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
};
pub use smithay_client_toolkit::seat::{
keyboard,
pointer::{PointerEvent, PointerEventKind},
};
pub trait WindowAble {
fn update(&mut self, context: Context) -> Option<WLibRequest>;
fn draw(&mut self, pixel_buffer: &mut [u8], frame_info: WindowSize);
}
#[derive(Debug, Clone)]
pub enum Event {
KeyPress(KeyEvent),
KeyRelease(KeyEvent),
PointerEvent(PointerEvent),
CloseRequested,
}
pub enum WLibRequest {
CloseAccepted,
}
#[derive(Debug, Clone)]
pub struct Context {
pub pressed_keys: HashMap<keys::RawKeyCode, keys::KeySym>,
pub mouse_state: MouseState,
pub delta_time: std::time::Duration,
pub close_requested: bool,
pub is_window_focused: bool,
pub event_queue: Vec<Event>,
pub window_size: WindowSize,
}
#[derive(Debug, Clone)]
pub struct MouseState {
pub position: (f64, f64),
pub mouse_buttons_pressed: HashSet<MouseButton>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum MouseButton {
BtnLeft,
BtnRight,
BtnMiddle,
BtnSide,
BtnExtra,
BtnForward,
BtnBack,
}
impl TryFrom<u32> for MouseButton {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0x110 => Ok(MouseButton::BtnLeft),
0x111 => Ok(MouseButton::BtnRight),
0x112 => Ok(MouseButton::BtnMiddle),
0x113 => Ok(MouseButton::BtnSide),
0x114 => Ok(MouseButton::BtnExtra),
0x115 => Ok(MouseButton::BtnForward),
0x116 => Ok(MouseButton::BtnBack),
_ => Err(()),
}
}
}
struct WindowManager {
registry_state: RegistryState,
seat_state: SeatState,
output_state: OutputState,
shm: Shm,
xdg_activation: Option<ActivationState>,
close_accepted: bool,
first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
buffer: Option<Buffer>,
window: Window,
keyboard: Option<wl_keyboard::WlKeyboard>,
keyboard_focus: bool,
pointer: Option<wl_pointer::WlPointer>,
last_frame_time: Option<std::time::Instant>,
managed_window: Box<dyn WindowAble>,
settings: WLibSettings,
context: Context,
}
#[derive(Debug, Clone)]
pub struct WindowSize {
pub width: u32,
pub height: u32,
}
#[derive(Default)]
pub struct WLibSettings {
window_static_size: Option<WindowSize>,
window_title: String,
app_id: String,
}
impl WLibSettings {
pub fn new() -> Self {
Self::default()
}
pub fn with_static_size(mut self, size: WindowSize) -> Self {
self.window_static_size = Some(size);
self
}
pub fn with_title(mut self, title: &str) -> Self {
self.window_title = title.to_string();
self
}
pub fn with_app_id(mut self, id: &str) -> Self {
self.app_id = id.to_string();
self
}
}
pub fn run(state: Box<dyn WindowAble>, settings: WLibSettings) {
let conn = Connection::connect_to_env().unwrap();
let (globals, event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle();
let mut event_loop: EventLoop<WindowManager> =
EventLoop::try_new().expect("Failed to initialize the event loop!");
let loop_handle = event_loop.handle();
WaylandSource::new(conn.clone(), event_queue)
.insert(loop_handle)
.unwrap();
let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available");
let shm = Shm::bind(&globals, &qh).expect("wl shm is not available.");
let xdg_activation = ActivationState::bind(&globals, &qh).ok();
let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
window.set_title(&settings.window_title);
window.set_app_id(&settings.app_id);
window.commit();
if let Some(activation) = xdg_activation.as_ref() {
activation.request_token(
&qh,
RequestData {
seat_and_serial: None,
surface: Some(window.wl_surface().clone()),
app_id: Some(String::from(
"io.github.smithay.client-toolkit.SimpleWindow",
)),
},
)
}
let (width, height) = if let Some(ref dimensions) = settings.window_static_size {
window.set_min_size(Some((dimensions.width, dimensions.height)));
window.set_max_size(Some((dimensions.width, dimensions.height)));
(dimensions.width, dimensions.height)
} else {
(200, 200)
};
let pool = SlotPool::new((width * height * 4) as usize, &shm).expect("Failed to create pool");
let mut window_manager = WindowManager {
registry_state: RegistryState::new(&globals),
seat_state: SeatState::new(&globals, &qh),
output_state: OutputState::new(&globals, &qh),
shm,
xdg_activation,
close_accepted: false,
first_configure: true,
pool,
width,
height,
buffer: None,
window,
keyboard: None,
keyboard_focus: false,
pointer: None,
last_frame_time: None,
managed_window: state,
context: Context {
delta_time: std::time::Duration::from_millis(0),
pressed_keys: HashMap::new(),
close_requested: false,
event_queue: Vec::new(),
is_window_focused: true,
mouse_state: MouseState {
position: (0.0, 0.0),
mouse_buttons_pressed: HashSet::new(),
},
window_size: WindowSize {
height: 0,
width: 0,
},
},
settings,
};
loop {
event_loop
.dispatch(Duration::ZERO, &mut window_manager)
.unwrap();
if window_manager.close_accepted {
break;
}
}
}
impl CompositorHandler for WindowManager {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_factor: i32,
) {
}
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,
) {
let now = std::time::Instant::now();
let delta = self
.last_frame_time
.map(|last| now - last)
.unwrap_or(Duration::ZERO);
self.last_frame_time = Some(now);
self.context.delta_time = delta;
let request = self.managed_window.update(self.context.clone());
self.handle_update(request);
self.draw(conn, qh);
self.context.event_queue.clear();
}
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 WindowManager {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
}
impl WindowHandler for WindowManager {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.context.close_requested = true;
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
self.buffer = None;
if self.settings.window_static_size.is_none() {
self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);
}
self.context.window_size = WindowSize {
width: self.width,
height: self.height,
};
if self.first_configure {
self.first_configure = false;
self.draw(conn, qh);
}
}
}
impl ActivationHandler for WindowManager {
type RequestData = RequestData;
fn new_token(&mut self, token: String, _data: &Self::RequestData) {
self.xdg_activation
.as_ref()
.unwrap()
.activate::<WindowManager>(self.window.wl_surface(), token);
}
}
impl SeatHandler for WindowManager {
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() {
let keyboard = self
.seat_state
.get_keyboard(qh, &seat, None)
.expect("Failed to create keyboard");
self.keyboard = Some(keyboard);
}
if capability == Capability::Pointer && self.pointer.is_none() {
let pointer = self
.seat_state
.get_pointer(qh, &seat)
.expect("Failed to create pointer");
self.pointer = Some(pointer);
}
}
fn remove_capability(
&mut self,
_conn: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Keyboard && self.keyboard.is_some() {
self.keyboard.take().unwrap().release();
}
if capability == Capability::Pointer && self.pointer.is_some() {
self.pointer.take().unwrap().release();
}
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
}
impl KeyboardHandler for WindowManager {
fn enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
surface: &wl_surface::WlSurface,
_: u32,
raw: &[u32],
keysyms: &[Keysym],
) {
if self.window.wl_surface() == surface {
self.keyboard_focus = true;
for (rawk, sym) in raw.iter().zip(keysyms.iter()) {
self.context.pressed_keys.insert(*rawk, *sym);
}
}
}
fn leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
surface: &wl_surface::WlSurface,
_: u32,
) {
if self.window.wl_surface() == surface {
self.keyboard_focus = false;
self.context.pressed_keys.clear();
}
}
fn press_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
event: KeyEvent,
) {
self.context
.event_queue
.push(Event::KeyPress(event.clone()));
self.context
.pressed_keys
.insert(event.raw_code, event.keysym);
}
fn release_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
event: KeyEvent,
) {
self.context
.event_queue
.push(Event::KeyRelease(event.clone()));
self.context.pressed_keys.remove(&event.raw_code);
}
fn repeat_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
_event: KeyEvent,
) {
}
fn update_modifiers(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_serial: u32,
_modifiers: Modifiers,
_raw_modifiers: RawModifiers,
_layout: u32,
) {
}
}
impl PointerHandler for WindowManager {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &wl_pointer::WlPointer,
events: &[PointerEvent],
) {
for event in events {
if &event.surface != self.window.wl_surface() {
continue;
}
use PointerEventKind as PEK;
match event.kind {
PEK::Press {
time: _,
button: b,
serial: _,
} => {
if let Ok(bttn) = MouseButton::try_from(b) {
self.context.mouse_state.mouse_buttons_pressed.insert(bttn);
}
}
PEK::Release { button: b, .. } => {
if let Ok(bttn) = MouseButton::try_from(b) {
self.context.mouse_state.mouse_buttons_pressed.remove(&bttn);
}
}
PEK::Enter { .. } => {
self.context.is_window_focused = true;
}
PEK::Leave { .. } => {
self.context.is_window_focused = false;
}
_ => {}
}
self.context.mouse_state.position = event.position;
self.context
.event_queue
.push(Event::PointerEvent(event.clone()));
}
}
}
impl ShmHandler for WindowManager {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl WindowManager {
pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
let width = self.width;
let height = self.height;
let stride = self.width as i32 * 4;
let buffer = self.buffer.get_or_insert_with(|| {
self.pool
.create_buffer(
width as i32,
height as i32,
stride,
wl_shm::Format::Argb8888,
)
.expect("create buffer")
.0
});
let canvas = match self.pool.canvas(buffer) {
Some(canvas) => canvas,
None => {
let (second_buffer, canvas) = self
.pool
.create_buffer(
self.width as i32,
self.height as i32,
stride,
wl_shm::Format::Argb8888,
)
.expect("create buffer");
*buffer = second_buffer;
canvas
}
};
self.managed_window.draw(
canvas,
WindowSize {
width: self.width,
height: self.height,
},
);
self.window
.wl_surface()
.damage_buffer(0, 0, self.width as i32, self.height as i32);
self.window
.wl_surface()
.frame(qh, self.window.wl_surface().clone());
buffer
.attach_to(self.window.wl_surface())
.expect("buffer attach");
self.window.commit();
}
fn handle_update(&mut self, request: Option<WLibRequest>) {
match request {
Some(WLibRequest::CloseAccepted) => self.close_accepted = true,
None => {}
}
}
}
delegate_compositor!(WindowManager);
delegate_output!(WindowManager);
delegate_shm!(WindowManager);
delegate_seat!(WindowManager);
delegate_keyboard!(WindowManager);
delegate_pointer!(WindowManager);
delegate_xdg_shell!(WindowManager);
delegate_xdg_window!(WindowManager);
delegate_activation!(WindowManager);
delegate_registry!(WindowManager);
impl ProvidesRegistryState for WindowManager {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState,];
}