hexchat_api/
context.rs

1
2//! Objects of the `Context` class represent Hexchat contexts, which are
3//! associated with channels the user is currently in. These are usually
4//! associated with an open window/tab in their client GUI. The `Context`
5//! objects provide a convenient set of commands that mirror those in the
6//! main `Hexchat` class, but when executed they perform their operation in
7//! the window/tab/channel that the `Context` is bound to. The network and
8//! server strings are used internally to acquire context pointers, which
9//! are then used to switch context for a command operation and switch back
10//! to the previously active context. On each command a check is performed to
11//! ensure the `Context` is still valid. If that fails a `AcquisitionFailed`
12//! error is returned with the network/channel strings as data.
13
14use std::fmt;
15use std::ffi::CString;
16use std::rc::Rc;
17#[cfg(feature = "threadsafe")]
18use std::thread;
19
20#[cfg(feature = "threadsafe")]
21use crate::MAIN_THREAD_ID;
22use crate::errors::HexchatError;
23use crate::hexchat::{Hexchat, hexchat_context};
24use crate::hexchat_entry_points::PHEXCHAT;
25use crate::list_iterator::ListIterator;
26use crate::utils::*;
27
28//use ContextError::*;
29//use HexchatError::*;
30use HexchatError::ContextAcquisitionFailed; 
31use HexchatError::ContextOperationFailed;
32
33#[derive(Debug)]
34struct ContextData {
35    hc          : &'static Hexchat,
36    network     : CString,
37    channel     : CString,
38}
39/// Any channel in Hexchat has an associated IRC network name and channel name.
40/// The network name and channel name are closely associated with the Hexchat
41/// concept of contexts. Hexchat contexts can also be thought of as the
42/// tabs, or windows, open in the UI that have the user joined to their various
43/// "chat rooms". To access a specific chat window in Hexchat, its context
44/// can be acquired and used. This library's `Context` objects represent the
45/// Hexchat contexts and can be used to interact with the specific
46/// channels/windows/tabs that he user has open. For instance if your plugin
47/// needs to output only to specific channels, rather than the default window
48/// (which is the one currently open) - it can acquire the appropriate context
49/// using `Context::find("some-network", "some-channel")`, and use the object
50/// returned to invoke a command, `context.command("SAY hello!")`, or print,
51/// `context.print("Hello!")`, or perform other operations.
52///
53#[derive(Clone)]
54pub struct Context {
55    data    : Rc<ContextData>,
56}
57
58impl Context {
59    /// This will create a new `Context` object holding an internal pointer to
60    /// the requested network/channel, if it exists. The object will be
61    /// returned as a `Some<Context>` if the context is found, or `None` if
62    /// not.
63    /// 
64    pub fn find(network: &str, channel: &str) -> Option<Self> {
65        #[cfg(feature = "threadsafe")]
66        assert!(thread::current().id() == unsafe { MAIN_THREAD_ID.unwrap() },
67                "Context::find() must be called from the Hexchat main thread.");
68        let csnetwork = str2cstring(network);
69        let cschannel = str2cstring(channel);
70        let hc = unsafe { &*PHEXCHAT };
71        let context_ptr;
72        unsafe {
73            context_ptr = (hc.c_find_context)(hc,
74                                              csnetwork.as_ptr(),
75                                              cschannel.as_ptr());
76        }
77        if !context_ptr.is_null() {
78            let ctx = Context {
79                data: Rc::new(
80                    ContextData {
81                        hc,
82                        network  : csnetwork,
83                        channel  : cschannel,
84                    })};
85            Some(ctx)
86        } else {
87            None
88        }
89    }
90
91    /// This will create a new `Context` that represents the currently active
92    /// context (window/tab, channel/network) open on the user's screen. An
93    /// `Context` object is returned, or `None` if it couldn't be obtained.
94    /// 
95    pub fn get() -> Option<Self> {
96        #[cfg(feature = "threadsafe")]
97        assert!(thread::current().id() == unsafe { MAIN_THREAD_ID.unwrap() },
98                "Context::get() must be called from the Hexchat main thread.");
99        unsafe {
100            let hc = &*PHEXCHAT;
101            let ctx_ptr = (hc.c_get_context)(hc);
102            if !ctx_ptr.is_null() {
103                let nwstr = str2cstring("network");
104                let chstr = str2cstring("channel");
105                let network = (hc.c_get_info)(hc, nwstr.as_ptr());
106                let channel = (hc.c_get_info)(hc, chstr.as_ptr());
107                let ctx = Context {
108                    data: Rc::new(
109                        ContextData {
110                            hc,
111                            network  : pchar2cstring(network),
112                            channel  : pchar2cstring(channel),
113                        })
114                };
115                Some(ctx)
116            } else{
117                None
118            }
119        }
120    }
121
122    /// Private method to try and acquire a context pointer for a `Context`
123    /// object. Contexts can go bad in Hexchat: if the user shuts a tab/window
124    /// or leaves a channel, using a context associated with that channel
125    /// is no longer valid. Or the Hexchat client could disconnect; in which
126    /// case, using old context pointers can cause unexpected problems.
127    /// So `Context` objects need to reacquire the pointer for each command
128    /// invocation. If successful, `Ok(ptr)` is returned with the pointer value;
129    /// `AcquisitionFailed(network, channel)` otherwise.
130    /// 
131    #[inline]
132    fn acquire(&self) -> Result<*const hexchat_context, HexchatError> {
133        let data = &*self.data;
134        let ptr = unsafe {
135            (data.hc.c_find_context)(data.hc,
136                                     data.network.as_ptr(),
137                                     data.channel.as_ptr())
138        };
139        if !ptr.is_null() {
140            Ok(ptr)
141        } else {
142            let msg = format!("{}, {}", 
143                              cstring2string(&data.network),
144                              cstring2string(&data.channel));
145            Err(ContextAcquisitionFailed(msg))
146        }
147    }
148
149    /// Sets the currently active context to the context the `Context` object
150    /// points to internally.
151    ///
152    pub fn set(&self) -> Result<(), HexchatError> {
153        let data = &*self.data;
154        unsafe {
155            let ptr = self.acquire()?;
156            if (data.hc.c_set_context)(data.hc, ptr) > 0 {
157                Ok(())
158            } else { Err(ContextOperationFailed(".set() failed.".into())) }
159        }
160    }
161
162    /// Prints the message to the `Context` object's Hexchat context. This is
163    /// how messages can be printed to Hexchat windows apart from the currently
164    /// active one.
165    ///
166    pub fn print(&self, message: &str) -> Result<(), HexchatError> {
167        let data = &*self.data;
168        unsafe {
169            let ptr = self.acquire()?;
170            let prior = (data.hc.c_get_context)(data.hc);
171            (data.hc.c_set_context)(data.hc, ptr);
172            data.hc.print(message);
173            (data.hc.c_set_context)(data.hc, prior);
174            Ok(())
175        }
176    }
177
178    /// Issues a print event to the context held by the `Context` object.
179    ///
180    pub fn emit_print(&self, event_name: &str, var_args: &[&str])
181        -> Result<(), HexchatError>
182    {
183        let data = &*self.data;
184        unsafe {
185            let ptr = self.acquire()?;
186            let prior = (data.hc.c_get_context)(data.hc);
187            (data.hc.c_set_context)(data.hc, ptr);
188            let result = data.hc.emit_print(event_name, var_args);
189            (data.hc.c_set_context)(data.hc, prior);
190            result?;
191            Ok(())
192        }
193    }
194
195    /// Issues a command in the context held by the `Context` object.
196    ///
197    pub fn command(&self, command: &str) -> Result<(), HexchatError> {
198        let data = &*self.data;
199        unsafe {
200            let ptr = self.acquire()?;
201            let prior  = (data.hc.c_get_context)(data.hc);
202            (data.hc.c_set_context)(data.hc, ptr);
203            data.hc.command(command);
204            (data.hc.c_set_context)(data.hc, prior);
205            Ok(())
206        }
207    }
208
209    /// Gets information from the channel/window that the `Context` object
210    /// holds an internal pointer to.
211    ///
212    pub fn get_info(&self, list: &str) -> Result<String, HexchatError> {
213        use HexchatError::*;
214        let data = &*self.data;
215        unsafe {
216            let ptr = self.acquire()?;
217            let prior = (data.hc.c_get_context)(data.hc);
218            (data.hc.c_set_context)(data.hc, ptr);
219            let result = data.hc.get_info(list);
220            (data.hc.c_set_context)(data.hc, prior);
221            result.ok_or_else(|| InfoNotFound(list.to_string()))
222        }
223    }
224
225    /// Gets a `ListIterator` from the context held by the `Context` object.
226    ///
227    pub fn list_get(&self, list: &str)
228        -> Result<ListIterator, HexchatError>
229    {
230        use HexchatError::ListNotFound;
231        let data = &*self.data;
232        unsafe {
233            let ptr = self.acquire()?;
234            let prior = (data.hc.c_get_context)(data.hc);
235            (data.hc.c_set_context)(data.hc, ptr);
236            let iter = ListIterator::new(list);
237            (data.hc.c_set_context)(data.hc, prior);
238            iter.ok_or_else(|| ListNotFound(list.to_string()))
239        }
240    }
241
242    /// Returns the network name associated with the `Context` object.
243    /// 
244    pub fn network(&self) -> String {
245        cstring2string(&self.data.network)
246    }
247
248    /// Returns the channel name associated with the `Context` object.
249    /// 
250    pub fn channel(&self) -> String {
251        cstring2string(&self.data.channel)
252    }
253}
254
255impl fmt::Display for Context {
256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257        write!(f, "{:?}", self)
258    }
259}
260
261impl fmt::Debug for Context {
262    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263        let data    = &*self.data;
264        let network = cstring2string(&data.network);
265        let channel = cstring2string(&data.channel);
266
267        write!(f, "Context(\"{}\", \"{}\")", network, channel)
268    }
269}