summaryrefslogtreecommitdiffstats
path: root/src/core/signals.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/signals.c')
-rw-r--r--src/core/signals.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/core/signals.c b/src/core/signals.c
new file mode 100644
index 0000000..e52e690
--- /dev/null
+++ b/src/core/signals.c
@@ -0,0 +1,439 @@
+/*
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/modules.h>
+
+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;
+
+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");
+}