use crate::ui::{App, DmabufImporter};
use crate::wl;
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::collections::HashMap;
use std::ffi::c_void;
use std::os::fd::AsRawFd;
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>>;
type EglImage = *mut c_void;
const EGL_LINUX_DMA_BUF_EXT: u32 = 0x3270;
const EGL_WIDTH: i32 = 0x3057;
const EGL_HEIGHT: i32 = 0x3056;
const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3271;
const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3272;
const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273;
const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3274;
const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443;
const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444;
const EGL_ATTRIB_NONE: i32 = 0x3038;
const GL_TEXTURE_2D: u32 = 0x0DE1;
const GL_TEXTURE_SWIZZLE_A: u32 = 0x8E45;
const GL_ONE: i32 = 1;
type EglCreateImageKhr =
unsafe extern "system" fn(*mut c_void, *mut c_void, u32, *mut c_void, *const i32) -> EglImage;
type EglDestroyImageKhr = unsafe extern "system" fn(*mut c_void, EglImage) -> u32;
type GlEglImageTargetTexture2dOes = unsafe extern "system" fn(u32, EglImage);
#[derive(Clone, Copy)]
struct DmabufEgl {
display: *mut c_void,
create_image: EglCreateImageKhr,
destroy_image: EglDestroyImageKhr,
image_target: GlEglImageTargetTexture2dOes,
}
fn load_dmabuf_egl(egl: &Egl, display: egl::Display) -> Option<DmabufEgl> {
let create = egl.get_proc_address("eglCreateImageKHR")?;
let destroy = egl.get_proc_address("eglDestroyImageKHR")?;
let target = egl.get_proc_address("glEGLImageTargetTexture2DOES")?;
Some(unsafe {
DmabufEgl {
display: display.as_ptr(),
create_image: std::mem::transmute::<extern "system" fn(), EglCreateImageKhr>(create),
destroy_image: std::mem::transmute::<extern "system" fn(), EglDestroyImageKhr>(destroy),
image_target: std::mem::transmute::<extern "system" fn(), GlEglImageTargetTexture2dOes>(
target,
),
}
})
}
struct NativeTex {
image: EglImage,
tex: glow::Texture,
id: egui::TextureId,
size: egui::Vec2,
}
struct HostImporter<'a> {
egl: Option<DmabufEgl>,
gl: Arc<glow::Context>,
painter: &'a mut egui_glow::Painter,
cache: &'a mut HashMap<String, NativeTex>,
}
impl DmabufImporter for HostImporter<'_> {
fn import(
&mut self,
key: &str,
frame: wl::DmabufFrame,
) -> Option<(egui::TextureId, egui::Vec2)> {
use glow::HasContext as _;
let egl = self.egl?;
let size = egui::vec2(frame.width as f32, frame.height as f32);
let attribs: [i32; 17] = [
EGL_WIDTH,
frame.width as i32,
EGL_HEIGHT,
frame.height as i32,
EGL_LINUX_DRM_FOURCC_EXT,
frame.fourcc as i32,
EGL_DMA_BUF_PLANE0_FD_EXT,
frame.fd.as_raw_fd(),
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
frame.offset as i32,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
frame.stride as i32,
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
(frame.modifier & 0xffff_ffff) as i32,
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
(frame.modifier >> 32) as i32,
EGL_ATTRIB_NONE,
];
let image = unsafe {
(egl.create_image)(
egl.display,
std::ptr::null_mut(),
EGL_LINUX_DMA_BUF_EXT,
std::ptr::null_mut(),
attribs.as_ptr(),
)
};
if image.is_null() {
return None;
}
let ckey = key.to_string();
if let Some(nt) = self.cache.get_mut(&ckey) {
unsafe {
self.gl.bind_texture(GL_TEXTURE_2D, Some(nt.tex));
(egl.image_target)(GL_TEXTURE_2D, image);
self.gl.bind_texture(GL_TEXTURE_2D, None);
(egl.destroy_image)(egl.display, nt.image);
}
nt.image = image;
nt.size = size;
return Some((nt.id, nt.size));
}
let tex = unsafe {
let t = self.gl.create_texture().ok()?;
self.gl.bind_texture(GL_TEXTURE_2D, Some(t));
let lin = glow::LINEAR as i32;
let clamp = glow::CLAMP_TO_EDGE as i32;
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_MIN_FILTER, lin);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_MAG_FILTER, lin);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_WRAP_S, clamp);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, glow::TEXTURE_WRAP_T, clamp);
self.gl
.tex_parameter_i32(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
(egl.image_target)(GL_TEXTURE_2D, image);
self.gl.bind_texture(GL_TEXTURE_2D, None);
t
};
let id = self.painter.register_native_texture(tex);
self.cache.insert(
ckey,
NativeTex {
image,
tex,
id,
size,
},
);
Some((id, size))
}
fn forget(&mut self, key: &str) {
use glow::HasContext as _;
let Some(egl) = self.egl else { return };
if let Some(nt) = self.cache.remove(key) {
self.painter.free_texture(nt.id);
unsafe {
self.gl.delete_texture(nt.tex);
(egl.destroy_image)(egl.display, nt.image);
}
}
}
}
struct Gpu {
egl: Egl,
display: egl::Display,
surface: egl::Surface,
context: egl::Context,
_egl_window: wayland_egl::WlEglSurface,
painter: egui_glow::Painter,
dmabuf_egl: Option<DmabufEgl>,
}
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>,
dmabuf_tex: HashMap<String, NativeTex>,
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,
dmabuf_tex: HashMap::new(),
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");
let dmabuf_egl = load_dmabuf_egl(&egl, display);
if dmabuf_egl.is_none() {
eprintln!("wlr-chooser: import dma-buf EGL indisponible (affichage GPU désactivé)");
}
self.gpu = Some(Gpu {
egl,
display,
surface,
context,
_egl_window: egl_window,
painter,
dmabuf_egl,
});
}
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 (prims, textures_delta) = {
let gl = gpu.painter.gl().clone();
let mut importer = HostImporter {
egl: gpu.dmabuf_egl,
gl,
painter: &mut gpu.painter,
cache: &mut self.dmabuf_tex,
};
let app = &mut self.app;
let full = self
.egui_ctx
.run(raw_input, |ctx| app.run_ui(ctx, &mut importer));
(
self.egui_ctx.tessellate(full.shapes, ppp),
full.textures_delta,
)
};
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, &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);