summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--src/internals.rs8
-rw-r--r--src/lib.rs155
3 files changed, 125 insertions, 40 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 341299a..54618e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "hexchat-plugin"
-version = "0.2.5"
+version = "0.2.6"
 authors = ["SoniEx2 <endermoneymod@gmail.com>"]
 description = "Lets you write HexChat plugins in Rust"
 license = "AGPL-3.0+"
diff --git a/src/internals.rs b/src/internals.rs
index 2a8febc..1975182 100644
--- a/src/internals.rs
+++ b/src/internals.rs
@@ -48,7 +48,7 @@ pub enum PluginGuiHandle {
 
 #[repr(C)]
 pub struct HexchatEventAttrs {
-    server_time_utc: libc::time_t,
+    pub server_time_utc: libc::time_t,
 }
 
 pub type HexchatPlugin = Ph;
@@ -181,17 +181,17 @@ pub struct Ph {
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
-            callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int>,
+            callback: unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
     pub hexchat_hook_print_attrs: unsafe extern "C" fn(ph: *mut HexchatPlugin,
             name: *const libc::c_char,
             pri: libc::c_int,
             /* CALLBACK */
-            callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int>,
+            callback: unsafe extern "C" fn(word: *const *const libc::c_char, attrs: *const HexchatEventAttrs, user_data: *mut libc::c_void) -> libc::c_int,
             userdata: *mut libc::c_void) -> *const HexchatHook,
     pub hexchat_emit_print_attrs: unsafe extern "C" fn(ph: *mut HexchatPlugin, attrs: *const HexchatEventAttrs,
             event_name: *const libc::c_char, ...) -> libc::c_int,
     pub hexchat_event_attrs_create: unsafe extern "C" fn(ph: *mut HexchatPlugin) -> *mut HexchatEventAttrs,
     pub hexchat_event_attrs_free: unsafe extern "C" fn(ph: *mut HexchatPlugin,
-            attrs: *const HexchatEventAttrs),
+            attrs: *mut HexchatEventAttrs),
 }
diff --git a/src/lib.rs b/src/lib.rs
index 9959dc7..8519a25 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -70,7 +70,7 @@
  * -[ ] Finish API support. [PRI-HIGH]
  *     -[x] word
  *     -[x] word_eol
- *     -[ ] HEXCHAT_PRI_{HIGHEST, HIGH, NORM, LOW, LOWEST}
+ *     -[#] HEXCHAT_PRI_{HIGHEST, HIGH, NORM, LOW, LOWEST}
  *     -[x] HEXCHAT_EAT_{NONE, HEXCHAT, PLUGIN, ALL}
  *     -[ ] HEXCHAT_FD_{READ, WRITE, EXCEPTION, NOTSOCKET}
  *     -[x] hexchat_command (for commandf, use command(&format!("...")), it is equivalent.)
@@ -81,7 +81,7 @@
  *     -[ ] hexchat_nickcmp
  *     -[ ] hexchat_strip
  *     -[x] ~~hexchat_free~~ not available - use Drop impls.
- *     -[ ] hexchat_event_attrs_create
+ *     -[x] ~~hexchat_event_attrs_create~~ not available - converted as needed
  *     -[x] ~~hexchat_event_attrs_free~~ not available - use Drop impls.
  *     -[x] hexchat_get_info
  *     -[ ] hexchat_get_prefs
@@ -89,10 +89,10 @@
  *         hexchat_list_int, hexchat_list_time, hexchat_list_free
  *     -[x] hexchat_hook_command
  *     -[ ] hexchat_hook_fd
- *     -[x] hexchat_hook_print
- *     -[ ] hexchat_hook_print_attrs
- *     -[x] hexchat_hook_server
- *     -[ ] hexchat_hook_server_attrs
+ *     -[#] hexchat_hook_print (implemented through _attrs)
+ *     -[x] hexchat_hook_print_attrs
+ *     -[#] hexchat_hook_server (implemented through _attrs)
+ *     -[x] hexchat_hook_server_attrs
  *     -[x] hexchat_hook_timer
  *     -[x] ~~hexchat_unhook~~ not available - use Drop impls
  *     -[x] hexchat_find_context
@@ -119,23 +119,50 @@ pub extern crate libc;
 
 mod internals;
 
-use std::panic::catch_unwind;
-use std::thread;
+use std::borrow::Cow;
+use std::cell::Cell;
 use std::ffi::{CString, CStr};
-use std::str::FromStr;
-use std::mem;
-use std::ptr;
 use std::marker::PhantomData;
+use std::mem;
 use std::ops;
+use std::panic::catch_unwind;
+use std::ptr;
 use std::rc::Rc;
-use std::cell::Cell;
-use std::borrow::Cow;
+use std::str::FromStr;
+use std::thread;
+use std::time::{SystemTime, UNIX_EPOCH, Duration};
 
 // ****** //
 // PUBLIC //
 // ****** //
 
-/// A hexchat plugin
+// Consts
+
+// EAT_*
+/// Equivalent to HEXCHAT_EAT_NONE.
+pub const EAT_NONE: Eat = Eat { do_eat: 0 };
+/// Equivalent to HEXCHAT_EAT_HEXCHAT.
+pub const EAT_HEXCHAT: Eat = Eat { do_eat: 1 };
+/// Equivalent to HEXCHAT_EAT_PLUGIN.
+pub const EAT_PLUGIN: Eat = Eat { do_eat: 2 };
+/// Equivalent to HEXCHAT_EAT_ALL.
+pub const EAT_ALL: Eat = Eat { do_eat: 1 | 2 };
+
+// PRI_*
+/// Equivalent to HEXCHAT_PRI_HIGHEST
+pub const PRI_HIGHEST: i32 = 127;
+/// Equivalent to HEXCHAT_PRI_HIGH
+pub const PRI_HIGH: i32 = 64;
+/// Equivalent to HEXCHAT_PRI_NORM
+pub const PRI_NORM: i32 = 0;
+/// Equivalent to HEXCHAT_PRI_LOW
+pub const PRI_LOW: i32 = -64;
+/// Equivalent to HEXCHAT_PRI_LOWEST
+pub const PRI_LOWEST: i32 = -128;
+
+// Traits
+
+/// A hexchat plugin.
 pub trait Plugin {
     /// Called to initialize the plugin.
     fn init(&self, ph: &mut PluginHandle, arg: Option<&str>) -> bool;
@@ -147,26 +174,38 @@ pub trait Plugin {
     }
 }
 
-/// A hexchat plugin handle
+// Structs
+
+/// A hexchat plugin handle.
 pub struct PluginHandle {
     ph: *mut internals::Ph,
     // Used for registration
     info: PluginInfo,
 }
 
+/// Arguments passed to a hook, until the next argument.
 pub struct Word<'a> {
     word: Vec<&'a str>
 }
 
+/// Arguments passed to a hook, until the end of the line.
 pub struct WordEol<'a> {
     word_eol: Vec<&'a str>
 }
 
-/// A safety wrapper that ensures you're working with a valid context.
+/// A safety wrapper to ensure you're working with a valid context.
 pub struct EnsureValidContext<'a> {
     ph: &'a mut PluginHandle,
 }
 
+/// Event attributes.
+#[derive(Clone)]
+pub struct EventAttrs<'a> {
+    /// Server time.
+    pub server_time: Option<SystemTime>,
+    _dummy: PhantomData<&'a ()>,
+}
+
 /// An status indicator for event callbacks. Indicates whether to continue processing, eat hexchat,
 /// eat plugin, or eat all.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -174,15 +213,6 @@ pub struct Eat {
     do_eat: i32,
 }
 
-/// Equivalent to HEXCHAT_EAT_NONE.
-pub const EAT_NONE: Eat = Eat { do_eat: 0 };
-/// Equivalent to HEXCHAT_EAT_HEXCHAT.
-pub const EAT_HEXCHAT: Eat = Eat { do_eat: 1 };
-/// Equivalent to HEXCHAT_EAT_PLUGIN.
-pub const EAT_PLUGIN: Eat = Eat { do_eat: 2 };
-/// Equivalent to HEXCHAT_EAT_ALL.
-pub const EAT_ALL: Eat = Eat { do_eat: 1 | 2 };
-
 /// A command hook handle.
 pub struct CommandHookHandle {
     ph: *mut internals::Ph,
@@ -228,6 +258,8 @@ pub struct Context {
 // #[derive(Debug)] // doesn't work
 pub struct InvalidContextError<F: FnOnce(EnsureValidContext) -> R, R>(F);
 
+// Enums
+
 /// A hexchat_get_info key.
 #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Copy, Clone)]
 pub enum InfoId<'a> {
@@ -576,9 +608,13 @@ impl PluginHandle {
             CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData }
         }
     }
-    /// Sets a server hook
+    /// Sets a server hook.
     pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
+        self.hook_server_attrs(cmd, move |ph, w, we, _| cb(ph, w, we), pri)
+    }
+    /// Sets a server hook, with attributes.
+    pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+        unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
             // hook may unhook itself.
             // however, we don't wanna free it until it has returned.
             let f: Rc<ServerHookUd> = rc_clone_from_raw(ud as *const ServerHookUd);
@@ -586,7 +622,7 @@ impl PluginHandle {
             match catch_unwind(move || {
                 let word = Word::new(word);
                 let word_eol = WordEol::new(word_eol);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol).do_eat as libc::c_int
+                (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol, (&*attrs).into()).do_eat as libc::c_int
             }) {
                 Result::Ok(v @ _) => v,
                 Result::Err(e @ _) => {
@@ -604,21 +640,25 @@ impl PluginHandle {
         let name = CString::new(cmd).unwrap();
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_server)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
             assert!(!res.is_null());
             ServerHookHandle { ph: self.ph, hh: res, _f: PhantomData }
         }
     }
-    /// Sets a print hook
+    /// Sets a print hook.
     pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe {
-        unsafe extern "C" fn callback(word: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int {
+        self.hook_print_attrs(name, move |ph, w, _| cb(ph, w), pri)
+    }
+    /// Sets a print hook, with attributes.
+    pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe {
+        unsafe extern "C" fn callback(word: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int {
             // hook may unhook itself.
             // however, we don't wanna free it until it has returned.
             let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd);
             let ph = f.1;
             match catch_unwind(move || {
                 let word = Word::new(word);
-                (f.0)(&mut PluginHandle::new(f.1, f.2), word).do_eat as libc::c_int
+                (f.0)(&mut PluginHandle::new(f.1, f.2), word, (&*attrs).into()).do_eat as libc::c_int
             }) {
                 Result::Ok(v @ _) => v,
                 Result::Err(e @ _) => {
@@ -636,7 +676,7 @@ impl PluginHandle {
         let name = CString::new(name).unwrap();
         let bp = Rc::into_raw(b);
         unsafe {
-            let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
+            let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _);
             assert!(!res.is_null());
             PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
         }
@@ -753,6 +793,24 @@ impl PluginHandle {
     }
 }
 
+impl<'a> EventAttrs<'a> {
+    fn new() -> EventAttrs<'a> {
+        EventAttrs {
+            server_time: None,
+            _dummy: PhantomData,
+        }
+    }
+}
+
+impl<'a> From<&'a internals::HexchatEventAttrs> for EventAttrs<'a> {
+    fn from(other: &'a internals::HexchatEventAttrs) -> EventAttrs<'a> {
+        EventAttrs {
+            server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None },
+            _dummy: PhantomData,
+        }
+    }
+}
+
 impl<'a> EnsureValidContext<'a> {
 /*
  * These cause UB:
@@ -908,9 +966,9 @@ impl<'a> EnsureValidContext<'a> {
 // but hexchat isn't particularly performance-critical.
 type CommandHookUd = (Box<Fn(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
 /// Userdata type used by a server hook.
-type ServerHookUd = (Box<Fn(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
+type ServerHookUd = (Box<Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
 /// Userdata type used by a print hook.
-type PrintHookUd = (Box<Fn(&mut PluginHandle, Word) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
+type PrintHookUd = (Box<Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
 /// Userdata type used by a timer hook.
 type TimerHookUd = (Rc<(Box<Fn(&mut PluginHandle) -> bool + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo)>, Rc<Cell<bool>>);
 
@@ -963,6 +1021,33 @@ unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool)
     }
 }
 
+/// Helper to manage owned internals::HexchatEventAttrs
+struct HexchatEventAttrsHelper(*mut internals::HexchatEventAttrs, *mut internals::Ph);
+
+impl HexchatEventAttrsHelper {
+    fn new(ph: *mut internals::Ph) -> Self {
+        HexchatEventAttrsHelper(unsafe { ((*ph).hexchat_event_attrs_create)(ph) }, ph)
+    }
+
+    fn new_with(ph: *mut internals::Ph, attrs: EventAttrs) -> Self {
+        let helper = Self::new(ph);
+        let v = attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| match st.duration_since(UNIX_EPOCH) {
+            Ok(n) => n.as_secs(),
+            Err(_) => 0
+        }).filter(|&st| st < (libc::time_t::max_value() as u64)).unwrap() as libc::time_t;
+        unsafe { (*helper.0).server_time_utc = v; }
+        helper
+    }
+}
+
+impl Drop for HexchatEventAttrsHelper {
+    fn drop(&mut self) {
+        unsafe {
+            ((*self.1).hexchat_event_attrs_free)(self.1, self.0)
+        }
+    }
+}
+
 /// Holds name, desc, vers
 // This is kinda naughty - we modify these values after returning from hexchat_plugin_init, during
 // the deinitialization.