use libc::c_char;
use std::{borrow::Cow, ffi::CStr, os::raw::c_void, ptr};
use weechat_sys::{t_gui_buffer, t_weechat_plugin};
use super::Hook;
use crate::{buffer::Buffer, LossyCString, Weechat};
#[cfg_attr(feature = "docs", doc(cfg(unsound)))]
pub struct ModifierHook {
_hook: Hook,
_hook_data: Box<ModifierHookData>,
}
struct ModifierHookData {
callback: Box<dyn ModifierCallback>,
weechat_ptr: *mut t_weechat_plugin,
}
pub enum ModifierData<'a> {
String(Cow<'a, str>),
Buffer(Buffer<'a>),
}
impl<'a> ModifierData<'a> {
fn pointer_is_buffer(modifier_name: &str) -> bool {
if modifier_name.starts_with("bar_condition_") {
true
} else {
match modifier_name {
"bar_condition_yyy"
| "history_add"
| "input_text_content"
| "input_text_display"
| "input_text_display_with_cursor"
| "input_text_for_buffer" => true,
_ => false,
}
}
}
fn from_name(
weechat: &'a Weechat,
modifier_name: &str,
data: *const c_char,
) -> Option<ModifierData<'a>> {
if data.is_null() {
return None;
}
let modifier_data = unsafe { CStr::from_ptr(data).to_string_lossy() };
if ModifierData::pointer_is_buffer(modifier_name) {
if modifier_data.len() < 2 || !modifier_data.starts_with("0x") {
None
} else {
let ptr = u64::from_str_radix(&modifier_data[2..], 16).ok()?;
Some(ModifierData::Buffer(
weechat.buffer_from_ptr(ptr as *mut t_gui_buffer),
))
}
} else {
Some(ModifierData::String(modifier_data))
}
}
}
pub trait ModifierCallback {
fn callback(
&mut self,
weechat: &Weechat,
modifier_name: &str,
data: Option<ModifierData>,
string: Cow<str>,
) -> Option<String>;
}
impl<T: FnMut(&Weechat, &str, Option<ModifierData>, Cow<str>) -> Option<String> + 'static>
ModifierCallback for T
{
fn callback(
&mut self,
weechat: &Weechat,
modifier_name: &str,
data: Option<ModifierData>,
string: Cow<str>,
) -> Option<String> {
self(weechat, modifier_name, data, string)
}
}
impl ModifierHook {
#[cfg_attr(feature = "docs", doc(cfg(unsound)))]
pub fn new(modifier_name: &str, callback: impl ModifierCallback + 'static) -> Result<Self, ()> {
unsafe extern "C" fn c_hook_cb(
pointer: *const c_void,
_data: *mut c_void,
modifier_name: *const c_char,
modifier_data: *const c_char,
string: *const c_char,
) -> *mut c_char {
let hook_data: &mut ModifierHookData = { &mut *(pointer as *mut ModifierHookData) };
let cb = &mut hook_data.callback;
let modifier_name = CStr::from_ptr(modifier_name).to_str().unwrap_or_default();
let string = if string.is_null() {
Cow::from("")
} else {
CStr::from_ptr(string).to_string_lossy()
};
let weechat = Weechat::from_ptr(hook_data.weechat_ptr);
let data = ModifierData::from_name(&weechat, modifier_name, modifier_data);
let modified_string = cb.callback(&weechat, modifier_name, data, string);
if let Some(modified_string) = modified_string {
let string_length = modified_string.len();
let modified_string = LossyCString::new(modified_string);
let strndup = weechat.get().strndup.unwrap();
strndup(modified_string.as_ptr(), string_length as i32)
} else {
ptr::null_mut()
}
}
Weechat::check_thread();
let weechat = unsafe { Weechat::weechat() };
let data = Box::new(ModifierHookData {
callback: Box::new(callback),
weechat_ptr: weechat.ptr,
});
let data_ref = Box::leak(data);
let hook_modifier = weechat.get().hook_modifier.unwrap();
let modifier_name = LossyCString::new(modifier_name);
let hook_ptr = unsafe {
hook_modifier(
weechat.ptr,
modifier_name.as_ptr(),
Some(c_hook_cb),
data_ref as *const _ as *const c_void,
ptr::null_mut(),
)
};
let hook_data = unsafe { Box::from_raw(data_ref) };
let hook = Hook {
ptr: hook_ptr,
weechat_ptr: weechat.ptr,
};
if hook_ptr.is_null() {
Err(())
} else {
Ok(Self {
_hook: hook,
_hook_data: hook_data,
})
}
}
}