use std::ffi::{c_char, c_int, c_void};
const PRIORITY_DEBUG: c_int = 10;
const PRIORITY_INFO: c_int = 20;
const PRIORITY_ERROR: c_int = 30;
const BUFFER: usize = 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum LogPriority {
Debug,
Info,
#[default]
Error,
}
impl LogPriority {
const fn raw(self) -> c_int {
match self {
Self::Debug => PRIORITY_DEBUG,
Self::Info => PRIORITY_INFO,
Self::Error => PRIORITY_ERROR,
}
}
}
unsafe extern "C" {
fn libinput_log_set_handler(libinput: *mut c_void, handler: Option<Handler>);
fn libinput_log_set_priority(libinput: *mut c_void, priority: c_int);
fn vsnprintf(out: *mut c_char, len: usize, format: *const c_char, args: *mut c_void) -> c_int;
}
type Handler = unsafe extern "C" fn(
libinput: *mut c_void,
priority: c_int,
format: *const c_char,
args: *mut c_void,
);
pub(crate) unsafe fn install(libinput: *mut c_void, priority: LogPriority) {
unsafe {
libinput_log_set_handler(libinput, Some(trampoline));
libinput_log_set_priority(libinput, priority.raw());
}
}
unsafe extern "C" fn trampoline(
_libinput: *mut c_void,
priority: c_int,
format: *const c_char,
args: *mut c_void,
) {
if format.is_null() {
return;
}
let mut buffer = [0_u8; BUFFER];
let written = unsafe {
vsnprintf(
buffer.as_mut_ptr().cast::<c_char>(),
buffer.len(),
format,
args,
)
};
let Ok(written) = usize::try_from(written) else {
return;
};
let written = written.min(buffer.len() - 1);
forward(priority, &buffer[..written]);
}
fn forward(priority: c_int, message: &[u8]) {
let message = String::from_utf8_lossy(message);
let message = trim(&message);
if message.is_empty() {
return;
}
match classify(priority) {
LogPriority::Debug => drmkit_log::log_debug!("[libinput] {message}"),
LogPriority::Info => drmkit_log::log_info!("[libinput] {message}"),
LogPriority::Error => drmkit_log::log_error!("[libinput] {message}"),
}
}
fn classify(priority: c_int) -> LogPriority {
if priority <= PRIORITY_DEBUG {
LogPriority::Debug
} else if priority <= PRIORITY_INFO {
LogPriority::Info
} else {
LogPriority::Error
}
}
fn trim(message: &str) -> &str {
message.trim_end_matches(['\n', '\r'])
}
#[cfg(test)]
mod tests {
use super::{LogPriority, classify, trim};
#[test]
fn each_libinput_priority_lands_where_it_should() {
assert_eq!(classify(10), LogPriority::Debug);
assert_eq!(classify(20), LogPriority::Info);
assert_eq!(classify(30), LogPriority::Error);
}
#[test]
fn a_priority_between_the_known_ones_lands_at_the_nearest() {
assert_eq!(classify(1), LogPriority::Debug);
assert_eq!(classify(15), LogPriority::Info);
assert_eq!(classify(25), LogPriority::Error);
assert_eq!(classify(999), LogPriority::Error);
}
#[test]
fn the_trailing_newline_comes_off() {
assert_eq!(trim("event5 - lagging\n"), "event5 - lagging");
assert_eq!(trim("windows\r\n"), "windows");
assert_eq!(trim("several\n\n\n"), "several");
assert_eq!(trim("none"), "none");
assert_eq!(trim("\n"), "");
}
#[test]
fn a_newline_inside_the_message_stays() {
assert_eq!(trim("two\nlines\n"), "two\nlines");
}
}