Version 0.2.1
This commit is contained in:
parent
6ce60768df
commit
b2be734b53
3 changed files with 294 additions and 21 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hexchat-plugin"
|
name = "hexchat-plugin"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["SoniEx2 <endermoneymod@gmail.com>"]
|
authors = ["SoniEx2 <endermoneymod@gmail.com>"]
|
||||||
description = "Lets you write HexChat plugins in Rust"
|
description = "Lets you write HexChat plugins in Rust"
|
||||||
license = "AGPL-3.0+"
|
license = "AGPL-3.0+"
|
||||||
|
|
|
@ -66,18 +66,18 @@ pub struct Ph {
|
||||||
name: *const libc::c_char,
|
name: *const libc::c_char,
|
||||||
pri: libc::c_int,
|
pri: libc::c_int,
|
||||||
/* CALLBACK */
|
/* CALLBACK */
|
||||||
callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, 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, user_data: *mut libc::c_void) -> libc::c_int,
|
||||||
userdata: *mut libc::c_void) -> *const HexchatHook,
|
userdata: *mut libc::c_void) -> *const HexchatHook,
|
||||||
pub hexchat_hook_print: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
pub hexchat_hook_print: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
||||||
name: *const libc::c_char,
|
name: *const libc::c_char,
|
||||||
pri: libc::c_int,
|
pri: libc::c_int,
|
||||||
/* CALLBACK */
|
/* CALLBACK */
|
||||||
callback: Option<unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int>,
|
callback: unsafe extern "C" fn(word: *const *const libc::c_char, user_data: *mut libc::c_void) -> libc::c_int,
|
||||||
userdata: *mut libc::c_void) -> *const HexchatHook,
|
userdata: *mut libc::c_void) -> *const HexchatHook,
|
||||||
pub hexchat_hook_timer: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
pub hexchat_hook_timer: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
||||||
timeout: libc::c_int,
|
timeout: libc::c_int,
|
||||||
/* CALLBACK */
|
/* CALLBACK */
|
||||||
callback: Option<unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int>,
|
callback: unsafe extern "C" fn(user_data: *mut libc::c_void) -> libc::c_int,
|
||||||
userdata: *mut libc::c_void) -> *const HexchatHook,
|
userdata: *mut libc::c_void) -> *const HexchatHook,
|
||||||
pub hexchat_hook_fd: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
pub hexchat_hook_fd: unsafe extern "C" fn(ph: *mut HexchatPlugin,
|
||||||
fd: libc::c_int,
|
fd: libc::c_int,
|
||||||
|
|
307
src/lib.rs
307
src/lib.rs
|
@ -67,9 +67,50 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Big list o' TODO:
|
* Big list o' TODO:
|
||||||
* -[ ] Finish basic API support. [PRI-HIGH]
|
* -[ ] Finish API support. [PRI-HIGH]
|
||||||
|
* -[x] word
|
||||||
|
* -[x] word_eol
|
||||||
|
* -[ ] 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.)
|
||||||
|
* -[x] hexchat_print (for printf, use print(&format!("...")), it is equivalent.)
|
||||||
|
* -[ ] hexchat_emit_print
|
||||||
|
* -[ ] hexchat_emit_print_attrs
|
||||||
|
* -[ ] hexchat_send_modes
|
||||||
|
* -[ ] hexchat_nickcmp
|
||||||
|
* -[ ] hexchat_strip
|
||||||
|
* -[x] ~~hexchat_free~~ not available - use Drop impls.
|
||||||
|
* -[ ] hexchat_event_attrs_create
|
||||||
|
* -[x] ~~hexchat_event_attrs_free~~ not available - use Drop impls.
|
||||||
|
* -[ ] hexchat_get_info
|
||||||
|
* -[ ] hexchat_get_prefs
|
||||||
|
* -[ ] hexchat_list_get, hexchat_list_fields, hexchat_list_next, hexchat_list_str,
|
||||||
|
* 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
|
||||||
|
* -[x] hexchat_hook_timer
|
||||||
|
* -[x] ~~hexchat_unhook~~ not available - use Drop impls
|
||||||
|
* -[ ] hexchat_find_context
|
||||||
|
* -[x] hexchat_get_context
|
||||||
|
* -[x] hexchat_set_context
|
||||||
|
* -[ ] hexchat_pluginpref_set_str
|
||||||
|
* -[ ] hexchat_pluginpref_get_str
|
||||||
|
* -[ ] hexchat_pluginpref_set_int
|
||||||
|
* -[ ] hexchat_pluginpref_get_int
|
||||||
|
* -[ ] hexchat_pluginpref_delete
|
||||||
|
* -[ ] hexchat_pluginpref_list
|
||||||
|
* -[ ] hexchat_plugingui_add
|
||||||
|
* -[x] ~~hexchat_plugingui_remove~~ not available - use Drop impls.
|
||||||
* -[ ] Wrap contexts within something we keep track of. Mark them invalid when contexts are
|
* -[ ] Wrap contexts within something we keep track of. Mark them invalid when contexts are
|
||||||
* closed. [PRI-MAYBE]
|
* closed. [PRI-MAYBE]
|
||||||
|
* -[x] Anchor closures on the stack using Rc<T>. Many (most?) hooks are reentrant. As far as I
|
||||||
|
* know, all of them need this.
|
||||||
|
* -[x] Additionally, use a Cell<bool> for timers.
|
||||||
* -[ ] ???
|
* -[ ] ???
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -86,6 +127,8 @@ use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
// ****** //
|
// ****** //
|
||||||
// PUBLIC //
|
// PUBLIC //
|
||||||
|
@ -139,12 +182,38 @@ pub const EAT_PLUGIN: Eat = Eat { do_eat: 2 };
|
||||||
/// Equivalent to HEXCHAT_EAT_ALL.
|
/// Equivalent to HEXCHAT_EAT_ALL.
|
||||||
pub const EAT_ALL: Eat = Eat { do_eat: 1 | 2 };
|
pub const EAT_ALL: Eat = Eat { do_eat: 1 | 2 };
|
||||||
|
|
||||||
/// A command hook handle, created with PluginHandle::hook_command.
|
/// A command hook handle.
|
||||||
pub struct CommandHookHandle {
|
pub struct CommandHookHandle {
|
||||||
ph: *mut internals::Ph,
|
ph: *mut internals::Ph,
|
||||||
hh: *const internals::HexchatHook,
|
hh: *const internals::HexchatHook,
|
||||||
// this does actually store an Box<...>, but on the other side of the FFI.
|
// this does actually store an Rc<...>, but on the other side of the FFI.
|
||||||
_f: PhantomData<Box<CommandHookUd>>,
|
_f: PhantomData<Rc<CommandHookUd>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A server hook handle.
|
||||||
|
pub struct ServerHookHandle {
|
||||||
|
ph: *mut internals::Ph,
|
||||||
|
hh: *const internals::HexchatHook,
|
||||||
|
// this does actually store an Rc<...>, but on the other side of the FFI.
|
||||||
|
_f: PhantomData<Rc<ServerHookUd>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A print hook handle.
|
||||||
|
pub struct PrintHookHandle {
|
||||||
|
ph: *mut internals::Ph,
|
||||||
|
hh: *const internals::HexchatHook,
|
||||||
|
// this does actually store an Rc<...>, but on the other side of the FFI.
|
||||||
|
_f: PhantomData<Rc<PrintHookUd>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A timer hook handle.
|
||||||
|
pub struct TimerHookHandle {
|
||||||
|
ph: *mut internals::Ph,
|
||||||
|
hh: *const internals::HexchatHook,
|
||||||
|
// avoids issues
|
||||||
|
alive: Rc<Cell<bool>>,
|
||||||
|
// this does actually store an Rc<...>, but on the other side of the FFI.
|
||||||
|
_f: PhantomData<Rc<TimerHookUd>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A context.
|
/// A context.
|
||||||
|
@ -170,7 +239,42 @@ impl Drop for CommandHookHandle {
|
||||||
let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut CommandHookUd;
|
let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut CommandHookUd;
|
||||||
// we assume b is not null. this should be safe.
|
// we assume b is not null. this should be safe.
|
||||||
// just drop it
|
// just drop it
|
||||||
mem::drop(Box::from_raw(b));
|
mem::drop(Rc::from_raw(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for ServerHookHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut ServerHookUd;
|
||||||
|
// we assume b is not null. this should be safe.
|
||||||
|
// just drop it
|
||||||
|
mem::drop(Rc::from_raw(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for PrintHookHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut PrintHookUd;
|
||||||
|
// we assume b is not null. this should be safe.
|
||||||
|
// just drop it
|
||||||
|
mem::drop(Rc::from_raw(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for TimerHookHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.alive.get() {
|
||||||
|
// already free'd.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.alive.set(false);
|
||||||
|
unsafe {
|
||||||
|
let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut TimerHookUd;
|
||||||
|
// we assume b is not null. this should be safe.
|
||||||
|
// just drop it
|
||||||
|
mem::drop(Rc::from_raw(b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,10 +286,11 @@ impl<'a> Word<'a> {
|
||||||
let w = *word.offset(i);
|
let w = *word.offset(i);
|
||||||
if !w.is_null() {
|
if !w.is_null() {
|
||||||
vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word - broken hexchat"))
|
vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word - broken hexchat"))
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while let Some(&"") = vec.last() {
|
||||||
|
vec.pop();
|
||||||
|
}
|
||||||
Word { word: vec }
|
Word { word: vec }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,10 +309,11 @@ impl<'a> WordEol<'a> {
|
||||||
let w = *word_eol.offset(i);
|
let w = *word_eol.offset(i);
|
||||||
if !w.is_null() {
|
if !w.is_null() {
|
||||||
vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word_eol - broken hexchat"))
|
vec.push(CStr::from_ptr(w).to_str().expect("non-utf8 word_eol - broken hexchat"))
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while let Some(&"") = vec.last() {
|
||||||
|
vec.pop();
|
||||||
|
}
|
||||||
WordEol { word_eol: vec }
|
WordEol { word_eol: vec }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,15 +421,17 @@ impl PluginHandle {
|
||||||
/// Sets a command hook
|
/// Sets a command hook
|
||||||
pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
|
pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle 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 {
|
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 {
|
||||||
let f: *const CommandHookUd = ud as *const CommandHookUd;
|
// hook may unhook itself.
|
||||||
match catch_unwind(|| {
|
// however, we don't wanna free it until it has returned.
|
||||||
|
let f: Rc<CommandHookUd> = rc_clone_from_raw(ud as *const CommandHookUd);
|
||||||
|
let ph = f.1;
|
||||||
|
match catch_unwind(move || {
|
||||||
let word = Word::new(word);
|
let word = Word::new(word);
|
||||||
let word_eol = WordEol::new(word_eol);
|
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).do_eat as libc::c_int
|
||||||
}) {
|
}) {
|
||||||
Result::Ok(v @ _) => v,
|
Result::Ok(v @ _) => v,
|
||||||
Result::Err(e @ _) => {
|
Result::Err(e @ _) => {
|
||||||
let ph = (*f).1;
|
|
||||||
// if it's a &str or String, just print it
|
// if it's a &str or String, just print it
|
||||||
if let Some(estr) = e.downcast_ref::<&str>() {
|
if let Some(estr) = e.downcast_ref::<&str>() {
|
||||||
hexchat_print_str(ph, estr, false);
|
hexchat_print_str(ph, estr, false);
|
||||||
|
@ -334,16 +442,136 @@ impl PluginHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let b: Box<CommandHookUd> = Box::new((Box::new(cb), self.ph, self.info));
|
let b: Rc<CommandHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
|
||||||
let name = CString::new(cmd).unwrap();
|
let name = CString::new(cmd).unwrap();
|
||||||
let help_text = help.map(CString::new).map(Result::unwrap);
|
let help_text = help.map(CString::new).map(Result::unwrap);
|
||||||
let bp = Box::into_raw(b);
|
let bp = Rc::into_raw(b);
|
||||||
unsafe {
|
unsafe {
|
||||||
let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as libc::c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
|
let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as libc::c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _);
|
||||||
assert!(!res.is_null());
|
assert!(!res.is_null());
|
||||||
CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData }
|
CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// 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 {
|
||||||
|
// 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);
|
||||||
|
let ph = f.1;
|
||||||
|
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
|
||||||
|
}) {
|
||||||
|
Result::Ok(v @ _) => v,
|
||||||
|
Result::Err(e @ _) => {
|
||||||
|
// if it's a &str or String, just print it
|
||||||
|
if let Some(estr) = e.downcast_ref::<&str>() {
|
||||||
|
hexchat_print_str(ph, estr, false);
|
||||||
|
} else if let Some(estring) = e.downcast_ref::<String>() {
|
||||||
|
hexchat_print_str(ph, &estring, false);
|
||||||
|
}
|
||||||
|
0 // EAT_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let b: Rc<ServerHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
|
||||||
|
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 _);
|
||||||
|
assert!(!res.is_null());
|
||||||
|
ServerHookHandle { ph: self.ph, hh: res, _f: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 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 {
|
||||||
|
// 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
|
||||||
|
}) {
|
||||||
|
Result::Ok(v @ _) => v,
|
||||||
|
Result::Err(e @ _) => {
|
||||||
|
// if it's a &str or String, just print it
|
||||||
|
if let Some(estr) = e.downcast_ref::<&str>() {
|
||||||
|
hexchat_print_str(ph, estr, false);
|
||||||
|
} else if let Some(estring) = e.downcast_ref::<String>() {
|
||||||
|
hexchat_print_str(ph, &estring, false);
|
||||||
|
}
|
||||||
|
0 // EAT_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let b: Rc<PrintHookUd> = Rc::new((Box::new(cb), self.ph, self.info));
|
||||||
|
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 _);
|
||||||
|
assert!(!res.is_null());
|
||||||
|
PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Sets a timer hook
|
||||||
|
pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
|
||||||
|
unsafe extern "C" fn callback(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<TimerHookUd> = rc_clone_from_raw(ud as *const TimerHookUd);
|
||||||
|
let alive = f.1.clone(); // clone the alive because why not
|
||||||
|
let f = f.0.clone();
|
||||||
|
let ph = f.1;
|
||||||
|
// we could technically free the Rc<TimerHookUd> here, I guess
|
||||||
|
match catch_unwind(move || {
|
||||||
|
(f.0)(&mut PluginHandle::new(f.1, f.2))
|
||||||
|
}) {
|
||||||
|
Result::Ok(true) => 1,
|
||||||
|
Result::Ok(false) => {
|
||||||
|
// avoid double-free
|
||||||
|
if !alive.get() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// mark it no longer alive
|
||||||
|
alive.set(false);
|
||||||
|
// HexChat will automatically free the hook.
|
||||||
|
// we just need to free the userdata.
|
||||||
|
mem::drop(Rc::from_raw(ud as *const TimerHookUd));
|
||||||
|
0
|
||||||
|
},
|
||||||
|
Result::Err(e @ _) => {
|
||||||
|
// if it's a &str or String, just print it
|
||||||
|
if let Some(estr) = e.downcast_ref::<&str>() {
|
||||||
|
hexchat_print_str(ph, estr, false);
|
||||||
|
} else if let Some(estring) = e.downcast_ref::<String>() {
|
||||||
|
hexchat_print_str(ph, &estring, false);
|
||||||
|
}
|
||||||
|
// avoid double-free
|
||||||
|
if !alive.get() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// mark it no longer alive
|
||||||
|
alive.set(false);
|
||||||
|
// HexChat will automatically free the hook.
|
||||||
|
// we just need to free the userdata.
|
||||||
|
mem::drop(Rc::from_raw(ud as *const TimerHookUd));
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let alive = Rc::new(Cell::new(true));
|
||||||
|
let b: Rc<TimerHookUd> = Rc::new((Rc::new((Box::new(cb), self.ph, self.info)), alive.clone()));
|
||||||
|
let bp = Rc::into_raw(b);
|
||||||
|
unsafe {
|
||||||
|
let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as libc::c_int, callback, bp as *mut _);
|
||||||
|
assert!(!res.is_null());
|
||||||
|
TimerHookHandle { ph: self.ph, hh: res, alive, _f: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prints to the hexchat buffer.
|
/// Prints to the hexchat buffer.
|
||||||
// this checks the context internally. if it didn't, it wouldn't be safe to have here.
|
// this checks the context internally. if it didn't, it wouldn't be safe to have here.
|
||||||
|
@ -418,6 +646,15 @@ impl<'a> EnsureValidContext<'a> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a command.
|
||||||
|
pub fn command(self, cmd: &str) {
|
||||||
|
// this was a mistake but oh well
|
||||||
|
let ph = self.ph.ph;
|
||||||
|
unsafe {
|
||||||
|
((*ph).hexchat_command)(ph, CString::new(cmd).unwrap().as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn emit_print(self) {
|
pub fn emit_print(self) {
|
||||||
// TODO
|
// TODO
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -444,11 +681,27 @@ impl<'a> EnsureValidContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints to the hexchat buffer.
|
/// Prints to the hexchat buffer.
|
||||||
// TODO check if this triggers event hooks (which could call /close and invalidate the
|
// as of hexchat 2.14.1, this does not call hooks.
|
||||||
// context).
|
|
||||||
pub fn print(&mut self, s: &str) {
|
pub fn print(&mut self, s: &str) {
|
||||||
self.ph.print(s)
|
self.ph.print(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a command hook
|
||||||
|
pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe {
|
||||||
|
self.ph.hook_command(cmd, cb, pri, help)
|
||||||
|
}
|
||||||
|
/// 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 {
|
||||||
|
self.ph.hook_server(cmd, cb, pri)
|
||||||
|
}
|
||||||
|
/// 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 {
|
||||||
|
self.ph.hook_print(name, cb, pri)
|
||||||
|
}
|
||||||
|
/// Sets a timer hook
|
||||||
|
pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe {
|
||||||
|
self.ph.hook_timer(timeout, cb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******* //
|
// ******* //
|
||||||
|
@ -463,6 +716,12 @@ impl<'a> EnsureValidContext<'a> {
|
||||||
// poisoning could also be used. std doesn't have anything for single-threaded performance (yet),
|
// poisoning could also be used. std doesn't have anything for single-threaded performance (yet),
|
||||||
// but hexchat isn't particularly performance-critical.
|
// but hexchat isn't particularly performance-critical.
|
||||||
type CommandHookUd = (Box<Fn(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo);
|
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);
|
||||||
|
/// Userdata type used by a print hook.
|
||||||
|
type PrintHookUd = (Box<Fn(&mut PluginHandle, Word) -> 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>>);
|
||||||
|
|
||||||
/// The contents of an empty CStr.
|
/// The contents of an empty CStr.
|
||||||
///
|
///
|
||||||
|
@ -480,6 +739,20 @@ fn cstr(b: &'static [u8]) -> *const libc::c_char {
|
||||||
CStr::from_bytes_with_nul(b).unwrap().as_ptr()
|
CStr::from_bytes_with_nul(b).unwrap().as_ptr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clones an Rc straight from a raw pointer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because `ptr` must hame come from `Rc::into_raw`.
|
||||||
|
unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> {
|
||||||
|
// this is a bit confusing to read, but basically, we get an Rc from the raw ptr, and increment
|
||||||
|
// the refcount.
|
||||||
|
// The construct mem::forget(rc.clone()) increments the refcount.
|
||||||
|
let rc = Rc::from_raw(ptr);
|
||||||
|
mem::forget(rc.clone());
|
||||||
|
rc
|
||||||
|
}
|
||||||
|
|
||||||
/// Prints an &str to hexchat, trying to avoid allocations.
|
/// Prints an &str to hexchat, trying to avoid allocations.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|
Loading…
Add table
Reference in a new issue