1use libc::{c_char, c_int};
2use std::{borrow::Cow, cell::Cell, ffi::CStr, marker::PhantomData, os::raw::c_void, ptr, rc::Rc};
3
4use weechat_sys::{t_gui_buffer, t_weechat_plugin};
5
6use super::Hook;
7use crate::{
8 buffer::{Buffer, InnerBuffer, InnerBuffers},
9 LossyCString, ReturnCode, Weechat,
10};
11
12pub struct SignalHook {
14 _hook: Hook,
15 _hook_data: Box<SignalHookData>,
16}
17
18struct SignalHookData {
19 callback: Box<dyn SignalCallback>,
20 weechat_ptr: *mut t_weechat_plugin,
21}
22
23#[non_exhaustive]
25pub enum SignalData<'a> {
26 String(Cow<'a, str>),
28 Integer(i32),
30 Buffer(Buffer<'a>),
32}
33
34impl<'a> Into<SignalData<'a>> for &'a str {
35 fn into(self) -> SignalData<'a> {
36 SignalData::String(Cow::from(self))
37 }
38}
39
40impl<'a> Into<SignalData<'a>> for String {
41 fn into(self) -> SignalData<'a> {
42 SignalData::String(Cow::from(self))
43 }
44}
45
46impl<'a> Into<SignalData<'a>> for i32 {
47 fn into(self) -> SignalData<'a> {
48 SignalData::Integer(self)
49 }
50}
51
52impl<'a> Into<SignalData<'a>> for Buffer<'a> {
53 fn into(self) -> SignalData<'a> {
54 SignalData::Buffer(self)
55 }
56}
57
58impl<'a> Into<SignalData<'a>> for &'a Buffer<'a> {
59 fn into(self) -> SignalData<'a> {
60 let ptr = self.ptr();
61
62 SignalData::Buffer(Buffer {
63 inner: InnerBuffers::BorrowedBuffer(InnerBuffer {
64 ptr,
65 weechat: self.inner.weechat_ptr(),
66 weechat_phantom: PhantomData,
67 closing: Rc::new(Cell::new(false)),
68 }),
69 })
70 }
71}
72
73impl<'a> SignalData<'a> {
74 fn pointer_is_buffer(signal_name: &str) -> bool {
75 match signal_name {
79 "irc_channel_opened" | "irc_pv_opened" | "irc_server_opened" => true,
80
81 "logger_start" | "logger_stop" | "logger_backlog" => true,
82
83 "spell_suggest" => true,
84
85 "buffer_opened" | "buffer_closing" | "buffer_closed" | "buffer_cleared" => true,
86
87 "buffer_filters_enabled"
88 | "buffer_filters_disabled"
89 | "buffer_hidden"
90 | "buffer_unhidden" => true,
91
92 "buffer_lines_hidden"
93 | "buffer_localvar_added"
94 | "buffer_localvar_changed"
95 | "buffer_localvar_removed"
96 | "buffer_merged"
97 | "buffer_unmerged"
98 | "buffer_moved"
99 | "buffer_renamed"
100 | "buffer_switch"
101 | "buffer_title_changed"
102 | "buffer_type_changed" => true,
103
104 "buffer_zoomed" | "buffer_unzoomed" => true,
105
106 "hotlist_changed" => true,
107
108 "input_search" | "input_text_changed" | "input_text_cursor_moved" => true,
109
110 _ => false,
116 }
117 }
118
119 fn from_type_and_name(
120 weechat: &'a Weechat,
121 signal_name: &str,
122 data_type: &str,
123 data: *mut c_void,
124 ) -> Option<SignalData<'a>> {
125 if data.is_null() {
129 return None;
130 }
131
132 match data_type {
133 "string" => unsafe {
134 Some(SignalData::String(
135 CStr::from_ptr(data as *const c_char).to_string_lossy(),
136 ))
137 },
138 "integer" => {
139 let data = data as *const c_int;
140 unsafe { Some(SignalData::Integer(*(data))) }
141 }
142 "pointer" => {
143 if SignalData::pointer_is_buffer(signal_name) {
144 Some(SignalData::Buffer(
145 weechat.buffer_from_ptr(data as *mut t_gui_buffer),
146 ))
147 } else {
148 None
149 }
150 }
151 _ => None,
152 }
153 }
154}
155
156pub trait SignalCallback {
161 fn callback(
172 &mut self,
173 weechat: &Weechat,
174 signal_name: &str,
175 data: Option<SignalData>,
176 ) -> ReturnCode;
177}
178
179impl<T: FnMut(&Weechat, &str, Option<SignalData>) -> ReturnCode + 'static> SignalCallback for T {
180 fn callback(
181 &mut self,
182 weechat: &Weechat,
183 signal_name: &str,
184 data: Option<SignalData>,
185 ) -> ReturnCode {
186 self(weechat, signal_name, data)
187 }
188}
189
190impl SignalHook {
191 pub fn new(signal_name: &str, callback: impl SignalCallback + 'static) -> Result<Self, ()> {
228 unsafe extern "C" fn c_hook_cb(
229 pointer: *const c_void,
230 _data: *mut c_void,
231 signal_name: *const c_char,
232 data_type: *const c_char,
233 signal_data: *mut c_void,
234 ) -> c_int {
235 let hook_data: &mut SignalHookData = { &mut *(pointer as *mut SignalHookData) };
236 let cb = &mut hook_data.callback;
237
238 let data_type = CStr::from_ptr(data_type).to_str().unwrap_or_default();
239 let signal_name = CStr::from_ptr(signal_name).to_str().unwrap_or_default();
240
241 let weechat = Weechat::from_ptr(hook_data.weechat_ptr);
242 let data =
243 SignalData::from_type_and_name(&weechat, signal_name, data_type, signal_data);
244
245 cb.callback(&weechat, signal_name, data) as i32
246 }
247
248 Weechat::check_thread();
249 let weechat = unsafe { Weechat::weechat() };
250
251 let data = Box::new(SignalHookData {
252 callback: Box::new(callback),
253 weechat_ptr: weechat.ptr,
254 });
255
256 let data_ref = Box::leak(data);
257 let hook_signal = weechat.get().hook_signal.unwrap();
258
259 let signal_name = LossyCString::new(signal_name);
260
261 let hook_ptr = unsafe {
262 hook_signal(
263 weechat.ptr,
264 signal_name.as_ptr(),
265 Some(c_hook_cb),
266 data_ref as *const _ as *const c_void,
267 ptr::null_mut(),
268 )
269 };
270
271 let hook_data = unsafe { Box::from_raw(data_ref) };
272 let hook = Hook {
273 ptr: hook_ptr,
274 weechat_ptr: weechat.ptr,
275 };
276
277 if hook_ptr.is_null() {
278 Err(())
279 } else {
280 Ok(SignalHook {
281 _hook: hook,
282 _hook_data: hook_data,
283 })
284 }
285 }
286}
287
288impl Weechat {
289 pub fn hook_signal_send<'a, D: Into<SignalData<'a>>>(signal_name: &str, data: D) -> ReturnCode {
318 Weechat::check_thread();
319 let weechat = unsafe { Weechat::weechat() };
320
321 let signal_name = LossyCString::new(signal_name);
322 let signal_send = weechat.get().hook_signal_send.unwrap();
323 let data = data.into();
324
325 let ret = if let SignalData::String(string) = data {
326 let string = LossyCString::new(string);
327 unsafe {
328 signal_send(
329 signal_name.as_ptr(),
330 weechat_sys::WEECHAT_HOOK_SIGNAL_STRING as *const _ as *const i8,
331 string.as_ptr() as *mut _,
332 )
333 }
334 } else {
335 let (ptr, data_type) = match data {
336 SignalData::Integer(number) => (
337 number as *mut _,
338 weechat_sys::WEECHAT_HOOK_SIGNAL_INT as *const u8,
339 ),
340 SignalData::Buffer(buffer) => (
341 buffer.ptr() as *mut _,
342 weechat_sys::WEECHAT_HOOK_SIGNAL_POINTER as *const u8,
343 ),
344 SignalData::String(_) => unreachable!(),
345 };
346 unsafe { signal_send(signal_name.as_ptr(), data_type as *const i8, ptr) }
347 };
348
349 match ret {
350 weechat_sys::WEECHAT_RC_OK => ReturnCode::Ok,
351 weechat_sys::WEECHAT_RC_OK_EAT => ReturnCode::OkEat,
352 weechat_sys::WEECHAT_RC_ERROR => ReturnCode::Error,
353 _ => ReturnCode::Error,
354 }
355 }
356}