#![allow(clippy::type_complexity)]
use crate::call;
use crate::reply::ServerReply;
use crate::server_event::ServerEvent;
use crate::{c, from_cstring, to_cstring, ChannelRef, PrintEvent, WindowEvent};
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use std::ffi::c_void;
use std::os::raw::{c_char, c_int};
use std::panic::{self, AssertUnwindSafe};
use std::sync::mpsc;
use std::time::Duration;
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct Command(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for Command {}
unsafe impl Sync for Command {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct PrintEventListener(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for PrintEventListener {}
unsafe impl Sync for PrintEventListener {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct WindowEventListener(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for WindowEventListener {}
unsafe impl Sync for WindowEventListener {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct RawServerEventListener(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for RawServerEventListener {}
unsafe impl Sync for RawServerEventListener {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct TimerTask(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for TimerTask {}
unsafe impl Sync for TimerTask {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct ServerEventListener(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for ServerEventListener {}
unsafe impl Sync for ServerEventListener {}
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct ReplyListener(pub(crate) *mut c::hexchat_hook);
unsafe impl Send for ReplyListener {}
unsafe impl Sync for ReplyListener {}
pub fn register_command(
name: &str,
help_text: &str,
priority: Priority,
function: impl Fn(&[String]) -> EatMode + 'static,
) -> Command {
let hook_ref = CommandHookRef {
function: Box::new(function),
};
let boxed = Box::new(hook_ref);
let ptr = Box::into_raw(boxed);
let name = to_cstring(name);
let help_text = to_cstring(help_text);
let hook_ptr = unsafe {
c!(
hexchat_hook_command,
name.as_ptr(),
c_int::from(priority.0),
command_hook,
help_text.as_ptr(),
ptr as _,
)
};
call::get_plugin().commands.insert(Command(hook_ptr));
Command(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn deregister_command(command: Command) {
dealloc_command(command.0);
call::get_plugin().commands.remove(&command);
}
pub(crate) fn dealloc_command(command: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, command);
let ptr = ptr as *mut CommandHookRef;
Box::from_raw(ptr);
}
}
pub fn add_print_event_listener(
event: PrintEvent,
priority: Priority,
function: impl Fn(&[String], DateTime<Utc>) -> EatMode + 'static,
) -> PrintEventListener {
let hook_ref = PrintHookRef {
function: Box::new(function),
};
let boxed = Box::new(hook_ref);
let ptr = Box::into_raw(boxed);
let name = to_cstring(event.0);
let hook_ptr = unsafe {
c!(
hexchat_hook_print_attrs,
name.as_ptr(),
c_int::from(priority.0),
print_hook,
ptr as _,
)
};
call::get_plugin()
.print_events
.insert(PrintEventListener(hook_ptr));
PrintEventListener(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_print_event_listener(listener: PrintEventListener) {
dealloc_print_event_listener(listener.0);
call::get_plugin().print_events.remove(&listener);
}
pub(crate) fn dealloc_print_event_listener(listener: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, listener);
let ptr = ptr as *mut PrintHookRef;
Box::from_raw(ptr);
}
}
pub fn add_window_event_listener(
event: WindowEvent,
priority: Priority,
function: impl Fn(ChannelRef) -> EatMode + 'static,
) -> WindowEventListener {
let context_ref = ContextHookRef {
function: Box::new(function),
};
let boxed = Box::new(context_ref);
let ptr = Box::into_raw(boxed);
let name = to_cstring(event.0);
let hook_ptr = unsafe {
c!(
hexchat_hook_print,
name.as_ptr(),
c_int::from(priority.0),
context_hook,
ptr as _,
)
};
call::get_plugin()
.window_events
.insert(WindowEventListener(hook_ptr));
WindowEventListener(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_window_event_listener(listener: WindowEventListener) {
dealloc_window_event_listener(listener.0);
call::get_plugin().window_events.remove(&listener);
}
pub(crate) fn dealloc_window_event_listener(listener: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, listener);
let ptr = ptr as *mut ContextHookRef;
Box::from_raw(ptr);
}
}
pub fn add_raw_server_event_listener(
event: &str,
priority: Priority,
function: impl Fn(&[String], DateTime<Utc>) -> EatMode + 'static,
) -> RawServerEventListener {
let server_ref = ServerHookRef {
function: Box::new(function),
};
let boxed = Box::new(server_ref);
let ptr = Box::into_raw(boxed);
let event = to_cstring(event);
let hook_ptr = unsafe {
c!(
hexchat_hook_server_attrs,
event.as_ptr(),
c_int::from(priority.0),
server_hook,
ptr as _,
)
};
call::get_plugin()
.server_events
.insert(RawServerEventListener(hook_ptr));
RawServerEventListener(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_raw_server_event_listener(listener: RawServerEventListener) {
dealloc_raw_server_event_listener(listener.0);
call::get_plugin().server_events.remove(&listener);
}
pub(crate) fn dealloc_raw_server_event_listener(listener: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, listener);
let ptr = ptr as *mut ServerHookRef;
Box::from_raw(ptr);
}
}
pub fn add_timer_task(interval: Duration, task: impl Fn() + 'static) -> TimerTask {
let timer_ref = TimerHookRef {
function: Box::new(task),
};
let boxed = Box::new(timer_ref);
let ptr = Box::into_raw(boxed);
let ms = interval.as_millis();
let ms = if ms > i32::max_value() as u128 {
i32::max_value()
} else {
ms as i32
};
let hook_ptr = unsafe { c!(hexchat_hook_timer, ms, timer_hook, ptr as _) };
call::get_plugin().timer_tasks.insert(TimerTask(hook_ptr));
TimerTask(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_timer_task(task: TimerTask) {
dealloc_timer_task(task.0);
call::get_plugin().timer_tasks.remove(&task);
}
pub(crate) fn dealloc_timer_task(task: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, task);
let ptr = ptr as *mut TimerHookRef;
Box::from_raw(ptr);
}
}
pub fn add_server_event_listener<T>(
priority: Priority,
function: impl Fn(T, DateTime<Utc>) -> EatMode + 'static,
) -> ServerEventListener
where
T: ServerEvent,
{
let server_ref = TypedServerHookRef {
function: Box::new(move |w, l, d| unsafe {
let t = T::create(w, l);
function(t, d)
}),
};
let boxed = Box::new(server_ref);
let ptr = Box::into_raw(boxed);
let event = to_cstring(T::NAME);
let hook_ptr = unsafe {
c!(
hexchat_hook_server_attrs,
event.as_ptr(),
c_int::from(priority.0),
server_event_hook,
ptr as _,
)
};
call::get_plugin()
.typed_server_events
.insert(ServerEventListener(hook_ptr));
ServerEventListener(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_server_event_listener(listener: ServerEventListener) {
dealloc_server_event_listener(listener.0);
call::get_plugin().typed_server_events.remove(&listener);
}
pub(crate) fn dealloc_server_event_listener(listener: *mut c::hexchat_hook) {
unsafe {
let ptr = c!(hexchat_unhook, listener);
let ptr = ptr as *mut TypedServerHookRef;
Box::from_raw(ptr);
}
}
pub fn add_reply_listener<T>(
priority: Priority,
function: impl Fn(T, DateTime<Utc>) -> EatMode + 'static,
) -> ReplyListener
where
T: ServerReply,
{
let server_ref = TypedServerHookRef {
function: Box::new(move |w, l, d| unsafe {
let t = T::create(w, l);
if let Some(t) = t {
function(t, d)
} else {
eprintln!("Invalid response '{}'", from_cstring(*l));
EatMode::None
}
}),
};
let boxed = Box::new(server_ref);
let ptr = Box::into_raw(boxed);
let event = to_cstring(T::ID);
let hook_ptr = unsafe {
c!(
hexchat_hook_server_attrs,
event.as_ptr(),
c_int::from(priority.0),
server_event_hook,
ptr as _,
)
};
call::get_plugin()
.typed_server_events
.insert(ServerEventListener(hook_ptr));
ReplyListener(hook_ptr)
}
#[allow(clippy::needless_pass_by_value)]
pub fn remove_reply_listener(listener: ReplyListener) {
remove_server_event_listener(ServerEventListener(listener.0));
}
pub fn add_reply_listener_once<T>(
priority: Priority,
function: impl Fn(T, DateTime<Utc>) -> EatMode + 'static,
) where
T: ServerReply,
{
let (tx, rx) = mpsc::channel();
let listener = add_reply_listener(priority, move |t, d| {
let listener = rx.recv().unwrap();
remove_reply_listener(listener);
function(t, d)
});
tx.send(listener).ok();
}
pub fn add_reply_listener_until<T, U, F>(priority: Priority, function: F)
where
T: ServerReply,
U: ServerReply,
F: Fn(T, DateTime<Utc>) -> EatMode + 'static,
{
let listener = add_reply_listener(priority, function);
add_reply_listener_once(priority, move |_t: U, _d| {
remove_reply_listener(ReplyListener(listener.0));
EatMode::None
});
}
struct CommandHookRef {
function: Box<dyn Fn(&[String]) -> EatMode>,
}
struct PrintHookRef {
function: Box<dyn Fn(&[String], DateTime<Utc>) -> EatMode>,
}
struct ContextHookRef {
function: Box<dyn Fn(ChannelRef) -> EatMode>,
}
struct ServerHookRef {
function: Box<dyn Fn(&[String], DateTime<Utc>) -> EatMode>,
}
struct TimerHookRef {
function: Box<dyn Fn()>,
}
struct TypedServerHookRef {
function: Box<dyn Fn(*mut *mut c_char, *mut *mut c_char, DateTime<Utc>) -> EatMode>,
}
unsafe extern "C" fn command_hook(
word: *mut *mut c_char,
_word_eol: *mut *mut c_char,
user_data: *mut c_void,
) -> c_int {
let user_data = user_data as *mut CommandHookRef;
let mut vec = Vec::new();
for i in 1..32 {
let offset = word.offset(i);
if !offset.is_null() {
let ptr = *offset;
if !ptr.is_null() {
let cstr = from_cstring(ptr);
vec.push(cstr);
}
}
}
let res = match panic::catch_unwind(AssertUnwindSafe(|| ((*user_data).function)(&vec))) {
Ok(eat) => eat,
Err(e) => {
crate::print_plain(&format!("Error in command '/{}'", &vec.join(" ")));
if let Some(string) = (*e).downcast_ref::<&str>() {
crate::print_plain(&format!("Error message: {}", string));
}
EatMode::All
}
};
res as _
}
unsafe extern "C" fn print_hook(
word: *mut *mut c_char,
attrs: *mut c::hexchat_event_attrs,
user_data: *mut c_void,
) -> c_int {
let user_data = user_data as *mut PrintHookRef;
let mut vec = Vec::new();
for i in 1..32 {
let offset = word.offset(i);
if !offset.is_null() {
let ptr = *offset;
if !ptr.is_null() {
let cstr = from_cstring(ptr);
vec.push(cstr);
}
}
}
let naive = NaiveDateTime::from_timestamp((*attrs).server_time_utc as _, 0);
let utc = Utc.from_utc_datetime(&naive);
panic::catch_unwind(AssertUnwindSafe(|| ((*user_data).function)(&vec, utc)))
.unwrap_or(EatMode::None) as _
}
unsafe extern "C" fn context_hook(_word: *mut *mut c_char, user_data: *mut c_void) -> c_int {
let user_data = user_data as *mut ContextHookRef;
let ctx = c!(hexchat_get_context);
let cref = ChannelRef { handle: ctx };
panic::catch_unwind(AssertUnwindSafe(|| ((*user_data).function)(cref))).unwrap_or(EatMode::None)
as _
}
unsafe extern "C" fn server_hook(
word: *mut *mut c_char,
_word_eol: *mut *mut c_char,
attrs: *mut c::hexchat_event_attrs,
user_data: *mut c_void,
) -> c_int {
let user_data = user_data as *mut ServerHookRef;
let mut vec = Vec::new();
for i in 1..32 {
let offset = word.offset(i);
if !offset.is_null() {
let ptr = *offset;
if !ptr.is_null() {
let cstr = from_cstring(ptr);
vec.push(cstr);
}
}
}
let naive = NaiveDateTime::from_timestamp((*attrs).server_time_utc as _, 0);
let utc = Utc.from_utc_datetime(&naive);
panic::catch_unwind(AssertUnwindSafe(|| ((*user_data).function)(&vec, utc)))
.unwrap_or(EatMode::None) as _
}
unsafe extern "C" fn timer_hook(user_data: *mut c_void) -> c_int {
let user_data = user_data as *mut TimerHookRef;
panic::catch_unwind(AssertUnwindSafe(|| {
((*user_data).function)();
}))
.ok();
EatMode::All as _
}
unsafe extern "C" fn server_event_hook(
word: *mut *mut c_char,
word_eol: *mut *mut c_char,
attrs: *mut c::hexchat_event_attrs,
user_data: *mut c_void,
) -> c_int {
let user_data = user_data as *mut TypedServerHookRef;
let naive = NaiveDateTime::from_timestamp((*attrs).server_time_utc as _, 0);
let utc = Utc.from_utc_datetime(&naive);
panic::catch_unwind(AssertUnwindSafe(|| {
((*user_data).function)(word, word_eol, utc) as c_int
}))
.unwrap_or(EatMode::None as c_int)
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct Priority(pub i8);
impl Priority {
pub const HIGHEST: Self = Self(127);
pub const HIGH: Self = Self(64);
pub const NORMAL: Self = Self(0);
pub const LOW: Self = Self(-64);
pub const LOWEST: Self = Self(-128);
}
pub enum EatMode {
None,
Hexchat,
Plugin,
All,
}