1 /*
2 signals.c : irssi
3
4 Copyright (C) 1999-2002 Timo Sirainen
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "modules.h"
24
25 typedef struct _SignalHook {
26 struct _SignalHook *next;
27
28 int priority;
29 const char *module;
30 SIGNAL_FUNC func;
31 void *user_data;
32 } SignalHook;
33
34 typedef struct {
35 int id; /* signal id */
36 int refcount;
37
38 int emitting; /* signal is being emitted */
39 int stop_emit; /* this signal was stopped */
40 int continue_emit; /* this signal emit was continued elsewhere */
41 int remove_count; /* hooks were removed from signal */
42
43 SignalHook *hooks;
44 } Signal;
45
46 void *signal_user_data;
47
48 static GHashTable *signals;
49 static Signal *current_emitted_signal;
50 static SignalHook *current_emitted_hook;
51
52 #define signal_ref(signal) ++(signal)->refcount
53 #define signal_unref(signal) (signal_unref_full(signal, TRUE))
54
signal_unref_full(Signal * rec,int remove)55 static int signal_unref_full(Signal *rec, int remove)
56 {
57 g_assert(rec->refcount > 0);
58
59 if (--rec->refcount != 0)
60 return TRUE;
61
62 /* remove whole signal from memory */
63 if (rec->hooks != NULL) {
64 g_error("signal_unref(%s) : BUG - hook list wasn't empty",
65 signal_get_id_str(rec->id));
66 }
67
68 if (remove)
69 g_hash_table_remove(signals, GINT_TO_POINTER(rec->id));
70 g_free(rec);
71
72 return FALSE;
73 }
74
signal_hash_ref(void * key,Signal * rec)75 static void signal_hash_ref(void *key, Signal *rec)
76 {
77 signal_ref(rec);
78 }
79
signal_hash_unref(void * key,Signal * rec)80 static int signal_hash_unref(void *key, Signal *rec)
81 {
82 return !signal_unref_full(rec, FALSE);
83 }
84
signal_add_full(const char * module,int priority,const char * signal,SIGNAL_FUNC func,void * user_data)85 void signal_add_full(const char *module, int priority,
86 const char *signal, SIGNAL_FUNC func, void *user_data)
87 {
88 signal_add_full_id(module, priority, signal_get_uniq_id(signal),
89 func, user_data);
90 }
91
92 /* bind a signal */
signal_add_full_id(const char * module,int priority,int signal_id,SIGNAL_FUNC func,void * user_data)93 void signal_add_full_id(const char *module, int priority,
94 int signal_id, SIGNAL_FUNC func, void *user_data)
95 {
96 Signal *signal;
97 SignalHook *hook, **tmp;
98
99 g_return_if_fail(signal_id >= 0);
100 g_return_if_fail(func != NULL);
101
102 signal = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
103 if (signal == NULL) {
104 /* new signal */
105 signal = g_new0(Signal, 1);
106 signal->id = signal_id;
107 g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), signal);
108 }
109
110 hook = g_new0(SignalHook, 1);
111 hook->priority = priority;
112 hook->module = module;
113 hook->func = func;
114 hook->user_data = user_data;
115
116 /* insert signal to proper position in list */
117 for (tmp = &signal->hooks; ; tmp = &(*tmp)->next) {
118 if (*tmp == NULL) {
119 /* last in list */
120 *tmp = hook;
121 break;
122 } else if (priority <= (*tmp)->priority) {
123 /* insert before others with same priority */
124 hook->next = *tmp;
125 *tmp = hook;
126 break;
127 }
128 }
129
130 signal_ref(signal);
131 }
132
signal_remove_hook(Signal * rec,SignalHook ** hook_pos)133 static void signal_remove_hook(Signal *rec, SignalHook **hook_pos)
134 {
135 SignalHook *hook;
136
137 hook = *hook_pos;
138 *hook_pos = hook->next;
139
140 g_free(hook);
141
142 signal_unref(rec);
143 }
144
145 /* Remove function from signal's emit list */
signal_remove_func(Signal * rec,SIGNAL_FUNC func,void * user_data)146 static int signal_remove_func(Signal *rec, SIGNAL_FUNC func, void *user_data)
147 {
148 SignalHook **hook;
149
150 for (hook = &rec->hooks; *hook != NULL; hook = &(*hook)->next) {
151 if ((*hook)->func == func && (*hook)->user_data == user_data) {
152 if (rec->emitting) {
153 /* mark it removed after emitting is done */
154 (*hook)->func = NULL;
155 rec->remove_count++;
156 } else {
157 /* remove the function from emit list */
158 signal_remove_hook(rec, hook);
159 }
160 return TRUE;
161 }
162 }
163
164 return FALSE;
165 }
166
signal_remove_id(int signal_id,SIGNAL_FUNC func,void * user_data)167 void signal_remove_id(int signal_id, SIGNAL_FUNC func, void *user_data)
168 {
169 Signal *rec;
170
171 g_return_if_fail(signal_id >= 0);
172 g_return_if_fail(func != NULL);
173
174 rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
175 if (rec != NULL)
176 signal_remove_func(rec, func, user_data);
177 }
178
179 /* unbind signal */
signal_remove_full(const char * signal,SIGNAL_FUNC func,void * user_data)180 void signal_remove_full(const char *signal, SIGNAL_FUNC func, void *user_data)
181 {
182 g_return_if_fail(signal != NULL);
183
184 signal_remove_id(signal_get_uniq_id(signal), func, user_data);
185 }
186
signal_hooks_clean(Signal * rec)187 static void signal_hooks_clean(Signal *rec)
188 {
189 SignalHook **hook, **next;
190 int count;
191
192 count = rec->remove_count;
193 rec->remove_count = 0;
194
195 for (hook = &rec->hooks; *hook != NULL; hook = next) {
196 next = &(*hook)->next;
197
198 if ((*hook)->func == NULL) {
199 next = hook;
200 signal_remove_hook(rec, hook);
201
202 if (--count == 0)
203 break;
204 }
205 }
206 }
207
signal_emit_real(Signal * rec,int params,va_list va,SignalHook * first_hook)208 static int signal_emit_real(Signal *rec, int params, va_list va,
209 SignalHook *first_hook)
210 {
211 const void *arglist[SIGNAL_MAX_ARGUMENTS];
212 Signal *prev_emitted_signal;
213 SignalHook *hook, *prev_emitted_hook;
214 int i, stopped, stop_emit_count, continue_emit_count;
215
216 for (i = 0; i < SIGNAL_MAX_ARGUMENTS; i++)
217 arglist[i] = i >= params ? NULL : va_arg(va, const void *);
218
219 /* signal_stop_by_name("signal"); signal_emit("signal", ...);
220 fails if we compare rec->stop_emit against 0. */
221 stop_emit_count = rec->stop_emit;
222 continue_emit_count = rec->continue_emit;
223
224 signal_ref(rec);
225
226 stopped = FALSE;
227 rec->emitting++;
228
229 prev_emitted_signal = current_emitted_signal;
230 prev_emitted_hook = current_emitted_hook;
231 current_emitted_signal = rec;
232
233 for (hook = first_hook; hook != NULL; hook = hook->next) {
234 if (hook->func == NULL)
235 continue; /* removed */
236
237 current_emitted_hook = hook;
238 #if SIGNAL_MAX_ARGUMENTS != 6
239 # error SIGNAL_MAX_ARGUMENTS changed - update code
240 #endif
241 signal_user_data = hook->user_data;
242 hook->func(arglist[0], arglist[1], arglist[2], arglist[3],
243 arglist[4], arglist[5]);
244
245 if (rec->continue_emit != continue_emit_count)
246 rec->continue_emit--;
247
248 if (rec->stop_emit != stop_emit_count) {
249 stopped = TRUE;
250 rec->stop_emit--;
251 break;
252 }
253 }
254
255 current_emitted_signal = prev_emitted_signal;
256 current_emitted_hook = prev_emitted_hook;
257
258 rec->emitting--;
259 signal_user_data = NULL;
260
261 if (!rec->emitting) {
262 g_assert(rec->stop_emit == 0);
263 g_assert(rec->continue_emit == 0);
264
265 if (rec->remove_count > 0)
266 signal_hooks_clean(rec);
267 }
268
269 signal_unref(rec);
270 return stopped;
271 }
272
signal_emit(const char * signal,int params,...)273 int signal_emit(const char *signal, int params, ...)
274 {
275 Signal *rec;
276 va_list va;
277 int signal_id;
278
279 g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
280
281 signal_id = signal_get_uniq_id(signal);
282
283 rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
284 if (rec != NULL) {
285 va_start(va, params);
286 signal_emit_real(rec, params, va, rec->hooks);
287 va_end(va);
288 }
289
290 return rec != NULL;
291 }
292
signal_emit_id(int signal_id,int params,...)293 int signal_emit_id(int signal_id, int params, ...)
294 {
295 Signal *rec;
296 va_list va;
297
298 g_return_val_if_fail(signal_id >= 0, FALSE);
299 g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
300
301 rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
302 if (rec != NULL) {
303 va_start(va, params);
304 signal_emit_real(rec, params, va, rec->hooks);
305 va_end(va);
306 }
307
308 return rec != NULL;
309 }
310
signal_continue(int params,...)311 void signal_continue(int params, ...)
312 {
313 Signal *rec;
314 va_list va;
315
316 rec = current_emitted_signal;
317 if (rec == NULL || rec->emitting <= rec->continue_emit)
318 g_warning("signal_continue() : no signals are being emitted currently");
319 else {
320 va_start(va, params);
321
322 /* stop the signal */
323 if (rec->emitting > rec->stop_emit)
324 rec->stop_emit++;
325
326 /* re-emit */
327 rec->continue_emit++;
328 signal_emit_real(rec, params, va, current_emitted_hook->next);
329 va_end(va);
330 }
331 }
332
333 /* stop the current ongoing signal emission */
signal_stop(void)334 void signal_stop(void)
335 {
336 Signal *rec;
337
338 rec = current_emitted_signal;
339 if (rec == NULL)
340 g_warning("signal_stop() : no signals are being emitted currently");
341 else if (rec->emitting > rec->stop_emit)
342 rec->stop_emit++;
343 }
344
345 /* stop ongoing signal emission by signal name */
signal_stop_by_name(const char * signal)346 void signal_stop_by_name(const char *signal)
347 {
348 Signal *rec;
349 int signal_id;
350
351 signal_id = signal_get_uniq_id(signal);
352 rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
353 if (rec == NULL)
354 g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal);
355 else if (rec->emitting > rec->stop_emit)
356 rec->stop_emit++;
357 }
358
359 /* return the name of the signal that is currently being emitted */
signal_get_emitted(void)360 const char *signal_get_emitted(void)
361 {
362 return signal_get_id_str(signal_get_emitted_id());
363 }
364
365 /* return the ID of the signal that is currently being emitted */
signal_get_emitted_id(void)366 int signal_get_emitted_id(void)
367 {
368 Signal *rec;
369
370 rec = current_emitted_signal;
371 g_return_val_if_fail(rec != NULL, -1);
372 return rec->id;
373 }
374
375 /* return TRUE if specified signal was stopped */
signal_is_stopped(int signal_id)376 int signal_is_stopped(int signal_id)
377 {
378 Signal *rec;
379
380 rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
381 g_return_val_if_fail(rec != NULL, FALSE);
382
383 return rec->emitting <= rec->stop_emit;
384 }
385
signal_remove_module(void * signal,Signal * rec,const char * module)386 static void signal_remove_module(void *signal, Signal *rec,
387 const char *module)
388 {
389 SignalHook **hook, **next;
390
391 for (hook = &rec->hooks; *hook != NULL; hook = next) {
392 next = &(*hook)->next;
393
394 if (strcasecmp((*hook)->module, module) == 0) {
395 next = hook;
396 signal_remove_hook(rec, hook);
397 }
398 }
399 }
400
401 /* remove all signals that belong to `module' */
signals_remove_module(const char * module)402 void signals_remove_module(const char *module)
403 {
404 g_return_if_fail(module != NULL);
405
406 g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
407 g_hash_table_foreach(signals, (GHFunc) signal_remove_module,
408 (void *) module);
409 g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
410 }
411
signals_init(void)412 void signals_init(void)
413 {
414 signals = g_hash_table_new(NULL, NULL);
415 }
416
signal_free(void * key,Signal * rec)417 static void signal_free(void *key, Signal *rec)
418 {
419 /* refcount-1 because we just referenced it ourself */
420 g_warning("signal_free(%s) : signal still has %d references:",
421 signal_get_id_str(rec->id), rec->refcount-1);
422
423 while (rec->hooks != NULL) {
424 g_warning(" - module '%s' function %p",
425 rec->hooks->module, rec->hooks->func);
426
427 signal_remove_hook(rec, &rec->hooks);
428 }
429 }
430
signals_deinit(void)431 void signals_deinit(void)
432 {
433 g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
434 g_hash_table_foreach(signals, (GHFunc) signal_free, NULL);
435 g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
436 g_hash_table_destroy(signals);
437
438 module_uniq_destroy("signals");
439 }
440