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}