use libloading::{Library, Symbol};
use std::os::raw::{c_int, c_short, c_uchar, c_ushort};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::OnceLock;
#[repr(C)]
pub struct GpmEventRaw {
pub buttons: c_uchar,
pub modifiers: c_uchar,
pub vc: c_ushort,
pub dx: c_short,
pub dy: c_short,
pub x: c_short,
pub y: c_short,
pub event_type: c_int, pub clicks: c_int,
pub margin: c_int, pub wdx: c_short,
pub wdy: c_short,
}
#[repr(C)]
pub struct GpmConnect {
pub event_mask: c_ushort,
pub default_mask: c_ushort,
pub min_mod: c_ushort,
pub max_mod: c_ushort,
pub pid: c_int,
pub vc: c_int,
}
pub const GPM_MOVE: c_ushort = 1;
pub const GPM_DRAG: c_ushort = 2;
pub const GPM_DOWN: c_ushort = 4;
pub const GPM_UP: c_ushort = 8;
pub const GPM_SINGLE: c_ushort = 16;
pub const GPM_DOUBLE: c_ushort = 32;
pub const GPM_TRIPLE: c_ushort = 64;
type GpmOpenFn = unsafe extern "C" fn(*mut GpmConnect, c_int) -> c_int;
type GpmCloseFn = unsafe extern "C" fn() -> c_int;
type GpmGetEventFn = unsafe extern "C" fn(*mut GpmEventRaw) -> c_int;
pub struct GpmLib {
_library: Library,
gpm_open: GpmOpenFn,
gpm_close: GpmCloseFn,
gpm_get_event: GpmGetEventFn,
gpm_visiblepointer: AtomicPtr<c_int>,
}
unsafe impl Send for GpmLib {}
unsafe impl Sync for GpmLib {}
static GPM_LIB: OnceLock<Option<GpmLib>> = OnceLock::new();
const LIBGPM_PATHS: &[&str] = &[
"libgpm.so.2",
"libgpm.so",
"/usr/lib/libgpm.so.2",
"/usr/lib/libgpm.so",
"/usr/lib64/libgpm.so.2",
"/usr/lib64/libgpm.so",
"/usr/lib/x86_64-linux-gnu/libgpm.so.2",
"/usr/lib/x86_64-linux-gnu/libgpm.so",
"/lib/libgpm.so.2",
"/lib/libgpm.so",
"/lib64/libgpm.so.2",
"/lib64/libgpm.so",
];
impl GpmLib {
fn try_load() -> Option<Self> {
tracing::debug!("GPM FFI: Attempting to load libgpm...");
for path in LIBGPM_PATHS {
tracing::trace!("GPM FFI: Trying path: {}", path);
match Self::load_from_path(path) {
Ok(lib) => {
tracing::debug!("GPM FFI: Loaded libgpm from: {}", path);
return Some(lib);
}
Err(e) => {
tracing::trace!("GPM FFI: Failed to load {}: {}", path, e);
}
}
}
tracing::debug!("GPM FFI: libgpm not found in any standard location");
None
}
fn load_from_path(path: &str) -> Result<Self, libloading::Error> {
unsafe {
let library = Library::new(path)?;
let gpm_open: Symbol<GpmOpenFn> = library.get(b"Gpm_Open")?;
let gpm_close: Symbol<GpmCloseFn> = library.get(b"Gpm_Close")?;
let gpm_get_event: Symbol<GpmGetEventFn> = library.get(b"Gpm_GetEvent")?;
let gpm_visiblepointer: *mut c_int = library
.get::<*mut c_int>(b"gpm_visiblepointer")
.ok()
.map(|s| *s)
.unwrap_or(std::ptr::null_mut());
Ok(Self {
gpm_open: *gpm_open,
gpm_close: *gpm_close,
gpm_get_event: *gpm_get_event,
gpm_visiblepointer: AtomicPtr::new(gpm_visiblepointer),
_library: library,
})
}
}
pub fn open(&self, conn: &mut GpmConnect) -> c_int {
unsafe { (self.gpm_open)(conn, 0) }
}
pub fn close(&self) -> c_int {
unsafe { (self.gpm_close)() }
}
pub fn get_event(&self, event: &mut GpmEventRaw) -> c_int {
unsafe { (self.gpm_get_event)(event) }
}
pub fn set_visible_pointer(&self, visible: bool) {
let ptr = self.gpm_visiblepointer.load(Ordering::Relaxed);
if !ptr.is_null() {
unsafe {
*ptr = if visible { 1 } else { 0 };
}
tracing::debug!(
"GPM: set gpm_visiblepointer = {}",
if visible { 1 } else { 0 }
);
} else {
tracing::debug!("GPM: gpm_visiblepointer symbol not found, cannot set visibility");
}
}
}
pub fn get_gpm_lib() -> Option<&'static GpmLib> {
GPM_LIB.get_or_init(GpmLib::try_load).as_ref()
}