(ptr: *const T) -> *mut T {
+ ptr as *mut T
+}
+pub trait IntoGlibPtr {
+ fn into_glib_full(self) -> *mut P;
+}
+
+impl IntoGlibPtr for P {
+ fn into_glib_full(mut self) -> *mut P::PtrType {
+ let ptr = self.as_mut_ptr();
+ std::mem::forget(self);
+ ptr
+ }
+}
+
+pub trait ToGlibContainerFromIterator
+where
+ Self: Sized,
+{
+ fn into_glib_full_from_iter>(iter: I) -> P;
+}
+
+impl ToGlibContainerFromIterator<*mut glib_sys::GList> for T
+where
+ T: AsPtr,
+ T: IntoGlibPtr,
+ P: 'static,
+{
+ fn into_glib_full_from_iter(iter: I) -> *mut glib_sys::GList
+ where
+ I: IntoIterator- ,
+ {
+ let mut list: *mut glib_sys::GList = std::ptr::null_mut();
+ unsafe {
+ for ptr in iter.into_iter().map(|v| v.into_glib_full()) {
+ list = glib_sys::g_list_prepend(list, Ptr::to(ptr));
+ }
+ glib_sys::g_list_reverse(list)
+ }
+ }
+}
+
+impl ToGlibContainerFromIterator<*mut glib_sys::GHashTable> for (&'static CStr, String) {
+ fn into_glib_full_from_iter(iter: I) -> *mut glib_sys::GHashTable
+ where
+ I: IntoIterator
- ,
+ {
+ unsafe {
+ let ptr = glib_sys::g_hash_table_new_full(
+ Some(glib_sys::g_str_hash),
+ Some(glib_sys::g_str_equal),
+ None,
+ Some(glib_sys::g_free),
+ );
+ for (k, v) in iter {
+ let k: *const c_char = k.as_ptr();
+ let v: *mut c_char = v.to_glib_full();
+ glib_sys::g_hash_table_insert(ptr, k as *mut _, v as *mut _);
+ }
+ ptr
+ }
+ }
+}
+
+pub trait AsPtr {
+ type PtrType;
+ fn as_ptr(&self) -> *const Self::PtrType;
+}
+
+pub trait AsMutPtr {
+ type PtrType;
+ fn as_mut_ptr(&mut self) -> *mut Self::PtrType;
+}
+
+impl AsMutPtr for T {
+ type PtrType = T::PtrType;
+ fn as_mut_ptr(&mut self) -> *mut Self::PtrType {
+ self.as_ptr() as *mut Self::PtrType
+ }
+}
+
+impl AsPtr for Option> {
+ type PtrType = T;
+ fn as_ptr(&self) -> *const Self::PtrType {
+ self.map_or_else(null, |x| x.as_ptr())
+ }
+}
+
+impl AsPtr for Option<*const T> {
+ type PtrType = T;
+ fn as_ptr(&self) -> *const T {
+ self.unwrap_or_else(null)
+ }
+}
+
+impl AsPtr for Option<*mut T> {
+ type PtrType = T;
+ fn as_ptr(&self) -> *const T {
+ self.unwrap_or_else(null_mut)
+ }
+}
diff --git a/src/purple/group.rs b/src/purple/group.rs
new file mode 100644
index 0000000..e4f0a39
--- /dev/null
+++ b/src/purple/group.rs
@@ -0,0 +1,40 @@
+use super::ffi::{AsMutPtr, AsPtr};
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::ptr::{null_mut, NonNull};
+
+pub struct Group(NonNull);
+
+impl Group {
+ pub unsafe fn from_ptr(ptr: *mut purple_sys::PurpleGroup) -> Option {
+ NonNull::new(ptr).map(Self)
+ }
+
+ pub fn find(name: &str) -> Option {
+ let c_name = CString::new(name).unwrap();
+ unsafe { Group::from_ptr(purple_sys::purple_find_group(c_name.as_ptr())) }
+ }
+
+ pub fn new(name: &str) -> Group {
+ let c_name = CString::new(name).unwrap();
+ unsafe { Group::from_ptr(purple_sys::purple_group_new(c_name.as_ptr())).unwrap() }
+ }
+
+ pub fn add_to_blist(&mut self, _node: Option<()>) {
+ unsafe { purple_sys::purple_blist_add_group(self.as_mut_ptr(), null_mut()) }
+ }
+
+ pub fn get_name(&mut self) -> String {
+ unsafe {
+ let name_ptr = purple_sys::purple_group_get_name(self.as_mut_ptr());
+ CStr::from_ptr(name_ptr).to_str().unwrap().to_string()
+ }
+ }
+}
+
+impl AsPtr for Group {
+ type PtrType = purple_sys::PurpleGroup;
+ fn as_ptr(&self) -> *const Self::PtrType {
+ self.0.as_ptr()
+ }
+}
diff --git a/src/purple/handlers/entrypoints.rs b/src/purple/handlers/entrypoints.rs
new file mode 100644
index 0000000..4663ded
--- /dev/null
+++ b/src/purple/handlers/entrypoints.rs
@@ -0,0 +1,297 @@
+use super::super::{prpl, Account, Connection, Plugin, PurpleMessageFlags, StrHashTable};
+use super::traits;
+use crate::purple::ffi::{IntoGlibPtr, ToGlibContainerFromIterator};
+use glib::translate::{ToGlibContainerFromSlice, ToGlibPtr};
+use log::{debug, error};
+use std::ffi::CStr;
+use std::os::raw::{c_char, c_void};
+use std::panic::catch_unwind;
+use std::ptr::NonNull;
+
+pub extern "C" fn actions(
+ _: *mut purple_sys::PurplePlugin,
+ _: *mut c_void,
+) -> *mut glib_sys::GList {
+ std::ptr::null_mut()
+}
+
+pub extern "C" fn load(plugin_ptr: *mut purple_sys::PurplePlugin) -> i32 {
+ match catch_unwind(|| {
+ debug!("load");
+ let mut plugin = unsafe { Plugin::from_raw(plugin_ptr) };
+ let prpl_plugin = unsafe { plugin.extra::
() };
+ prpl_plugin.load(&plugin) as i32
+ }) {
+ Ok(r) => r,
+ Err(_) => 0,
+ }
+}
+
+pub extern "C" fn login(account_ptr: *mut purple_sys::PurpleAccount) {
+ if let Err(error) = catch_unwind(|| {
+ debug!("login");
+ let mut account = unsafe { Account::from_raw(account_ptr) };
+ let mut plugin = account
+ .get_connection()
+ .expect("No connection found for account")
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ prpl_plugin.login(&mut account);
+ }) {
+ error!("Failure in login: {:?}", error)
+ };
+}
+
+pub extern "C" fn get_cb_alias(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ id: i32,
+ c_who: *const c_char,
+) -> *mut c_char {
+ match catch_unwind(|| {
+ debug!("get_cb_alias");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ let who = unsafe { CStr::from_ptr(c_who).to_str().unwrap() };
+ prpl_plugin
+ .get_cb_alias(&mut connection, id, who)
+ .as_ref()
+ .map(ToGlibPtr::to_glib_full)
+ .unwrap_or_else(std::ptr::null_mut)
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in get_cb_alias: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+
+pub extern "C" fn chat_info(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+) -> *mut glib_sys::GList {
+ match catch_unwind(|| {
+ debug!("chat_info");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ ToGlibContainerFromSlice::to_glib_full_from_slice(
+ &prpl_plugin
+ .chat_info(&mut connection)
+ .into_iter()
+ .map(|x| x.into())
+ .collect::>(),
+ )
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in chat_info: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+
+pub extern "C" fn close(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+) {
+ if let Err(error) = catch_unwind(|| {
+ debug!("close");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ prpl_plugin.close(&mut connection)
+ }) {
+ error!("Failure in close: {:?}", error)
+ }
+}
+
+pub extern "C" fn list_icon(
+ account_ptr: *mut purple_sys::PurpleAccount,
+ _: *mut purple_sys::PurpleBuddy,
+) -> *const c_char {
+ match catch_unwind(|| {
+ debug!("list_icon");
+ let mut account = unsafe { Account::from_raw(account_ptr) };
+ P::list_icon(&mut account).as_ptr()
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in list_icon: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+
+pub extern "C" fn status_types(
+ account_ptr: *mut purple_sys::PurpleAccount,
+) -> *mut glib_sys::GList {
+ match catch_unwind(|| {
+ debug!("status_types");
+ let mut account = unsafe { Account::from_raw(account_ptr) };
+ ToGlibContainerFromIterator::into_glib_full_from_iter(P::status_types(&mut account))
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in status_types: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+
+pub extern "C" fn join_chat(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ components: *mut glib_sys::GHashTable,
+) {
+ if let Err(error) = catch_unwind(|| {
+ debug!("join_chat");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ let mut data = unsafe { StrHashTable::from_ptr(components) };
+ prpl_plugin.join_chat(&mut connection, data.as_mut())
+ }) {
+ error!("Failure in join_chat: {:?}", error)
+ }
+}
+
+pub extern "C" fn chat_leave(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ id: i32,
+) {
+ if let Err(error) = catch_unwind(|| {
+ debug!("chat_leave");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ prpl_plugin.chat_leave(&mut connection, id)
+ }) {
+ error!("Failure in chat_leave: {:?}", error)
+ }
+}
+
+pub extern "C" fn convo_closed(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ c_who: *const c_char,
+) {
+ if let Err(error) = catch_unwind(|| {
+ debug!("convo_closed");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ let who = NonNull::new(c_who as *mut _)
+ .map(|p| unsafe { CStr::from_ptr(p.as_ptr()).to_str().unwrap() });
+ prpl_plugin.convo_closed(&mut connection, who)
+ }) {
+ error!("Failure in convo_closed: {:?}", error)
+ }
+}
+
+pub extern "C" fn get_chat_name(
+ data: *mut glib_sys::GHashTable,
+) -> *mut c_char {
+ match catch_unwind(|| {
+ debug!("get_chat_name");
+ let mut data = unsafe { StrHashTable::from_ptr(data) };
+ let name = P::get_chat_name(data.as_mut());
+ name.to_glib_full()
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in get_chat_name: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+
+pub extern "C" fn chat_info_defaults(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ c_chat_name: *const c_char,
+) -> *mut glib_sys::GHashTable {
+ match catch_unwind(|| {
+ debug!("chat_info_defaults_handler");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+
+ let chat_name = NonNull::new(c_chat_name as *mut _)
+ .map(|p| unsafe { CStr::from_ptr(p.as_ptr()).to_string_lossy() });
+ prpl_plugin
+ .chat_info_defaults(&mut connection, chat_name.as_deref())
+ .into_glib_full()
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in chat_info_defaults: {:?}", error);
+ std::ptr::null_mut()
+ }
+ }
+}
+pub extern "C" fn roomlist_get_list_handler(
+ _: *mut purple_sys::PurpleConnection,
+) -> *mut purple_sys::PurpleRoomlist {
+ println!("roomlist_get_list_handler");
+ std::ptr::null_mut()
+}
+
+pub extern "C" fn chat_send(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ id: i32,
+ c_message: *const c_char,
+ flags: PurpleMessageFlags,
+) -> i32 {
+ match catch_unwind(|| {
+ debug!("convo_closed");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ let message = unsafe { CStr::from_ptr(c_message).to_str().unwrap() };
+ prpl_plugin.chat_send(&mut connection, id, message, flags)
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in convo_closed: {:?}", error);
+ -1
+ }
+ }
+}
+pub extern "C" fn send_im(
+ connection_ptr: *mut purple_sys::PurpleConnection,
+ c_who: *const c_char,
+ c_message: *const c_char,
+ flags: PurpleMessageFlags,
+) -> i32 {
+ match catch_unwind(|| {
+ debug!("convo_closed");
+ let mut connection = unsafe { Connection::from_raw(connection_ptr).unwrap() };
+ let mut plugin = connection
+ .get_protocol_plugin()
+ .expect("No plugin found for connection");
+ let prpl_plugin = unsafe { plugin.extra::() };
+ let who = unsafe { CStr::from_ptr(c_who).to_str().unwrap() };
+ let message = unsafe { CStr::from_ptr(c_message).to_str().unwrap() };
+ prpl_plugin.send_im(&mut connection, who, message, flags)
+ }) {
+ Ok(r) => r,
+ Err(error) => {
+ error!("Failure in convo_closed: {:?}", error);
+ -1
+ }
+ }
+}
diff --git a/src/purple/handlers/mod.rs b/src/purple/handlers/mod.rs
new file mode 100644
index 0000000..d77d3f0
--- /dev/null
+++ b/src/purple/handlers/mod.rs
@@ -0,0 +1,2 @@
+pub mod entrypoints;
+pub mod traits;
diff --git a/src/purple/handlers/traits.rs b/src/purple/handlers/traits.rs
new file mode 100644
index 0000000..c6cecf5
--- /dev/null
+++ b/src/purple/handlers/traits.rs
@@ -0,0 +1,116 @@
+use super::super::{
+ prpl, Account, Connection, Conversation, Plugin, PurpleMessageFlags, StatusType, StrHashTable,
+};
+use std::ffi::CStr;
+
+pub trait LoadHandler {
+ fn load(&mut self, plugin: &Plugin) -> bool;
+}
+
+pub trait LoginHandler {
+ fn login(&mut self, account: &mut Account);
+}
+
+pub trait CloseHandler {
+ fn close(&mut self, connection: &mut Connection);
+}
+
+pub trait StatusTypeHandler {
+ fn status_types(account: &mut Account) -> Vec;
+}
+
+pub trait ListIconHandler {
+ fn list_icon(account: &mut Account) -> &'static CStr;
+}
+
+pub trait ChatInfoHandler {
+ fn chat_info(&mut self, connection: &mut Connection) -> Vec;
+}
+
+pub trait ChatInfoDefaultsHandler {
+ fn chat_info_defaults(
+ &mut self,
+ connection: &mut Connection,
+ chat_name: Option<&str>,
+ ) -> StrHashTable;
+}
+
+pub trait JoinChatHandler {
+ fn join_chat(&mut self, connection: &mut Connection, data: Option<&mut StrHashTable>);
+}
+
+pub trait ChatLeaveHandler {
+ fn chat_leave(&mut self, connection: &mut Connection, id: i32);
+}
+
+pub trait ConvoClosedHandler {
+ fn convo_closed(&mut self, connection: &mut Connection, who: Option<&str>);
+}
+
+pub trait GetChatBuddyAlias {
+ fn get_cb_alias(&mut self, connection: &mut Connection, id: i32, who: &str) -> Option;
+}
+
+pub trait GetChatNameHandler {
+ fn get_chat_name(data: Option<&mut StrHashTable>) -> Option;
+}
+
+pub trait SendIMHandler {
+ fn send_im(
+ &mut self,
+ connection: &mut Connection,
+ who: &str,
+ message: &str,
+ flags: PurpleMessageFlags,
+ ) -> i32;
+}
+
+pub trait ChatSendHandler {
+ fn chat_send(
+ &mut self,
+ connection: &mut Connection,
+ id: i32,
+ message: &str,
+ flags: PurpleMessageFlags,
+ ) -> i32;
+}
+
+pub trait InputHandler {
+ fn input(&mut self, fd: i32, cond: crate::purple::PurpleInputCondition);
+ fn enable_input(&mut self, fd: i32, cond: crate::purple::PurpleInputCondition) -> u32
+ where
+ Self: 'static,
+ {
+ let self_ptr: *mut Self = self;
+ crate::purple::input_add(fd, cond, move |fd, cond| {
+ let this = unsafe { &mut *self_ptr };
+ this.input(fd, cond);
+ })
+ }
+
+ fn disable_input(&self, input_handle: u32) -> bool {
+ unsafe { purple_sys::purple_input_remove(input_handle) != 0 }
+ }
+}
+
+pub trait CommandHandler {
+ fn command(
+ &mut self,
+ conversation: &mut Conversation,
+ command: &str,
+ args: &[&str],
+ ) -> purple_sys::PurpleCmdRet;
+ fn enable_command(&mut self, cmd: &str, args: &str, help_text: &str) -> purple_sys::PurpleCmdId
+ where
+ Self: 'static,
+ {
+ let self_ptr: *mut Self = self;
+ crate::purple::register_cmd(cmd, args, help_text, move |conversation, cmd, args| {
+ let this = unsafe { &mut *self_ptr };
+ this.command(conversation, cmd, args)
+ })
+ }
+ fn disable_command(&self, command_id: purple_sys::PurpleCmdId) {
+ unsafe { purple_sys::purple_cmd_unregister(command_id) }
+ }
+}
diff --git a/src/purple/hashtable.rs b/src/purple/hashtable.rs
new file mode 100644
index 0000000..c39e5a3
--- /dev/null
+++ b/src/purple/hashtable.rs
@@ -0,0 +1,71 @@
+use super::ffi::{mut_override, AsPtr};
+use glib::translate::{FromGlib, FromGlibPtrContainer, ToGlibPtr};
+use std::ffi::CStr;
+use std::os::raw::{c_char, c_void};
+use std::ptr::NonNull;
+
+pub type StrHashTable<'a> = HashTable<&'static CStr, &'a str>;
+
+pub struct HashTable(
+ NonNull,
+ std::marker::PhantomData<(K, V)>,
+);
+
+impl Default for HashTable<&'static CStr, &str> {
+ fn default() -> Self {
+ Self(
+ NonNull::new(unsafe {
+ glib_sys::g_hash_table_new_full(
+ Some(glib_sys::g_str_hash),
+ Some(glib_sys::g_str_equal),
+ None,
+ Some(glib_sys::g_free),
+ )
+ })
+ .unwrap(),
+ std::marker::PhantomData,
+ )
+ }
+}
+
+impl HashTable<&'static CStr, &str> {
+ pub unsafe fn from_ptr(ptr: *mut glib_sys::GHashTable) -> Option {
+ NonNull::new(ptr).map(|p| Self(p, std::marker::PhantomData))
+ }
+
+ pub fn insert(&mut self, key: &'static CStr, value: &str) -> bool {
+ FromGlib::from_glib(unsafe {
+ glib_sys::g_hash_table_insert(
+ self.0.as_ptr(),
+ key.as_ptr() as *mut c_void,
+ ToGlibPtr::<*mut c_char>::to_glib_full(value) as *mut c_void,
+ )
+ })
+ }
+
+ pub fn lookup(&self, key: &'static CStr) -> Option<&str> {
+ unsafe {
+ NonNull::new(glib_sys::g_hash_table_lookup(
+ mut_override(self.as_ptr()),
+ key.as_ptr() as *const c_void,
+ ) as *mut c_char)
+ .map(|p| CStr::from_ptr(p.as_ptr()).to_str().unwrap())
+ }
+ }
+}
+
+impl std::fmt::Debug for HashTable {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ use std::collections::HashMap;
+ let hashmap: HashMap =
+ unsafe { FromGlibPtrContainer::from_glib_none(self.0.as_ptr()) };
+ write!(f, "HashTable({:?})", hashmap)
+ }
+}
+
+impl AsPtr for HashTable {
+ type PtrType = glib_sys::GHashTable;
+ fn as_ptr(&self) -> *const Self::PtrType {
+ self.0.as_ptr()
+ }
+}
diff --git a/src/purple/loader.rs b/src/purple/loader.rs
new file mode 100644
index 0000000..10c5ab6
--- /dev/null
+++ b/src/purple/loader.rs
@@ -0,0 +1,138 @@
+use super::handlers::entrypoints;
+use crate::purple::PrplPlugin;
+use log::info;
+use std::ffi::CString;
+use std::marker::PhantomData;
+use std::os::raw::c_void;
+
+#[derive(Default)]
+pub struct RegisterContext {
+ info: Box,
+ extra_info: Box,
+ _phantom_marker: PhantomData,
+}
+
+impl
RegisterContext
{
+ pub fn new() -> Self {
+ RegisterContext {
+ info: Box::new(purple_sys::PurplePluginInfo::default()),
+ extra_info: Box::new(purple_sys::PurplePluginProtocolInfo::default()),
+ _phantom_marker: PhantomData,
+ }
+ }
+ pub fn into_raw(mut self) -> *mut purple_sys::PurplePluginInfo {
+ self.extra_info.roomlist_get_list = Some(entrypoints::roomlist_get_list_handler);
+
+ self.info.extra_info = Box::into_raw(self.extra_info) as *mut c_void;
+
+ Box::into_raw(self.info)
+ }
+
+ pub fn with_info(mut self, info: PrplInfo) -> Self {
+ self.info.id = CString::new(info.id).unwrap().into_raw();
+ self.info.name = CString::new(info.name).unwrap().into_raw();
+ self.info.version = CString::new(info.version).unwrap().into_raw();
+ self.info.summary = CString::new(info.summary).unwrap().into_raw();
+ self.info.description = CString::new(info.description).unwrap().into_raw();
+ self.info.author = CString::new(info.author).unwrap().into_raw();
+ self.info.homepage = CString::new(info.homepage).unwrap().into_raw();
+ self.info.actions = Some(entrypoints::actions);
+ self
+ }
+}
+
+pub struct PrplPluginLoader(*mut purple_sys::PurplePlugin, PhantomData);
+
+impl PrplPluginLoader {
+ pub unsafe fn from_raw(ptr: *mut purple_sys::PurplePlugin) -> Self {
+ Self(ptr, PhantomData)
+ }
+
+ pub fn init(&self) -> i32 {
+ let prpl_plugin = Box::new(P::new());
+ let register_context: RegisterContext = RegisterContext::new();
+ let register_context = prpl_plugin.register(register_context);
+
+ // Unsafe required to dereference the pointers and call
+ // purple_plugin_register. Safe otherwise.
+ unsafe {
+ (*self.0).info = register_context.into_raw();
+ (*self.0).extra = Box::into_raw(prpl_plugin) as *mut c_void;
+
+ info!("Registering");
+ purple_sys::purple_plugin_register(self.0)
+ }
+ }
+}
+
+pub struct PrplInfo {
+ pub id: String,
+ pub name: String,
+ pub version: String,
+ pub summary: String,
+ pub description: String,
+ pub author: String,
+ pub homepage: String,
+}
+
+impl Default for PrplInfo {
+ fn default() -> Self {
+ PrplInfo {
+ id: "".into(),
+ name: "".into(),
+ version: "".into(),
+ summary: "".into(),
+ description: "".into(),
+ author: "".into(),
+ homepage: "".into(),
+ }
+ }
+}
+
+macro_rules! impl_handler_builder {
+ ($($f:ident => $t:ident)*) => ($(
+ paste::item! {
+ impl]> RegisterContext {
+ #[allow(dead_code)]
+ pub fn [](mut self) -> Self {
+ self.info.[<$f>] = Some(crate::purple::handlers::entrypoints::[<$f>]::);
+ self
+ }
+ }
+ }
+ )*)
+}
+
+macro_rules! impl_extra_handler_builder {
+ ($($f:ident => $t:ident)*) => ($(
+ paste::item! {
+ impl]> RegisterContext {
+ #[allow(dead_code)]
+ pub fn [](mut self) -> Self {
+ self.extra_info.[<$f>] = Some(crate::purple::handlers::entrypoints::[<$f>]::);
+ self
+ }
+ }
+ }
+ )*)
+}
+
+impl_handler_builder! {
+ load => LoadHandler
+}
+
+impl_extra_handler_builder! {
+ login => LoginHandler
+ chat_info => ChatInfoHandler
+ chat_info_defaults => ChatInfoDefaultsHandler
+ close => CloseHandler
+ status_types => StatusTypeHandler
+ list_icon => ListIconHandler
+ join_chat => JoinChatHandler
+ chat_leave => ChatLeaveHandler
+ convo_closed => ConvoClosedHandler
+ get_chat_name => GetChatNameHandler
+ send_im => SendIMHandler
+ chat_send => ChatSendHandler
+// get_cb_alias => GetChatBuddyAlias
+}
diff --git a/src/purple/mod.rs b/src/purple/mod.rs
new file mode 100644
index 0000000..10dd319
--- /dev/null
+++ b/src/purple/mod.rs
@@ -0,0 +1,150 @@
+pub use self::account::Account;
+pub use self::blist::BlistNode;
+pub use self::chat::Chat;
+pub use self::connection::protocol_data::ProtocolData;
+pub use self::connection::{Connection, Connections, Handle};
+pub use self::conversation::Conversation;
+pub use self::group::Group;
+pub use self::handlers::traits::*;
+pub use self::hashtable::StrHashTable;
+pub use self::loader::{PrplInfo, PrplPluginLoader, RegisterContext};
+pub use self::plugin::Plugin;
+pub use self::status_type::{PurpleStatusPrimitive, StatusType};
+use glib::translate::FromGlibPtrContainer;
+pub use purple_sys;
+pub use purple_sys::{
+ PurpleCmdId, PurpleCmdRet, PurpleConnectionError, PurpleConnectionState,
+ PurpleConvChatBuddyFlags, PurpleConversationType, PurpleDebugLevel, PurpleInputCondition,
+ PurpleMessageFlags,
+};
+use std::ffi::{CStr, CString};
+use std::os::raw::{c_char, c_void};
+use std::panic::catch_unwind;
+use std::ptr;
+
+lazy_static::lazy_static! {
+ static ref STR_FORMAT: CString = CString::new("%s").unwrap();
+}
+
+pub mod account;
+mod blist;
+mod chat;
+mod connection;
+mod conversation;
+pub mod ffi;
+mod group;
+mod handlers;
+mod hashtable;
+mod loader;
+mod plugin;
+pub mod prpl;
+mod status_type;
+
+pub trait PrplPlugin {
+ type Plugin;
+ fn new() -> Self;
+ fn register(&self, context: RegisterContext) -> RegisterContext;
+}
+
+macro_rules! purple_prpl_plugin {
+ ($plugin:ty) => {
+ /// # Safety
+ /// This function is the plugin entrypoints and should not be called manually.
+ #[no_mangle]
+ pub unsafe extern "C" fn purple_init_plugin(
+ plugin_ptr: *mut purple_sys::PurplePlugin,
+ ) -> i32 {
+ // Safe as long as called from libpurple. Should be the
+ // case since this function is called by libpurple.
+ let plugin = purple::PrplPluginLoader::<$plugin>::from_raw(plugin_ptr);
+ plugin.init()
+ }
+ };
+}
+
+pub fn input_add(fd: i32, cond: PurpleInputCondition, callback: F) -> u32
+where
+ F: Fn(i32, PurpleInputCondition) + 'static,
+{
+ let user_data = Box::into_raw(Box::new(callback)) as *mut c_void;
+ unsafe { purple_sys::purple_input_add(fd, cond, Some(trampoline::), user_data) }
+}
+
+unsafe extern "C" fn trampoline(user_data: *mut c_void, df: i32, cond: PurpleInputCondition)
+where
+ F: Fn(i32, PurpleInputCondition),
+{
+ if let Err(error) = catch_unwind(|| {
+ let closure = &*(user_data as *mut F);
+ closure(df, cond);
+ }) {
+ log::error!("Failure in input handler: {:?}", error);
+ }
+}
+
+pub fn register_cmd(
+ cmd: &str,
+ args: &str,
+ help_text: &str,
+ callback: F,
+) -> purple_sys::PurpleCmdId
+where
+ F: Fn(&mut Conversation, &str, &[&str]) -> purple_sys::PurpleCmdRet + 'static,
+{
+ let user_data = Box::into_raw(Box::new(callback)) as *mut c_void;
+ let c_cmd = CString::new(cmd).unwrap();
+ let c_args = CString::new(args).unwrap();
+ let c_help = CString::new(help_text).unwrap();
+
+ unsafe {
+ purple_sys::purple_cmd_register(
+ c_cmd.as_ptr(),
+ c_args.as_ptr(),
+ purple_sys::PurpleCmdPriority::PURPLE_CMD_P_DEFAULT,
+ purple_sys::PurpleCmdFlag::PURPLE_CMD_FLAG_CHAT,
+ ptr::null(),
+ Some(trampoline_cmd::),
+ c_help.as_ptr(),
+ user_data,
+ )
+ }
+}
+
+unsafe extern "C" fn trampoline_cmd(
+ conversation_ptr: *mut purple_sys::PurpleConversation,
+ c_cmd: *const c_char,
+ c_args: *mut *mut c_char,
+ _c_error: *mut *mut c_char,
+ user_data: *mut c_void,
+) -> purple_sys::PurpleCmdRet
+where
+ F: Fn(&mut Conversation, &str, &[&str]) -> purple_sys::PurpleCmdRet,
+{
+ let closure = &*(user_data as *mut F);
+
+ let cmd = CStr::from_ptr(c_cmd).to_str().unwrap();
+ let args: Vec = FromGlibPtrContainer::from_glib_none(c_args);
+ let mut conversation = Conversation::from_ptr(conversation_ptr).unwrap();
+
+ closure(
+ &mut conversation,
+ cmd,
+ args.iter()
+ .map(std::ops::Deref::deref)
+ .collect::>()
+ .as_slice(),
+ )
+}
+
+pub fn debug(level: PurpleDebugLevel, target: &str, message: &str) {
+ let c_target = CString::new(target).unwrap();
+ let c_message = CString::new(message).unwrap();
+ unsafe {
+ purple_sys::purple_debug(
+ level,
+ c_target.as_ptr(),
+ STR_FORMAT.as_ptr(),
+ c_message.as_ptr(),
+ )
+ }
+}
diff --git a/src/purple/plugin.rs b/src/purple/plugin.rs
new file mode 100644
index 0000000..d6a3cd4
--- /dev/null
+++ b/src/purple/plugin.rs
@@ -0,0 +1,11 @@
+pub struct Plugin(*mut purple_sys::PurplePlugin);
+
+impl Plugin {
+ pub unsafe fn from_raw(ptr: *mut purple_sys::PurplePlugin) -> Self {
+ Plugin(ptr)
+ }
+
+ pub unsafe fn extra<'a, T>(&mut self) -> &'a mut T {
+ &mut *((*self.0).extra as *mut T)
+ }
+}
diff --git a/src/purple/prpl.rs b/src/purple/prpl.rs
new file mode 100644
index 0000000..3f6885e
--- /dev/null
+++ b/src/purple/prpl.rs
@@ -0,0 +1,50 @@
+use glib::translate::{GlibPtrDefault, Stash, ToGlib, ToGlibPtr};
+use std::ffi::CStr;
+
+pub struct ChatEntry {
+ pub label: &'static CStr,
+ pub identifier: &'static CStr,
+ pub required: bool,
+ pub is_int: bool,
+ pub min: i32,
+ pub max: i32,
+ pub secret: bool,
+}
+
+pub struct ProtoChatEntry(purple_sys::proto_chat_entry);
+
+impl Into for ChatEntry {
+ fn into(self) -> ProtoChatEntry {
+ ProtoChatEntry(purple_sys::proto_chat_entry {
+ label: self.label.as_ptr(),
+ identifier: self.identifier.as_ptr(),
+ required: self.required.to_glib(),
+ is_int: self.is_int.to_glib(),
+ min: self.min,
+ max: self.max,
+ secret: self.secret.to_glib(),
+ })
+ }
+}
+
+impl GlibPtrDefault for ProtoChatEntry {
+ type GlibType = *const purple_sys::proto_chat_entry;
+}
+
+impl<'a> ToGlibPtr<'a, *const purple_sys::proto_chat_entry> for ProtoChatEntry {
+ type Storage = &'a ProtoChatEntry;
+
+ fn to_glib_none(&'a self) -> Stash<'a, *const purple_sys::proto_chat_entry, Self> {
+ Stash(&self.0 as *const purple_sys::proto_chat_entry, self)
+ }
+
+ fn to_glib_full(&self) -> *const purple_sys::proto_chat_entry {
+ unsafe {
+ let res = glib_sys::g_malloc(std::mem::size_of::())
+ as *mut purple_sys::proto_chat_entry;
+
+ *res = self.0;
+ res
+ }
+ }
+}
diff --git a/src/purple/status_type.rs b/src/purple/status_type.rs
new file mode 100644
index 0000000..112afef
--- /dev/null
+++ b/src/purple/status_type.rs
@@ -0,0 +1,31 @@
+use super::ffi::{mut_override, AsPtr};
+use glib::translate::ToGlib;
+pub use purple_sys::PurpleStatusPrimitive;
+use std::ffi::CStr;
+
+pub struct StatusType(*mut purple_sys::PurpleStatusType);
+
+impl StatusType {
+ pub fn new(
+ primitive: PurpleStatusPrimitive,
+ id: Option<&'static CStr>,
+ name: Option<&'static CStr>,
+ user_settable: bool,
+ ) -> Self {
+ unsafe {
+ Self(purple_sys::purple_status_type_new(
+ primitive,
+ id.map_or_else(std::ptr::null_mut, |s| mut_override(s.as_ptr())),
+ name.map_or_else(std::ptr::null_mut, |s| mut_override(s.as_ptr())),
+ user_settable.to_glib(),
+ ))
+ }
+ }
+}
+
+impl AsPtr for StatusType {
+ type PtrType = purple_sys::PurpleStatusType;
+ fn as_ptr(&self) -> *const Self::PtrType {
+ self.0
+ }
+}