diff options
author | SoniEx2 <endermoneymod@gmail.com> | 2022-04-26 00:06:29 -0300 |
---|---|---|
committer | SoniEx2 <endermoneymod@gmail.com> | 2022-04-26 00:06:29 -0300 |
commit | 25fde2f4231ae5c651d2e7a3a4d17868dd0d9c1a (patch) | |
tree | 42ebe008ef1f08e630aec329e539ee89f94810b8 /src | |
parent | f9f854aa51e384358d935740bd64cf4f6d3057c5 (diff) |
Add support for lists
Diffstat (limited to 'src')
-rw-r--r-- | src/internals.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 61 | ||||
-rw-r--r-- | src/list.rs | 413 |
3 files changed, 460 insertions, 16 deletions
diff --git a/src/internals.rs b/src/internals.rs index e3b18fa..dc93aaa 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -118,7 +118,7 @@ pub struct Ph { string: *mut *const libc::c_char, integer: *mut libc::c_int) -> libc::c_int, pub hexchat_list_get: unsafe extern "C" fn(ph: *mut HexchatPlugin, - name: *const libc::c_char) -> *const HexchatList, + name: *const libc::c_char) -> *mut HexchatList, pub hexchat_list_free: unsafe extern "C" fn(ph: *mut HexchatPlugin, xlist: *const HexchatList), pub hexchat_list_fields: unsafe extern "C" fn(ph: *mut HexchatPlugin, diff --git a/src/lib.rs b/src/lib.rs index e7db70b..0852a24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,22 +188,35 @@ extern crate impl_trait; #[doc(hidden)] pub extern crate libc; +// private macros + +/// Calls a function on a PluginHandle struct. +macro_rules! ph_call { + ($f:ident($ph:expr, $($args:expr),*)) => { + ((*$ph.data.ph).$f)($ph.plugin, $($args),*) + }; + ($f:ident($ph:expr $(,)?)) => { + ((*$ph.data.ph).$f)($ph.plugin) + }; +} + mod eat; mod extra_tests; mod infoid; mod internals; +pub mod list; mod pluginfo; mod strip; mod word; pub use eat::*; pub use infoid::InfoId; -pub use word::*; pub use strip::*; +pub use word::*; -use pluginfo::PluginInfo; -use internals::Ph as RawPh; use internals::HexchatEventAttrs as RawAttrs; +use internals::Ph as RawPh; +use pluginfo::PluginInfo; use std::borrow::Cow; use std::cell::Cell; @@ -227,18 +240,6 @@ use std::time::{SystemTime, UNIX_EPOCH, Duration}; #[doc(hidden)] pub use libc::{c_char, c_int, c_void, time_t}; -// private macros - -/// Calls a function on a PluginHandle struct. -macro_rules! ph_call { - ($f:ident($ph:expr, $($args:expr),*)) => { - ((*$ph.data.ph).$f)($ph.plugin, $($args),*) - }; - ($f:ident($ph:expr $(,)?)) => { - ((*$ph.data.ph).$f)($ph.plugin) - }; -} - // ****** // // PUBLIC // // ****** // @@ -1520,6 +1521,36 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> { } } + /// Retrieves a list. + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_unsafe_plugin::list::Contexts; + /// use hexchat_unsafe_plugin::PluginHandle; + /// + /// fn print_contexts(ph: &mut PluginHandle<'_>) { + /// ph.ensure_valid_context(|ph| { + /// for context in ph.list(&Contexts) { + /// write!(ph, "{}", context.name().unwrap()); + /// } + /// }) + /// } + /// ``` + pub fn list<T: list::List>(&'a self, t: &'a T) -> list::Entries<'a, 'ph, T> { + let ph = &self.ph; + let list = CString::new(&*t.name()).unwrap(); + let list = unsafe { + ph_call!(hexchat_list_get(ph, list.as_ptr())) + }; + list::Entries { + context: self, + list: list, + t: t, + valid: Default::default(), + } + } + // ******** // // FORWARDS // // ******** // diff --git a/src/list.rs b/src/list.rs new file mode 100644 index 0000000..257ef95 --- /dev/null +++ b/src/list.rs @@ -0,0 +1,413 @@ +// This file is part of Hexchat Plugin API Bindings for Rust +// Copyright (C) 2022 Soni L. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! List support module. + +use std::borrow::Cow; +use std::cell::Cell; +use std::ffi::CStr; +use std::rc::Rc; + +use crate::Context; +use crate::wrap_context; + +mod sealed { + pub trait Sealed { + } +} + +/// A list. This trait is sealed. +pub trait List: sealed::Sealed { + fn name(&self) -> Cow<'static, str>; +} + +// List types + +/// A "channels" list. +pub struct Contexts; + +/// A "dcc" list. +pub struct Dcc; + +/// An "ignore" list. +pub struct Ignore; + +/// A "notify" list. +pub struct Notify; + +/// An "users" list. +pub struct Users; + +/// Entries. +pub struct Entries<'a, 'ph, T> { + pub(crate) context: &'a crate::ValidContext<'a, 'ph>, + pub(crate) list: *mut crate::internals::HexchatList, + pub(crate) t: &'a T, + pub(crate) valid: Rc<Cell<usize>>, +} + +/// Fields. +pub struct Fields<'a, 'ph, T> { + context: &'a crate::ValidContext<'a, 'ph>, + list: *mut crate::internals::HexchatList, + _t: &'a T, + id: usize, + valid: Rc<Cell<usize>>, +} + +// impls + +impl sealed::Sealed for Contexts {} +impl sealed::Sealed for Dcc {} +impl sealed::Sealed for Ignore {} +impl sealed::Sealed for Notify {} +impl sealed::Sealed for Users {} + +impl List for Contexts { + fn name(&self) -> Cow<'static, str> { "channels".into() } +} + +impl List for Dcc { + fn name(&self) -> Cow<'static, str> { "dcc".into() } +} + +impl List for Ignore { + fn name(&self) -> Cow<'static, str> { "ignore".into() } +} + +impl List for Notify { + fn name(&self) -> Cow<'static, str> { "notify".into() } +} + +impl List for Users { + fn name(&self) -> Cow<'static, str> { "users".into() } +} + +impl<'a, 'ph, T> Drop for Entries<'a, 'ph, T> { + fn drop(&mut self) { + let ph = &self.context.ph; + unsafe { + ph_call!(hexchat_list_free(ph, self.list)); + } + } +} + +impl<'a, 'ph, T> Iterator for Entries<'a, 'ph, T> { + type Item = Fields<'a, 'ph, T>; + + fn next(&mut self) -> Option<Self::Item> { + let ph = &self.context.ph; + let id = self.valid.get(); + if unsafe { + ph_call!(hexchat_list_next(ph, self.list)) + } != 0 { + debug_assert!(self.valid.get() < usize::MAX); + self.valid.set(id + 1); + Some(Fields { + context: self.context, + list: self.list, + _t: self.t, + valid: Rc::clone(&self.valid), + id: id, + }) + } else { + self.valid.set(usize::MAX); + None + } + } +} + +macro_rules! field { + ($(#[$m:meta])* $f:ident, unsafe {$n:expr}, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => { + $(#[$m])* pub fn $f(&self) -> $r { + assert_eq!( + self.id, self.valid.get(), + "instances of Fields are invalidated by next()", + ); + let $ph = &self.context.ph; + const NAME: &'static CStr = unsafe { + CStr::from_bytes_with_nul_unchecked( + $n.as_bytes() + ) + }; + match unsafe { + ph_call!($c($ph, self.list, NAME.as_ptr())) + } { + $($conv)+ + } + } + }; + ($(#[$m:meta])* $f:ident, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => { + field!( + $(#[$m])* $f, + unsafe { ::std::concat!(::std::stringify!($f), "\0") }, + $r, + $ph, + $c, + $($conv)+ + ); + }; +} + +macro_rules! field_int { + ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => { + field!($(#[$m])* $f, unsafe { $n }, i32, ph, hexchat_list_int, r => r as i32); + }; + ($(#[$m:meta])* $f:ident) => { + field_int!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") }); + }; +} + +macro_rules! field_bool { + ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => { + field!($(#[$m])* $f, unsafe { $n }, bool, ph, hexchat_list_int, r => r != 0); + }; + ($(#[$m:meta])* $f:ident) => { + field_bool!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") }); + }; +} + +macro_rules! field_str { + ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => { + field!( + $(#[$m])* $f, unsafe { $n }, Option<String>, ph, hexchat_list_str, + r if r.is_null() => { + None + }, + r => Some(unsafe { + CStr::from_ptr(r) + }.to_owned().into_string().unwrap()) + ); + }; + ($(#[$m:meta])* $f:ident) => { + field_str!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") }); + }; +} + + +macro_rules! field_time { + ($(#[$m:meta])* $f:ident) => { + $(#[$m])* pub fn $f(&self) -> ! { + todo!() + } + }; +} + +/// Contexts fields. +impl<'a, 'ph> Fields<'a, 'ph, Contexts> { + field_str!( + /// The context's name. + name, unsafe { "channel" } + ); + field_str!( + /// The channel key. + channelkey + ); + field_str!( + /// The server's channel modes. Requires HexChat 2.12.2+. + chanmodes + ); + field_str!( + /// The server's channel types. + chantypes + ); + field!( + /// The context. + context, Context<'ph>, ph, hexchat_list_str, r => unsafe { + wrap_context(ph, r as *const _) + } + ); + field_int!( + /// Raw flags about this context. + raw_flags, unsafe { "flags" } + ); + field_int!( + /// The server ID. + server_id, unsafe { "id" } + ); + field_int!( + /// The latency to server in milliseconds. + lag + ); + field_int!( + /// The maximum number of mode changes per line accepted by the server. + maxmodes + ); + field_str!( + /// The network name. + network + ); + field_str!( + /// The server's nick prefixes. + nickprefixes + ); + field_str!( + /// The server's nick modes. + nickmodes + ); + field_int!( + /// The current length of the send-queue, in bytes. + queue_len, unsafe { "queue" } + ); + field_str!( + /// The server's name. + server + ); + field_int!( + /// The context's raw type. + raw_type, unsafe { "type" } + ); + field_int!( + /// The number of users in the channel. + users + ); +} + +/// Dcc fields. +impl<'a, 'ph> Fields<'a, 'ph, Dcc> { + field_int!( + /// The raw 32-bit IPv4 address of the remote user. + address32 + ); + field_int!( + /// The transfer rate (speed), in bytes per second. + rate, unsafe { "cps" } + ); + field_str!( + /// The destination file path. + destfile + ); + field_str!( + /// The filename. + file + ); + field_str!( + /// The remote user's nick. + nick + ); + field_int!( + /// The listener's port number. + port + ); + field_int!( + /// File position, LSB 32 bits. + raw_pos, unsafe { "pos" } + ); + field_int!( + /// File position, MSB 32 bits. + raw_poshigh, unsafe { "poshigh" } + ); + field_int!( + /// Resumed position, LSB 32 bits. + raw_resume, unsafe { "resume" } + ); + field_int!( + /// Resumed position, MSB 32 bits. + raw_resumehigh, unsafe { "resumehigh" } + ); + field_int!( + /// File size, LSB 32 bits. + raw_size, unsafe { "size" } + ); + field_int!( + /// File size, MSB 32 bits. + raw_sizehigh, unsafe { "sizehigh" } + ); + field_int!( + /// Raw DCC status. + raw_status, unsafe { "status" } + ); + field_int!( + /// Raw type. + raw_type, unsafe { "type" } + ); +} + +/// Ignore fields. +impl<'a, 'ph> Fields<'a, 'ph, Ignore> { + field_str!( + /// The ignore mask. + mask + ); + field_int!( + /// Raw ignore flags. + raw_flags, unsafe { "flags" } + ); +} + +/// Notify fields. +impl<'a, 'ph> Fields<'a, 'ph, Notify> { + field_str!( + /// Comma-separated list of networks this notify entry applies to. + networks + ); + field_str!( + /// The nick. + nick + ); + field_int!( + /// Raw flags. + raw_flags, unsafe { "flags" } + ); + field_time!( + /// Time when the user went online. [NYI] + on + ); + field_time!( + /// Time when the user went offline. [NYI] + off + ); + field_time!( + /// Time when the user was last seen. [NYI] + seen + ); +} + +/// Users fields. +impl<'a, 'ph> Fields<'a, 'ph, Users> { + field_str!( + /// Account name. + account + ); + field_bool!( + /// Whether the user is away. + away + ); + field_time!( + /// Time when the user last talked. [NYI] + lasttalk + ); + field_str!( + /// User's nick. + nick + ); + field_str!( + /// User's user and hostname (`user@host`). + host + ); + field_str!( + /// User's prefix. + prefix + ); + field_str!( + /// User's userdata. + userdata, unsafe { "realname" } + ); + field_bool!( + /// Whether the user is selected in the UI. + selected + ); +} |