/* signals.c : irssi Copyright (C) 1999-2002 Timo Sirainen 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" #include "signals.h" #include "modules.h" #define SIGNAL_LISTS 3 typedef struct _SignalHook { struct _SignalHook *next; int priority; const char *module; SIGNAL_FUNC func; void *user_data; } SignalHook; typedef struct { int id; /* signal id */ int refcount; int emitting; /* signal is being emitted */ int stop_emit; /* this signal was stopped */ int continue_emit; /* this signal emit was continued elsewhere */ int remove_count; /* hooks were removed from signal */ SignalHook *hooks; } Signal; #define signal_is_emitlist_empty(a) \ (!(a)->siglist[0] && !(a)->siglist[1] && !(a)->siglist[2]) void *signal_user_data; static GHashTable *signals; static Signal *current_emitted_signal; static SignalHook *current_emitted_hook; #define signal_ref(signal) ++(signal)->refcount #define signal_unref(signal) (signal_unref_full(signal, TRUE)) static int signal_unref_full(Signal *rec, int remove) { g_assert(rec->refcount > 0); if (--rec->refcount != 0) return TRUE; /* remove whole signal from memory */ if (rec->hooks != NULL) { g_error("signal_unref(%s) : BUG - hook list wasn't empty", signal_get_id_str(rec->id)); } if (remove) g_hash_table_remove(signals, GINT_TO_POINTER(rec->id)); g_free(rec); return FALSE; } static void signal_hash_ref(void *key, Signal *rec) { signal_ref(rec); } static int signal_hash_unref(void *key, Signal *rec) { return !signal_unref_full(rec, FALSE); } void signal_add_full(const char *module, int priority, const char *signal, SIGNAL_FUNC func, void *user_data) { signal_add_full_id(module, priority, signal_get_uniq_id(signal), func, user_data); } /* bind a signal */ void signal_add_full_id(const char *module, int priority, int signal_id, SIGNAL_FUNC func, void *user_data) { Signal *signal; SignalHook *hook, **tmp; g_return_if_fail(signal_id >= 0); g_return_if_fail(func != NULL); signal = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); if (signal == NULL) { /* new signal */ signal = g_new0(Signal, 1); signal->id = signal_id; g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), signal); } hook = g_new0(SignalHook, 1); hook->priority = priority; hook->module = module; hook->func = func; hook->user_data = user_data; /* insert signal to proper position in list */ for (tmp = &signal->hooks; ; tmp = &(*tmp)->next) { if (*tmp == NULL) { /* last in list */ *tmp = hook; break; } else if (priority <= (*tmp)->priority) { /* insert before others with same priority */ hook->next = *tmp; *tmp = hook; break; } } signal_ref(signal); } static void signal_remove_hook(Signal *rec, SignalHook **hook_pos) { SignalHook *hook; hook = *hook_pos; *hook_pos = hook->next; g_free(hook); signal_unref(rec); } /* Remove function from signal's emit list */ static int signal_remove_func(Signal *rec, SIGNAL_FUNC func, void *user_data) { SignalHook **hook; for (hook = &rec->hooks; *hook != NULL; hook = &(*hook)->next) { if ((*hook)->func == func && (*hook)->user_data == user_data) { if (rec->emitting) { /* mark it removed after emitting is done */ (*hook)->func = NULL; rec->remove_count++; } else { /* remove the function from emit list */ signal_remove_hook(rec, hook); } return TRUE; } } return FALSE; } void signal_remove_id(int signal_id, SIGNAL_FUNC func, void *user_data) { Signal *rec; g_return_if_fail(signal_id >= 0); g_return_if_fail(func != NULL); rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); if (rec != NULL) signal_remove_func(rec, func, user_data); } /* unbind signal */ void signal_remove_full(const char *signal, SIGNAL_FUNC func, void *user_data) { g_return_if_fail(signal != NULL); signal_remove_id(signal_get_uniq_id(signal), func, user_data); } static void signal_hooks_clean(Signal *rec) { SignalHook **hook, **next; int count; count = rec->remove_count; rec->remove_count = 0; for (hook = &rec->hooks; *hook != NULL; hook = next) { next = &(*hook)->next; if ((*hook)->func == NULL) { next = hook; signal_remove_hook(rec, hook); if (--count == 0) break; } } } static int signal_emit_real(Signal *rec, int params, va_list va, SignalHook *first_hook) { const void *arglist[SIGNAL_MAX_ARGUMENTS]; Signal *prev_emitted_signal; SignalHook *hook, *prev_emitted_hook; int i, stopped, stop_emit_count, continue_emit_count; for (i = 0; i < SIGNAL_MAX_ARGUMENTS; i++) arglist[i] = i >= params ? NULL : va_arg(va, const void *); /* signal_stop_by_name("signal"); signal_emit("signal", ...); fails if we compare rec->stop_emit against 0. */ stop_emit_count = rec->stop_emit; continue_emit_count = rec->continue_emit; signal_ref(rec); stopped = FALSE; rec->emitting++; prev_emitted_signal = current_emitted_signal; prev_emitted_hook = current_emitted_hook; current_emitted_signal = rec; for (hook = first_hook; hook != NULL; hook = hook->next) { if (hook->func == NULL) continue; /* removed */ current_emitted_hook = hook; #if SIGNAL_MAX_ARGUMENTS != 6 # error SIGNAL_MAX_ARGUMENTS changed - update code #endif signal_user_data = hook->user_data; hook->func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5]); if (rec->continue_emit != continue_emit_count) rec->continue_emit--; if (rec->stop_emit != stop_emit_count) { stopped = TRUE; rec->stop_emit--; break; } } current_emitted_signal = prev_emitted_signal; current_emitted_hook = prev_emitted_hook; rec->emitting--; signal_user_data = NULL; if (!rec->emitting) { g_assert(rec->stop_emit == 0); g_assert(rec->continue_emit == 0); if (rec->remove_count > 0) signal_hooks_clean(rec); } signal_unref(rec); return stopped; } int signal_emit(const char *signal, int params, ...) { Signal *rec; va_list va; int signal_id; g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE); signal_id = signal_get_uniq_id(signal); rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); if (rec != NULL) { va_start(va, params); signal_emit_real(rec, params, va, rec->hooks); va_end(va); } return rec != NULL; } int signal_emit_id(int signal_id, int params, ...) { Signal *rec; va_list va; g_return_val_if_fail(signal_id >= 0, FALSE); g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE); rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); if (rec != NULL) { va_start(va, params); signal_emit_real(rec, params, va, rec->hooks); va_end(va); } return rec != NULL; } void signal_continue(int params, ...) { Signal *rec; va_list va; rec = current_emitted_signal; if (rec == NULL || rec->emitting <= rec->continue_emit) g_warning("signal_continue() : no signals are being emitted currently"); else { va_start(va, params); /* stop the signal */ if (rec->emitting > rec->stop_emit) rec->stop_emit++; /* re-emit */ rec->continue_emit++; signal_emit_real(rec, params, va, current_emitted_hook->next); va_end(va); } } /* stop the current ongoing signal emission */ void signal_stop(void) { Signal *rec; rec = current_emitted_signal; if (rec == NULL) g_warning("signal_stop() : no signals are being emitted currently"); else if (rec->emitting > rec->stop_emit) rec->stop_emit++; } /* stop ongoing signal emission by signal name */ void signal_stop_by_name(const char *signal) { Signal *rec; int signal_id; signal_id = signal_get_uniq_id(signal); rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); if (rec == NULL) g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal); else if (rec->emitting > rec->stop_emit) rec->stop_emit++; } /* return the name of the signal that is currently being emitted */ const char *signal_get_emitted(void) { return signal_get_id_str(signal_get_emitted_id()); } /* return the ID of the signal that is currently being emitted */ int signal_get_emitted_id(void) { Signal *rec; rec = current_emitted_signal; g_return_val_if_fail(rec != NULL, -1); return rec->id; } /* return TRUE if specified signal was stopped */ int signal_is_stopped(int signal_id) { Signal *rec; rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); g_return_val_if_fail(rec != NULL, FALSE); return rec->emitting <= rec->stop_emit; } static void signal_remove_module(void *signal, Signal *rec, const char *module) { SignalHook **hook, **next; for (hook = &rec->hooks; *hook != NULL; hook = next) { next = &(*hook)->next; if (strcasecmp((*hook)->module, module) == 0) { next = hook; signal_remove_hook(rec, hook); } } } /* remove all signals that belong to `module' */ void signals_remove_module(const char *module) { g_return_if_fail(module != NULL); g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL); g_hash_table_foreach(signals, (GHFunc) signal_remove_module, (void *) module); g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL); } void signals_init(void) { signals = g_hash_table_new(NULL, NULL); } static void signal_free(void *key, Signal *rec) { /* refcount-1 because we just referenced it ourself */ g_warning("signal_free(%s) : signal still has %d references:", signal_get_id_str(rec->id), rec->refcount-1); while (rec->hooks != NULL) { g_warning(" - module '%s' function %p", rec->hooks->module, rec->hooks->func); signal_remove_hook(rec, &rec->hooks); } } void signals_deinit(void) { g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL); g_hash_table_foreach(signals, (GHFunc) signal_free, NULL); g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL); g_hash_table_destroy(signals); module_uniq_destroy("signals"); }