use crate::ui::App;
use khronos_egl as egl;
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer,
delegate_registry, delegate_seat,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{
Capability, SeatHandler, SeatState,
keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
pointer::{PointerEvent, PointerEventKind, PointerHandler},
},
shell::{
WaylandSurface,
wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
LayerSurfaceConfigure,
},
},
};
use std::sync::Arc;
use std::time::Instant;
use wayland_client::{
Connection, Proxy, QueueHandle,
globals::registry_queue_init,
protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface},
};
type Egl = egl::Instance<egl::Dynamic<libloading::Library, egl::EGL1_4>>;
struct Gpu {
egl: Egl,
display: egl::Display,
surface: egl::Surface,
context: egl::Context,
_egl_window: wayland_egl::WlEglSurface,
painter: egui_glow::Painter,
}
struct State {
registry_state: RegistryState,
seat_state: SeatState,
output_state: OutputState,
layer: LayerSurface,
keyboard: Option<wl_keyboard::WlKeyboard>,
pointer: Option<wl_pointer::WlPointer>,
egui_ctx: egui::Context,
app: App,
gpu: Option<Gpu>,
width: u32,
height: u32,
scale: u32,
start: Instant,
events: Vec<egui::Event>,
modifiers: egui::Modifiers,
pointer_pos: egui::Pos2,
}
pub fn run(app: App) -> anyhow::Result<()> {
let conn = Connection::connect_to_env()?;
let (globals, mut event_queue) = registry_queue_init(&conn)?;
let qh = event_queue.handle();
let compositor =
CompositorState::bind(&globals, &qh).map_err(|e| anyhow::anyhow!("wl_compositor: {e}"))?;
let layer_shell =
LayerShell::bind(&globals, &qh).map_err(|e| anyhow::anyhow!("layer-shell absent: {e}"))?;
let surface = compositor.create_surface(&qh);
let layer = layer_shell.create_layer_surface(
&qh,
surface,
Layer::Overlay,
Some(crate::ui::APP_ID),
None,
);
layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
layer.set_exclusive_zone(-1); layer.commit();
let egui_ctx = egui::Context::default();
app.apply_theme(&egui_ctx);
let mut state = State {
registry_state: RegistryState::new(&globals),
seat_state: SeatState::new(&globals, &qh),
output_state: OutputState::new(&globals, &qh),
layer,
keyboard: None,
pointer: None,
egui_ctx,
app,
gpu: None,
width: 0,
height: 0,
scale: 1,
start: Instant::now(),
events: Vec::new(),
modifiers: egui::Modifiers::default(),
pointer_pos: egui::Pos2::ZERO,
};
while !state.app.closing() {
event_queue.blocking_dispatch(&mut state)?;
}
Ok(())
}
impl State {
fn ensure_gpu(&mut self, conn: &Connection) {
if self.gpu.is_some() || self.width == 0 {
return;
}
let (pw, ph) = (
(self.width * self.scale) as i32,
(self.height * self.scale) as i32,
);
let lib = unsafe { egl::DynamicInstance::<egl::EGL1_4>::load_required() }
.expect("libEGL introuvable");
let egl: Egl = lib;
let display_ptr = conn.backend().display_ptr() as *mut std::ffi::c_void;
let display = unsafe { egl.get_display(display_ptr).expect("eglGetDisplay") };
egl.initialize(display).expect("eglInitialize");
egl.bind_api(egl::OPENGL_ES_API).expect("eglBindAPI");
let attribs = [
egl::SURFACE_TYPE,
egl::WINDOW_BIT,
egl::RENDERABLE_TYPE,
egl::OPENGL_ES2_BIT,
egl::RED_SIZE,
8,
egl::GREEN_SIZE,
8,
egl::BLUE_SIZE,
8,
egl::ALPHA_SIZE,
8,
egl::NONE,
];
let config = egl
.choose_first_config(display, &attribs)
.expect("eglChooseConfig")
.expect("aucune config EGL avec alpha");
let ctx_attribs = [egl::CONTEXT_CLIENT_VERSION, 3, egl::NONE];
let context = egl
.create_context(display, config, None, &ctx_attribs)
.or_else(|_| {
let a = [egl::CONTEXT_CLIENT_VERSION, 2, egl::NONE];
egl.create_context(display, config, None, &a)
})
.expect("eglCreateContext");
let egl_window =
wayland_egl::WlEglSurface::new(self.layer.wl_surface().id(), pw, ph).expect("wl_egl");
let surface = unsafe {
egl.create_window_surface(
display,
config,
egl_window.ptr() as egl::NativeWindowType,
None,
)
.expect("eglCreateWindowSurface")
};
egl.make_current(display, Some(surface), Some(surface), Some(context))
.expect("eglMakeCurrent");
let gl = unsafe {
glow::Context::from_loader_function(|s| {
egl.get_proc_address(s)
.map_or(std::ptr::null(), |p| p as *const _)
})
};
let painter = egui_glow::Painter::new(Arc::new(gl), "", None, false).expect("egui_glow");
self.gpu = Some(Gpu {
egl,
display,
surface,
context,
_egl_window: egl_window,
painter,
});
}
fn render(&mut self) {
let Some(gpu) = self.gpu.as_mut() else {
return;
};
let ppp = self.scale as f32;
let (pw, ph) = (self.width * self.scale, self.height * self.scale);
gpu.egl
.make_current(
gpu.display,
Some(gpu.surface),
Some(gpu.surface),
Some(gpu.context),
)
.ok();
let raw_input = egui::RawInput {
screen_rect: Some(egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(self.width as f32, self.height as f32),
)),
time: Some(self.start.elapsed().as_secs_f64()),
modifiers: self.modifiers,
events: std::mem::take(&mut self.events),
focused: true,
..Default::default()
};
let full = self.egui_ctx.run(raw_input, |ctx| self.app.run_ui(ctx));
let prims = self.egui_ctx.tessellate(full.shapes, ppp);
unsafe {
use glow::HasContext as _;
let gl = gpu.painter.gl();
gl.viewport(0, 0, pw as i32, ph as i32);
let [r, g, b, a] = self.app.backdrop();
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
gpu.painter
.paint_and_update_textures([pw, ph], ppp, &prims, &full.textures_delta);
gpu.egl.swap_buffers(gpu.display, gpu.surface).ok();
}
fn draw_frame(&mut self, conn: &Connection, qh: &QueueHandle<Self>) {
self.ensure_gpu(conn);
let surface = self.layer.wl_surface().clone();
surface.frame(qh, surface.clone());
self.render();
self.layer.commit();
}
}
impl CompositorHandler for State {
fn scale_factor_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
new_factor: i32,
) {
self.scale = new_factor.max(1) as u32;
self.layer.wl_surface().set_buffer_scale(new_factor.max(1));
if let (Some(gpu), true) = (self.gpu.as_ref(), self.width > 0) {
gpu._egl_window.resize(
(self.width * self.scale) as i32,
(self.height * self.scale) as i32,
0,
0,
);
}
}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: wl_output::Transform,
) {
}
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: u32,
) {
self.draw_frame(conn, qh);
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: &wl_output::WlOutput,
) {
}
}
impl LayerShellHandler for State {
fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
self.app.cancel();
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_: &LayerSurface,
configure: LayerSurfaceConfigure,
_: u32,
) {
let (w, h) = configure.new_size;
if w > 0 && h > 0 {
self.width = w;
self.height = h;
}
if self.width == 0 {
return;
}
if let Some(gpu) = self.gpu.as_ref() {
gpu._egl_window.resize(
(self.width * self.scale) as i32,
(self.height * self.scale) as i32,
0,
0,
);
}
self.draw_frame(conn, qh);
}
}
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,
_: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
cap: Capability,
) {
if cap == Capability::Keyboard && self.keyboard.is_none() {
self.keyboard = self.seat_state.get_keyboard(qh, &seat, None).ok();
}
if cap == 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,
) {
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
}
impl KeyboardHandler for State {
fn enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: &wl_surface::WlSurface,
_: u32,
_: &[u32],
_: &[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: KeyEvent,
) {
self.key(event, true);
}
fn release_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
event: KeyEvent,
) {
self.key(event, false);
}
fn repeat_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
event: KeyEvent,
) {
self.key(event, true);
}
fn update_modifiers(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_keyboard::WlKeyboard,
_: u32,
modifiers: Modifiers,
_: RawModifiers,
_: u32,
) {
self.modifiers = egui::Modifiers {
alt: modifiers.alt,
ctrl: modifiers.ctrl,
shift: modifiers.shift,
mac_cmd: false,
command: modifiers.ctrl,
};
}
}
impl State {
fn key(&mut self, event: KeyEvent, pressed: bool) {
if let Some(key) = map_key(event.keysym) {
self.events.push(egui::Event::Key {
key,
physical_key: None,
pressed,
repeat: false,
modifiers: self.modifiers,
});
}
if pressed && !self.modifiers.ctrl && !self.modifiers.alt {
if let Some(txt) = event.utf8 {
if !txt.chars().any(|c| c.is_control()) && !txt.is_empty() {
self.events.push(egui::Event::Text(txt));
}
}
}
}
}
impl PointerHandler for State {
fn pointer_frame(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_pointer::WlPointer,
events: &[PointerEvent],
) {
for e in events {
let pos = egui::pos2(e.position.0 as f32, e.position.1 as f32);
match e.kind {
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } => {
self.pointer_pos = pos;
self.events.push(egui::Event::PointerMoved(pos));
}
PointerEventKind::Leave { .. } => {
self.events.push(egui::Event::PointerGone);
}
PointerEventKind::Press { button, .. }
| PointerEventKind::Release { button, .. } => {
let pressed = matches!(e.kind, PointerEventKind::Press { .. });
let btn = match button {
0x110 => egui::PointerButton::Primary,
0x111 => egui::PointerButton::Secondary,
0x112 => egui::PointerButton::Middle,
_ => continue,
};
self.events.push(egui::Event::PointerButton {
pos: self.pointer_pos,
button: btn,
pressed,
modifiers: self.modifiers,
});
}
PointerEventKind::Axis {
vertical,
horizontal,
..
} => {
let delta = egui::vec2(-horizontal.absolute as f32, -vertical.absolute as f32);
self.events.push(egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta,
modifiers: self.modifiers,
});
}
}
}
}
}
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 ProvidesRegistryState for State {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState];
}
fn map_key(k: Keysym) -> Option<egui::Key> {
use egui::Key;
Some(match k {
Keysym::Escape => Key::Escape,
Keysym::Return | Keysym::KP_Enter => Key::Enter,
Keysym::Tab => Key::Tab,
Keysym::BackSpace => Key::Backspace,
Keysym::Delete => Key::Delete,
Keysym::Left => Key::ArrowLeft,
Keysym::Right => Key::ArrowRight,
Keysym::Up => Key::ArrowUp,
Keysym::Down => Key::ArrowDown,
Keysym::Home => Key::Home,
Keysym::End => Key::End,
Keysym::space => Key::Space,
_ => return None,
})
}
delegate_compositor!(State);
delegate_output!(State);
delegate_seat!(State);
delegate_keyboard!(State);
delegate_pointer!(State);
delegate_layer!(State);
delegate_registry!(State);