--- /dev/null
+noinst_LIBRARIES = libcore.a
+
+INCLUDES = \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DMODULEDIR=\""$(libdir)/irssi/modules"\" \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/core
+
+if BUILD_MEMDEBUG
+memdebug_src=memdebug.c
+else
+memdebug_src=
+endif
+
+libcore_a_SOURCES = \
+ args.c \
+ channels.c \
+ channels-setup.c \
+ commands.c \
+ chat-commands.c \
+ chat-protocols.c \
+ chatnets.c \
+ core.c \
+ expandos.c \
+ ignore.c \
+ levels.c \
+ line-split.c \
+ log.c \
+ masks.c \
+ $(memdebug_src) \
+ misc.c \
+ modules.c \
+ net-disconnect.c \
+ net-nonblock.c \
+ net-sendbuffer.c \
+ network.c \
+ nicklist.c \
+ nickmatch-cache.c \
+ pidwait.c \
+ queries.c \
+ rawlog.c \
+ servers.c \
+ servers-reconnect.c \
+ servers-redirect.c \
+ servers-setup.c \
+ settings.c \
+ signals.c \
+ special-vars.c \
+ write-buffer.c
+
+structure_headers = \
+ channel-rec.h \
+ channel-setup-rec.h \
+ chatnet-rec.h \
+ query-rec.h \
+ server-rec.h \
+ server-setup-rec.h \
+ server-connect-rec.h \
+ window-item-rec.h
+
+noinst_HEADERS = \
+ args.h \
+ channels.h \
+ channels-setup.h \
+ commands.h \
+ chat-protocols.h \
+ chatnets.h \
+ core.h \
+ expandos.h \
+ ignore.h \
+ levels.h \
+ line-split.h \
+ log.h \
+ masks.h \
+ memdebug.h \
+ misc.h \
+ module.h \
+ modules.h \
+ net-disconnect.h \
+ net-nonblock.h \
+ net-sendbuffer.h \
+ network.h \
+ nick-rec.h \
+ nicklist.h \
+ nickmatch-cache.h \
+ pidwait.h \
+ queries.h \
+ rawlog.h \
+ servers.h \
+ servers-reconnect.h \
+ servers-redirect.h \
+ servers-setup.h \
+ settings.h \
+ signals.h \
+ special-vars.h \
+ window-item-def.h \
+ write-buffer.h \
+ $(structure_headers)
+
+EXTRA_DIST = \
+ memdebug.c
--- /dev/null
+/*
+ args.c : small frontend to libPopt command line argument parser
+
+ Copyright (C) 1999-2001 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 "args.h"
+
+GArray *iopt_tables = NULL;
+
+void args_register(struct poptOption *options)
+{
+ if (iopt_tables == NULL) {
+ iopt_tables = g_array_new(TRUE, TRUE,
+ sizeof(struct poptOption));
+ }
+
+ while (options->longName != NULL || options->shortName != '\0' ||
+ options->arg != NULL) {
+ g_array_append_val(iopt_tables, *options);
+ options = options+1;
+ }
+}
+
+void args_execute(int argc, char *argv[])
+{
+ poptContext con;
+ int nextopt;
+
+ if (iopt_tables == NULL)
+ return;
+
+ con = poptGetContext(PACKAGE, argc, argv,
+ (struct poptOption *) (iopt_tables->data), 0);
+ poptReadDefaultConfig(con, TRUE);
+
+ while ((nextopt = poptGetNextOpt(con)) > 0) ;
+
+ if (nextopt != -1) {
+ printf("Error on option %s: %s.\n"
+ "Run '%s --help' to see a full list of "
+ "available command line options.\n",
+ poptBadOption(con, 0), poptStrerror(nextopt), argv[0]);
+ exit(1);
+ }
+
+ g_array_free(iopt_tables, TRUE);
+ iopt_tables = NULL;
+}
--- /dev/null
+#ifndef __ARGS_H
+#define __ARGS_H
+
+#ifdef HAVE_POPT_H
+# include <popt.h>
+#else
+# include "lib-popt/popt.h"
+#endif
+
+extern GArray *iopt_tables;
+
+void args_register(struct poptOption *options);
+void args_execute(int argc, char *argv[]);
+
+#endif
--- /dev/null
+/* CHANNEL_REC definition, used for inheritance */
+
+#include "window-item-rec.h"
+
+char *topic;
+char *topic_by;
+time_t topic_time;
+
+GHashTable *nicks; /* list of nicks */
+NICK_REC *ownnick; /* our own nick */
+
+unsigned int no_modes:1; /* channel doesn't support modes */
+char *mode;
+int limit; /* user limit */
+char *key; /* password key */
+
+unsigned int chanop:1; /* You're a channel operator */
+unsigned int names_got:1; /* Received /NAMES list */
+unsigned int wholist:1; /* WHO list got */
+unsigned int synced:1; /* Channel synced - all queries done */
+
+unsigned int joined:1; /* Have we even received JOIN event for this channel? */
+unsigned int left:1; /* You just left the channel */
+unsigned int kicked:1; /* You just got kicked */
+unsigned int destroying:1;
+
+/* Return the information needed to call SERVER_REC->channels_join() for
+ this channel. Usually just the channel name, but may contain also the
+ channel key. */
+char *(*get_join_data)(CHANNEL_REC *channel);
--- /dev/null
+int type; /* module_get_uniq_id("CHANNEL SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+char *chatnet;
+char *password;
+
+char *botmasks;
+char *autosendcmd;
+
+unsigned int autojoin:1;
+GHashTable *module_data;
--- /dev/null
+/*
+ channels-setup.c : irssi
+
+ Copyright (C) 1999-2000 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 "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers-setup.h"
+#include "channels-setup.h"
+
+GSList *setupchannels;
+
+static void channel_setup_save(CHANNEL_SETUP_REC *channel)
+{
+ CONFIG_NODE *parentnode, *node;
+ int index;
+
+ index = g_slist_index(setupchannels, channel);
+
+ parentnode = iconfig_node_traverse("(channels", TRUE);
+ node = config_node_index(parentnode, index);
+ if (node == NULL)
+ node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_clear(node);
+ iconfig_node_set_str(node, "name", channel->name);
+ iconfig_node_set_str(node, "chatnet", channel->chatnet);
+ if (channel->autojoin)
+ iconfig_node_set_bool(node, "autojoin", TRUE);
+ iconfig_node_set_str(node, "password", channel->password);
+ iconfig_node_set_str(node, "botmasks", channel->botmasks);
+ iconfig_node_set_str(node, "autosendcmd", channel->autosendcmd);
+}
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel)
+{
+ if (g_slist_find(setupchannels, channel) == NULL)
+ setupchannels = g_slist_append(setupchannels, channel);
+ channel_setup_save(channel);
+
+ signal_emit("channel setup created", 1, channel);
+}
+
+static void channel_config_remove(CHANNEL_SETUP_REC *channel)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("channels", FALSE);
+ if (node != NULL) iconfig_node_list_remove(node, g_slist_index(setupchannels, channel));
+}
+
+static void channel_setup_destroy(CHANNEL_SETUP_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ setupchannels = g_slist_remove(setupchannels, channel);
+ signal_emit("channel setup destroyed", 1, channel);
+
+ g_free_not_null(channel->chatnet);
+ g_free_not_null(channel->password);
+ g_free_not_null(channel->botmasks);
+ g_free_not_null(channel->autosendcmd);
+ g_free(channel->name);
+ g_free(channel);
+}
+
+void channel_setup_remove(CHANNEL_SETUP_REC *channel)
+{
+ channel_config_remove(channel);
+ channel_setup_destroy(channel);
+}
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+ const char *chatnet)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, channel) == 0 &&
+ channel_chatnet_match(rec->chatnet, chatnet))
+ return rec;
+ }
+
+ return NULL;
+}
+
+static CHANNEL_SETUP_REC *channel_setup_read(CONFIG_NODE *node)
+{
+ CHANNEL_SETUP_REC *rec;
+ CHATNET_REC *chatnetrec;
+ char *channel, *chatnet;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ channel = config_node_get_str(node, "name", NULL);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+ if (chatnet == NULL) /* FIXME: remove this after .98... */ {
+ chatnet = g_strdup(config_node_get_str(node, "ircnet", NULL));
+ if (chatnet != NULL) {
+ iconfig_node_set_str(node, "chatnet", chatnet);
+ iconfig_node_set_str(node, "ircnet", NULL);
+ }
+ }
+
+ chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+ if (channel == NULL || chatnetrec == NULL) {
+ /* missing information.. */
+ return NULL;
+ }
+
+ rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+ rec->type = module_get_uniq_id("CHANNEL SETUP", 0);
+ rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+ rec->autojoin = config_node_get_bool(node, "autojoin", FALSE);
+ rec->name = g_strdup(channel);
+ rec->chatnet = g_strdup(chatnetrec != NULL ? chatnetrec->name : chatnet);
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+ rec->botmasks = g_strdup(config_node_get_str(node, "botmasks", NULL));
+ rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+ setupchannels = g_slist_append(setupchannels, rec);
+ signal_emit("channel setup created", 2, rec, node);
+ return rec;
+}
+
+static void channels_read_config(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupchannels != NULL)
+ channel_setup_destroy(setupchannels->data);
+
+ /* Read channels */
+ node = iconfig_node_traverse("channels", FALSE);
+ if (node != NULL) {
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ channel_setup_read(tmp->data);
+ }
+}
+
+void channels_setup_init(void)
+{
+ setupchannels = NULL;
+ source_host_ok = FALSE;
+
+ signal_add("setup reread", (SIGNAL_FUNC) channels_read_config);
+ signal_add("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
+
+void channels_setup_deinit(void)
+{
+ while (setupchannels != NULL)
+ channel_setup_destroy(setupchannels->data);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
--- /dev/null
+#ifndef __CHANNELS_SETUP_H
+#define __CHANNELS_SETUP_H
+
+#include "modules.h"
+
+#define CHANNEL_SETUP(server) \
+ MODULE_CHECK_CAST(server, CHANNEL_SETUP_REC, type, "CHANNEL SETUP")
+
+#define IS_CHANNEL_SETUP(server) \
+ (CHANNEL_SETUP(server) ? TRUE : FALSE)
+
+struct _CHANNEL_SETUP_REC {
+#include "channel-setup-rec.h"
+};
+
+extern GSList *setupchannels;
+
+void channels_setup_init(void);
+void channels_setup_deinit(void);
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel);
+void channel_setup_remove(CHANNEL_SETUP_REC *channel);
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+ const char *chatnet);
+
+#define channel_chatnet_match(rec, chatnet) \
+ ((rec) == NULL || (rec)[0] == '\0' || \
+ ((chatnet) != NULL && g_strcasecmp(rec, chatnet) == 0))
+
+#endif
--- /dev/null
+/*
+ channel.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+#include "special-vars.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "nicklist.h"
+
+GSList *channels; /* List of all channels */
+
+static char *get_join_data(CHANNEL_REC *channel)
+{
+ return g_strdup(channel->name);
+}
+
+void channel_init(CHANNEL_REC *channel, int automatic)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(channel->name != NULL);
+
+ channels = g_slist_append(channels, channel);
+ if (channel->server != NULL) {
+ channel->server->channels =
+ g_slist_append(channel->server->channels, channel);
+ }
+
+ MODULE_DATA_INIT(channel);
+ channel->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL");
+ channel->mode = g_strdup("");
+ channel->createtime = time(NULL);
+ channel->get_join_data = get_join_data;
+
+ signal_emit("channel created", 2, channel, GINT_TO_POINTER(automatic));
+}
+
+void channel_destroy(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ if (channel->destroying) return;
+ channel->destroying = TRUE;
+
+ channels = g_slist_remove(channels, channel);
+ if (channel->server != NULL)
+ channel->server->channels = g_slist_remove(channel->server->channels, channel);
+ signal_emit("channel destroyed", 1, channel);
+
+ MODULE_DATA_DEINIT(channel);
+ g_free_not_null(channel->hilight_color);
+ g_free_not_null(channel->topic);
+ g_free_not_null(channel->topic_by);
+ g_free_not_null(channel->key);
+ g_free(channel->mode);
+ g_free(channel->name);
+ g_free(channel);
+}
+
+static CHANNEL_REC *channel_find_server(SERVER_REC *server,
+ const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ if (server->channel_find_func != NULL) {
+ /* use the server specific channel find function */
+ return server->channel_find_func(server, name);
+ }
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (g_strcasecmp(name, rec->name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHANNEL_REC *channel_find(SERVER_REC *server, const char *name)
+{
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (server != NULL)
+ return channel_find_server(server, name);
+
+ /* find from any server */
+ return gslist_foreach_find(servers,
+ (FOREACH_FIND_FUNC) channel_find_server,
+ (void *) name);
+}
+
+/* connected to server, autojoin to channels. */
+static void event_connected(SERVER_REC *server)
+{
+ GString *chans;
+ GSList *tmp;
+
+ g_return_if_fail(SERVER(server));
+
+ if (server->connrec->reconnection)
+ return;
+
+ /* join to the channels marked with autojoin in setup */
+ chans = g_string_new(NULL);
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (!rec->autojoin ||
+ !channel_chatnet_match(rec->chatnet,
+ server->connrec->chatnet))
+ continue;
+
+ g_string_sprintfa(chans, "%s,", rec->name);
+ }
+
+ if (chans->len > 0) {
+ g_string_truncate(chans, chans->len-1);
+ server->channels_join(server, chans->str, TRUE);
+ }
+
+ g_string_free(chans, TRUE);
+}
+
+static int match_nick_flags(SERVER_REC *server, NICK_REC *nick, char flag)
+{
+ const char *flags = server->get_nick_flags();
+
+ return strchr(flags, flag) == NULL ||
+ (flag == flags[0] && nick->op) ||
+ (flag == flags[1] && (nick->voice || nick->halfop ||
+ nick->op)) ||
+ (flag == flags[2] && (nick->halfop || nick->op));
+}
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel)
+{
+ CHANNEL_SETUP_REC *rec;
+ NICK_REC *nick;
+ char **bots, **bot;
+
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ rec = channel_setup_find(channel->name, channel->server->connrec->chatnet);
+ if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd)
+ return;
+
+ if (rec->botmasks == NULL || !*rec->botmasks) {
+ /* just send the command. */
+ eval_special_string(rec->autosendcmd, "", channel->server, channel);
+ return;
+ }
+
+ /* find first available bot.. */
+ bots = g_strsplit(rec->botmasks, " ", -1);
+ for (bot = bots; *bot != NULL; bot++) {
+ const char *botnick = *bot;
+
+ nick = nicklist_find_mask(channel,
+ channel->server->isnickflag(*botnick) ?
+ botnick+1 : botnick);
+ if (nick != NULL &&
+ match_nick_flags(channel->server, nick, *botnick)) {
+ eval_special_string(rec->autosendcmd, nick->nick,
+ channel->server, channel);
+ break;
+ }
+ }
+ g_strfreev(bots);
+}
+
+void channels_init(void)
+{
+ channels_setup_init();
+
+ signal_add("event connected", (SIGNAL_FUNC) event_connected);
+}
+
+void channels_deinit(void)
+{
+ channels_setup_deinit();
+
+ signal_remove("event connected", (SIGNAL_FUNC) event_connected);
+ module_uniq_destroy("CHANNEL");
+}
--- /dev/null
+#ifndef __CHANNELS_H
+#define __CHANNELS_H
+
+#include "modules.h"
+
+/* Returns CHANNEL_REC if it's channel, NULL if it isn't. */
+#define CHANNEL(channel) \
+ MODULE_CHECK_CAST_MODULE(channel, CHANNEL_REC, type, \
+ "WINDOW ITEM TYPE", "CHANNEL")
+
+#define IS_CHANNEL(channel) \
+ (CHANNEL(channel) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _CHANNEL_REC {
+#include "channel-rec.h"
+};
+
+extern GSList *channels;
+
+/* Create new channel record */
+void channel_init(CHANNEL_REC *channel, int automatic);
+void channel_destroy(CHANNEL_REC *channel);
+
+/* find channel by name, if `server' is NULL, search from all servers */
+CHANNEL_REC *channel_find(SERVER_REC *server, const char *name);
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel);
+
+void channels_init(void);
+void channels_deinit(void);
+
+#endif
--- /dev/null
+/*
+ chat-commands.c : irssi
+
+ Copyright (C) 2000 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 "network.h"
+#include "signals.h"
+#include "commands.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+#include "channels.h"
+#include "queries.h"
+#include "window-item-def.h"
+
+static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_CONNECT_REC *conn;
+ GHashTable *optlist;
+ char *addr, *portstr, *password, *nick, *chatnet, *host;
+ void *free_arg;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
+ "connect", &optlist, &addr, &portstr,
+ &password, &nick))
+ return NULL;
+ if (plus_addr != NULL) *plus_addr = *addr == '+';
+ if (*addr == '+') addr++;
+ if (*addr == '\0') {
+ signal_emit("error command", 1,
+ GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ if (strcmp(password, "-") == 0)
+ *password = '\0';
+
+ /* check if -<chatnet> option is used to specify chat protocol */
+ proto = chat_protocol_find_net(optlist);
+
+ /* connect to server */
+ chatnet = proto == NULL ? NULL :
+ g_hash_table_lookup(optlist, proto->chatnet);
+ conn = server_create_conn(proto != NULL ? proto->id : -1, addr,
+ atoi(portstr), chatnet, password, nick);
+ if (proto == NULL)
+ proto = chat_protocol_find_id(conn->chat_type);
+
+ if (proto->not_initialized) {
+ /* trying to use protocol that isn't yet initialized */
+ signal_emit("chat protocol unknown", 1, proto->name);
+ server_connect_free(conn);
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ if (g_hash_table_lookup(optlist, "6") != NULL)
+ conn->family = AF_INET6;
+ else if (g_hash_table_lookup(optlist, "4") != NULL)
+ conn->family = AF_INET;
+
+ host = g_hash_table_lookup(optlist, "host");
+ if (host != NULL && *host != '\0') {
+ IPADDR ip4, ip6;
+
+ if (net_gethostbyname(host, &ip4, &ip6) == 0)
+ server_connect_own_ip_save(conn, &ip4, &ip6);
+ }
+
+ cmd_params_free(free_arg);
+ return conn;
+}
+
+/* SYNTAX: CONNECT [-4 | -6] [-ircnet <ircnet>] [-host <hostname>]
+ <address>|<chatnet> [<port> [<password> [<nick>]]] */
+static void cmd_connect(const char *data)
+{
+ SERVER_CONNECT_REC *conn;
+
+ conn = get_server_connect(data, NULL);
+ if (conn != NULL)
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+}
+
+static RECONNECT_REC *find_reconnect_server(int chat_type,
+ const char *addr, int port)
+{
+ RECONNECT_REC *match, *last_proto_match;
+ GSList *tmp;
+ int count;
+
+ g_return_val_if_fail(addr != NULL, NULL);
+
+ /* check if there's a reconnection to the same host and maybe even
+ the same port */
+ match = last_proto_match = NULL; count = 0;
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (rec->conn->chat_type == chat_type) {
+ count++; last_proto_match = rec;
+ if (g_strcasecmp(rec->conn->address, addr) == 0) {
+ if (rec->conn->port == port)
+ return rec;
+ match = rec;
+ }
+ }
+ }
+
+ if (count == 1) {
+ /* only one reconnection with wanted protocol,
+ we probably want to use it */
+ return last_proto_match;
+ }
+
+ return match;
+}
+
+static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *oldconn;
+ RECONNECT_REC *recon;
+
+ if (server != NULL) {
+ oldconn = server->connrec;
+ reconnect_save_status(conn, server);
+ } else {
+ /* maybe we can reconnect some server from
+ reconnection queue */
+ recon = find_reconnect_server(conn->chat_type,
+ conn->address, conn->port);
+ if (recon == NULL) return;
+
+ oldconn = recon->conn;
+ server_reconnect_destroy(recon, FALSE);
+
+ conn->away_reason = g_strdup(oldconn->away_reason);
+ conn->channels = g_strdup(oldconn->channels);
+ }
+
+ conn->reconnection = TRUE;
+
+ if (conn->chatnet == NULL && oldconn->chatnet != NULL)
+ conn->chatnet = g_strdup(oldconn->chatnet);
+
+ if (server != NULL) {
+ signal_emit("command disconnect", 2,
+ "* Changing server", server);
+ } else {
+ server_connect_free(oldconn);
+ }
+}
+
+/* SYNTAX: SERVER [-4 | -6] [-ircnet <ircnet>] [-host <hostname>]
+ [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
+static void cmd_server(const char *data, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ int plus_addr;
+
+ g_return_if_fail(data != NULL);
+
+ /* create connection record */
+ conn = get_server_connect(data, &plus_addr);
+ if (conn != NULL) {
+ if (!plus_addr)
+ update_reconnection(conn, server);
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ }
+}
+
+/* SYNTAX: DISCONNECT *|<tag> [<message>] */
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+ char *tag, *msg;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
+ return;
+
+ if (*tag != '\0' && strcmp(tag, "*") != 0)
+ server = server_find_tag(tag);
+ if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
+ signal_emit("server quit", 2, server, msg);
+
+ cmd_params_free(free_arg);
+ server_disconnect(server);
+}
+
+/* SYNTAX: QUIT [<message>] */
+static void cmd_quit(const char *data)
+{
+ GSList *tmp, *next;
+ const char *quitmsg;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ quitmsg = *data != '\0' ? data :
+ settings_get_str("quit_message");
+
+ /* disconnect from every server */
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ next = tmp->next;
+
+ str = g_strdup_printf("* %s", quitmsg);
+ cmd_disconnect(str, tmp->data);
+ g_free(str);
+ }
+
+ signal_emit("gui exit", 0);
+}
+
+/* SYNTAX: JOIN [-invite] [-<server tag>] <channels> [<keys>] */
+static void cmd_join(const char *data, SERVER_REC *server)
+{
+ GHashTable *optlist;
+ char *channels;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "join", &optlist, &channels))
+ return;
+
+ if (g_hash_table_lookup(optlist, "invite"))
+ channels = server->last_invite;
+ else {
+ if (*channels == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ /* -<server tag> */
+ server = cmd_options_get_server("join", optlist, server);
+ }
+
+ if (server != NULL && channels != NULL)
+ server->channels_join(server, channels, FALSE);
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: MSG [-<server tag>] <targets> <message> */
+static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ char *target, *origtarget, *msg;
+ void *free_arg;
+ int free_ret;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "msg", &optlist, &target, &msg))
+ return;
+ if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ server = cmd_options_get_server("msg", optlist, SERVER(server));
+ if (server == NULL || !server->connected)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ origtarget = target;
+ free_ret = FALSE;
+ if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) {
+ target = parse_special(&target, server, item,
+ NULL, &free_ret, NULL, 0);
+ if (target != NULL && *target == '\0')
+ target = NULL;
+ } else if (strcmp(target, "*") == 0 && item != NULL)
+ target = item->name;
+
+ if (target != NULL)
+ server->send_message(server, target, msg);
+
+ signal_emit(target != NULL && server->ischannel(target) ?
+ "message own_public" : "message own_private", 4,
+ server, msg, target, origtarget);
+
+ if (free_ret && target != NULL) g_free(target);
+ cmd_params_free(free_arg);
+}
+
+static void cmd_foreach(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ command_runsub("foreach", data, server, item);
+}
+
+/* SYNTAX: FOREACH SERVER <command> */
+static void cmd_foreach_server(const char *data, SERVER_REC *server)
+{
+ GSList *tmp;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next)
+ signal_emit("send command", 3, data, tmp->data, NULL);
+}
+
+/* SYNTAX: FOREACH CHANNEL <command> */
+static void cmd_foreach_channel(const char *data)
+{
+ GSList *tmp;
+
+ for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ signal_emit("send command", 3, data, rec->server, rec);
+ }
+}
+
+/* SYNTAX: FOREACH QUERY <command> */
+static void cmd_foreach_query(const char *data)
+{
+ GSList *tmp;
+
+ for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ signal_emit("send command", 3, data, rec->server, rec);
+ }
+}
+
+void chat_commands_init(void)
+{
+ settings_add_str("misc", "quit_message", "leaving");
+
+ command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+ command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
+ command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+ command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
+ command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+ command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
+ command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
+ command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
+ command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
+ command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
+
+ command_set_options("connect", "4 6 +host");
+ command_set_options("join", "invite");
+}
+
+void chat_commands_deinit(void)
+{
+ command_unbind("server", (SIGNAL_FUNC) cmd_server);
+ command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+ command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
+ command_unbind("join", (SIGNAL_FUNC) cmd_join);
+ command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
+ command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
+ command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
+ command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
+ command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
+}
--- /dev/null
+/*
+ chat-protocol.c : irssi
+
+ Copyright (C) 2000 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 "modules.h"
+#include "signals.h"
+#include "chat-protocols.h"
+
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "channels-setup.h"
+
+GSList *chat_protocols;
+
+static CHAT_PROTOCOL_REC *default_proto;
+
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id)
+{
+ return object == NULL ||
+ chat_protocol_lookup(id) !=
+ G_STRUCT_MEMBER(int, object, type_pos) ? NULL : object;
+}
+
+/* Return the ID for the specified chat protocol. */
+int chat_protocol_lookup(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec;
+
+ g_return_val_if_fail(name != NULL, -1);
+
+ rec = chat_protocol_find(name);
+ return rec == NULL ? -1 : rec->id;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(id > 0, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (rec->id == id)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(optlist != NULL, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (rec->chatnet != NULL &&
+ g_hash_table_lookup(optlist, rec->chatnet) != NULL)
+ return rec;
+ }
+
+ return NULL;
+}
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec)
+{
+ CHAT_PROTOCOL_REC *newrec;
+ int created;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ newrec = chat_protocol_find(rec->name);
+ created = newrec == NULL;
+ if (newrec == NULL) {
+ newrec = g_new0(CHAT_PROTOCOL_REC, 1);
+ chat_protocols = g_slist_append(chat_protocols, newrec);
+ } else {
+ /* updating existing protocol */
+ g_free(newrec->name);
+ }
+
+ memcpy(newrec, rec, sizeof(CHAT_PROTOCOL_REC));
+ newrec->id = module_get_uniq_id_str("PROTOCOL", rec->name);
+ newrec->name = g_strdup(rec->name);
+
+ if (default_proto == NULL)
+ chat_protocol_set_default(newrec);
+
+ if (created)
+ signal_emit("chat protocol created", 1, newrec);
+ else
+ signal_emit("chat protocol updated", 1, newrec);
+ return newrec;
+}
+
+static void chat_protocol_destroy(CHAT_PROTOCOL_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ chat_protocols = g_slist_remove(chat_protocols, rec);
+
+ if (default_proto == rec) {
+ chat_protocol_set_default(chat_protocols == NULL ? NULL :
+ chat_protocols->data);
+ }
+
+ signal_emit("chat protocol destroyed", 1, rec);
+
+ g_free(rec->name);
+ g_free(rec);
+}
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec;
+
+ g_return_if_fail(name != NULL);
+
+ rec = chat_protocol_find(name);
+ if (rec != NULL) chat_protocol_destroy(rec);
+}
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec)
+{
+ default_proto = rec;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void)
+{
+ return default_proto;
+}
+
+static CHATNET_REC *create_chatnet(void)
+{
+ return g_new0(CHATNET_REC, 1);
+}
+
+static SERVER_SETUP_REC *create_server_setup(void)
+{
+ return g_new0(SERVER_SETUP_REC, 1);
+}
+
+static CHANNEL_SETUP_REC *create_channel_setup(void)
+{
+ return g_new0(CHANNEL_SETUP_REC, 1);
+}
+
+static SERVER_CONNECT_REC *create_server_connect(void)
+{
+ return g_new0(SERVER_CONNECT_REC, 1);
+}
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+ specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec, *newrec;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = chat_protocol_find(name);
+ if (rec != NULL)
+ return rec;
+
+ rec = g_new0(CHAT_PROTOCOL_REC, 1);
+ rec->not_initialized = TRUE;
+ rec->name = (char *) name;
+ rec->create_chatnet = create_chatnet;
+ rec->create_server_setup = create_server_setup;
+ rec->create_channel_setup = create_channel_setup;
+ rec->create_server_connect = create_server_connect;
+
+ newrec = chat_protocol_register(rec);
+ g_free(rec);
+ return newrec;
+}
+
+void chat_protocols_init(void)
+{
+ default_proto = NULL;
+ chat_protocols = NULL;
+}
+
+void chat_protocols_deinit(void)
+{
+ while (chat_protocols != NULL)
+ chat_protocol_destroy(chat_protocols->data);
+}
--- /dev/null
+#ifndef __CHAT_PROTOCOLS_H
+#define __CHAT_PROTOCOLS_H
+
+typedef struct {
+ int id;
+
+ unsigned int not_initialized:1;
+
+ char *name;
+ char *fullname;
+ char *chatnet;
+
+ CHATNET_REC *(*create_chatnet) (void);
+ SERVER_SETUP_REC *(*create_server_setup) (void);
+ CHANNEL_SETUP_REC *(*create_channel_setup) (void);
+ SERVER_CONNECT_REC *(*create_server_connect) (void);
+
+ SERVER_REC *(*server_connect) (SERVER_CONNECT_REC *);
+ CHANNEL_REC *(*channel_create) (SERVER_REC *, const char *, int);
+ QUERY_REC *(*query_create) (const char *, const char *, int);
+} CHAT_PROTOCOL_REC;
+
+extern GSList *chat_protocols;
+
+#define PROTO_CHECK_CAST(object, cast, type_field, id) \
+ ((cast *) chat_protocol_check_cast(object, \
+ offsetof(cast, type_field), id))
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id);
+
+#define CHAT_PROTOCOL(object) \
+ ((object) == NULL ? chat_protocol_get_default() : \
+ chat_protocol_find_id((object)->chat_type))
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec);
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name);
+
+/* Find functions */
+int chat_protocol_lookup(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id);
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist);
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec);
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void);
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+ specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name);
+
+void chat_protocols_init(void);
+void chat_protocols_deinit(void);
+
+#endif
--- /dev/null
+int type; /* module_get_uniq_id("CHATNET", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+
+char *nick;
+char *username;
+char *realname;
+
+char *own_host; /* address to use when connecting this server */
+char *autosendcmd; /* command to send after connecting to this ircnet */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
--- /dev/null
+/*
+ chatnets.c : irssi
+
+ Copyright (C) 1999-2000 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 "network.h"
+#include "signals.h"
+#include "special-vars.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+
+GSList *chatnets; /* list of available chat networks */
+
+static void chatnet_config_save(CHATNET_REC *chatnet)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("chatnets", TRUE);
+ node = config_node_section(node, chatnet->name, NODE_TYPE_BLOCK);
+ iconfig_node_clear(node);
+
+ iconfig_node_set_str(node, "type", chat_protocol_find_id(chatnet->chat_type)->name);
+ iconfig_node_set_str(node, "nick", chatnet->nick);
+ iconfig_node_set_str(node, "username", chatnet->username);
+ iconfig_node_set_str(node, "realname", chatnet->realname);
+ iconfig_node_set_str(node, "host", chatnet->own_host);
+ iconfig_node_set_str(node, "autosendcmd", chatnet->autosendcmd);
+
+ signal_emit("chatnet saved", 2, chatnet, node);
+}
+
+static void chatnet_config_remove(CHATNET_REC *chatnet)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("chatnets", FALSE);
+ if (node != NULL) iconfig_node_set_str(node, chatnet->name, NULL);
+}
+
+void chatnet_create(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(chatnet != NULL);
+
+ chatnet->type = module_get_uniq_id("CHATNET", 0);
+ if (g_slist_find(chatnets, chatnet) == NULL)
+ chatnets = g_slist_append(chatnets, chatnet);
+
+ chatnet_config_save(chatnet);
+ signal_emit("chatnet created", 1, chatnet);
+}
+
+void chatnet_remove(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ signal_emit("chatnet removed", 1, chatnet);
+
+ chatnet_config_remove(chatnet);
+ chatnet_destroy(chatnet);
+}
+
+void chatnet_destroy(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ chatnets = g_slist_remove(chatnets, chatnet);
+ signal_emit("chatnet destroyed", 1, chatnet);
+
+ g_free_not_null(chatnet->nick);
+ g_free_not_null(chatnet->username);
+ g_free_not_null(chatnet->realname);
+ g_free_not_null(chatnet->own_host);
+ g_free_not_null(chatnet->autosendcmd);
+ g_free(chatnet->name);
+ g_free(chatnet);
+}
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ CHATNET_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+ CHATNET_REC *rec;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ if (server->connrec->chatnet == NULL)
+ return;
+
+ rec = chatnet_find(server->connrec->chatnet);
+ if (rec != NULL && rec->autosendcmd)
+ eval_special_string(rec->autosendcmd, "", server, NULL);
+}
+
+static void chatnet_read(CONFIG_NODE *node)
+{
+ CHAT_PROTOCOL_REC *proto;
+ CHATNET_REC *rec;
+ char *type;
+
+ if (node == NULL || node->key == NULL)
+ return;
+
+ type = config_node_get_str(node, "type", NULL);
+ proto = type == NULL ? NULL : chat_protocol_find(type);
+ if (proto == NULL) {
+ proto = type == NULL ? chat_protocol_get_default() :
+ chat_protocol_get_unknown(type);
+ }
+
+ if (type == NULL)
+ iconfig_node_set_str(node, "type", proto->name);
+
+ rec = proto->create_chatnet();
+ rec->type = module_get_uniq_id("CHATNET", 0);
+ rec->chat_type = proto->id;
+ rec->name = g_strdup(node->key);
+ rec->nick = g_strdup(config_node_get_str(node, "nick", NULL));
+ rec->username = g_strdup(config_node_get_str(node, "username", NULL));
+ rec->realname = g_strdup(config_node_get_str(node, "realname", NULL));
+ rec->own_host = g_strdup(config_node_get_str(node, "host", NULL));
+ rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+ chatnets = g_slist_append(chatnets, rec);
+ signal_emit("chatnet read", 2, rec, node);
+}
+
+static void read_chatnets(void)
+{
+ CONFIG_NODE *node;
+
+ while (chatnets != NULL)
+ chatnet_destroy(chatnets->data);
+
+ node = iconfig_node_traverse("chatnets", FALSE);
+ if (node == NULL) {
+ /* FIXME: remove after .98 */
+ node = iconfig_node_traverse("ircnets", FALSE);
+ if (node != NULL) {
+ /* very dirty method - doesn't update hashtables
+ but this will do temporarily.. */
+ g_free(node->key);
+ node->key = g_strdup("chatnets");
+ }
+ }
+
+ if (node != NULL)
+ g_slist_foreach(node->value, (GFunc) chatnet_read, NULL);
+}
+
+void chatnets_init(void)
+{
+ chatnets = NULL;
+
+ signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("setup reread", (SIGNAL_FUNC) read_chatnets);
+ signal_add_first("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
+
+void chatnets_deinit(void)
+{
+ while (chatnets != NULL)
+ chatnet_destroy(chatnets->data);
+ module_uniq_destroy("CHATNET");
+
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_chatnets);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
--- /dev/null
+#ifndef __CHATNETS_H
+#define __CHATNETS_H
+
+#include "modules.h"
+
+/* Returns CHATNET_REC if it's chatnet, NULL if it isn't. */
+#define CHATNET(chatnet) \
+ MODULE_CHECK_CAST(chatnet, CHATNET_REC, type, "CHATNET")
+
+#define IS_CHATNET(chatnet) \
+ (CHATNET(chatnet) ? TRUE : FALSE)
+
+struct _CHATNET_REC {
+#include "chatnet-rec.h"
+};
+
+extern GSList *chatnets; /* list of available chat networks */
+
+/* add the chatnet to chat networks list */
+void chatnet_create(CHATNET_REC *chatnet);
+/* remove the chatnet from chat networks list */
+void chatnet_remove(CHATNET_REC *chatnet);
+/* destroy the chatnet structure. doesn't remove from config file */
+void chatnet_destroy(CHATNET_REC *chatnet);
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name);
+
+void chatnets_init(void);
+void chatnets_deinit(void);
+
+#endif
--- /dev/null
+/*
+ commands.c : irssi
+
+ Copyright (C) 1999-2000 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 "commands.h"
+#include "misc.h"
+#include "special-vars.h"
+
+#include "servers.h"
+#include "servers-redirect.h"
+#include "channels.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+GSList *commands;
+char *current_command;
+
+static int signal_default_command;
+
+static GSList *alias_runstack;
+
+COMMAND_REC *command_find(const char *cmd)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->cmd, cmd) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
+ const char *module)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(module != NULL, NULL);
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, module) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec,
+ SIGNAL_FUNC func)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(func != NULL, NULL);
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (g_slist_find(rec->signals, func) != NULL)
+ return rec;
+ }
+
+ return NULL;
+}
+
+int command_have_sub(const char *command)
+{
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(command != NULL, FALSE);
+
+ /* find "command "s */
+ len = strlen(command);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->cmd, command, len) == 0 &&
+ rec->cmd[len] == ' ')
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static COMMAND_MODULE_REC *command_module_get(COMMAND_REC *rec,
+ const char *module)
+{
+ COMMAND_MODULE_REC *modrec;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ modrec = command_module_find(rec, module);
+ if (modrec == NULL) {
+ modrec = g_new0(COMMAND_MODULE_REC, 1);
+ modrec->name = g_strdup(module);
+ rec->modules = g_slist_append(rec->modules, modrec);
+ }
+
+ return modrec;
+}
+
+void command_bind_to(const char *module, int pos, const char *cmd,
+ const char *category, SIGNAL_FUNC func)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ char *str;
+
+ g_return_if_fail(module != NULL);
+ g_return_if_fail(cmd != NULL);
+
+ rec = command_find(cmd);
+ if (rec == NULL) {
+ rec = g_new0(COMMAND_REC, 1);
+ rec->cmd = g_strdup(cmd);
+ rec->category = category == NULL ? NULL : g_strdup(category);
+ commands = g_slist_append(commands, rec);
+ }
+ modrec = command_module_get(rec, module);
+
+ modrec->signals = g_slist_append(modrec->signals, func);
+
+ if (func != NULL) {
+ str = g_strconcat("command ", cmd, NULL);
+ signal_add_to(module, pos, str, func);
+ g_free(str);
+ }
+
+ signal_emit("commandlist new", 1, rec);
+}
+
+static void command_free(COMMAND_REC *rec)
+{
+ commands = g_slist_remove(commands, rec);
+ signal_emit("commandlist remove", 1, rec);
+
+ g_free_not_null(rec->category);
+ g_strfreev(rec->options);
+ g_free(rec->cmd);
+ g_free(rec);
+}
+
+static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
+{
+ rec->modules = g_slist_remove(rec->modules, modrec);
+
+ g_slist_free(modrec->signals);
+ g_free(modrec->name);
+ g_free_not_null(modrec->options);
+ g_free(modrec);
+}
+
+static void command_module_destroy(COMMAND_REC *rec,
+ COMMAND_MODULE_REC *modrec)
+{
+ GSList *tmp, *freelist;
+
+ command_module_free(modrec, rec);
+
+ /* command_set_options() might have added module declaration of it's
+ own without any signals .. check if they're the only ones left
+ and if so, destroy them. */
+ freelist = NULL;
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (rec->signals == NULL)
+ freelist = g_slist_append(freelist, rec);
+ else {
+ g_slist_free(freelist);
+ freelist = NULL;
+ break;
+ }
+ }
+
+ g_slist_foreach(freelist, (GFunc) command_module_free, rec);
+ g_slist_free(freelist);
+
+ if (rec->modules == NULL)
+ command_free(rec);
+}
+
+void command_unbind(const char *cmd, SIGNAL_FUNC func)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ char *str;
+
+ g_return_if_fail(cmd != NULL);
+ g_return_if_fail(func != NULL);
+
+ rec = command_find(cmd);
+ if (rec != NULL) {
+ modrec = command_module_find_func(rec, func);
+ modrec->signals = g_slist_remove(modrec->signals, func);
+ if (modrec->signals == NULL)
+ command_module_destroy(rec, modrec);
+ }
+
+ str = g_strconcat("command ", cmd, NULL);
+ signal_remove(str, func);
+ g_free(str);
+}
+
+/* Expand `cmd' - returns `cmd' if not found, NULL if more than one
+ match is found */
+static const char *command_expand(char *cmd)
+{
+ GSList *tmp;
+ const char *match;
+ int len, multiple;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ multiple = FALSE;
+ match = NULL;
+ len = strlen(cmd);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
+ strchr(rec->cmd+len, ' ') == NULL) {
+ if (rec->cmd[len] == '\0') {
+ /* full match */
+ return rec->cmd;
+ }
+
+ if (match != NULL) {
+ /* multiple matches, we still need to check
+ if there's some command left that is a
+ full match.. */
+ multiple = TRUE;
+ }
+
+ /* check that this is the only match */
+ match = rec->cmd;
+ }
+ }
+
+ if (multiple) {
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
+ return NULL;
+ }
+
+ return match != NULL ? match : cmd;
+}
+
+void command_runsub(const char *cmd, const char *data,
+ void *server, void *item)
+{
+ const char *newcmd;
+ char *orig, *subcmd, *defcmd, *args;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ /* no subcommand given - list the subcommands */
+ signal_emit("list subcommands", 2, cmd);
+ return;
+ }
+
+ /* get command.. */
+ orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
+ args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+ while (*args == ' ') args++;
+
+ /* check if this command can be expanded */
+ newcmd = command_expand(subcmd+8);
+ if (newcmd == NULL) {
+ /* ambiguous command */
+ g_free(orig);
+ return;
+ }
+
+ subcmd = g_strconcat("command ", newcmd, NULL);
+
+ g_strdown(subcmd);
+ if (!signal_emit(subcmd, 3, args, server, item)) {
+ defcmd = g_strdup_printf("default command %s", cmd);
+ if (!signal_emit(defcmd, 3, data, server, item)) {
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
+ }
+ g_free(defcmd);
+ }
+
+ g_free(subcmd);
+ g_free(orig);
+}
+
+static GSList *optlist_find(GSList *optlist, const char *option)
+{
+ while (optlist != NULL) {
+ char *name = optlist->data;
+ if (iscmdtype(*name)) name++;
+
+ if (g_strcasecmp(name, option) == 0)
+ return optlist;
+
+ optlist = optlist->next;
+ }
+
+ return NULL;
+}
+
+int command_have_option(const char *cmd, const char *option)
+{
+ COMMAND_REC *rec;
+ char **tmp;
+
+ g_return_val_if_fail(cmd != NULL, FALSE);
+ g_return_val_if_fail(option != NULL, FALSE);
+
+ rec = command_find(cmd);
+ g_return_val_if_fail(rec != NULL, FALSE);
+
+ if (rec->options == NULL)
+ return FALSE;
+
+ for (tmp = rec->options; *tmp != NULL; tmp++) {
+ char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
+
+ if (g_strcasecmp(name, option) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void command_calc_options(COMMAND_REC *rec, const char *options)
+{
+ char **optlist, **tmp, *name, *str;
+ GSList *list, *oldopt;
+
+ optlist = g_strsplit(options, " ", -1);
+
+ if (rec->options == NULL) {
+ /* first call - use specified args directly */
+ rec->options = optlist;
+ return;
+ }
+
+ /* save old options to linked list */
+ list = NULL;
+ for (tmp = rec->options; *tmp != NULL; tmp++)
+ list = g_slist_append(list, g_strdup(*tmp));
+ g_strfreev(rec->options);
+
+ /* merge the options */
+ for (tmp = optlist; *tmp != NULL; tmp++) {
+ name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
+
+ oldopt = optlist_find(list, name);
+ if (oldopt != NULL) {
+ /* already specified - overwrite old defination */
+ g_free(oldopt->data);
+ oldopt->data = g_strdup(*tmp);
+ } else {
+ /* new option, append to list */
+ list = g_slist_append(list, g_strdup(*tmp));
+ }
+ }
+ g_strfreev(optlist);
+
+ /* linked list -> string[] */
+ str = gslist_to_string(list, " ");
+ rec->options = g_strsplit(str, " ", -1);
+ g_free(str);
+
+ g_slist_foreach(list, (GFunc) g_free, NULL);
+ g_slist_free(list);
+}
+
+/* recalculate options to command from options in all modules */
+static void command_update_options(COMMAND_REC *rec)
+{
+ GSList *tmp;
+
+ g_strfreev(rec->options);
+ rec->options = NULL;
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *modrec = tmp->data;
+
+ if (modrec->options != NULL)
+ command_calc_options(rec, modrec->options);
+ }
+}
+
+void command_set_options_module(const char *module,
+ const char *cmd, const char *options)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ int reload;
+
+ g_return_if_fail(module != NULL);
+ g_return_if_fail(cmd != NULL);
+ g_return_if_fail(options != NULL);
+
+ rec = command_find(cmd);
+ g_return_if_fail(rec != NULL);
+ modrec = command_module_get(rec, module);
+
+ reload = modrec->options != NULL;
+ if (reload) {
+ /* options already set for the module ..
+ we need to recalculate everything */
+ g_free(modrec->options);
+ }
+
+ modrec->options = g_strdup(options);
+
+ if (reload)
+ command_update_options(rec);
+ else
+ command_calc_options(rec, options);
+}
+
+char *cmd_get_param(char **data)
+{
+ char *pos;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ pos = *data;
+
+ while (**data != '\0' && **data != ' ') (*data)++;
+ if (**data == ' ') *(*data)++ = '\0';
+
+ return pos;
+}
+
+static char *cmd_get_quoted_param(char **data)
+{
+ char *pos, quote;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ if (**data != '\'' && **data != '"')
+ return cmd_get_param(data);
+
+ quote = **data; (*data)++;
+
+ pos = *data;
+ while (**data != '\0' && **data != quote) {
+ if (**data == '\\' && (*data)[1] != '\0')
+ g_memmove(*data, (*data)+1, strlen(*data));
+ (*data)++;
+ }
+
+ if (**data != '\0') *(*data)++ = '\0';
+
+ return pos;
+}
+
+/* Find specified option from list of options - the `option' might be
+ shortened version of the full command. Returns index where the
+ option was found, -1 if not found or -2 if there was multiple matches. */
+static int option_find(char **array, const char *option)
+{
+ char **tmp;
+ int index, found, len, multiple;
+
+ g_return_val_if_fail(array != NULL, -1);
+ g_return_val_if_fail(option != NULL, -1);
+
+ len = strlen(option);
+
+ found = -1; index = 0; multiple = FALSE;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ const char *text = *tmp + iscmdtype(**tmp);
+
+ if (g_strncasecmp(text, option, len) == 0) {
+ if (text[len] == '\0') {
+ /* full match */
+ return index;
+ }
+
+ if (found != -1) {
+ /* multiple matches - we still need to check
+ if there's a full match left.. */
+ multiple = TRUE;
+ }
+
+ /* partial match, check that it's the only one */
+ found = index;
+ }
+ }
+
+ if (multiple)
+ return -2;
+
+ return found;
+}
+
+static int get_cmd_options(char **data, int ignore_unknown,
+ const char *cmd, GHashTable *options)
+{
+ COMMAND_REC *rec;
+ char *option, *arg, **optlist;
+ int pos;
+
+ /* get option definations */
+ rec = cmd == NULL ? NULL : command_find(cmd);
+ optlist = rec == NULL ? NULL : rec->options;
+
+ option = NULL; pos = -1;
+ for (;;) {
+ if (**data == '-') {
+ if (option != NULL && *optlist[pos] == '+') {
+ /* required argument missing! */
+ *data = optlist[pos] + 1;
+ return CMDERR_OPTION_ARG_MISSING;
+ }
+
+ (*data)++;
+ if (**data == '-' && isspace((*data)[1])) {
+ /* -- option means end of options even
+ if next word starts with - */
+ (*data)++;
+ while (isspace(**data)) (*data)++;
+ break;
+ }
+
+ if (!isspace(**data))
+ option = cmd_get_param(data);
+ else {
+ option = "-";
+ (*data)++;
+ }
+
+ /* check if this option can have argument */
+ pos = optlist == NULL ? -1 :
+ option_find(optlist, option);
+ if (pos == -1 && !ignore_unknown) {
+ /* unknown option! */
+ *data = option;
+ return CMDERR_OPTION_UNKNOWN;
+ }
+ if (pos == -2 && !ignore_unknown) {
+ /* multiple matches */
+ *data = option;
+ return CMDERR_OPTION_AMBIGUOUS;
+ }
+ if (pos >= 0) {
+ /* if we used a shortcut of parameter, put
+ the whole parameter name in options table */
+ option = optlist[pos] +
+ iscmdtype(*optlist[pos]);
+ }
+ if (options != NULL)
+ g_hash_table_insert(options, option, "");
+
+ if (pos < 0 || !iscmdtype(*optlist[pos]) ||
+ *optlist[pos] == '!')
+ option = NULL;
+
+ while (isspace(**data)) (*data)++;
+ continue;
+ }
+
+ if (option == NULL)
+ break;
+
+ if (*optlist[pos] == '@' && !isdigit(**data))
+ break; /* expected a numeric argument */
+
+ /* save the argument */
+ arg = cmd_get_quoted_param(data);
+ if (options != NULL) {
+ g_hash_table_remove(options, option);
+ g_hash_table_insert(options, option, arg);
+ }
+ option = NULL;
+
+ while (isspace(**data)) (*data)++;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ char *data;
+ GHashTable *options;
+} CMD_TEMP_REC;
+
+static char *get_optional_channel(CHANNEL_REC *active_channel, char **data)
+{
+ CHANNEL_REC *chanrec;
+ char *tmp, *origtmp, *channel, *ret;
+
+ if (active_channel == NULL) {
+ /* no active channel in window, channel required */
+ return cmd_get_param(data);
+ }
+
+ origtmp = tmp = g_strdup(*data);
+ channel = cmd_get_param(&tmp);
+
+ if (strcmp(channel, "*") == 0 ||
+ !active_channel->server->ischannel(channel))
+ ret = active_channel->name;
+ else {
+ /* Find the channel first and use it's name if found.
+ This allows automatic !channel -> !XXXXXchannel replaces. */
+ chanrec = channel_find(active_channel->server, channel);
+ ret = chanrec == NULL ? channel : chanrec->name;
+ cmd_get_param(data);
+ }
+
+ g_free(origtmp);
+ return ret;
+}
+
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
+{
+ CHANNEL_REC *chanrec;
+ CMD_TEMP_REC *rec;
+ GHashTable **opthash;
+ char **str, *arg, *datad;
+ va_list args;
+ int cnt, error, ignore_unknown;
+
+ g_return_val_if_fail(data != NULL, FALSE);
+
+ va_start(args, count);
+
+ rec = g_new0(CMD_TEMP_REC, 1);
+ rec->data = g_strdup(data);
+ *free_me = rec;
+
+ datad = rec->data;
+ error = FALSE;
+
+ chanrec = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
+ (CHANNEL_REC *) va_arg(args, CHANNEL_REC *);
+
+ if (count & PARAM_FLAG_OPTIONS) {
+ arg = (char *) va_arg(args, char *);
+ opthash = (GHashTable **) va_arg(args, GHashTable **);
+
+ rec->options = *opthash =
+ g_hash_table_new((GHashFunc) g_istr_hash,
+ (GCompareFunc) g_istr_equal);
+
+ ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
+ error = get_cmd_options(&datad, ignore_unknown,
+ arg, *opthash);
+ }
+
+ if (!error) {
+ /* and now handle the string */
+ cnt = PARAM_WITHOUT_FLAGS(count);
+ if (count & PARAM_FLAG_OPTCHAN) {
+ /* optional channel as first parameter */
+ arg = get_optional_channel(chanrec, &datad);
+
+ str = (char **) va_arg(args, char **);
+ if (str != NULL) *str = arg;
+ cnt--;
+ }
+
+ while (cnt-- > 0) {
+ if (cnt == 0 && count & PARAM_FLAG_GETREST) {
+ /* get rest */
+ arg = datad;
+ } else {
+ arg = (count & PARAM_FLAG_NOQUOTES) ?
+ cmd_get_param(&datad) :
+ cmd_get_quoted_param(&datad);
+ }
+
+ str = (char **) va_arg(args, char **);
+ if (str != NULL) *str = arg;
+ }
+ }
+ va_end(args);
+
+ if (error) {
+ signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
+ signal_stop();
+
+ cmd_params_free(rec);
+ *free_me = NULL;
+ }
+
+ return !error;
+}
+
+void cmd_params_free(void *free_me)
+{
+ CMD_TEMP_REC *rec = free_me;
+
+ if (rec->options != NULL) g_hash_table_destroy(rec->options);
+ g_free(rec->data);
+ g_free(rec);
+}
+
+static void command_module_unbind_all(COMMAND_REC *rec,
+ COMMAND_MODULE_REC *modrec)
+{
+ GSList *tmp, *next;
+
+ for (tmp = modrec->signals; tmp != NULL; tmp = next) {
+ next = tmp->next;
+
+ command_unbind(rec->cmd, tmp->data);
+ }
+
+ if (g_slist_find(commands, rec) != NULL) {
+ /* this module might have removed some options
+ from command, update them. */
+ command_update_options(rec);
+ }
+}
+
+void commands_remove_module(const char *module)
+{
+ GSList *tmp, *next, *modlist;
+
+ g_return_if_fail(module != NULL);
+
+ for (tmp = commands; tmp != NULL; tmp = next) {
+ COMMAND_REC *rec = tmp->data;
+
+ next = tmp->next;
+ modlist = gslist_find_string(rec->modules, module);
+ if (modlist != NULL)
+ command_module_unbind_all(rec, modlist->data);
+ }
+}
+
+#define alias_runstack_push(alias) \
+ alias_runstack = g_slist_append(alias_runstack, alias)
+
+#define alias_runstack_pop(alias) \
+ alias_runstack = g_slist_remove(alias_runstack, alias)
+
+#define alias_runstack_find(alias) \
+ (gslist_find_icase_string(alias_runstack, alias) != NULL)
+
+static void parse_command(const char *command, int expand_aliases,
+ SERVER_REC *server, void *item)
+{
+ const char *alias, *newcmd;
+ char *cmd, *orig, *args, *oldcmd;
+
+ g_return_if_fail(command != NULL);
+
+ cmd = orig = g_strconcat("command ", command, NULL);
+ args = strchr(cmd+8, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ /* check if there's an alias for command. Don't allow
+ recursive aliases */
+ alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
+ alias_find(cmd+8);
+ if (alias != NULL) {
+ alias_runstack_push(cmd+8);
+ eval_special_string(alias, args, server, item);
+ alias_runstack_pop(cmd+8);
+ g_free(orig);
+ return;
+ }
+
+ /* check if this command can be expanded */
+ newcmd = command_expand(cmd+8);
+ if (newcmd == NULL) {
+ /* ambiguous command */
+ g_free(orig);
+ return;
+ }
+
+ cmd = g_strconcat("command ", newcmd, NULL);
+ if (server != NULL)
+ server_redirect_default(SERVER(server), cmd);
+
+ g_strdown(cmd);
+ oldcmd = current_command;
+ current_command = cmd+8;
+ if (!signal_emit(cmd, 3, args, server, item)) {
+ signal_emit_id(signal_default_command, 3,
+ command, server, item);
+ }
+ current_command = oldcmd;
+
+ g_free(cmd);
+ g_free(orig);
+}
+
+static void event_command(const char *line, SERVER_REC *server, void *item)
+{
+ char *cmdchar;
+ int expand_aliases = TRUE;
+
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0') {
+ /* empty line, forget it. */
+ signal_stop();
+ return;
+ }
+
+ cmdchar = strchr(settings_get_str("cmdchars"), *line);
+ if (cmdchar != NULL && line[1] == ' ') {
+ /* "/ text" = same as sending "text" to active channel. */
+ line += 2;
+ cmdchar = NULL;
+ }
+ if (cmdchar == NULL) {
+ /* non-command - let someone else handle this */
+ signal_emit("send text", 3, line, server, item);
+ return;
+ }
+
+ /* same cmdchar twice ignores aliases ignores aliases */
+ line++;
+ if (*line == *cmdchar) {
+ line++;
+ expand_aliases = FALSE;
+ }
+
+ /* ^command hides the output - we'll do this at fe-common but
+ we have to skip the ^ char here.. */
+ if (*line == '^') line++;
+
+ parse_command(line, expand_aliases, server, item);
+}
+
+/* SYNTAX: EVAL <command(s)> */
+static void cmd_eval(const char *data, SERVER_REC *server, void *item)
+{
+ g_return_if_fail(data != NULL);
+
+ eval_special_string(data, "", server, item);
+}
+
+/* SYNTAX: CD <directory> */
+static void cmd_cd(const char *data)
+{
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') return;
+
+ str = convert_home(data);
+ chdir(str);
+ g_free(str);
+}
+
+void commands_init(void)
+{
+ commands = NULL;
+ current_command = NULL;
+ alias_runstack = NULL;
+
+ signal_default_command = signal_get_uniq_id("default command");
+
+ settings_add_str("misc", "cmdchars", "/");
+ signal_add("send command", (SIGNAL_FUNC) event_command);
+
+ command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
+ command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
+}
+
+void commands_deinit(void)
+{
+ g_free_not_null(current_command);
+
+ signal_remove("send command", (SIGNAL_FUNC) event_command);
+
+ command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
+ command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
+}
--- /dev/null
+#ifndef __COMMANDS_H
+#define __COMMANDS_H
+
+#include "signals.h"
+
+typedef struct {
+ char *name;
+ char *options;
+ GSList *signals;
+} COMMAND_MODULE_REC;
+
+typedef struct {
+ GSList *modules;
+ char *category;
+ char *cmd;
+ char **options; /* combined from modules[..]->options */
+} COMMAND_REC;
+
+enum {
+ CMDERR_OPTION_UNKNOWN = -3, /* unknown -option */
+ CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */
+ CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */
+
+ CMDERR_UNKNOWN, /* unknown command */
+ CMDERR_AMBIGUOUS, /* ambiguous command */
+
+ CMDERR_ERRNO, /* get the error from errno */
+ CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */
+ CMDERR_NOT_CONNECTED, /* not connected to IRC server */
+ CMDERR_NOT_JOINED, /* not joined to any channels in this window */
+ CMDERR_CHAN_NOT_FOUND, /* channel not found */
+ CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */
+ CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */
+};
+
+/* Return the full command for `alias' */
+#define alias_find(alias) \
+ iconfig_get_str("aliases", alias, NULL)
+
+/* Returning from command function with error */
+#define cmd_return_error(a) \
+ G_STMT_START { \
+ signal_emit("error command", 1, GINT_TO_POINTER(a)); \
+ signal_stop(); \
+ return; \
+ } G_STMT_END
+
+#define cmd_param_error(a) \
+ G_STMT_START { \
+ cmd_params_free(free_arg); \
+ cmd_return_error(a); \
+ } G_STMT_END
+
+extern GSList *commands;
+extern char *current_command; /* the command we're right now. */
+
+/* Bind command to specified function. */
+void command_bind_to(const char *module, int pos, const char *cmd,
+ const char *category, SIGNAL_FUNC func);
+#define command_bind(a, b, c) command_bind_to(MODULE_NAME, 1, a, b, c)
+#define command_bind_first(a, b, c) command_bind_to(MODULE_NAME, 0, a, b, c)
+#define command_bind_last(a, b, c) command_bind_to(MODULE_NAME, 2, a, b, c)
+
+void command_unbind(const char *cmd, SIGNAL_FUNC func);
+
+/* Run subcommand, `cmd' contains the base command, first word in `data'
+ contains the subcommand */
+void command_runsub(const char *cmd, const char *data,
+ void *server, void *item);
+
+COMMAND_REC *command_find(const char *cmd);
+int command_have_sub(const char *command);
+
+/* Specify options that command can accept. `options' contains list of
+ options separated with space, each option can contain a special
+ char in front of it:
+
+ '!': no argument (default)
+ '-': optional argument
+ '+': argument required
+ '@': optional numeric argument
+
+ for example if options = "save -file +nick", you can use
+ /command -save -file [<filename>] -nick <nickname>
+
+ You can call this command multiple times for same command, options
+ will be merged. If there's any conflicts with option types, the last
+ call will override the previous */
+#define iscmdtype(c) \
+ ((c) == '!' || (c) == '-' || (c) == '+' || (c) == '@')
+void command_set_options_module(const char *module,
+ const char *cmd, const char *options);
+#define command_set_options(cmd, options) \
+ command_set_options_module(MODULE_NAME, cmd, options)
+
+/* Returns TRUE if command has specified option. */
+int command_have_option(const char *cmd, const char *option);
+
+/* count can have these flags: */
+#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00000fff)
+/* don't check for quotes - "arg1 arg2" is NOT treated as one argument */
+#define PARAM_FLAG_NOQUOTES 0x00001000
+/* final argument gets all the rest of the arguments */
+#define PARAM_FLAG_GETREST 0x00002000
+/* command contains options - first you need to specify them with
+ command_set_options() function. Example:
+
+ -cmd requiredarg -noargcmd -cmd2 "another arg" -optnumarg rest of text
+
+ You would call this with:
+
+ // only once in init
+ command_set_options("mycmd", "+cmd noargcmd -cmd2 @optnumarg");
+
+ GHashTable *optlist;
+
+ cmd_get_params(data, &free_me, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "mycmd", &optlist, &rest);
+
+ The optlist hash table is filled:
+
+ "cmd" = "requiredarg"
+ "noargcmd" = ""
+ "cmd2" = "another arg"
+ "optnumarg" = "" - this is because "rest" isn't a numeric value
+*/
+#define PARAM_FLAG_OPTIONS 0x00004000
+/* don't complain about unknown options */
+#define PARAM_FLAG_UNKNOWN_OPTIONS 0x00008000
+/* optional channel in first argument */
+#define PARAM_FLAG_OPTCHAN 0x00010000
+
+char *cmd_get_param(char **data);
+/* get parameters from command - you should point free_me somewhere and
+ cmd_params_free() it after you don't use any of the parameters anymore.
+
+ Returns TRUE if all ok, FALSE if error occured. */
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...);
+
+void cmd_params_free(void *free_me);
+
+void commands_remove_module(const char *module);
+
+void commands_init(void);
+void commands_deinit(void);
+
+#endif
--- /dev/null
+/*
+ core.c : irssi
+
+ Copyright (C) 1999-2000 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 "pidwait.h"
+
+#include "net-disconnect.h"
+#include "net-sendbuffer.h"
+#include "signals.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "chatnets.h"
+#include "commands.h"
+#include "expandos.h"
+#include "write-buffer.h"
+#include "log.h"
+#include "rawlog.h"
+#include "ignore.h"
+
+#include "channels.h"
+#include "queries.h"
+#include "nicklist.h"
+#include "nickmatch-cache.h"
+
+void chat_commands_init(void);
+void chat_commands_deinit(void);
+
+int irssi_gui;
+
+void core_init(void)
+{
+ modules_init();
+#ifndef WIN32
+ pidwait_init();
+#endif
+
+ net_disconnect_init();
+ net_sendbuffer_init();
+ signals_init();
+ settings_init();
+ commands_init();
+ nickmatch_cache_init();
+
+ chat_protocols_init();
+ chatnets_init();
+ expandos_init();
+ ignore_init();
+ servers_init();
+ write_buffer_init();
+ log_init();
+ rawlog_init();
+
+ channels_init();
+ queries_init();
+ nicklist_init();
+
+ chat_commands_init();
+ settings_check();
+}
+
+void core_deinit(void)
+{
+ chat_commands_deinit();
+
+ nicklist_deinit();
+ queries_deinit();
+ channels_deinit();
+
+ rawlog_deinit();
+ log_deinit();
+ write_buffer_deinit();
+ servers_deinit();
+ ignore_deinit();
+ expandos_deinit();
+ chatnets_deinit();
+ chat_protocols_deinit();
+
+ nickmatch_cache_deinit();
+ commands_deinit();
+ settings_deinit();
+ signals_deinit();
+ net_sendbuffer_deinit();
+ net_disconnect_deinit();
+
+#ifndef WIN32
+ pidwait_deinit();
+#endif
+ modules_deinit();
+}
--- /dev/null
+#ifndef __IRSSI_CORE_H
+#define __IRSSI_CORE_H
+
+/* for determining what GUI is currently in use: */
+#define IRSSI_GUI_NONE 0
+#define IRSSI_GUI_TEXT 1
+#define IRSSI_GUI_GTK 2
+#define IRSSI_GUI_GNOME 3
+#define IRSSI_GUI_QT 4
+#define IRSSI_GUI_KDE 5
+
+extern int irssi_gui;
+
+void core_init(void);
+void core_deinit(void);
+
+#endif
--- /dev/null
+/*
+ expandos.c : irssi
+
+ Copyright (C) 2000 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 "modules.h"
+#include "signals.h"
+#include "expandos.h"
+#include "settings.h"
+#include "commands.h"
+#include "misc.h"
+#include "irssi-version.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "queries.h"
+#include "window-item-def.h"
+
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+
+#define MAX_EXPANDO_SIGNALS 10
+
+typedef struct {
+ EXPANDO_FUNC func;
+
+ int signals;
+ int signal_ids[MAX_EXPANDO_SIGNALS];
+ int signal_args[MAX_EXPANDO_SIGNALS];
+} EXPANDO_REC;
+
+static int timer_tag;
+
+static EXPANDO_REC *char_expandos[127];
+static GHashTable *expandos;
+static time_t client_start_time;
+static char *last_sent_msg, *last_sent_msg_body;
+static char *last_privmsg_from, *last_public_from;
+static char *sysname, *sysrelease, *sysarch;
+static const char *timestamp_format;
+
+#define CHAR_EXPANDOS_COUNT \
+ ((int) (sizeof(char_expandos) / sizeof(char_expandos[0])))
+
+/* Create expando - overrides any existing ones. */
+void expando_create(const char *key, EXPANDO_FUNC func, ...)
+{
+ EXPANDO_REC *rec;
+ const char *signal;
+ va_list va;
+
+ g_return_if_fail(key != NULL || *key == '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] != '\0')
+ rec = g_hash_table_lookup(expandos, key);
+ else {
+ /* single character expando */
+ rec = char_expandos[(int) *key];
+ }
+
+ if (rec != NULL)
+ rec->signals = 0;
+ else {
+ rec = g_new0(EXPANDO_REC, 1);
+ if (key[1] != '\0')
+ g_hash_table_insert(expandos, g_strdup(key), rec);
+ else
+ char_expandos[(int) *key] = rec;
+ }
+
+ rec->func = func;
+
+ va_start(va, func);
+ while ((signal = (const char *) va_arg(va, const char *)) != NULL)
+ expando_add_signal(key, signal, (int) va_arg(va, int));
+ va_end(va);
+}
+
+static EXPANDO_REC *expando_find(const char *key)
+{
+ if (key[1] != '\0')
+ return g_hash_table_lookup(expandos, key);
+ else
+ return char_expandos[(int) *key];
+}
+
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg)
+{
+ EXPANDO_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(signal != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (arg == EXPANDO_NEVER) {
+ /* expando changes never */
+ rec->signals = -1;
+ } else if (rec->signals < MAX_EXPANDO_SIGNALS) {
+ g_return_if_fail(rec->signals != -1);
+
+ rec->signal_ids[rec->signals] = signal_get_uniq_id(signal);
+ rec->signal_args[rec->signals] = arg;
+ rec->signals++;
+ }
+}
+
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func)
+{
+ gpointer origkey;
+ EXPANDO_REC *rec;
+
+ g_return_if_fail(key != NULL || *key == '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] == '\0') {
+ /* single character expando */
+ rec = char_expandos[(int) *key];
+ if (rec != NULL && rec->func == func) {
+ char_expandos[(int) *key] = NULL;
+ g_free(rec);
+ }
+ } else if (g_hash_table_lookup_extended(expandos, key, &origkey,
+ (gpointer *) &rec)) {
+ if (rec->func == func) {
+ g_free(origkey);
+ g_hash_table_remove(expandos, key);
+ g_free(rec);
+ }
+ }
+}
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+ SIGNAL_FUNC func;
+ EXPANDO_REC *rec;
+ int n, arg;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(funccount >= 1);
+ g_return_if_fail(funcs != NULL);
+ g_return_if_fail(funcs[0] != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (rec->signals == 0) {
+ /* it's unknown when this expando changes..
+ check it once in a second */
+ signal_add("expando timer", funcs[EXPANDO_ARG_NONE]);
+ }
+
+ for (n = 0; n < rec->signals; n++) {
+ arg = rec->signal_args[n];
+ func = arg < funccount ? funcs[arg] : NULL;
+ if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+ signal_add_to_id(MODULE_NAME, 1, rec->signal_ids[n], func);
+ }
+}
+
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+ SIGNAL_FUNC func;
+ EXPANDO_REC *rec;
+ int n, arg;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(funccount >= 1);
+ g_return_if_fail(funcs != NULL);
+ g_return_if_fail(funcs[0] != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (rec->signals == 0) {
+ /* it's unknown when this expando changes..
+ check it once in a second */
+ signal_remove("expando timer", funcs[EXPANDO_ARG_NONE]);
+ }
+
+ for (n = 0; n < rec->signals; n++) {
+ arg = rec->signal_args[n];
+ func = arg < funccount ? funcs[arg] : NULL;
+ if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+ signal_remove_id(rec->signal_ids[n], func);
+ }
+}
+
+EXPANDO_FUNC expando_find_char(char chr)
+{
+ g_return_val_if_fail(chr < CHAR_EXPANDOS_COUNT, NULL);
+
+ return char_expandos[(int) chr] == NULL ? NULL :
+ char_expandos[(int) chr]->func;
+}
+
+EXPANDO_FUNC expando_find_long(const char *key)
+{
+ EXPANDO_REC *rec = g_hash_table_lookup(expandos, key);
+ return rec == NULL ? NULL : rec->func;
+}
+
+/* last person who sent you a MSG */
+static char *expando_lastmsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_privmsg_from;
+}
+
+/* last person to whom you sent a MSG */
+static char *expando_lastmymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_sent_msg;
+}
+
+/* last person to send a public message to a channel you are on */
+static char *expando_lastpublic(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_public_from;
+}
+
+/* text of your AWAY message, if any */
+static char *expando_awaymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->away_reason;
+}
+
+/* body of last MSG you sent */
+static char *expando_lastmymsg_body(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_sent_msg_body;
+}
+
+/* current channel */
+static char *expando_channel(SERVER_REC *server, void *item, int *free_ret)
+{
+ return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->name;
+}
+
+/* time client was started, $time() format */
+static char *expando_clientstarted(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%ld", (long) client_start_time);
+}
+
+/* channel you were last INVITEd to */
+static char *expando_last_invite(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->last_invite;
+}
+
+/* client version text string */
+static char *expando_version(SERVER_REC *server, void *item, int *free_ret)
+{
+ return IRSSI_VERSION;
+}
+
+/* current value of CMDCHARS */
+static char *expando_cmdchars(SERVER_REC *server, void *item, int *free_ret)
+{
+ return (char *) settings_get_str("cmdchars");
+}
+
+/* modes of current channel, if any */
+static char *expando_chanmode(SERVER_REC *server, void *item, int *free_ret)
+{
+ return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->mode;
+}
+
+/* current nickname */
+static char *expando_nick(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->nick;
+}
+
+/* value of STATUS_OPER if you are an irc operator */
+static char *expando_statusoper(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL || !server->server_operator ? "" :
+ (char *) settings_get_str("STATUS_OPER");
+}
+
+/* if you are a channel operator in $C, expands to a '@' */
+static char *expando_chanop(SERVER_REC *server, void *item, int *free_ret)
+{
+ return IS_CHANNEL(item) && CHANNEL(item)->chanop ? "@" : "";
+}
+
+/* nickname of whomever you are QUERYing */
+static char *expando_query(SERVER_REC *server, void *item, int *free_ret)
+{
+ return !IS_QUERY(item) ? "" : QUERY(item)->name;
+}
+
+/* version of current server */
+static char *expando_serverversion(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->version;
+}
+
+/* target of current input (channel or QUERY nickname) */
+static char *expando_target(SERVER_REC *server, void *item, int *free_ret)
+{
+ return item == NULL ? "" : ((WI_ITEM_REC *) item)->name;
+}
+
+/* client release date (numeric version string) */
+static char *expando_releasedate(SERVER_REC *server, void *item, int *free_ret)
+{
+ return IRSSI_VERSION_DATE;
+}
+
+/* current working directory */
+static char *expando_workdir(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_get_current_dir();
+}
+
+/* value of REALNAME */
+static char *expando_realname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->connrec->realname;
+}
+
+/* time of day (hh:mm) */
+static char *expando_time(SERVER_REC *server, void *item, int *free_ret)
+{
+ time_t now;
+ struct tm *tm;
+ char str[256];
+
+ now = time(NULL);
+ tm = localtime(&now);
+
+ if (strftime(str, sizeof(str), timestamp_format, tm) == 0)
+ return "";
+
+ *free_ret = TRUE;
+ return g_strdup(str);
+}
+
+/* a literal '$' */
+static char *expando_dollar(SERVER_REC *server, void *item, int *free_ret)
+{
+ return "$";
+}
+
+/* system name */
+static char *expando_sysname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysname;
+}
+
+/* system release */
+static char *expando_sysrelease(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysrelease;
+}
+
+/* system architecture */
+static char *expando_sysarch(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysarch;
+}
+
+/* Topic of active channel (or address of queried nick) */
+static char *expando_topic(SERVER_REC *server, void *item, int *free_ret)
+{
+ return IS_CHANNEL(item) ? CHANNEL(item)->topic :
+ IS_QUERY(item) ? QUERY(item)->address : "";
+}
+
+/* Server tag */
+static char *expando_servertag(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->tag;
+}
+
+/* Server chatnet */
+static char *expando_chatnet(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->connrec->chatnet;
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ g_free_not_null(last_public_from);
+ last_public_from = g_strdup(nick);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ g_free_not_null(last_privmsg_from);
+ last_privmsg_from = g_strdup(nick);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+
+ if (target != NULL) {
+ if (target != last_sent_msg) {
+ g_free_not_null(last_sent_msg);
+ last_sent_msg = g_strdup(target);
+ }
+ g_free_not_null(last_sent_msg_body);
+ last_sent_msg_body = g_strdup(msg);
+ }
+}
+
+static int sig_timer(void)
+{
+ signal_emit("expando timer", 0);
+ return 1;
+}
+
+static void read_settings(void)
+{
+ timestamp_format = settings_get_str("timestamp_format");
+}
+
+void expandos_init(void)
+{
+#ifdef HAVE_SYS_UTSNAME_H
+ struct utsname un;
+#endif
+ settings_add_str("misc", "STATUS_OPER", "*");
+ settings_add_str("misc", "timestamp_format", "%H:%M");
+
+ client_start_time = time(NULL);
+ last_sent_msg = NULL; last_sent_msg_body = NULL;
+ last_privmsg_from = NULL; last_public_from = NULL;
+
+ sysname = sysrelease = sysarch = NULL;
+#ifdef HAVE_SYS_UTSNAME_H
+ if (uname(&un) == 0) {
+ sysname = g_strdup(un.sysname);
+ sysrelease = g_strdup(un.release);
+ sysarch = g_strdup(un.machine);
+ }
+#endif
+
+ memset(char_expandos, 0, sizeof(char_expandos));
+ expandos = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ expando_create(",", expando_lastmsg,
+ "message private", EXPANDO_ARG_SERVER, NULL);
+ expando_create(".", expando_lastmymsg,
+ "command msg", EXPANDO_ARG_NONE, NULL);
+ expando_create(";", expando_lastpublic,
+ "message public", EXPANDO_ARG_SERVER, NULL);
+ expando_create("A", expando_awaymsg,
+ "away mode changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("B", expando_lastmymsg_body,
+ "command msg", EXPANDO_ARG_NONE, NULL);
+ expando_create("C", expando_channel,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("F", expando_clientstarted,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("I", expando_last_invite, NULL);
+ expando_create("J", expando_version,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("K", expando_cmdchars,
+ "setup changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("M", expando_chanmode,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "channel mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("N", expando_nick,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW,
+ "server nick changed", EXPANDO_ARG_SERVER, NULL);
+ expando_create("O", expando_statusoper,
+ "setup changed", EXPANDO_ARG_NONE,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW,
+ "user mode changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("P", expando_chanop,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("Q", expando_query,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("R", expando_serverversion,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("T", expando_target,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("V", expando_releasedate,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("W", expando_workdir, NULL);
+ expando_create("Y", expando_realname,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("Z", expando_time, NULL);
+ expando_create("$", expando_dollar,
+ "", EXPANDO_NEVER, NULL);
+
+ expando_create("sysname", expando_sysname,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("sysrelease", expando_sysrelease,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("sysarch", expando_sysarch,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("topic", expando_topic,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "channel topic changed", EXPANDO_ARG_WINDOW_ITEM,
+ "query address changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("tag", expando_servertag,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("chatnet", expando_chatnet,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+
+ read_settings();
+
+ timer_tag = g_timeout_add(1000, (GSourceFunc) sig_timer, NULL);
+ signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void expandos_deinit(void)
+{
+ int n;
+
+ for (n = 0; n < CHAR_EXPANDOS_COUNT; n++)
+ g_free_not_null(char_expandos[n]);
+
+ expando_destroy("sysname", expando_sysname);
+ expando_destroy("sysrelease", expando_sysrelease);
+ expando_destroy("sysarch", expando_sysarch);
+ expando_destroy("topic", expando_topic);
+ expando_destroy("tag", expando_servertag);
+ expando_destroy("chatnet", expando_chatnet);
+
+ g_hash_table_destroy(expandos);
+
+ g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body);
+ g_free_not_null(last_privmsg_from); g_free_not_null(last_public_from);
+ g_free_not_null(sysname); g_free_not_null(sysrelease);
+ g_free_not_null(sysarch);
+
+ g_source_remove(timer_tag);
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __EXPANDOS_H
+#define __EXPANDOS_H
+
+#include "signals.h"
+
+/* first argument of signal must match to active .. */
+typedef enum {
+ EXPANDO_ARG_NONE,
+ EXPANDO_ARG_SERVER,
+ EXPANDO_ARG_WINDOW,
+ EXPANDO_ARG_WINDOW_ITEM,
+
+ EXPANDO_NEVER /* special: expando never changes */
+} ExpandoArg;
+
+typedef char* (*EXPANDO_FUNC)
+ (SERVER_REC *server, void *item, int *free_ret);
+
+/* Create expando - overrides any existing ones.
+ ... = signal, type, ..., NULL - list of signals that might change the
+ value of this expando */
+void expando_create(const char *key, EXPANDO_FUNC func, ...);
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg);
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func);
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+
+/* internal: */
+EXPANDO_FUNC expando_find_char(char chr);
+EXPANDO_FUNC expando_find_long(const char *key);
+
+void expandos_init(void);
+void expandos_deinit(void);
+
+#endif
--- /dev/null
+/*
+ ignore.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+#include "levels.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "masks.h"
+#include "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "nickmatch-cache.h"
+
+#include "ignore.h"
+
+GSList *ignores;
+
+static NICKMATCH_REC *nickmatch;
+static int time_tag;
+
+/* check if `text' contains ignored nick at the start of the line. */
+static int ignore_check_replies_rec(IGNORE_REC *rec, CHANNEL_REC *channel,
+ const char *text)
+{
+ GSList *nicks, *tmp;
+
+ nicks = nicklist_find_multiple(channel, rec->mask);
+ if (nicks == NULL) return FALSE;
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *nick = tmp->data;
+
+ if (nick_match_msg(channel, text, nick->nick))
+ return TRUE;
+ }
+ g_slist_free(nicks);
+
+ return FALSE;
+}
+
+static int ignore_check_replies(CHANNEL_REC *chanrec, const char *text)
+{
+ GSList *tmp;
+
+ if (text == NULL || chanrec == NULL)
+ return FALSE;
+
+ /* check reply ignores */
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec->mask != NULL && rec->replies &&
+ ignore_check_replies_rec(rec, chanrec, text))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int ignore_match_pattern(IGNORE_REC *rec, const char *text)
+{
+ if (rec->pattern == NULL)
+ return TRUE;
+
+ if (text == NULL)
+ return FALSE;
+
+ if (rec->regexp) {
+#ifdef HAVE_REGEX_H
+ return rec->regexp_compiled &&
+ regexec(&rec->preg, text, 0, NULL, 0) == 0;
+#else
+ return FALSE;
+#endif
+ }
+
+ return rec->fullword ?
+ stristr_full(text, rec->pattern) != NULL :
+ stristr(text, rec->pattern) != NULL;
+}
+
+#define ignore_match_level(rec, level) \
+ ((level & (rec)->level) != 0)
+
+#define ignore_match_nickmask(rec, nick, nickmask) \
+ ((rec)->mask == NULL || \
+ (strchr((rec)->mask, '!') != NULL ? \
+ match_wildcards((rec)->mask, nickmask) : \
+ match_wildcards((rec)->mask, nick)))
+
+#define ignore_match_server(rec, server) \
+ ((rec)->servertag == NULL || \
+ g_strcasecmp((server)->tag, (rec)->servertag) == 0)
+
+#define ignore_match_channel(rec, channel) \
+ ((rec)->channels == NULL || ((channel) != NULL && \
+ strarray_find((rec)->channels, (channel)) != -1))
+
+static int ignore_check_without_mask(GSList *list, CHANNEL_REC *channel,
+ int level, const char *text)
+{
+ GSList *tmp;
+ int len, best_mask, best_match, best_patt;
+
+ best_mask = best_patt = -1; best_match = FALSE;
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (ignore_match_level(rec, level) &&
+ ignore_match_pattern(rec, text)) {
+ len = rec->mask == NULL ? 0 : strlen(rec->mask);
+ if (len > best_mask) {
+ best_mask = len;
+ best_match = !rec->exception;
+ } else if (len == best_mask && rec->pattern != NULL) {
+ len = strlen(rec->pattern);
+ if (len > best_patt) {
+ best_patt = len;
+ best_match = !rec->exception;
+ }
+ }
+ }
+ }
+
+ if (best_match || (level & MSGLEVEL_PUBLIC) == 0)
+ return best_match;
+
+ return ignore_check_replies(channel, text);
+}
+
+int ignore_check(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level)
+{
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+ IGNORE_REC *rec;
+ GSList *tmp, *list;
+ char *nickmask;
+ int len, best_mask, best_match, best_patt;
+
+ g_return_val_if_fail(server != NULL, 0);
+ if (nick == NULL) nick = "";
+
+ chanrec = (channel != NULL && server != NULL &&
+ server->ischannel(channel)) ?
+ channel_find(server, channel) : NULL;
+ if (chanrec != NULL && nick != NULL &&
+ (nickrec = nicklist_find(chanrec, nick)) != NULL) {
+ /* nick found - check only ignores in nickmatch cache */
+ if (nickrec->host == NULL)
+ nicklist_set_host(chanrec, nickrec, host);
+
+ list = nickmatch_find(nickmatch, nickrec);
+ return ignore_check_without_mask(list, chanrec, level, text);
+ }
+
+ nickmask = g_strconcat(nick, "!", host, NULL);
+
+ best_mask = best_patt = -1; best_match = FALSE;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (ignore_match_level(rec, level) &&
+ ignore_match_server(rec, server) &&
+ ignore_match_channel(rec, channel) &&
+ ignore_match_nickmask(rec, nick, nickmask) &&
+ ignore_match_pattern(rec, text)) {
+ len = rec->mask == NULL ? 0 : strlen(rec->mask);
+ if (len > best_mask) {
+ best_mask = len;
+ best_match = !rec->exception;
+ } else if (len == best_mask && rec->pattern != NULL) {
+ len = strlen(rec->pattern);
+ if (len > best_patt) {
+ best_patt = len;
+ best_match = !rec->exception;
+ }
+ }
+ }
+ }
+ g_free(nickmask);
+
+ if (best_match || (level & MSGLEVEL_PUBLIC) == 0)
+ return best_match;
+
+ return ignore_check_replies(chanrec, text);
+}
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask,
+ char **channels)
+{
+ GSList *tmp;
+ char **chan;
+ int ignore_servertag;
+
+ if (mask != NULL && (*mask == '\0' || strcmp(mask, "*") == 0))
+ mask = NULL;
+
+ ignore_servertag = servertag != NULL && strcmp(servertag, "*") == 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (!ignore_servertag) {
+ if ((servertag == NULL && rec->servertag != NULL) ||
+ (servertag != NULL && rec->servertag == NULL))
+ continue;
+
+ if (servertag != NULL && g_strcasecmp(servertag, rec->servertag) != 0)
+ continue;
+ }
+
+ if ((rec->mask == NULL && mask != NULL) ||
+ (rec->mask != NULL && mask == NULL)) continue;
+
+ if (rec->mask != NULL && g_strcasecmp(rec->mask, mask) != 0)
+ continue;
+
+ if ((channels == NULL && rec->channels == NULL))
+ return rec; /* no channels - ok */
+
+ if (channels != NULL && strcmp(*channels, "*") == 0)
+ return rec; /* ignore channels */
+
+ if (channels == NULL || rec->channels == NULL)
+ continue; /* other doesn't have channels */
+
+ if (strarray_length(channels) != strarray_length(rec->channels))
+ continue; /* different amount of channels */
+
+ /* check that channels match */
+ for (chan = channels; *chan != NULL; chan++) {
+ if (strarray_find(rec->channels, *chan) == -1)
+ break;
+ }
+
+ if (*chan == NULL)
+ return rec; /* channels ok */
+ }
+
+ return NULL;
+}
+
+static void ignore_set_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ if (rec->level == 0 || rec->unignore_time > 0)
+ return;
+
+ node = iconfig_node_traverse("(ignores", TRUE);
+ node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ if (rec->mask != NULL) iconfig_node_set_str(node, "mask", rec->mask);
+ if (rec->level) {
+ levelstr = bits2level(rec->level);
+ iconfig_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+ }
+ iconfig_node_set_str(node, "pattern", rec->pattern);
+ if (rec->exception) iconfig_node_set_bool(node, "exception", TRUE);
+ if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+ if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+ if (rec->replies) iconfig_node_set_bool(node, "replies", TRUE);
+
+ if (rec->channels != NULL && *rec->channels != NULL) {
+ node = config_node_section(node, "channels", NODE_TYPE_LIST);
+ iconfig_node_add_list(node, rec->channels);
+ }
+}
+
+static int ignore_index(IGNORE_REC *find)
+{
+ GSList *tmp;
+ int index;
+
+ index = 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec->servertag != NULL)
+ continue;
+
+ if (rec == find)
+ return index;
+ index++;
+ }
+
+ return -1;
+}
+
+static void ignore_remove_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node != NULL) iconfig_node_list_remove(node, ignore_index(rec));
+}
+
+void ignore_add_rec(IGNORE_REC *rec)
+{
+#ifdef HAVE_REGEX_H
+ rec->regexp_compiled = !rec->regexp || rec->pattern == NULL ? FALSE :
+ regcomp(&rec->preg, rec->pattern,
+ REG_EXTENDED|REG_ICASE|REG_NOSUB) == 0;
+#endif
+
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+
+ signal_emit("ignore created", 1, rec);
+}
+
+static void ignore_destroy(IGNORE_REC *rec, int send_signal)
+{
+ ignores = g_slist_remove(ignores, rec);
+ if (send_signal)
+ signal_emit("ignore destroyed", 1, rec);
+
+#ifdef HAVE_REGEX_H
+ if (rec->regexp_compiled) regfree(&rec->preg);
+#endif
+ if (rec->channels != NULL) g_strfreev(rec->channels);
+ g_free_not_null(rec->mask);
+ g_free_not_null(rec->servertag);
+ g_free_not_null(rec->pattern);
+ g_free(rec);
+
+ nickmatch_rebuild(nickmatch);
+}
+
+void ignore_update_rec(IGNORE_REC *rec)
+{
+ if (rec->level == 0) {
+ /* unignored everything */
+ ignore_remove_config(rec);
+ ignore_destroy(rec, TRUE);
+ } else {
+ /* unignore just some levels.. */
+ ignore_remove_config(rec);
+ ignores = g_slist_remove(ignores, rec);
+
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+
+ signal_emit("ignore changed", 1, rec);
+ nickmatch_rebuild(nickmatch);
+ }
+}
+
+static int unignore_timeout(void)
+{
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+ for (tmp = ignores; tmp != NULL; tmp = next) {
+ IGNORE_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->unignore_time > 0 && now >= rec->unignore_time) {
+ rec->level = 0;
+ ignore_update_rec(rec);
+ }
+ }
+
+ return TRUE;
+}
+
+static void read_ignores(void)
+{
+ IGNORE_REC *rec;
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (ignores != NULL)
+ ignore_destroy(ignores->data, FALSE);
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node == NULL) {
+ nickmatch_rebuild(nickmatch);
+ return;
+ }
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ rec = g_new0(IGNORE_REC, 1);
+ ignores = g_slist_append(ignores, rec);
+
+ rec->mask = g_strdup(config_node_get_str(node, "mask", NULL));
+ if (rec->mask != NULL && strcmp(rec->mask, "*") == 0) {
+ /* FIXME: remove after .98 */
+ g_free(rec->mask);
+ rec->mask = NULL;
+ }
+ rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL));
+ rec->level = level2bits(config_node_get_str(node, "level", ""));
+ rec->exception = config_node_get_bool(node, "exception", FALSE);
+ if (*config_node_get_str(node, "except_level", "") != '\0') {
+ /* FIXME: remove after .98 */
+ rec->level = level2bits(config_node_get_str(node, "except_level", ""));
+ rec->exception = TRUE;
+ }
+ rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+ rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+ rec->replies = config_node_get_bool(node, "replies", FALSE);
+
+ node = config_node_section(node, "channels", -1);
+ if (node != NULL) rec->channels = config_node_get_list(node);
+ }
+
+ nickmatch_rebuild(nickmatch);
+}
+
+static void ignore_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+ NICK_REC *nick)
+{
+ GSList *tmp, *matches;
+ char *nickmask;
+
+ if (nick->host == NULL)
+ return; /* don't check until host is known */
+
+ matches = NULL;
+ nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (ignore_match_nickmask(rec, nick->nick, nickmask) &&
+ ignore_match_server(rec, channel->server) &&
+ ignore_match_channel(rec, channel->name))
+ matches = g_slist_append(matches, rec);
+ }
+ g_free_not_null(nickmask);
+
+ if (matches == NULL)
+ g_hash_table_remove(list, nick);
+ else
+ g_hash_table_insert(list, nick, matches);
+}
+
+void ignore_init(void)
+{
+ ignores = NULL;
+ nickmatch = nickmatch_init(ignore_nick_cache);
+ time_tag = g_timeout_add(1000, (GSourceFunc) unignore_timeout, NULL);
+
+ read_ignores();
+ signal_add("setup reread", (SIGNAL_FUNC) read_ignores);
+}
+
+void ignore_deinit(void)
+{
+ g_source_remove(time_tag);
+ while (ignores != NULL)
+ ignore_destroy(ignores->data, TRUE);
+ nickmatch_deinit(nickmatch);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) read_ignores);
+}
--- /dev/null
+#ifndef __IGNORE_H
+#define __IGNORE_H
+
+#ifdef HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+typedef struct {
+ int level; /* ignore these levels */
+ char *mask; /* nick mask */
+ char *servertag; /* this is for autoignoring */
+ char **channels; /* ignore only in these channels */
+ char *pattern; /* text body must match this pattern */
+
+ time_t unignore_time; /* time in sec for temp ignores */
+
+ unsigned int exception:1; /* *don't* ignore */
+ unsigned int regexp:1;
+ unsigned int fullword:1;
+ unsigned int replies:1; /* ignore replies to nick in channel */
+#ifdef HAVE_REGEX_H
+ unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */
+ regex_t preg;
+#endif
+} IGNORE_REC;
+
+extern GSList *ignores;
+
+int ignore_check(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level);
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels);
+
+void ignore_add_rec(IGNORE_REC *rec);
+void ignore_update_rec(IGNORE_REC *rec);
+
+void ignore_init(void);
+void ignore_deinit(void);
+
+#endif
--- /dev/null
+/*
+ levels.c : irssi
+
+ Copyright (C) 1999-2000 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 "levels.h"
+
+static const char *levels[] = {
+ "CRAP",
+ "MSGS",
+ "PUBLICS",
+ "NOTICES",
+ "SNOTES",
+ "CTCPS",
+ "ACTIONS",
+ "JOINS",
+ "PARTS",
+ "QUITS",
+ "KICKS",
+ "MODES",
+ "TOPICS",
+ "WALLOPS",
+ "INVITES",
+ "NICKS",
+ "DCC",
+ "DCCMSGS",
+ "CLIENTNOTICES",
+ "CLIENTCRAP",
+ "CLIENTERRORS",
+ "HILIGHT",
+
+ "NOHILIGHT",
+ NULL
+};
+
+int level_get(const char *level)
+{
+ int n, len, match;
+
+ if (g_strcasecmp(level, "ALL") == 0 || strcmp(level, "*") == 0)
+ return MSGLEVEL_ALL;
+
+ if (g_strcasecmp(level, "NEVER") == 0)
+ return MSGLEVEL_NEVER;
+
+ len = strlen(level);
+ if (len == 0) return 0;
+
+ /* partial match allowed, as long as it's the only one that matches */
+ match = 0;
+ for (n = 0; levels[n] != NULL; n++) {
+ if (g_strncasecmp(levels[n], level, len) == 0) {
+ if ((int)strlen(levels[n]) == len) {
+ /* full match */
+ return 1L << n;
+ }
+ if (match > 0) {
+ /* ambiguous - abort */
+ return 0;
+ }
+ match = 1L << n;
+ }
+ }
+
+ return match;
+}
+
+int level2bits(const char *level)
+{
+ char *orig, *str, *ptr;
+ int ret, singlelevel, negative;
+
+ g_return_val_if_fail(level != NULL, 0);
+
+ if (*level == '\0')
+ return 0;
+
+ orig = str = g_strdup(level);
+
+ ret = 0;
+ for (ptr = str; ; str++) {
+ if (*str == ' ')
+ *str++ = '\0';
+ else if (*str != '\0')
+ continue;
+
+ negative = *ptr == '-';
+ if (*ptr == '-' || *ptr == '+') ptr++;
+
+ singlelevel = level_get(ptr);
+ if (singlelevel != 0) {
+ ret = !negative ? (ret | singlelevel) :
+ (ret & ~singlelevel);
+ }
+
+ while (*str == ' ') str++;
+ if (*str == '\0') break;
+
+ ptr = str;
+ }
+ g_free(orig);
+
+ return ret;
+}
+
+char *bits2level(int bits)
+{
+ GString *str;
+ char *ret;
+ int n;
+
+ if (bits == 0)
+ return g_strdup("");
+
+ if (bits == MSGLEVEL_ALL)
+ return g_strdup("ALL");
+
+ str = g_string_new(NULL);
+ if (bits & MSGLEVEL_NEVER)
+ g_string_append(str, "NEVER ");
+
+ for (n = 0; levels[n] != NULL; n++) {
+ if (bits & (1L << n))
+ g_string_sprintfa(str, "%s ", levels[n]);
+ }
+ if (str->len > 0)
+ g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+
+ return ret;
+}
+
+int combine_level(int dest, const char *src)
+{
+ char **list, **item, *itemname;
+ int itemlevel;
+
+ g_return_val_if_fail(src != NULL, dest);
+
+ list = g_strsplit(src, " ", -1);
+ for (item = list; *item != NULL; item++) {
+ itemname = *item + (**item == '+' || **item == '-' ? 1 : 0);
+ g_strup(itemname);
+ itemlevel = level_get(itemname);
+
+ if (strcmp(itemname, "NONE") == 0)
+ dest = 0;
+ else if (**item == '-')
+ dest &= ~(itemlevel);
+ else
+ dest |= itemlevel;
+ }
+ g_strfreev(list);
+
+ return dest;
+}
--- /dev/null
+#ifndef __LEVELS_H
+#define __LEVELS_H
+
+/* This is pretty much IRC specific, but I think it would be easier for
+ other chats to try to use these same levels instead of implementing too
+ difficult message leveling system (which might be done if really
+ needed..). */
+
+/* Message levels */
+#define MSGLEVEL_CRAP 0x0000001
+#define MSGLEVEL_MSGS 0x0000002
+#define MSGLEVEL_PUBLIC 0x0000004
+#define MSGLEVEL_NOTICES 0x0000008
+#define MSGLEVEL_SNOTES 0x0000010
+#define MSGLEVEL_CTCPS 0x0000020
+#define MSGLEVEL_ACTIONS 0x0000040
+#define MSGLEVEL_JOINS 0x0000080
+#define MSGLEVEL_PARTS 0x0000100
+#define MSGLEVEL_QUITS 0x0000200
+#define MSGLEVEL_KICKS 0x0000400
+#define MSGLEVEL_MODES 0x0000800
+#define MSGLEVEL_TOPICS 0x0001000
+#define MSGLEVEL_WALLOPS 0x0002000
+#define MSGLEVEL_INVITES 0x0004000
+#define MSGLEVEL_NICKS 0x0008000
+#define MSGLEVEL_DCC 0x0010000
+#define MSGLEVEL_DCCMSGS 0x0020000
+#define MSGLEVEL_CLIENTNOTICE 0x0040000
+#define MSGLEVEL_CLIENTCRAP 0x0080000
+#define MSGLEVEL_CLIENTERROR 0x0100000
+#define MSGLEVEL_HILIGHT 0x0200000
+
+#define MSGLEVEL_ALL 0x03fffff
+
+#define MSGLEVEL_NOHILIGHT 0x1000000 /* Don't highlight this message */
+#define MSGLEVEL_NO_ACT 0x2000000 /* Don't trigger channel activity */
+#define MSGLEVEL_NEVER 0x4000000 /* never ignore / never log */
+#define MSGLEVEL_LASTLOG 0x8000000 /* never ignore / never log */
+
+int level_get(const char *level);
+int level2bits(const char *level);
+char *bits2level(int bits);
+int combine_level(int dest, const char *src);
+
+#endif
--- /dev/null
+/*
+ line-split.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+
+/* Maximum line length - split to two lines if it's longer than this.
+
+ This is mostly to prevent excessive memory usage. Like if someone DCC
+ chats you, you both have very fast connections and the other side sends
+ you 100 megs of text without any line feeds -> irssi will (try to)
+ allocate 128M of memory for the line and will eventually crash when it
+ can't allocate any more memory. If the line is split at every 64k the
+ text buffer will free the old lines and the memory usage never gets
+ too high. */
+#define MAX_CHARS_IN_LINE 65536
+
+struct _LINEBUF_REC {
+ int len;
+ int alloc;
+ int remove;
+ char *str;
+};
+
+static void linebuf_append(LINEBUF_REC *rec, const char *data, int len)
+{
+ if (rec->len+len > rec->alloc) {
+ rec->alloc = nearest_power(rec->len+len);;
+ rec->str = rec->str == NULL ? g_malloc(rec->alloc) :
+ g_realloc(rec->str, rec->alloc);
+ }
+
+ memcpy(rec->str + rec->len, data, len);
+ rec->len += len;
+}
+
+static char *linebuf_find(LINEBUF_REC *rec, char chr)
+{
+ int n;
+
+ for (n = 0; n < rec->len; n++)
+ if (rec->str[n] == chr) return rec->str+n;
+
+ return NULL;
+}
+
+static int remove_newline(LINEBUF_REC *rec)
+{
+ char *ptr;
+
+ ptr = linebuf_find(rec, '\n');
+ if (ptr == NULL) {
+ /* LF wasn't found, wait for more data.. */
+ if (rec->len < MAX_CHARS_IN_LINE)
+ return 0;
+
+ /* line buffer is too big - force a newline. */
+ linebuf_append(rec, "\n", 1);
+ ptr = rec->str+rec->len-1;
+ }
+
+ rec->remove = (int) (ptr-rec->str)+1;
+ if (ptr != rec->str && ptr[-1] == '\r') {
+ /* remove CR too. */
+ ptr--;
+ }
+
+ *ptr = '\0';
+ return 1;
+}
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer)
+{
+ LINEBUF_REC *rec;
+
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(output != NULL, -1);
+ g_return_val_if_fail(buffer != NULL, -1);
+
+ if (*buffer == NULL)
+ *buffer = g_new0(LINEBUF_REC, 1);
+ rec = *buffer;
+
+ if (rec->remove > 0) {
+ rec->len -= rec->remove;
+ g_memmove(rec->str, rec->str+rec->remove, rec->len);
+ rec->remove = 0;
+ }
+
+ if (len > 0)
+ linebuf_append(rec, data, len);
+ else if (len < 0) {
+ /* connection closed.. */
+ if (rec->len == 0)
+ return -1;
+
+ /* no new data got but still something in buffer.. */
+ len = 0;
+ if (linebuf_find(rec, '\n') == NULL) {
+ /* connection closed and last line is missing \n ..
+ just add it so we can see if it had
+ anything useful.. */
+ linebuf_append(rec, "\n", 1);
+ }
+ }
+
+ *output = rec->str;
+ return remove_newline(rec);
+}
+
+void line_split_free(LINEBUF_REC *buffer)
+{
+ if (buffer != NULL) {
+ if (buffer->str != NULL) g_free(buffer->str);
+ g_free(buffer);
+ }
+}
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer)
+{
+ return buffer->len == 0;
+}
--- /dev/null
+#ifndef __LINE_SPLIT_H
+#define __LINE_SPLIT_H
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer);
+void line_split_free(LINEBUF_REC *buffer);
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer);
+
+#endif
--- /dev/null
+/*
+ log.c : irssi
+
+ Copyright (C) 1999-2000 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 "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "servers.h"
+#include "log.h"
+#include "write-buffer.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#define DEFAULT_LOG_FILE_CREATE_MODE 644
+
+#ifdef HAVE_FCNTL
+static struct flock lock;
+#endif
+
+GSList *logs;
+
+static const char *log_item_types[] = {
+ "target",
+ "window",
+
+ NULL
+};
+
+const char *log_timestamp;
+static int log_file_create_mode;
+static int rotate_tag;
+
+static int log_item_str2type(const char *type)
+{
+ int n;
+
+ for (n = 0; log_item_types[n] != NULL; n++) {
+ if (g_strcasecmp(log_item_types[n], type) == 0)
+ return n;
+ }
+
+ return -1;
+}
+
+static void log_write_timestamp(int handle, const char *format,
+ const char *text, time_t stamp)
+{
+ struct tm *tm;
+ char str[256];
+
+ g_return_if_fail(format != NULL);
+ if (*format == '\0') return;
+
+ tm = localtime(&stamp);
+ if (strftime(str, sizeof(str), format, tm) > 0)
+ write_buffer(handle, str, strlen(str));
+ if (text != NULL) write_buffer(handle, text, strlen(text));
+}
+
+static char *log_filename(LOG_REC *log)
+{
+ char *str, fname[1024];
+ struct tm *tm;
+ size_t ret;
+ time_t now;
+
+ now = time(NULL);
+ tm = localtime(&now);
+
+ str = convert_home(log->fname);
+ ret = strftime(fname, sizeof(fname), str, tm);
+ g_free(str);
+
+ if (ret <= 0) {
+ g_warning("log_filename() : strftime() failed");
+ return NULL;
+ }
+
+ return g_strdup(fname);
+}
+
+int log_start_logging(LOG_REC *log)
+{
+ g_return_val_if_fail(log != NULL, FALSE);
+
+ if (log->handle != -1)
+ return TRUE;
+
+ /* Append/create log file */
+ g_free_not_null(log->real_fname);
+ log->real_fname = log_filename(log);
+ log->handle = log->real_fname == NULL ? -1 :
+ open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+ if (log->handle == -1) {
+ signal_emit("log create failed", 1, log);
+ log->failed = TRUE;
+ return FALSE;
+ }
+#ifdef HAVE_FCNTL
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) {
+ close(log->handle);
+ log->handle = -1;
+ signal_emit("log locked", 1, log);
+ log->failed = TRUE;
+ return FALSE;
+ }
+#endif
+ lseek(log->handle, 0, SEEK_END);
+
+ log->opened = log->last = time(NULL);
+ log_write_timestamp(log->handle,
+ settings_get_str("log_open_string"),
+ "\n", log->last);
+
+ signal_emit("log started", 1, log);
+ log->failed = FALSE;
+ return TRUE;
+}
+
+void log_stop_logging(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ signal_emit("log stopped", 1, log);
+
+ log_write_timestamp(log->handle,
+ settings_get_str("log_close_string"),
+ "\n", time(NULL));
+
+#ifdef HAVE_FCNTL
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ fcntl(log->handle, F_SETLK, &lock);
+#endif
+
+ write_buffer_flush();
+ close(log->handle);
+ log->handle = -1;
+}
+
+static void log_rotate_check(LOG_REC *log)
+{
+ char *new_fname;
+
+ g_return_if_fail(log != NULL);
+
+ if (log->handle == -1 || log->real_fname == NULL)
+ return;
+
+ new_fname = log_filename(log);
+ if (strcmp(new_fname, log->real_fname) != 0) {
+ /* rotate log */
+ log_stop_logging(log);
+ log_start_logging(log);
+ }
+ g_free(new_fname);
+}
+
+void log_write_rec(LOG_REC *log, const char *str, int level)
+{
+ struct tm *tm;
+ time_t now;
+ int hour, day;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(str != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ now = time(NULL);
+ tm = localtime(&now);
+ hour = tm->tm_hour;
+ day = tm->tm_mday;
+
+ tm = localtime(&log->last);
+ day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */
+ if (tm->tm_hour != hour) {
+ /* hour changed, check if we need to rotate log file */
+ log_rotate_check(log);
+ }
+
+ if (day != 0) {
+ /* day changed */
+ log_write_timestamp(log->handle,
+ settings_get_str("log_day_changed"),
+ "\n", now);
+ }
+
+ log->last = now;
+
+ if ((level & MSGLEVEL_LASTLOG) == 0)
+ log_write_timestamp(log->handle, log_timestamp, str, now);
+ else
+ write_buffer(log->handle, str, strlen(str));
+ write_buffer(log->handle, "\n", 1);
+
+ signal_emit("log written", 2, log, str);
+}
+
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+ const char *servertag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(log != NULL, NULL);
+ g_return_val_if_fail(item != NULL, NULL);
+
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ LOG_ITEM_REC *rec = tmp->data;
+
+ if (rec->type == type && g_strcasecmp(rec->name, item) == 0 &&
+ (rec->servertag == NULL || (servertag != NULL &&
+ g_strcasecmp(rec->servertag, servertag) == 0)))
+ return rec;
+ }
+
+ return NULL;
+}
+
+void log_file_write(SERVER_REC *server, const char *item, int level,
+ const char *str, int no_fallbacks)
+{
+ GSList *tmp, *fallbacks;
+ char *tmpstr, *servertag;
+ int found;
+
+ g_return_if_fail(str != NULL);
+
+ if (logs == NULL)
+ return;
+
+ servertag = server == NULL ? NULL : server->tag;
+ fallbacks = NULL; found = FALSE;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (rec->handle == -1)
+ continue; /* log not opened yet */
+
+ if ((level & rec->level) == 0)
+ continue;
+
+ if (rec->items == NULL)
+ fallbacks = g_slist_append(fallbacks, rec);
+ else if (item != NULL &&
+ log_item_find(rec, LOG_ITEM_TARGET, item,
+ servertag) != NULL)
+ log_write_rec(rec, str, level);
+ }
+
+ if (!found && !no_fallbacks && fallbacks != NULL) {
+ /* not found from any items, so write it to all main logs */
+ tmpstr = (level & MSGLEVEL_PUBLIC) ?
+ g_strconcat(item, ": ", str, NULL) :
+ g_strdup(str);
+
+ for (tmp = fallbacks; tmp != NULL; tmp = tmp->next)
+ log_write_rec(tmp->data, tmpstr, level);
+
+ g_free(tmpstr);
+ }
+ g_slist_free(fallbacks);
+}
+
+LOG_REC *log_find(const char *fname)
+{
+ GSList *tmp;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (strcmp(rec->fname, fname) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+
+ parent = config_node_section(parent, "items", NODE_TYPE_LIST);
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ LOG_ITEM_REC *rec = tmp->data;
+
+ node = config_node_section(parent, NULL, NODE_TYPE_BLOCK);
+ iconfig_node_set_str(node, "type", log_item_types[rec->type]);
+ iconfig_node_set_str(node, "name", rec->name);
+ iconfig_node_set_str(node, "server", rec->servertag);
+ }
+}
+
+static void log_update_config(LOG_REC *log)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ if (log->temp)
+ return;
+
+ node = iconfig_node_traverse("logs", TRUE);
+ node = config_node_section(node, log->fname, NODE_TYPE_BLOCK);
+
+ if (log->autoopen)
+ iconfig_node_set_bool(node, "auto_open", TRUE);
+ else
+ iconfig_node_set_str(node, "auto_open", NULL);
+
+ levelstr = bits2level(log->level);
+ iconfig_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+
+ iconfig_node_set_str(node, "items", NULL);
+
+ if (log->items != NULL)
+ log_items_update_config(log, node);
+}
+
+static void log_remove_config(LOG_REC *log)
+{
+ iconfig_set_str("logs", log->fname, NULL);
+}
+
+LOG_REC *log_create_rec(const char *fname, int level)
+{
+ LOG_REC *rec;
+
+ g_return_val_if_fail(fname != NULL, NULL);
+
+ rec = log_find(fname);
+ if (rec == NULL) {
+ rec = g_new0(LOG_REC, 1);
+ rec->fname = g_strdup(fname);
+ rec->real_fname = log_filename(rec);
+ rec->handle = -1;
+ }
+
+ rec->level = level;
+ return rec;
+}
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+ const char *servertag)
+{
+ LOG_ITEM_REC *rec;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(name != NULL);
+
+ if (log_item_find(log, type, name, servertag))
+ return;
+
+ rec = g_new0(LOG_ITEM_REC, 1);
+ rec->type = type;
+ rec->name = g_strdup(name);
+ rec->servertag = g_strdup(servertag);
+
+ log->items = g_slist_append(log->items, rec);
+}
+
+void log_update(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log_find(log->fname) == NULL) {
+ logs = g_slist_append(logs, log);
+ log->handle = -1;
+ }
+
+ log_update_config(log);
+ signal_emit("log new", 1, log);
+}
+
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item)
+{
+ log->items = g_slist_remove(log->items, item);
+
+ g_free(item->name);
+ g_free_not_null(item->servertag);
+ g_free(item);
+}
+
+static void log_destroy(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log->handle != -1)
+ log_stop_logging(log);
+
+ logs = g_slist_remove(logs, log);
+ signal_emit("log remove", 1, log);
+
+ while (log->items != NULL)
+ log_item_destroy(log, log->items->data);
+ g_free(log->fname);
+ g_free_not_null(log->real_fname);
+ g_free(log);
+}
+
+void log_close(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ log_remove_config(log);
+ log_destroy(log);
+}
+
+static int sig_rotate_check(void)
+{
+ static int last_hour = -1;
+ struct tm tm;
+ time_t now;
+
+ /* don't do anything until hour is changed */
+ now = time(NULL);
+ memcpy(&tm, localtime(&now), sizeof(tm));
+ if (tm.tm_hour != last_hour) {
+ last_hour = tm.tm_hour;
+ g_slist_foreach(logs, (GFunc) log_rotate_check, NULL);
+ }
+ return 1;
+}
+
+static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log)
+{
+ LOG_ITEM_REC *rec;
+ GSList *tmp;
+ char *item;
+ int type;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ item = config_node_get_str(node, "name", NULL);
+ type = log_item_str2type(config_node_get_str(node, "type", NULL));
+ if (item == NULL || type == -1)
+ continue;
+
+ rec = g_new0(LOG_ITEM_REC, 1);
+ rec->type = type;
+ rec->name = g_strdup(item);
+ rec->servertag = g_strdup(config_node_get_str(node, "server", NULL));
+
+ log->items = g_slist_append(log->items, rec);
+ }
+}
+
+static void log_read_config(void)
+{
+ CONFIG_NODE *node;
+ LOG_REC *log;
+ GSList *tmp, *next, *fnames;
+
+ /* close old logs, save list of open logs */
+ fnames = NULL;
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ log = tmp->data;
+
+ next = tmp->next;
+ if (log->temp)
+ continue;
+
+ if (log->handle != -1)
+ fnames = g_slist_append(fnames, g_strdup(log->fname));
+ log_destroy(log);
+ }
+
+ node = iconfig_node_traverse("logs", FALSE);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ log = g_new0(LOG_REC, 1);
+ logs = g_slist_append(logs, log);
+
+ log->handle = -1;
+ log->fname = g_strdup(node->key);
+ log->autoopen = config_node_get_bool(node, "auto_open", FALSE);
+ log->level = level2bits(config_node_get_str(node, "level", 0));
+
+ node = config_node_section(node, "items", -1);
+ if (node != NULL)
+ log_items_read_config(node, log);
+
+ if (log->autoopen || gslist_find_string(fnames, log->fname))
+ log_start_logging(log);
+ }
+
+ g_slist_foreach(fnames, (GFunc) g_free, NULL);
+ g_slist_free(fnames);
+}
+
+static void read_settings(void)
+{
+ log_timestamp = settings_get_str("log_timestamp");
+ log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
+}
+
+void log_init(void)
+{
+ rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL);
+ logs = NULL;
+
+ settings_add_int("log", "log_create_mode",
+ DEFAULT_LOG_FILE_CREATE_MODE);
+ settings_add_str("log", "log_timestamp", "%H:%M ");
+ settings_add_str("log", "log_open_string",
+ "--- Log opened %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_close_string",
+ "--- Log closed %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_day_changed",
+ "--- Day changed %a %b %d %Y");
+
+ read_settings();
+ log_read_config();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) log_read_config);
+}
+
+void log_deinit(void)
+{
+ g_source_remove(rotate_tag);
+
+ while (logs != NULL)
+ log_close(logs->data);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) log_read_config);
+}
--- /dev/null
+#ifndef __LOG_H
+#define __LOG_H
+
+enum {
+ LOG_ITEM_TARGET, /* channel, query, .. */
+ LOG_ITEM_WINDOW_REFNUM
+};
+
+typedef struct {
+ int type;
+ char *name;
+ char *servertag;
+} LOG_ITEM_REC;
+
+typedef struct {
+ char *fname; /* file name, in strftime() format */
+ char *real_fname; /* the current expanded file name */
+ int handle; /* file handle */
+ time_t opened;
+
+ int level; /* log only these levels */
+ GSList *items; /* log only on these items */
+
+ time_t last; /* when last message was written */
+
+ unsigned int autoopen:1; /* automatically start logging at startup */
+ unsigned int failed:1; /* opening log failed last time */
+ unsigned int temp:1; /* don't save this to config file */
+} LOG_REC;
+
+extern GSList *logs;
+
+/* Create log record - you still need to call log_update() to actually add it
+ into log list */
+LOG_REC *log_create_rec(const char *fname, int level);
+void log_update(LOG_REC *log);
+void log_close(LOG_REC *log);
+LOG_REC *log_find(const char *fname);
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+ const char *servertag);
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item);
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+ const char *servertag);
+
+void log_file_write(SERVER_REC *server, const char *item, int level,
+ const char *str, int no_fallbacks);
+void log_write_rec(LOG_REC *log, const char *str, int level);
+
+int log_start_logging(LOG_REC *log);
+void log_stop_logging(LOG_REC *log);
+
+void log_init(void);
+void log_deinit(void);
+
+#endif
--- /dev/null
+/*
+ masks.c : irssi
+
+ Copyright (C) 1999-2000 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 "network.h"
+#include "misc.h"
+
+#include "servers.h"
+
+/* Returns TRUE if mask contains '!' ie. address should be checked too.
+ Also checks if mask contained any wildcards. */
+static int check_address(const char *mask, int *wildcards)
+{
+ int ret;
+
+ ret = FALSE;
+ while (*mask != '\0') {
+ if (*mask == '!') {
+ if (*wildcards) return TRUE;
+ ret = TRUE;
+ }
+
+ if (*mask == '?' || *mask == '*') {
+ *wildcards = TRUE;
+ if (ret) return TRUE;
+ }
+ mask++;
+ }
+
+ return ret;
+}
+
+static int check_mask(SERVER_REC *server, const char *mask,
+ const char *str, int wildcards)
+{
+ if (server != NULL && server->mask_match_func != NULL) {
+ /* use server specified mask match function */
+ return server->mask_match_func(mask, str);
+ }
+
+ return wildcards ? match_wildcards(mask, str) :
+ g_strcasecmp(mask, str) == 0;
+}
+
+int mask_match(SERVER_REC *server, const char *mask,
+ const char *nick, const char *user, const char *host)
+{
+ char *str;
+ int ret, wildcards;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(mask != NULL && nick != NULL &&
+ nick != NULL && host != NULL, FALSE);
+
+ str = !check_address(mask, &wildcards) ? (char *) nick :
+ g_strdup_printf("%s!%s@%s", nick, user, host);
+ ret = check_mask(server, mask, str, wildcards);
+ if (str != nick) g_free(str);
+
+ return ret;
+}
+
+int mask_match_address(SERVER_REC *server, const char *mask,
+ const char *nick, const char *address)
+{
+ char *str;
+ int ret, wildcards;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(mask != NULL && nick != NULL, FALSE);
+ if (address == NULL) address = "";
+
+ str = !check_address(mask, &wildcards) ? (char *) nick :
+ g_strdup_printf("%s!%s", nick, address);
+ ret = check_mask(server, mask, str, wildcards);
+ if (str != nick) g_free(str);
+
+ return ret;
+}
+
+int masks_match(SERVER_REC *server, const char *masks,
+ const char *nick, const char *address)
+{
+ int (*mask_match_func)(const char *, const char *);
+ char **list, **tmp, *mask;
+ int found;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(masks != NULL &&
+ nick != NULL && address != NULL, FALSE);
+
+ if (*masks == '\0')
+ return FALSE;
+
+ mask_match_func = server != NULL && server->mask_match_func != NULL ?
+ server->mask_match_func : match_wildcards;
+
+ found = FALSE;
+ mask = g_strdup_printf("%s!%s", nick, address);
+ list = g_strsplit(masks, " ", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ if (g_strcasecmp(*tmp, nick) == 0) {
+ found = TRUE;
+ break;
+ }
+
+ if (mask_match_func(*tmp, mask)) {
+ found = TRUE;
+ break;
+ }
+ }
+ g_strfreev(list);
+ g_free(mask);
+
+ return found;
+}
--- /dev/null
+#ifndef __MASKS_H
+#define __MASKS_H
+
+int mask_match(SERVER_REC *server, const char *mask,
+ const char *nick, const char *user, const char *host);
+int mask_match_address(SERVER_REC *server, const char *mask,
+ const char *nick, const char *address);
+int masks_match(SERVER_REC *server, const char *masks,
+ const char *nick, const char *address);
+
+#endif
--- /dev/null
+/*
+ memdebug.c : irssi
+
+ Copyright (C) 1999-2000 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gmodule.h>
+
+/*#define ENABLE_BUFFER_CHECKS*/
+#define BUFFER_CHECK_SIZE 5
+#define MIN_BUFFER_CHECK_SIZE 2
+
+typedef struct {
+ void *p;
+ int size;
+ char *file;
+ int line;
+ char *comment;
+} MEM_REC;
+
+static GHashTable *data = NULL, *preallocs = NULL;
+static const char *comment = "";
+
+static void add_flow_checks(char *p, unsigned long size)
+{
+#ifdef ENABLE_BUFFER_CHECKS
+ int n;
+
+ for (n = 0; n < BUFFER_CHECK_SIZE; n++)
+ p[n] = n ^ 0x7f;
+ for (n = 0; n < BUFFER_CHECK_SIZE; n++)
+ p[size-BUFFER_CHECK_SIZE+n] = n ^ 0x7f;
+#endif
+}
+
+void ig_memcheck_rec(void *key, MEM_REC *rec)
+{
+ guchar *p;
+ int n;
+
+ if (rec->size != INT_MIN){
+ p = rec->p;
+
+ for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++)
+ if (p[n] != (n ^ 0x7f))
+ g_error("buffer underflow, file %s line %d!\n", rec->file, rec->line);
+
+ for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++)
+ if (p[rec->size-BUFFER_CHECK_SIZE+n] != (n ^ 0x7f))
+ g_error("buffer overflow, file %s line %d!\n", rec->file, rec->line);
+ }
+}
+
+static void mem_check(void)
+{
+#ifdef ENABLE_BUFFER_CHECKS
+ g_hash_table_foreach(data, (GHFunc) ig_memcheck_rec, NULL);
+#endif
+}
+
+static void data_add(char *p, int size, const char *file, int line)
+{
+ MEM_REC *rec;
+
+ if (size <= 0 && size != INT_MIN)
+ g_error("size = %d, file %s line %d", size, file, line);
+
+ if (data == NULL) {
+ data = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ preallocs = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ }
+
+ if (g_hash_table_lookup(data, p) != NULL)
+ g_error("data_add() already malloc()'ed %p (in %s:%d)", p, file, line);
+
+ rec = g_new(MEM_REC, 1);
+ g_hash_table_insert(data, p, rec);
+
+ rec->p = p;
+ rec->size = size;
+ rec->file = g_strdup(file);
+ rec->line = line;
+ rec->comment = g_strdup(comment);
+
+ if (size == INT_MIN)
+ g_hash_table_insert(preallocs, p-BUFFER_CHECK_SIZE, p);
+ else
+ add_flow_checks(p, size);
+ mem_check();
+}
+
+static void data_clear(char *p)
+{
+ MEM_REC *rec;
+
+ if (g_hash_table_lookup(preallocs, p) != NULL)
+ p += BUFFER_CHECK_SIZE;
+
+ rec = g_hash_table_lookup(data, p);
+ if (rec != NULL && rec->size > 0)
+ memset(p, 'F', rec->size);
+}
+
+static void *data_remove(char *p, const char *file, int line)
+{
+ MEM_REC *rec;
+
+ mem_check();
+
+ if (g_hash_table_lookup(preallocs, p) != NULL) {
+ g_hash_table_remove(preallocs, p);
+ p += BUFFER_CHECK_SIZE;
+ }
+
+ rec = g_hash_table_lookup(data, p);
+ if (rec == NULL) {
+ g_warning("data_remove() data %p not found (in %s:%d)", p, file, line);
+ return p+BUFFER_CHECK_SIZE;
+ }
+
+ g_hash_table_remove(data, p);
+ g_free(rec->file);
+ g_free(rec->comment);
+ g_free(rec);
+
+ return p;
+}
+
+void *ig_malloc(int size, const char *file, int line)
+{
+ char *p;
+
+ size += BUFFER_CHECK_SIZE*2;
+ p = g_malloc(size);
+ data_add(p, size, file, line);
+ return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+void *ig_malloc0(int size, const char *file, int line)
+{
+ char *p;
+
+ size += BUFFER_CHECK_SIZE*2;
+ p = g_malloc0(size);
+ data_add(p, size, file, line);
+ return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+void *ig_realloc(void *mem, unsigned long size, const char *file, int line)
+{
+ char *p, *oldmem = mem;
+
+ size += BUFFER_CHECK_SIZE*2;
+ oldmem -= BUFFER_CHECK_SIZE;
+ data_remove(oldmem, file, line);
+ p = g_realloc(oldmem, size);
+ data_add(p, size, file, line);
+ return (void *) (p+BUFFER_CHECK_SIZE);
+}
+
+char *ig_strdup(const char *str, const char *file, int line)
+{
+ void *p;
+
+ if (str == NULL) return NULL;
+
+ p = ig_malloc(strlen(str)+1, file, line);
+ strcpy(p, str);
+
+ return p;
+}
+
+char *ig_strndup(const char *str, int count, const char *file, int line)
+{
+ char *p;
+
+ if (str == NULL) return NULL;
+
+ p = ig_malloc(count+1, file, line);
+ strncpy(p, str, count); p[count] = '\0';
+
+ return p;
+}
+
+char *ig_strconcat(const char *file, int line, const char *str, ...)
+{
+ guint l;
+ va_list args;
+ char *s;
+ char *concat;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ l = 1 + strlen (str);
+ va_start (args, str);
+ s = va_arg (args, char*);
+ while (s)
+ {
+ l += strlen (s);
+ s = va_arg (args, char*);
+ }
+ va_end (args);
+
+ concat = ig_malloc(l, file, line);
+ concat[0] = 0;
+
+ strcat (concat, str);
+ va_start (args, str);
+ s = va_arg (args, char*);
+ while (s)
+ {
+ strcat (concat, s);
+ s = va_arg (args, char*);
+ }
+ va_end (args);
+
+ return concat;
+}
+
+char *ig_strdup_printf(const char *file, int line, const char *format, ...)
+{
+ char *buffer, *p;
+ va_list args;
+
+ va_start (args, format);
+ buffer = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ p = ig_malloc(strlen(buffer)+1, file, line);
+ strcpy(p, buffer);
+ g_free(buffer);
+
+ return p;
+}
+
+char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args)
+{
+ char *buffer, *p;
+
+ buffer = g_strdup_vprintf (format, args);
+
+ p = ig_malloc(strlen(buffer)+1, file, line);
+ strcpy(p, buffer);
+ g_free(buffer);
+
+ return p;
+}
+
+void ig_free(void *p)
+{
+ char *cp = p;
+
+ if (cp == NULL) g_error("ig_free() : trying to free NULL");
+
+ cp -= BUFFER_CHECK_SIZE;
+ data_clear(cp);
+ cp = data_remove(cp, "??", 0);
+ if (cp != NULL) g_free(cp);
+}
+
+GString *ig_string_new(const char *file, int line, const char *str)
+{
+ GString *ret;
+
+ ret = g_string_new(str);
+ data_add((void *) ret, INT_MIN, file, line);
+ return ret;
+}
+
+void ig_string_free(const char *file, int line, GString *str, gboolean freeit)
+{
+ data_remove((void *) str, file, line);
+ if (!freeit)
+ data_add(str->str, INT_MIN, file, line);
+
+ g_string_free(str, freeit);
+}
+
+char *ig_strjoinv(const char *file, int line, const char *sepa, char **array)
+{
+ char *ret;
+
+ ret = g_strjoinv(sepa, array);
+ data_add(ret, INT_MIN, file, line);
+ return ret;
+}
+
+char *ig_dirname(const char *file, int line, const char *fname)
+{
+ char *ret;
+
+ ret = g_dirname(fname);
+ data_add(ret, INT_MIN, file, line);
+ return ret;
+}
+
+char *ig_module_build_path(const char *file, int line, const char *dir, const char *module)
+{
+ char *ret;
+
+ ret = g_module_build_path(dir, module);
+ data_add(ret, INT_MIN, file, line);
+ return ret;
+}
+
+void ig_profile_line(void *key, MEM_REC *rec)
+{
+ char *data;
+
+ if (*rec->comment == '\0' &&
+ (strcmp(rec->file, "ig_strdup_printf") == 0 ||
+ strcmp(rec->file, "ig_strdup_vprintf") == 0 ||
+ strcmp(rec->file, "ig_strconcat") == 0 ||
+ strcmp(rec->file, "ig_string_free (free = FALSE)") == 0))
+ data = (char *) rec->p + BUFFER_CHECK_SIZE;
+ else
+ data = rec->comment;
+ fprintf(stderr, "%s:%d %d bytes (%s)\n", rec->file, rec->line, rec->size, data);
+}
+
+void ig_mem_profile(void)
+{
+ g_hash_table_foreach(data, (GHFunc) ig_profile_line, NULL);
+ g_hash_table_destroy(data);
+ g_hash_table_destroy(preallocs);
+}
+
+static MEM_REC *largest[10];
+
+void ig_profile_largest(void *key, MEM_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < 10; n++)
+ {
+ if (largest[n] == NULL || rec->size > largest[n]->size)
+ {
+ g_memmove(largest+n+1, largest+n, sizeof(void *)*(9-n));
+ largest[n] = rec;
+ }
+ }
+}
+
+void ig_mem_profile_largest(void)
+{
+ /*int n;*/
+
+ memset(&largest, 0, sizeof(MEM_REC*)*10);
+ /*g_hash_table_foreach(data, (GHFunc) ig_profile_largest, NULL);
+
+ for (n = 0; n < 10 && largest[n] != NULL; n++)
+ {
+ ig_profile_line(NULL, largest[n]);
+ }*/
+}
+
+void ig_set_data(const char *data)
+{
+ comment = data;
+}
--- /dev/null
+#ifdef MEM_DEBUG
+void ig_mem_profile(void);
+
+void ig_set_data(const char *data);
+
+void *ig_malloc(int size, const char *file, int line);
+void *ig_malloc0(int size, const char *file, int line);
+void *ig_realloc(void *mem, unsigned long size, const char *file, int line);
+char *ig_strdup(const char *str, const char *file, int line);
+char *ig_strndup(const char *str, int count, const char *file, int line);
+char *ig_strconcat(const char *file, int line, const char *str, ...);
+char *ig_strdup_printf(const char *file, int line, const char *format, ...) G_GNUC_PRINTF (3, 4);
+char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args);
+void ig_free(void *p);
+GString *ig_string_new(const char *file, int line, const char *str);
+void ig_string_free(const char *file, int line, GString *str, int freeit);
+char *ig_strjoinv(const char *file, int line, const char *sepa, char **array);
+char *ig_dirname(const char *file, int line, const char *fname);
+char *ig_module_build_path(const char *file, int line, const char *dir, const char *module);
+
+#define g_malloc(a) ig_malloc(a, __FILE__, __LINE__)
+#define g_malloc0(a) ig_malloc0(a, __FILE__, __LINE__)
+#define g_free ig_free
+#define g_realloc(a,b) ig_realloc(a, b, __FILE__, __LINE__)
+#define g_strdup(a) ig_strdup(a, __FILE__, __LINE__)
+#define g_strndup(a, b) ig_strndup(a, b, __FILE__, __LINE__)
+#define g_string_new(a) ig_string_new(__FILE__, __LINE__, a)
+#define g_string_free(a, b) ig_string_free(__FILE__, __LINE__, a, b)
+#define g_strjoinv(a,b) ig_strjoinv(__FILE__, __LINE__, a, b)
+#define g_dirname(a) ig_dirname(__FILE__, __LINE__, a)
+#define g_module_build_path(a, b) ig_module_build_path(__FILE__, __LINE__, a, b)
+
+#ifndef __STRICT_ANSI__
+#define g_strconcat(a...) ig_strconcat(__FILE__, __LINE__, ##a)
+#define g_strdup_printf(a, b...) ig_strdup_printf(__FILE__, __LINE__, a, ##b)
+#define g_strdup_vprintf(a, b...) ig_strdup_vprintf(__FILE__, __LINE__, a, ##b)
+#endif
+#endif
--- /dev/null
+/*
+ misc.c : irssi
+
+ Copyright (C) 1999 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 "misc.h"
+#include "pidwait.h"
+
+#include <errno.h>
+#ifdef HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+typedef struct {
+ int condition;
+ GInputFunction function;
+ void *data;
+} IRSSI_INPUT_REC;
+
+static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
+ void *data)
+{
+ IRSSI_INPUT_REC *rec = data;
+ int icond = 0;
+
+ if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+ /* error, we have to call the function.. */
+ if (rec->condition & G_IO_IN)
+ icond |= G_INPUT_READ;
+ else
+ icond |= G_INPUT_WRITE;
+ }
+
+ if (condition & (G_IO_IN | G_IO_PRI))
+ icond |= G_INPUT_READ;
+ if (condition & G_IO_OUT)
+ icond |= G_INPUT_WRITE;
+
+ if (rec->condition & icond)
+ rec->function(rec->data, source, icond);
+
+ return TRUE;
+}
+
+int g_input_add_full(GIOChannel *source, int priority, int condition,
+ GInputFunction function, void *data)
+{
+ IRSSI_INPUT_REC *rec;
+ unsigned int result;
+ GIOCondition cond;
+
+ rec = g_new(IRSSI_INPUT_REC, 1);
+ rec->condition = condition;
+ rec->function = function;
+ rec->data = data;
+
+ cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
+ if (condition & G_INPUT_READ)
+ cond |= G_IO_IN|G_IO_PRI;
+ if (condition & G_INPUT_WRITE)
+ cond |= G_IO_OUT;
+
+ result = g_io_add_watch_full(source, priority, cond,
+ irssi_io_invoke, rec, g_free);
+
+ return result;
+}
+
+int g_input_add(GIOChannel *source, int condition,
+ GInputFunction function, void *data)
+{
+ return g_input_add_full(source, G_PRIORITY_DEFAULT, condition,
+ function, data);
+}
+
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+ if (tv1->tv_sec < tv2->tv_sec)
+ return -1;
+ if (tv1->tv_sec > tv2->tv_sec)
+ return 1;
+
+ return tv1->tv_usec < tv2->tv_usec ? -1 :
+ tv1->tv_usec > tv2->tv_usec ? 1 : 0;
+}
+
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+ long secs, usecs;
+
+ secs = tv1->tv_sec - tv2->tv_sec;
+ usecs = tv1->tv_usec - tv2->tv_usec;
+ if (usecs < 0) {
+ usecs += 1000000;
+ secs--;
+ }
+ usecs = usecs/1000 + secs * 1000;
+
+ return usecs;
+}
+
+int find_substr(const char *list, const char *item)
+{
+ const char *ptr;
+
+ g_return_val_if_fail(list != NULL, FALSE);
+ g_return_val_if_fail(item != NULL, FALSE);
+
+ if (*item == '\0')
+ return FALSE;
+
+ for (;;) {
+ while (isspace((gint) *list)) list++;
+ if (*list == '\0') break;
+
+ ptr = strchr(list, ' ');
+ if (ptr == NULL) ptr = list+strlen(list);
+
+ if (g_strncasecmp(list, item, ptr-list) == 0 &&
+ item[ptr-list] == '\0')
+ return TRUE;
+
+ list = ptr;
+ }
+
+ return FALSE;
+}
+
+int strarray_length(char **array)
+{
+ int len;
+
+ g_return_val_if_fail(array != NULL, 0);
+
+ len = 0;
+ while (*array) {
+ len++;
+ array++;
+ }
+ return len;
+}
+
+int strarray_find(char **array, const char *item)
+{
+ char **tmp;
+ int index;
+
+ g_return_val_if_fail(array != NULL, 0);
+ g_return_val_if_fail(item != NULL, 0);
+
+ index = 0;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ if (g_strcasecmp(*tmp, item) == 0)
+ return index;
+ }
+
+ return -1;
+}
+
+int execute(const char *cmd)
+{
+ char **args;
+#ifndef WIN32
+ int pid;
+#endif
+
+ g_return_val_if_fail(cmd != NULL, -1);
+
+#ifndef WIN32
+ pid = fork();
+ if (pid == -1) return FALSE;
+ if (pid != 0) {
+ pidwait_add(pid);
+ return pid;
+ }
+
+ args = g_strsplit(cmd, " ", -1);
+ execvp(args[0], args);
+ g_strfreev(args);
+
+ _exit(99);
+ return -1;
+#else
+ args = g_strsplit(cmd, " ", -1);
+ _spawnvp(_P_DETACH, args[0], args);
+ g_strfreev(args);
+ return 0;
+#endif
+}
+
+GSList *gslist_find_string(GSList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (strcmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GSList *gslist_find_icase_string(GSList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (g_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data)
+{
+ void *ret;
+
+ while (list != NULL) {
+ ret = func(list->data, (void *) data);
+ if (ret != NULL) return ret;
+
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
+{
+ GString *str;
+ char **data, *ret;
+
+ str = g_string_new(NULL);
+ while (list != NULL) {
+ data = G_STRUCT_MEMBER_P(list->data, offset);
+
+ if (str->len != 0) g_string_append(str, delimiter);
+ g_string_append(str, *data);
+ list = list->next;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* `list' contains char* */
+char *gslist_to_string(GSList *list, const char *delimiter)
+{
+ GString *str;
+ char *ret;
+
+ str = g_string_new(NULL);
+ while (list != NULL) {
+ if (str->len != 0) g_string_append(str, delimiter);
+ g_string_append(str, list->data);
+
+ list = list->next;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+void hash_save_key(char *key, void *value, GSList **list)
+{
+ *list = g_slist_append(*list, key);
+}
+
+/* save all keys in hash table to linked list - you shouldn't remove any
+ items while using this list, use g_slist_free() after you're done with it */
+GSList *hashtable_get_keys(GHashTable *hash)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(hash, (GHFunc) hash_save_key, &list);
+ return list;
+}
+
+GList *glist_find_string(GList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (strcmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GList *glist_find_icase_string(GList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (g_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+char *stristr(const char *data, const char *key)
+{
+ const char *max;
+ int keylen, datalen, pos;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+ if (keylen == 0)
+ return (char *) data;
+
+ max = data+datalen-keylen;
+ pos = 0;
+ while (data <= max) {
+ if (key[pos] == '\0')
+ return (char *) data;
+
+ if (toupper(data[pos]) == toupper(key[pos]))
+ pos++;
+ else {
+ data++;
+ pos = 0;
+ }
+ }
+
+ return NULL;
+}
+
+#define isbound(c) \
+ ((unsigned char) (c) < 128 && \
+ (isspace((int) (c)) || ispunct((int) (c))))
+
+char *strstr_full_case(const char *data, const char *key, int icase)
+{
+ const char *start, *max;
+ int keylen, datalen, pos, match;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+ if (keylen == 0)
+ return (char *) data;
+
+ max = data+datalen-keylen;
+ start = data; pos = 0;
+ while (data <= max) {
+ if (key[pos] == '\0') {
+ if (data[pos] != '\0' && !isbound(data[pos])) {
+ data++;
+ pos = 0;
+ continue;
+ }
+ return (char *) data;
+ }
+
+ match = icase ? (toupper(data[pos]) == toupper(key[pos])) :
+ data[pos] == key[pos];
+
+ if (match && (pos != 0 || data == start || isbound(data[-1])))
+ pos++;
+ else {
+ data++;
+ pos = 0;
+ }
+ }
+
+ return NULL;
+}
+
+char *strstr_full(const char *data, const char *key)
+{
+ return strstr_full_case(data, key, FALSE);
+}
+
+char *stristr_full(const char *data, const char *key)
+{
+ return strstr_full_case(data, key, TRUE);
+}
+
+int regexp_match(const char *str, const char *regexp)
+{
+#ifdef HAVE_REGEX_H
+ regex_t preg;
+ int ret;
+
+ if (regcomp(&preg, regexp, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0)
+ return 0;
+
+ ret = regexec(&preg, str, 0, NULL, 0);
+ regfree(&preg);
+
+ return ret == 0;
+#else
+ return FALSE;
+#endif
+}
+
+/* Create the directory and all it's parent directories */
+int mkpath(const char *path, int mode)
+{
+ struct stat statbuf;
+ const char *p;
+ char *dir;
+
+ g_return_val_if_fail(path != NULL, -1);
+
+ p = g_path_skip_root((char *) path);
+ if (p == NULL) {
+ /* not a full path, maybe not what we wanted
+ but continue anyway.. */
+ p = path;
+ }
+ for (;;) {
+ if (*p != G_DIR_SEPARATOR && *p != '\0') {
+ p++;
+ continue;
+ }
+
+ dir = g_strndup(path, (int) (p-path));
+ if (stat(dir, &statbuf) != 0) {
+#ifndef WIN32
+ if (mkdir(dir, mode) == -1) {
+#else
+ if (_mkdir(dir) == -1) {
+#endif
+ g_free(dir);
+ return -1;
+ }
+ }
+ g_free(dir);
+
+ if (*p++ == '\0')
+ break;
+ }
+
+ return 0;
+}
+
+/* convert ~/ to $HOME */
+char *convert_home(const char *path)
+{
+ return *path == '~' && (*(path+1) == '/' || *(path+1) == '\0') ?
+ g_strconcat(g_get_home_dir(), path+1, NULL) :
+ g_strdup(path);
+}
+
+int g_istr_equal(gconstpointer v, gconstpointer v2)
+{
+ return g_strcasecmp((const char *) v, (const char *) v2) == 0;
+}
+
+int g_istr_cmp(gconstpointer v, gconstpointer v2)
+{
+ return g_strcasecmp((const char *) v, (const char *) v2);
+}
+
+/* a char* hash function from ASU */
+unsigned int g_istr_hash(gconstpointer v)
+{
+ const char *s = (const char *) v;
+ unsigned int h = 0, g;
+
+ while (*s != '\0') {
+ h = (h << 4) + toupper(*s);
+ if ((g = h & 0xf0000000UL)) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ s++;
+ }
+
+ return h /* % M */;
+}
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *cmask, const char *data)
+{
+ char *mask, *newmask, *p1, *p2;
+ int ret;
+
+ newmask = mask = g_strdup(cmask);
+ for (; *mask != '\0' && *data != '\0'; mask++) {
+ if (*mask != '*') {
+ if (*mask != '?' && toupper(*mask) != toupper(*data))
+ break;
+
+ data++;
+ continue;
+ }
+
+ while (*mask == '?' || *mask == '*') mask++;
+ if (*mask == '\0') {
+ data += strlen(data);
+ break;
+ }
+
+ p1 = strchr(mask, '*');
+ p2 = strchr(mask, '?');
+ if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;
+
+ if (p1 != NULL) *p1 = '\0';
+
+ data = stristr(data, mask);
+ if (data == NULL) break;
+
+ data += strlen(mask);
+ mask += strlen(mask)-1;
+
+ if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
+ }
+
+ while (*mask == '*') mask++;
+
+ ret = data != NULL && *data == '\0' && *mask == '\0';
+ g_free(newmask);
+
+ return ret;
+}
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char)
+{
+ g_return_val_if_fail(str != NULL, FALSE);
+
+ if (*str == '\0' || *str == end_char)
+ return FALSE;
+
+ while (*str != '\0' && *str != end_char) {
+ if (!isdigit(*str)) return FALSE;
+ str++;
+ }
+
+ return TRUE;
+}
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to)
+{
+ char *p;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == from) *p = to;
+ }
+ return str;
+}
+
+int octal2dec(int octal)
+{
+ int dec, n;
+
+ dec = 0; n = 1;
+ while (octal != 0) {
+ dec += n*(octal%10);
+ octal /= 10; n *= 8;
+ }
+
+ return dec;
+}
+
+int dec2octal(int decimal)
+{
+ int octal, pos;
+
+ octal = 0; pos = 0;
+ while (decimal > 0) {
+ octal += (decimal & 7)*(pos == 0 ? 1 : pos);
+ decimal /= 8;
+ pos += 10;
+ }
+
+ return octal;
+}
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *channel)
+{
+ char *str, *p;
+
+ str = p = g_malloc(strlen(channel)*2+1);
+ while (*channel != '\0') {
+ if ((unsigned char) *channel >= 32)
+ *p++ = *channel;
+ else {
+ *p++ = '^';
+ *p++ = *channel + 'A'-1;
+ }
+ channel++;
+ }
+ *p = '\0';
+
+ return str;
+}
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t)
+{
+ struct tm *tm;
+ char *str;
+ int len;
+
+ tm = localtime(&t);
+ str = g_strdup(asctime(tm));
+
+ len = strlen(str);
+ if (len > 0) str[len-1] = '\0';
+ return str;
+}
+
+/* Returns number of columns needed to print items.
+ save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+ int max_width, int max_columns,
+ int item_extra, int item_min_size,
+ int **save_column_widths, int *rows)
+{
+ GSList *tmp;
+ int **columns, *columns_width, *columns_rows;
+ int item_pos, items_count;
+ int ret, len, max_len, n, col;
+
+ items_count = g_slist_length(items);
+ if (items_count == 0) {
+ *save_column_widths = NULL;
+ *rows = 0;
+ return 0;
+ }
+
+ len = max_width/(item_extra+item_min_size);
+ if (len <= 0) len = 1;
+ if (max_columns <= 0 || len < max_columns)
+ max_columns = len;
+
+ columns = g_new0(int *, max_columns);
+ columns_width = g_new0(int, max_columns);
+ columns_rows = g_new0(int, max_columns);
+
+ for (n = 1; n < max_columns; n++) {
+ columns[n] = g_new0(int, n+1);
+ columns_rows[n] = items_count <= n+1 ? 1 :
+ (items_count+n)/(n+1);
+ }
+
+ /* for each possible column count, save the column widths and
+ find the biggest column count that fits to screen. */
+ item_pos = 0; max_len = 0;
+ for (tmp = items; tmp != NULL; tmp = tmp->next) {
+ len = item_extra+len_func(tmp->data);
+ if (max_len < len)
+ max_len = len;
+
+ for (n = 1; n < max_columns; n++) {
+ if (columns_width[n] > max_width)
+ continue; /* too wide */
+
+ col = item_pos/columns_rows[n];
+ if (columns[n][col] < len) {
+ columns_width[n] += len-columns[n][col];
+ columns[n][col] = len;
+ }
+ }
+
+ item_pos++;
+ }
+
+ for (n = max_columns-1; n > 1; n--) {
+ if (columns_width[n] <= max_width &&
+ columns[n][n] > 0)
+ break;
+ }
+ ret = n+1;
+
+ *save_column_widths = g_new(int, ret);
+ if (ret == 1) {
+ **save_column_widths = max_len;
+ *rows = 1;
+ } else {
+ memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
+ *rows = columns_rows[ret-1];
+ }
+
+ for (n = 1; n < max_columns; n++)
+ g_free(columns[n]);
+ g_free(columns_width);
+ g_free(columns_rows);
+ g_free(columns);
+
+ return ret;
+}
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows)
+{
+ GSList *tmp, *sorted;
+ int row, skip;
+
+ if (list == NULL || rows == 0)
+ return list;
+
+ sorted = NULL;
+
+ for (row = 0; row < rows; row++) {
+ tmp = g_slist_nth(list, row);
+ skip = 1;
+ for (; tmp != NULL; tmp = tmp->next) {
+ if (--skip == 0) {
+ skip = rows;
+ sorted = g_slist_append(sorted, tmp->data);
+ }
+ }
+ }
+
+ g_return_val_if_fail(g_slist_length(sorted) ==
+ g_slist_length(list), sorted);
+ return sorted;
+}
--- /dev/null
+#ifndef __MISC_H
+#define __MISC_H
+
+/* `str' should be type char[MAX_INT_STRLEN] */
+#define ltoa(str, num) \
+ g_snprintf(str, sizeof(str), "%d", num)
+
+typedef void* (*FOREACH_FIND_FUNC) (void *item, void *data);
+typedef int (*COLUMN_LEN_FUNC)(void *data);
+
+static inline int nearest_power(int num)
+{
+ int n = 1;
+
+ while (n < num) n <<= 1;
+ return n;
+}
+
+/* Returns 1 if tv1 > tv2, -1 if tv2 > tv1 or 0 if they're equal. */
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2);
+/* Returns "tv1 - tv2", returns the result in milliseconds. Note that
+ if the difference is too large, the result might be invalid. */
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2);
+
+/* find `item' from a space separated `list' */
+int find_substr(const char *list, const char *item);
+/* return how many items `array' has */
+int strarray_length(char **array);
+/* return index of `item' in `array' or -1 if not found */
+int strarray_find(char **array, const char *item);
+
+int execute(const char *cmd); /* returns pid or -1 = error */
+
+GSList *gslist_find_string(GSList *list, const char *key);
+GSList *gslist_find_icase_string(GSList *list, const char *key);
+GList *glist_find_string(GList *list, const char *key);
+GList *glist_find_icase_string(GList *list, const char *key);
+
+void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data);
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_to_string(GSList *list, int offset, const char *delimiter);
+/* `list' contains char* */
+char *gslist_to_string(GSList *list, const char *delimiter);
+
+/* save all keys in hash table to linked list - you shouldn't remove any
+ items while using this list, use g_slist_free() after you're done with it */
+GSList *hashtable_get_keys(GHashTable *hash);
+
+/* strstr() with case-ignoring */
+char *stristr(const char *data, const char *key);
+
+/* like strstr(), but matches only for full words.
+ `icase' specifies if match is case sensitive */
+char *strstr_full_case(const char *data, const char *key, int icase);
+char *strstr_full(const char *data, const char *key);
+char *stristr_full(const char *data, const char *key);
+
+/* easy way to check if regexp matches */
+int regexp_match(const char *str, const char *regexp);
+
+/* Create the directory and all it's parent directories */
+int mkpath(const char *path, int mode);
+/* convert ~/ to $HOME */
+char *convert_home(const char *path);
+
+/* Case-insensitive string hash functions */
+int g_istr_equal(gconstpointer v, gconstpointer v2);
+unsigned int g_istr_hash(gconstpointer v);
+
+/* Case-insensitive GCompareFunc func */
+int g_istr_cmp(gconstpointer v, gconstpointer v2);
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *mask, const char *data);
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char);
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to);
+
+/* octal <-> decimal conversions */
+int octal2dec(int octal);
+int dec2octal(int decimal);
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *channel);
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t);
+
+/* Returns number of columns needed to print items.
+ save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+ int max_width, int max_columns,
+ int item_extra, int item_min_size,
+ int **save_column_widths, int *rows);
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows);
+
+#endif
--- /dev/null
+#include "common.h"
+
+#define MODULE_NAME "core"
--- /dev/null
+/*
+ modules.c : irssi
+
+ Copyright (C) 1999-2000 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 "modules.h"
+#include "signals.h"
+
+#include "commands.h"
+#include "settings.h"
+
+GSList *modules;
+
+static GHashTable *uniqids, *uniqstrids;
+static GHashTable *idlookup, *stridlookup;
+static int next_uniq_id;
+
+void *module_check_cast(void *object, int type_pos, const char *id)
+{
+ return object == NULL || module_find_id(id,
+ G_STRUCT_MEMBER(int, object, type_pos)) == -1 ? NULL : object;
+}
+
+void *module_check_cast_module(void *object, int type_pos,
+ const char *module, const char *id)
+{
+ const char *str;
+
+ if (object == NULL)
+ return NULL;
+
+ str = module_find_id_str(module,
+ G_STRUCT_MEMBER(int, object, type_pos));
+ return str == NULL || strcmp(str, id) != 0 ? NULL : object;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid, idp;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(idlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ g_hash_table_insert(idlookup, g_strdup(module), ids);
+ }
+
+ idp = GINT_TO_POINTER(id);
+ if (!g_hash_table_lookup_extended(ids, idp, &origkey, &uniqid)) {
+ /* not found */
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, idp, GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), idp);
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id_str(const char *module, const char *id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(stridlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ g_hash_table_insert(stridlookup, g_strdup(module), ids);
+ }
+
+ if (!g_hash_table_lookup_extended(ids, id, &origkey, &uniqid)) {
+ /* not found */
+ char *saveid;
+
+ saveid = g_strdup(id);
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, saveid, GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqstrids, GINT_TO_POINTER(ret), saveid);
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid)
+{
+ GHashTable *idlist;
+ gpointer origkey, id;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ if (!g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid),
+ &origkey, &id))
+ return -1;
+
+ /* check that module matches */
+ idlist = g_hash_table_lookup(idlookup, module);
+ if (idlist == NULL)
+ return -1;
+
+ ret = GPOINTER_TO_INT(id);
+ if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+ GPOINTER_TO_INT(id) != uniqid)
+ ret = -1;
+
+ return ret;
+}
+
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid)
+{
+ GHashTable *idlist;
+ gpointer origkey, id;
+ const char *ret;
+
+ g_return_val_if_fail(module != NULL, NULL);
+
+ if (!g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid),
+ &origkey, &id))
+ return NULL;
+
+ /* check that module matches */
+ idlist = g_hash_table_lookup(stridlookup, module);
+ if (idlist == NULL)
+ return NULL;
+
+ ret = id;
+ if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+ GPOINTER_TO_INT(id) != uniqid)
+ ret = NULL;
+
+ return ret;
+}
+
+static void uniq_destroy(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqids, value);
+}
+
+static void uniq_destroy_str(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqstrids, value);
+ g_free(key);
+}
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module)
+{
+ GHashTable *idlist;
+ gpointer key;
+
+ if (g_hash_table_lookup_extended(idlookup, module, &key,
+ (gpointer *) &idlist)) {
+ g_hash_table_remove(idlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(idlist, (GHFunc) uniq_destroy, NULL);
+ g_hash_table_destroy(idlist);
+ }
+
+ if (g_hash_table_lookup_extended(stridlookup, module, &key,
+ (gpointer *) &idlist)) {
+ g_hash_table_remove(stridlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(idlist, (GHFunc) uniq_destroy_str, NULL);
+ g_hash_table_destroy(idlist);
+ }
+}
+
+MODULE_REC *module_find(const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ MODULE_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+#ifdef HAVE_GMODULE
+static char *module_get_name(const char *path, int *start, int *end)
+{
+ const char *name;
+ char *module_name, *ptr;
+
+ name = NULL;
+ if (g_path_is_absolute(path)) {
+ name = strrchr(path, G_DIR_SEPARATOR);
+ if (name != NULL) name++;
+ }
+
+ if (name == NULL)
+ name = path;
+
+ if (strncmp(name, "lib", 3) == 0)
+ name += 3;
+
+ module_name = g_strdup(name);
+ ptr = strchr(module_name, '.');
+ if (ptr != NULL) *ptr = '\0';
+
+ *start = (int) (name-path);
+ *end = *start + (ptr == NULL ? strlen(name) :
+ (int) (module_name-ptr));
+
+ return module_name;
+}
+
+static GModule *module_open(const char *name)
+{
+ struct stat statbuf;
+ GModule *module;
+ char *path, *str;
+
+ if (g_path_is_absolute(name) ||
+ (*name == '.' && name[1] == G_DIR_SEPARATOR))
+ path = g_strdup(name);
+ else {
+ /* first try from home dir */
+ str = g_strdup_printf("%s/.irssi/modules", g_get_home_dir());
+ path = g_module_build_path(str, name);
+ g_free(str);
+
+ if (stat(path, &statbuf) == 0) {
+ module = g_module_open(path, (GModuleFlags) 0);
+ g_free(path);
+ return module;
+ }
+
+ /* module not found from home dir, try global module dir */
+ g_free(path);
+ path = g_module_build_path(MODULEDIR, name);
+ }
+
+ module = g_module_open(path, (GModuleFlags) 0);
+ g_free(path);
+ return module;
+}
+
+#define module_error(error, module, text) \
+ signal_emit("module error", 3, GINT_TO_POINTER(error), module, text)
+
+static int module_load_name(const char *path, const char *name, int silent)
+{
+ void (*module_init) (void);
+ GModule *gmodule;
+ MODULE_REC *rec;
+ char *initfunc;
+
+ gmodule = module_open(path);
+ if (gmodule == NULL) {
+ if (!silent) {
+ module_error(MODULE_ERROR_LOAD, name,
+ g_module_error());
+ }
+ return FALSE;
+ }
+
+ /* get the module's init() function */
+ initfunc = g_strconcat(name, "_init", NULL);
+ if (!g_module_symbol(gmodule, initfunc, (gpointer *) &module_init)) {
+ if (!silent)
+ module_error(MODULE_ERROR_INVALID, name, NULL);
+ g_module_close(gmodule);
+ g_free(initfunc);
+ return FALSE;
+ }
+ g_free(initfunc);
+
+ rec = g_new0(MODULE_REC, 1);
+ rec->name = g_strdup(name);
+ rec->gmodule = gmodule;
+ modules = g_slist_append(modules, rec);
+
+ module_init();
+ settings_check_module(name);
+
+ signal_emit("module loaded", 1, rec);
+ return TRUE;
+}
+#endif
+
+/* Load module - automatically tries to load also the related non-core
+ modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes)
+{
+#ifdef HAVE_GMODULE
+ GString *realpath;
+ char *name, *pname;
+ int ret, start, end;
+
+ g_return_val_if_fail(path != NULL, FALSE);
+
+ if (!g_module_supported())
+ return FALSE;
+
+ name = module_get_name(path, &start, &end);
+ if (module_find(name)) {
+ module_error(MODULE_ERROR_ALREADY_LOADED, name, NULL);
+ g_free(name);
+ return FALSE;
+ }
+
+ /* load "module_core" instead of "module" if it exists */
+ realpath = g_string_new(path);
+ g_string_insert(realpath, end, "_core");
+
+ pname = g_strconcat(name, "_core", NULL);
+ ret = module_load_name(realpath->str, pname, TRUE);
+ g_free(pname);
+
+ if (!ret) {
+ /* load "module" - complain if it's not found */
+ ret = module_load_name(path, name, FALSE);
+ } else if (prefixes != NULL) {
+ /* load all the "prefix modules", like the fe-common, irc,
+ etc. part of the module */
+ while (*prefixes != NULL) {
+ g_string_assign(realpath, path);
+ g_string_insert(realpath, start, "_");
+ g_string_insert(realpath, start, *prefixes);
+
+ pname = g_strconcat(*prefixes, "_", name, NULL);
+ module_load_name(realpath->str, pname, TRUE);
+ g_free(pname);
+
+ prefixes++;
+ }
+ }
+
+ g_string_free(realpath, TRUE);
+ g_free(name);
+ return ret;
+#else
+ return FALSE;
+#endif
+}
+
+void module_unload(MODULE_REC *module)
+{
+#ifdef HAVE_GMODULE
+ void (*module_deinit) (void);
+ char *deinitfunc;
+
+ g_return_if_fail(module != NULL);
+
+ modules = g_slist_remove(modules, module);
+
+ signal_emit("module unloaded", 1, module);
+
+ /* call the module's deinit() function */
+ deinitfunc = g_strconcat(module->name, "_deinit", NULL);
+ if (g_module_symbol(module->gmodule, deinitfunc,
+ (gpointer *) &module_deinit))
+ module_deinit();
+ g_free(deinitfunc);
+
+ settings_remove_module(module->name);
+ commands_remove_module(module->name);
+ signals_remove_module(module->name);
+
+ g_module_close(module->gmodule);
+ g_free(module->name);
+ g_free(module);
+#endif
+}
+
+static void uniq_get_modules(char *key, void *value, GSList **list)
+{
+ *list = g_slist_append(*list, key);
+}
+
+void modules_init(void)
+{
+ modules = NULL;
+
+ idlookup = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ uniqids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ stridlookup = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ uniqstrids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ next_uniq_id = 0;
+}
+
+void modules_deinit(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(idlookup, (GHFunc) uniq_get_modules, &list);
+ g_hash_table_foreach(stridlookup, (GHFunc) uniq_get_modules, &list);
+
+ while (list != NULL) {
+ module_uniq_destroy(list->data);
+ list = g_slist_remove(list, list->data);
+ }
+
+ g_hash_table_destroy(idlookup);
+ g_hash_table_destroy(stridlookup);
+ g_hash_table_destroy(uniqids);
+ g_hash_table_destroy(uniqstrids);
+}
--- /dev/null
+#ifndef __MODULES_H
+#define __MODULES_H
+
+#define MODULE_DATA_INIT(rec) \
+ (rec)->module_data = g_hash_table_new(g_str_hash, g_str_equal)
+
+#define MODULE_DATA_DEINIT(rec) \
+ g_hash_table_destroy((rec)->module_data)
+
+#define MODULE_DATA_SET(rec, data) \
+ g_hash_table_insert((rec)->module_data, MODULE_NAME, data)
+
+#define MODULE_DATA(rec) \
+ g_hash_table_lookup((rec)->module_data, MODULE_NAME)
+
+enum {
+ MODULE_ERROR_ALREADY_LOADED,
+ MODULE_ERROR_LOAD,
+ MODULE_ERROR_INVALID
+};
+
+typedef struct {
+ char *name;
+#ifdef HAVE_GMODULE
+ GModule *gmodule;
+#endif
+} MODULE_REC;
+
+extern GSList *modules;
+
+MODULE_REC *module_find(const char *name);
+
+/* Load module - automatically tries to load also the related non-core
+ modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes);
+void module_unload(MODULE_REC *module);
+
+#define MODULE_CHECK_CAST(object, cast, type_field, id) \
+ ((cast *) module_check_cast(object, offsetof(cast, type_field), id))
+#define MODULE_CHECK_CAST_MODULE(object, cast, type_field, module, id) \
+ ((cast *) module_check_cast_module(object, \
+ offsetof(cast, type_field), module, id))
+void *module_check_cast(void *object, int type_pos, const char *id);
+void *module_check_cast_module(void *object, int type_pos,
+ const char *module, const char *id);
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id);
+/* return unique number across all modules for `id'. */
+int module_get_uniq_id_str(const char *module, const char *id);
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid);
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid);
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module);
+
+void modules_init(void);
+void modules_deinit(void);
+
+#endif
--- /dev/null
+/*
+ net-disconnect.c :
+
+ Copyright (C) 1999-2000 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 "network.h"
+
+/* when quitting, wait for max. 5 seconds before forcing to close the socket */
+#define MAX_QUIT_CLOSE_WAIT 5
+
+/* wait for max. 2 minutes for other side to close the socket */
+#define MAX_CLOSE_WAIT (60*2)
+
+typedef struct {
+ time_t created;
+ GIOChannel *handle;
+ int tag;
+} NET_DISCONNECT_REC;
+
+static GSList *disconnects;
+
+static int timeout_tag;
+
+static void net_disconnect_remove(NET_DISCONNECT_REC *rec)
+{
+ disconnects = g_slist_remove(disconnects, rec);
+
+ g_source_remove(rec->tag);
+ g_free(rec);
+}
+
+static void sig_disconnect(NET_DISCONNECT_REC *rec)
+{
+ char buf[512];
+ int count, ret;
+
+ /* check if there's any data waiting in socket. read max. 5kB so
+ if server just keeps sending us stuff we won't get stuck */
+ count = 0;
+ do {
+ ret = net_receive(rec->handle, buf, sizeof(buf));
+ if (ret == -1) {
+ /* socket was closed */
+ net_disconnect_remove(rec);
+ }
+ count++;
+ } while (ret == sizeof(buf) && count < 10);
+}
+
+static int sig_timeout_disconnect(void)
+{
+ NET_DISCONNECT_REC *rec;
+ GSList *tmp, *next;
+ time_t now;
+
+ /* check if we've waited enough for sockets to close themselves */
+ now = time(NULL);
+ for (tmp = disconnects; tmp != NULL; tmp = next) {
+ rec = tmp->data;
+ next = tmp->next;
+
+ if (rec->created+MAX_CLOSE_WAIT <= now)
+ net_disconnect_remove(rec);
+ }
+
+ if (disconnects == NULL) {
+ /* no more sockets in disconnect queue, stop calling this
+ function */
+ timeout_tag = -1;
+ }
+ return disconnects != NULL;
+}
+
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(GIOChannel *handle)
+{
+ NET_DISCONNECT_REC *rec;
+
+ rec = g_new(NET_DISCONNECT_REC, 1);
+ rec->created = time(NULL);
+ rec->handle = handle;
+ rec->tag = g_input_add(handle, G_INPUT_READ,
+ (GInputFunction) sig_disconnect, rec);
+
+ if (timeout_tag == -1) {
+ timeout_tag = g_timeout_add(10000, (GSourceFunc)
+ sig_timeout_disconnect, NULL);
+ }
+
+ disconnects = g_slist_append(disconnects, rec);
+}
+
+void net_disconnect_init(void)
+{
+ disconnects = NULL;
+ timeout_tag = -1;
+}
+
+void net_disconnect_deinit(void)
+{
+#ifndef WIN32
+ NET_DISCONNECT_REC *rec;
+ time_t now, max;
+ int first, fd;
+ struct timeval tv;
+ fd_set set;
+
+ /* give the sockets a chance to disconnect themselves.. */
+ max = time(NULL)+MAX_QUIT_CLOSE_WAIT;
+ first = 1;
+ while (disconnects != NULL) {
+ rec = disconnects->data;
+
+ now = time(NULL);
+ if (rec->created+MAX_QUIT_CLOSE_WAIT <= now || max <= now) {
+ /* this one has waited enough */
+ net_disconnect_remove(rec);
+ continue;
+ }
+
+ fd = g_io_channel_unix_get_fd(rec->handle);
+ FD_ZERO(&set);
+ FD_SET(fd, &set);
+ tv.tv_sec = first ? 0 : max-now;
+ tv.tv_usec = first ? 100000 : 0;
+ if (select(fd+1, &set, NULL, NULL, &tv) > 0 &&
+ FD_ISSET(fd, &set)) {
+ /* data coming .. check if we can close the handle */
+ sig_disconnect(rec);
+ } else if (first) {
+ /* Display the text when we have already waited
+ for a while */
+ printf("Please wait, waiting for servers to close "
+ "connections..\n");
+ fflush(stdout);
+
+ first = 0;
+ }
+ }
+#endif
+}
--- /dev/null
+#ifndef __NET_DISCONNECT_H
+#define __NET_DISCONNECT_H
+
+void net_disconnect_init(void);
+void net_disconnect_deinit(void);
+
+#endif
--- /dev/null
+/*
+ net-nonblock.c : Nonblocking net_connect()
+
+ Copyright (C) 1998-2000 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 <signal.h>
+
+#include "pidwait.h"
+#include "net-nonblock.h"
+
+typedef struct {
+ NET_CALLBACK func;
+ void *data;
+
+ GIOChannel *pipes[2];
+ int port;
+ IPADDR *my_ip;
+ int tag;
+} SIMPLE_THREAD_REC;
+
+#define is_fatal_error(err) \
+ (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
+
+static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
+{
+ unsigned int ret;
+ int err, sent;
+
+ sent = 0;
+ do {
+ err = g_io_channel_write(channel, (char *) data + sent,
+ len-sent, &ret);
+ sent += ret;
+ } while (sent < len && !is_fatal_error(err));
+
+ return err != 0 ? -1 : 0;
+}
+
+static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
+{
+ time_t maxwait;
+ unsigned int ret;
+ int err, received;
+
+ maxwait = time(NULL)+2;
+ received = 0;
+ do {
+ err = g_io_channel_read(channel, (char *) data + received,
+ len-received, &ret);
+ received += ret;
+ } while (received < len && time(NULL) < maxwait &&
+ (ret != 0 || !is_fatal_error(err)));
+
+ return received < len ? -1 : 0;
+}
+
+/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
+ written to pipe when found PID of the resolver child is returned */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe)
+{
+ RESOLVED_IP_REC rec;
+ const char *errorstr;
+#ifndef WIN32
+ int pid;
+#endif
+
+ g_return_val_if_fail(addr != NULL, FALSE);
+
+#ifndef WIN32
+ pid = fork();
+ if (pid > 0) {
+ /* parent */
+ pidwait_add(pid);
+ return pid;
+ }
+
+ if (pid != 0) {
+ /* failed! */
+ g_warning("net_connect_thread(): fork() failed! "
+ "Using blocking resolving");
+ }
+#endif
+
+ /* child */
+ memset(&rec, 0, sizeof(rec));
+ rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
+ if (rec.error == 0) {
+ errorstr = NULL;
+ } else {
+ errorstr = net_gethosterror(rec.error);
+ rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
+ }
+
+ g_io_channel_write_block(pipe, &rec, sizeof(rec));
+ if (rec.errlen != 0)
+ g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
+
+#ifndef WIN32
+ if (pid == 0)
+ _exit(99);
+#endif
+
+ /* we used blocking lookup */
+ return 0;
+}
+
+/* get the resolved IP address */
+int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
+{
+ rec->error = -1;
+ rec->errorstr = NULL;
+
+#ifndef WIN32
+ fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
+#endif
+
+ /* get ip+error */
+ if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
+ rec->errorstr = g_strdup_printf("Host name lookup: %s",
+ g_strerror(errno));
+ return -1;
+ }
+
+ if (rec->error) {
+ /* read error string, if we can't read everything for some
+ reason, just ignore it. */
+ rec->errorstr = g_malloc0(rec->errlen+1);
+ g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
+ }
+
+ return 0;
+}
+
+/* Get host name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
+{
+ /* FIXME: not implemented */
+ return FALSE;
+}
+
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid)
+{
+ g_return_if_fail(pid > 0);
+
+#ifndef WIN32
+ kill(pid, SIGKILL);
+#endif
+}
+
+static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle)
+{
+ g_return_if_fail(rec != NULL);
+
+ g_source_remove(rec->tag);
+
+ if (net_geterror(handle) != 0) {
+ /* failed */
+ g_io_channel_close(handle);
+ g_io_channel_unref(handle);
+ handle = NULL;
+ }
+
+ rec->func(handle, rec->data);
+ g_free(rec);
+}
+
+static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
+{
+ RESOLVED_IP_REC iprec;
+ GIOChannel *handle;
+ IPADDR *ip;
+
+ g_return_if_fail(rec != NULL);
+
+ g_source_remove(rec->tag);
+
+ net_gethostbyname_return(pipe, &iprec);
+ g_free_not_null(iprec.errorstr);
+
+ g_io_channel_close(rec->pipes[0]);
+ g_io_channel_unref(rec->pipes[0]);
+ g_io_channel_close(rec->pipes[1]);
+ g_io_channel_unref(rec->pipes[1]);
+
+ ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
+ handle = iprec.error == -1 ? NULL :
+ net_connect_ip(ip, rec->port, rec->my_ip);
+
+ g_free_not_null(rec->my_ip);
+
+ if (handle == NULL) {
+ /* failed */
+ rec->func(NULL, rec->data);
+ g_free(rec);
+ return;
+ }
+
+ rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE,
+ (GInputFunction) simple_init, rec);
+}
+
+/* Connect to server, call func when finished */
+int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip,
+ NET_CALLBACK func, void *data)
+{
+ SIMPLE_THREAD_REC *rec;
+ int fd[2];
+
+ g_return_val_if_fail(server != NULL, FALSE);
+ g_return_val_if_fail(func != NULL, FALSE);
+
+ if (pipe(fd) != 0) {
+ g_warning("net_connect_nonblock(): pipe() failed.");
+ return FALSE;
+ }
+
+ rec = g_new0(SIMPLE_THREAD_REC, 1);
+ rec->port = port;
+ if (my_ip != NULL) {
+ rec->my_ip = g_malloc(sizeof(IPADDR));
+ memcpy(rec->my_ip, my_ip, sizeof(IPADDR));
+ }
+ rec->func = func;
+ rec->data = data;
+ rec->pipes[0] = g_io_channel_unix_new(fd[0]);
+ rec->pipes[1] = g_io_channel_unix_new(fd[1]);
+
+ /* start nonblocking host name lookup */
+ net_gethostbyname_nonblock(server, rec->pipes[1]);
+ rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ,
+ (GInputFunction) simple_readpipe, rec);
+
+ return TRUE;
+}
--- /dev/null
+#ifndef __NET_NONBLOCK_H
+#define __NET_NONBLOCK_H
+
+#include "network.h"
+
+typedef struct {
+ IPADDR ip4, ip6; /* resolved ip addresses */
+ int error; /* error, 0 = no error, -1 = error: */
+ int errlen; /* error text length */
+ char *errorstr; /* error string - dynamically allocated, you'll
+ need to free() it yourself unless it's NULL */
+} RESOLVED_IP_REC;
+
+typedef struct {
+ int namelen;
+ char *name;
+
+ int error;
+ int errlen;
+ char *errorstr;
+} RESOLVED_NAME_REC;
+
+typedef void (*NET_CALLBACK) (GIOChannel *, void *);
+typedef void (*NET_HOST_CALLBACK) (RESOLVED_NAME_REC *, void *);
+
+/* nonblocking gethostbyname(), PID of the resolver child is returned. */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe);
+/* Get host's name, call func when finished */
+int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data);
+/* get the resolved IP address. returns -1 if some error occured with read() */
+int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec);
+
+/* Connect to server, call func when finished */
+int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip,
+ NET_CALLBACK func, void *data);
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid);
+
+#endif
--- /dev/null
+/*
+ net-sendbuffer.c : Buffered send()
+
+ Copyright (C) 1998-2000 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 "network.h"
+#include "net-sendbuffer.h"
+
+struct _NET_SENDBUF_REC {
+ GIOChannel *handle;
+
+ int send_tag;
+ int bufsize;
+ int bufpos;
+ char *buffer; /* Buffer is NULL until it's actually needed. */
+};
+
+static GSList *buffers;
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+ is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize)
+{
+ NET_SENDBUF_REC *rec;
+
+ g_return_val_if_fail(handle != NULL, NULL);
+
+ rec = g_new0(NET_SENDBUF_REC, 1);
+ rec->send_tag = -1;
+ rec->handle = handle;
+ rec->bufsize = bufsize > 0 ? bufsize : DEFAULT_BUFFER_SIZE;
+
+ buffers = g_slist_append(buffers, rec);
+ return rec;
+}
+
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close)
+{
+ buffers = g_slist_remove(buffers, rec);
+
+ if (rec->send_tag != -1) g_source_remove(rec->send_tag);
+ if (close) net_disconnect(rec->handle);
+ g_free_not_null(rec->buffer);
+ g_free(rec);
+}
+
+/* Transmit all data from buffer - return TRUE if successful */
+static int buffer_send(NET_SENDBUF_REC *rec)
+{
+ int ret;
+
+ ret = net_transmit(rec->handle, rec->buffer, rec->bufpos);
+ if (ret < 0 || rec->bufpos == ret) {
+ /* error/all sent - don't try to send it anymore */
+ g_free_and_null(rec->buffer);
+ return TRUE;
+ }
+
+ if (ret > 0) {
+ rec->bufpos -= ret;
+ g_memmove(rec->buffer, rec->buffer+ret, rec->bufpos);
+ }
+ return FALSE;
+}
+
+static void sig_sendbuffer(NET_SENDBUF_REC *rec)
+{
+ if (rec->buffer != NULL) {
+ if (!buffer_send(rec))
+ return;
+ }
+
+ g_source_remove(rec->send_tag);
+ rec->send_tag = -1;
+}
+
+/* Add `data' to transmit buffer - return FALSE if buffer is full */
+static int buffer_add(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+ if (rec->buffer == NULL) {
+ rec->buffer = g_malloc(rec->bufsize);
+ rec->bufpos = 0;
+ }
+
+ if (rec->bufpos+size > rec->bufsize)
+ return FALSE;
+
+ memcpy(rec->buffer+rec->bufpos, data, size);
+ rec->bufpos += size;
+ return TRUE;
+}
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+ automatically after a while. Returns -1 if some unrecoverable error
+ occured. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+ int ret;
+
+ g_return_val_if_fail(rec != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+ if (size <= 0) return 0;
+
+ if (rec->buffer == NULL) {
+ /* nothing in buffer - transmit immediately */
+ ret = net_transmit(rec->handle, data, size);
+ if (ret < 0) return -1;
+ size -= ret;
+ data = ((const char *) data) + ret;
+ }
+
+ if (size <= 0)
+ return 0;
+
+ /* everything couldn't be sent. */
+ if (rec->send_tag == -1) {
+ rec->send_tag =
+ g_input_add(rec->handle, G_INPUT_WRITE,
+ (GInputFunction) sig_sendbuffer, rec);
+ }
+
+ return buffer_add(rec, data, size) ? 0 : -1;
+}
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec)
+{
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return rec->handle;
+}
+
+void net_sendbuffer_init(void)
+{
+ buffers = NULL;
+}
+
+void net_sendbuffer_deinit(void)
+{
+}
--- /dev/null
+#ifndef __NET_SENDBUFFER_H
+#define __NET_SENDBUFFER_H
+
+#define DEFAULT_BUFFER_SIZE 8192
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+ is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize);
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close);
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+ automatically after a while. Returns -1 if some unrecoverable error
+ occured. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size);
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec);
+
+void net_sendbuffer_init(void);
+void net_sendbuffer_deinit(void);
+
+#endif
--- /dev/null
+/*
+ network.c : Network stuff
+
+ Copyright (C) 1999-2000 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 "network.h"
+
+#ifndef INADDR_NONE
+# define INADDR_NONE INADDR_BROADCAST
+#endif
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 sin6;
+#endif
+};
+
+#ifdef HAVE_IPV6
+# define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \
+ sizeof(so.sin6) : sizeof(so.sin))
+#else
+# define SIZEOF_SOCKADDR(so) (sizeof(so.sin))
+#endif
+
+#ifdef WIN32
+# define g_io_channel_new(handle) g_io_channel_win32_new_stream_socket(handle)
+#else
+# define g_io_channel_new(handle) g_io_channel_unix_new(handle)
+#endif
+
+/* Cygwin need this, don't know others.. */
+/*#define BLOCKING_SOCKETS 1*/
+
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2)
+{
+ if (ip1->family != ip2->family)
+ return 0;
+
+#ifdef HAVE_IPV6
+ if (ip1->family == AF_INET6)
+ return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0;
+#endif
+
+ return memcmp(&ip1->ip, &ip2->ip, 4) == 0;
+}
+
+
+/* copy IP to sockaddr */
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
+{
+ if (ip == NULL) {
+#ifdef HAVE_IPV6
+ so->sin6.sin6_family = AF_INET6;
+ so->sin6.sin6_addr = in6addr_any;
+#else
+ so->sin.sin_family = AF_INET;
+ so->sin.sin_addr.s_addr = INADDR_ANY;
+#endif
+ return;
+ }
+
+ so->sin.sin_family = ip->family;
+#ifdef HAVE_IPV6
+ if (ip->family == AF_INET6)
+ memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip));
+ else
+#endif
+ memcpy(&so->sin.sin_addr, &ip->ip, 4);
+}
+
+void sin_get_ip(const union sockaddr_union *so, IPADDR *ip)
+{
+ ip->family = so->sin.sin_family;
+
+#ifdef HAVE_IPV6
+ if (ip->family == AF_INET6)
+ memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip));
+ else
+#endif
+ memcpy(&ip->ip, &so->sin.sin_addr, 4);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void sin_set_port(union sockaddr_union *so, int port)
+{
+#ifdef HAVE_IPV6
+ if (so->sin.sin_family == AF_INET6)
+ so->sin6.sin6_port = htons(port);
+ else
+#endif
+ so->sin.sin_port = htons((unsigned short)port);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+int sin_get_port(union sockaddr_union *so)
+{
+#ifdef HAVE_IPV6
+ if (so->sin.sin_family == AF_INET6)
+ return ntohs(so->sin6.sin6_port);
+#endif
+ return ntohs(so->sin.sin_port);
+}
+
+/* Connect to socket */
+GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip)
+{
+ IPADDR ip4, ip6, *ip;
+ int family;
+
+ g_return_val_if_fail(addr != NULL, NULL);
+
+ family = my_ip == NULL ? 0 : my_ip->family;
+ if (net_gethostbyname(addr, &ip4, &ip6) == -1)
+ return NULL;
+
+ if (my_ip == NULL) {
+ /* prefer IPv4 addresses */
+ ip = ip4.family != 0 ? &ip4 : &ip6;
+ } else if (IPADDR_IS_V6(my_ip)) {
+ /* my_ip is IPv6 address, use it if possible */
+ if (ip6.family != 0)
+ ip = &ip6;
+ else {
+ my_ip = NULL;
+ ip = &ip4;
+ }
+ } else {
+ /* my_ip is IPv4 address, use it if possible */
+ if (ip4.family != 0)
+ ip = &ip4;
+ else {
+ my_ip = NULL;
+ ip = &ip6;
+ }
+ }
+
+ return net_connect_ip(ip, port, my_ip);
+}
+
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+ union sockaddr_union so;
+ int handle, ret, opt = 1;
+
+ if (my_ip != NULL && ip->family != my_ip->family) {
+ g_warning("net_connect_ip(): ip->family != my_ip->family");
+ my_ip = NULL;
+ }
+
+ /* create the socket */
+ memset(&so, 0, sizeof(so));
+ so.sin.sin_family = ip->family;
+ handle = socket(ip->family, SOCK_STREAM, 0);
+
+ if (handle == -1)
+ return NULL;
+
+ /* set socket options */
+#ifndef WIN32
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+#endif
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &opt, sizeof(opt));
+
+ /* set our own address, ignore if bind() fails */
+ if (my_ip != NULL) {
+ sin_set_ip(&so, my_ip);
+ bind(handle, &so.sa, SIZEOF_SOCKADDR(so));
+ }
+
+ /* connect */
+ sin_set_ip(&so, ip);
+ sin_set_port(&so, port);
+ ret = connect(handle, &so.sa, SIZEOF_SOCKADDR(so));
+
+#ifndef WIN32
+ if (ret < 0 && errno != EINPROGRESS) {
+#else
+ if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) {
+#endif
+ close(handle);
+ return NULL;
+ }
+
+ return g_io_channel_new(handle);
+}
+
+/* Disconnect socket */
+void net_disconnect(GIOChannel *handle)
+{
+ g_return_if_fail(handle != NULL);
+
+ g_io_channel_close(handle);
+ g_io_channel_unref(handle);
+}
+
+/* Listen for connections on a socket. if `my_ip' is NULL, listen in any
+ address. */
+GIOChannel *net_listen(IPADDR *my_ip, int *port)
+{
+ union sockaddr_union so;
+ int ret, handle, opt = 1;
+ socklen_t len;
+
+ g_return_val_if_fail(port != NULL, NULL);
+
+ memset(&so, 0, sizeof(so));
+ sin_set_port(&so, *port);
+ sin_set_ip(&so, my_ip);
+
+ /* create the socket */
+ handle = socket(so.sin.sin_family, SOCK_STREAM, 0);
+#ifdef HAVE_IPV6
+ if (handle == -1 && errno == EINVAL) {
+ /* IPv6 is not supported by OS */
+ so.sin.sin_family = AF_INET;
+ so.sin.sin_addr.s_addr = INADDR_ANY;
+
+ handle = socket(AF_INET, SOCK_STREAM, 0);
+ }
+#endif
+ if (handle == -1)
+ return NULL;
+
+ /* set socket options */
+#ifndef WIN32
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+#endif
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &opt, sizeof(opt));
+
+ /* specify the address/port we want to listen in */
+ ret = bind(handle, &so.sa, SIZEOF_SOCKADDR(so));
+ if (ret >= 0) {
+ /* get the actual port we started listen */
+ len = SIZEOF_SOCKADDR(so);
+ ret = getsockname(handle, &so.sa, &len);
+ if (ret >= 0) {
+ *port = sin_get_port(&so);
+
+ /* start listening */
+ if (listen(handle, 1) >= 0)
+ return g_io_channel_new(handle);
+ }
+
+ }
+
+ /* error */
+ close(handle);
+ return NULL;
+}
+
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+ int ret;
+ socklen_t addrlen;
+
+ g_return_val_if_fail(handle != NULL, NULL);
+
+ addrlen = sizeof(so);
+ ret = accept(g_io_channel_unix_get_fd(handle), &so.sa, &addrlen);
+
+ if (ret < 0)
+ return NULL;
+
+ if (addr != NULL) sin_get_ip(&so, addr);
+ if (port != NULL) *port = sin_get_port(&so);
+
+#ifndef WIN32
+ fcntl(ret, F_SETFL, O_NONBLOCK);
+#endif
+ return g_io_channel_new(ret);
+}
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len)
+{
+ unsigned int ret;
+ int err;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(buf != NULL, -1);
+
+ err = g_io_channel_read(handle, buf, len, &ret);
+ if (err == 0 && ret == 0)
+ return -1; /* disconnected */
+
+ if (err == G_IO_ERROR_AGAIN || (err != 0 && errno == EINTR))
+ return 0; /* no bytes received */
+
+ return err == 0 ? (int)ret : -1;
+}
+
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len)
+{
+ unsigned int ret;
+ int err;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+
+ err = g_io_channel_write(handle, (char *) data, len, &ret);
+ if (err == G_IO_ERROR_AGAIN ||
+ (err != 0 && (errno == EINTR || errno == EPIPE)))
+ return 0;
+
+ return err == 0 ? (int)ret : -1;
+}
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+ socklen_t addrlen;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(addr != NULL, -1);
+
+ addrlen = sizeof(so);
+ if (getsockname(g_io_channel_unix_get_fd(handle),
+ (struct sockaddr *) &so, &addrlen) == -1)
+ return -1;
+
+ sin_get_ip(&so, addr);
+ if (port) *port = sin_get_port(&so);
+
+ return 0;
+}
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+ If ip->family is 0, the address wasn't found.
+ Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6)
+{
+#ifdef HAVE_IPV6
+ union sockaddr_union *so;
+ struct addrinfo hints, *ai, *origai;
+ char hbuf[NI_MAXHOST];
+ int host_error, count;
+#else
+ struct hostent *hp;
+#endif
+
+ g_return_val_if_fail(addr != NULL, -1);
+
+ memset(ip4, 0, sizeof(IPADDR));
+ memset(ip6, 0, sizeof(IPADDR));
+
+#ifdef HAVE_IPV6
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* save error to host_error for later use */
+ host_error = getaddrinfo(addr, NULL, &hints, &ai);
+ if (host_error != 0)
+ return host_error;
+
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
+ return 1;
+
+ origai = ai; count = 0;
+ while (ai != NULL && count < 2) {
+ so = (union sockaddr_union *) ai->ai_addr;
+
+ if (ai->ai_family == AF_INET6 && ip6->family == 0) {
+ sin_get_ip(so, ip6);
+ count++;
+ } else if (ai->ai_family == AF_INET && ip4->family == 0) {
+ sin_get_ip(so, ip4);
+ count++;
+ }
+ ai = ai->ai_next;
+ }
+ freeaddrinfo(origai);
+#else
+ hp = gethostbyname(addr);
+ if (hp == NULL) return h_errno;
+
+ ip4->family = AF_INET;
+ memcpy(&ip4->ip, hp->h_addr, 4);
+#endif
+
+ return 0;
+}
+
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name)
+{
+#ifdef HAVE_IPV6
+ struct addrinfo req, *ai;
+ int host_error;
+#else
+ struct hostent *hp;
+#endif
+ char ipname[MAX_IP_LEN];
+
+ g_return_val_if_fail(ip != NULL, -1);
+ g_return_val_if_fail(name != NULL, -1);
+
+ net_ip2host(ip, ipname);
+
+ *name = NULL;
+#ifdef HAVE_IPV6
+ memset(&req, 0, sizeof(struct addrinfo));
+ req.ai_socktype = SOCK_STREAM;
+ req.ai_flags = AI_CANONNAME;
+
+ /* save error to host_error for later use */
+ host_error = getaddrinfo(ipname, NULL, &req, &ai);
+ if (host_error != 0)
+ return host_error;
+ *name = g_strdup(ai->ai_canonname);
+
+ freeaddrinfo(ai);
+#else
+ hp = gethostbyaddr(ipname, strlen(ipname), AF_INET);
+ if (hp == NULL) return -1;
+
+ *name = g_strdup(hp->h_name);
+#endif
+
+ return 0;
+}
+
+int net_ip2host(IPADDR *ip, char *host)
+{
+#ifdef HAVE_IPV6
+ if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN))
+ return -1;
+#else
+ unsigned long ip4;
+
+ ip4 = ntohl(ip->ip.s_addr);
+ g_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu",
+ (ip4 & 0xff000000UL) >> 24,
+ (ip4 & 0x00ff0000) >> 16,
+ (ip4 & 0x0000ff00) >> 8,
+ (ip4 & 0x000000ff));
+#endif
+ return 0;
+}
+
+int net_host2ip(const char *host, IPADDR *ip)
+{
+ unsigned long addr;
+
+#ifdef HAVE_IPV6
+ if (strchr(host, ':') != NULL) {
+ /* IPv6 */
+ ip->family = AF_INET6;
+ if (inet_pton(AF_INET6, host, &ip->ip) == 0)
+ return -1;
+ } else
+#endif
+ {
+ /* IPv4 */
+ ip->family = AF_INET;
+#ifdef HAVE_INET_ATON
+ if (inet_aton(host, &ip->ip.s_addr) == 0)
+ return -1;
+#else
+ addr = inet_addr(host);
+ if (addr == INADDR_NONE)
+ return -1;
+
+ memcpy(&ip->ip, &addr, 4);
+#endif
+ }
+
+ return 0;
+}
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle)
+{
+ int data;
+ socklen_t len = sizeof(data);
+
+ if (getsockopt(g_io_channel_unix_get_fd(handle),
+ SOL_SOCKET, SO_ERROR, (void *) &data, &len) == -1)
+ return -1;
+
+ return data;
+}
+
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error)
+{
+#ifdef HAVE_IPV6
+ g_return_val_if_fail(error != 0, NULL);
+
+ if (error == 1) {
+ /* getnameinfo() failed ..
+ FIXME: does strerror return the right error message? */
+ return g_strerror(errno);
+ }
+
+ return gai_strerror(error);
+#else
+ switch (error) {
+ case HOST_NOT_FOUND:
+ return "Host not found";
+ case NO_ADDRESS:
+ return "No IP address found for name";
+ case NO_RECOVERY:
+ return "A non-recovable name server error occurred";
+ case TRY_AGAIN:
+ return "A temporary error on an authoritative name server";
+ }
+
+ /* unknown error */
+ return NULL;
+#endif
+}
+
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+ some error with name server) */
+int net_hosterror_notfound(int error)
+{
+#ifdef HAVE_IPV6
+ return error != 1 && (error == EAI_NONAME || error == EAI_NODATA);
+#else
+ return error == HOST_NOT_FOUND || error == NO_ADDRESS;
+#endif
+}
+
+/* Get name of TCP service */
+char *net_getservbyport(int port)
+{
+ struct servent *entry;
+
+ entry = getservbyport(htons((unsigned short) port), "tcp");
+ return entry == NULL ? NULL : entry->s_name;
+}
+
+int is_ipv4_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != '.' && !isdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
+
+int is_ipv6_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != ':' && !isxdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
--- /dev/null
+#ifndef __NETWORK_H
+#define __NETWORK_H
+
+#ifdef HAVE_SOCKS_H
+#include <socks.h>
+#endif
+
+#include <sys/types.h>
+#ifndef WIN32
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# include <arpa/inet.h>
+#endif
+
+#ifndef AF_INET6
+# ifdef PF_INET6
+# define AF_INET6 PF_INET6
+# else
+# define AF_INET6 10
+# endif
+#endif
+
+struct _IPADDR {
+ unsigned short family;
+#ifdef HAVE_IPV6
+ struct in6_addr ip;
+#else
+ struct in_addr ip;
+#endif
+};
+
+/* maxmimum string length of IP address */
+#ifdef HAVE_IPV6
+# define MAX_IP_LEN INET6_ADDRSTRLEN
+#else
+# define MAX_IP_LEN 20
+#endif
+
+#define IPADDR_IS_V6(ip) ((ip)->family != AF_INET)
+
+/* returns 1 if IPADDRs are the same */
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2);
+
+/* Connect to socket */
+GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip);
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+/* Disconnect socket */
+void net_disconnect(GIOChannel *handle);
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(GIOChannel *handle);
+
+/* Listen for connections on a socket */
+GIOChannel *net_listen(IPADDR *my_ip, int *port);
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len);
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len);
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+ If ip->family is 0, the address wasn't found.
+ Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6);
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name);
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error);
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+ some error with name server) */
+int net_hosterror_notfound(int error);
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */
+int net_ip2host(IPADDR *ip, char *host);
+/* char* -> IPADDR translation. */
+int net_host2ip(const char *host, IPADDR *ip);
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle);
+
+/* Get name of TCP service */
+char *net_getservbyport(int port);
+
+int is_ipv4_address(const char *host);
+int is_ipv6_address(const char *host);
+
+#endif
--- /dev/null
+/* NICK_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("NICK", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+time_t last_check; /* last time gone was checked */
+
+char *nick;
+char *host;
+char *realname;
+int hops;
+
+/* status in server */
+unsigned int gone:1;
+unsigned int serverop:1;
+
+/* status in channel */
+unsigned int send_massjoin:1; /* Waiting to be sent in massjoin signal */
+unsigned int op:1;
+unsigned int halfop:1;
+unsigned int voice:1;
+
+GHashTable *module_data;
+
+void *unique_id; /* unique ID to use for comparing if one nick is in another channels,
+ or NULL = nicks are unique, just keep comparing them. */
+NICK_REC *next; /* support for multiple identically named nicks */
--- /dev/null
+/*
+ nicklist.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "masks.h"
+
+#define isalnumhigh(a) \
+ (isalnum(a) || (unsigned char) (a) >= 128)
+
+static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *list;
+
+ nick->next = NULL;
+
+ list = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (list == NULL)
+ g_hash_table_insert(channel->nicks, nick->nick, nick);
+ else {
+ /* multiple nicks with same name */
+ while (list->next != NULL)
+ list = list->next;
+ list->next = nick;
+ }
+
+ if (nick == channel->ownnick) {
+ /* move our own nick to beginning of the nick list.. */
+ nicklist_set_own(channel, nick);
+ }
+}
+
+static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *list;
+
+ list = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (list == NULL)
+ return;
+
+ if (list == nick || list->next == NULL) {
+ g_hash_table_remove(channel->nicks, nick->nick);
+ if (list->next != NULL) {
+ g_hash_table_insert(channel->nicks, nick->next->nick,
+ nick->next);
+ }
+ } else {
+ while (list->next != nick)
+ list = list->next;
+ list->next = nick->next;
+ }
+}
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ MODULE_DATA_INIT(nick);
+
+ nick->type = module_get_uniq_id("NICK", 0);
+ nick->chat_type = channel->chat_type;
+
+ nick_hash_add(channel, nick);
+ signal_emit("nicklist new", 2, channel, nick);
+}
+
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(host != NULL);
+
+ g_free_not_null(nick->host);
+ nick->host = g_strdup(host);
+
+ signal_emit("nicklist host changed", 2, channel, nick);
+}
+
+static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ signal_emit("nicklist remove", 2, channel, nick);
+
+ g_free(nick->nick);
+ g_free_not_null(nick->realname);
+ g_free_not_null(nick->host);
+ g_free(nick);
+}
+
+/* Remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+ g_return_if_fail(nick != NULL);
+
+ nick_hash_remove(channel, nick);
+ nicklist_destroy(channel, nick);
+}
+
+static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
+ const char *old_nick, const char *new_nick,
+ GSList *nicks)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nickrec;
+ GSList *tmp;
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ nickrec = tmp->next->data;
+
+ /* remove old nick from hash table */
+ nick_hash_remove(channel, nickrec);
+
+ if (new_nick_id != NULL)
+ nickrec->unique_id = new_nick_id;
+
+ g_free(nickrec->nick);
+ nickrec->nick = g_strdup(new_nick);
+
+ /* add new nick to hash table */
+ nick_hash_add(channel, nickrec);
+
+ signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
+ }
+ g_slist_free(nicks);
+}
+
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+ const char *new_nick)
+{
+ nicklist_rename_list(server, NULL, old_nick, new_nick,
+ nicklist_get_same(server, old_nick));
+}
+
+void nicklist_rename_unique(SERVER_REC *server,
+ void *old_nick_id, const char *old_nick,
+ void *new_nick_id, const char *new_nick)
+{
+ nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
+ nicklist_get_same_unique(server, old_nick_id));
+}
+
+static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel,
+ const char *mask)
+{
+ GSList *nicks, *tmp;
+ NICK_REC *nick;
+
+ nicks = nicklist_getnicks(channel);
+ nick = NULL;
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ nick = tmp->data;
+
+ if (mask_match_address(channel->server, mask,
+ nick->nick, nick->host))
+ break;
+ }
+ g_slist_free(nicks);
+ return tmp == NULL ? NULL : nick;
+}
+
+GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
+{
+ GSList *nicks, *tmp, *next;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = next) {
+ NICK_REC *nick = tmp->data;
+
+ next = tmp->next;
+ if (!mask_match_address(channel->server, mask,
+ nick->nick, nick->host))
+ nicks = g_slist_remove(nicks, tmp->data);
+ }
+
+ return nicks;
+}
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
+{
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ return g_hash_table_lookup(channel->nicks, nick);
+}
+
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+ void *id)
+{
+ NICK_REC *rec;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_hash_table_lookup(channel->nicks, nick);
+ while (rec != NULL && rec->unique_id != id)
+ rec = rec->next;
+
+ return rec;
+}
+
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
+{
+ NICK_REC *nickrec;
+ char *nick, *host;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ nick = g_strdup(mask);
+ host = strchr(nick, '!');
+ if (host != NULL) *host++ = '\0';
+
+ if (strchr(nick, '*') || strchr(nick, '?')) {
+ g_free(nick);
+ return nicklist_find_wildcards(channel, mask);
+ }
+
+ nickrec = g_hash_table_lookup(channel->nicks, nick);
+
+ if (host != NULL) {
+ while (nickrec != NULL) {
+ if (nickrec->host != NULL &&
+ match_wildcards(host, nickrec->host))
+ break; /* match */
+ nickrec = nickrec->next;
+ }
+ }
+ g_free(nick);
+ return nickrec;
+}
+
+static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
+{
+ while (rec != NULL) {
+ *list = g_slist_append(*list, rec);
+ rec = rec->next;
+ }
+}
+
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel)
+{
+ GSList *list;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+
+ list = NULL;
+ g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
+ return list;
+}
+
+typedef struct {
+ CHANNEL_REC *channel;
+ const char *nick;
+ GSList *list;
+} NICKLIST_GET_SAME_REC;
+
+static void get_nicks_same_hash(gpointer key, NICK_REC *nick,
+ NICKLIST_GET_SAME_REC *rec)
+{
+ while (nick != NULL) {
+ if (g_strcasecmp(nick->nick, rec->nick) == 0) {
+ rec->list = g_slist_append(rec->list, rec->channel);
+ rec->list = g_slist_append(rec->list, nick);
+ }
+
+ nick = nick->next;
+ }
+}
+
+GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
+{
+ NICKLIST_GET_SAME_REC rec;
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ rec.nick = nick;
+ rec.list = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ rec.channel = tmp->data;
+ g_hash_table_foreach(rec.channel->nicks,
+ (GHFunc) get_nicks_same_hash, &rec);
+ }
+ return rec.list;
+}
+
+typedef struct {
+ CHANNEL_REC *channel;
+ void *id;
+ GSList *list;
+} NICKLIST_GET_SAME_UNIQUE_REC;
+
+static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
+ NICKLIST_GET_SAME_UNIQUE_REC *rec)
+{
+ while (nick != NULL) {
+ if (nick->unique_id == rec->id) {
+ rec->list = g_slist_append(rec->list, rec->channel);
+ rec->list = g_slist_append(rec->list, nick);
+ break;
+ }
+
+ nick = nick->next;
+ }
+}
+
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
+{
+ NICKLIST_GET_SAME_UNIQUE_REC rec;
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+ g_return_val_if_fail(id != NULL, NULL);
+
+ rec.id = id;
+ rec.list = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ rec.channel = tmp->data;
+ g_hash_table_foreach(rec.channel->nicks,
+ (GHFunc) get_nicks_same_hash_unique,
+ &rec);
+ }
+ return rec.list;
+}
+
+/* nick record comparision for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2)
+{
+ if (p1 == NULL) return -1;
+ if (p2 == NULL) return 1;
+
+ if (p1->op && !p2->op) return -1;
+ if (!p1->op && p2->op) return 1;
+
+ if (!p1->op) {
+ if (p1->voice && !p2->voice) return -1;
+ if (!p1->voice && p2->voice) return 1;
+ }
+
+ return g_strcasecmp(p1->nick, p2->nick);
+}
+
+static void nicklist_update_flags_list(SERVER_REC *server, int gone,
+ int serverop, GSList *nicks)
+{
+ GSList *tmp;
+ CHANNEL_REC *channel;
+ NICK_REC *rec;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ rec = tmp->next->data;
+
+ rec->last_check = time(NULL);
+
+ if (gone != -1 && (int)rec->gone != gone) {
+ rec->gone = gone;
+ signal_emit("nicklist gone changed", 2, channel, rec);
+ }
+
+ if (serverop != -1 && (int)rec->serverop != serverop) {
+ rec->serverop = serverop;
+ signal_emit("nicklist serverop changed", 2, channel, rec);
+ }
+ }
+ g_slist_free(nicks);
+}
+
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+ int gone, int serverop)
+{
+ nicklist_update_flags_list(server, gone, serverop,
+ nicklist_get_same(server, nick));
+}
+
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+ int gone, int serverop)
+{
+ nicklist_update_flags_list(server, gone, serverop,
+ nicklist_get_same_unique(server, id));
+}
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *first, *next;
+
+ channel->ownnick = nick;
+
+ /* move our nick in the list to first, makes some things easier
+ (like handling multiple identical nicks in fe-messages.c) */
+ first = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (first->next == NULL)
+ return;
+
+ next = nick->next;
+ nick->next = first;
+
+ while (first->next != nick)
+ first = first->next;
+ first->next = next;
+
+ g_hash_table_insert(channel->nicks, nick->nick, nick);
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash,
+ (GCompareFunc) g_istr_equal);
+}
+
+static void nicklist_remove_hash(gpointer key, NICK_REC *nick,
+ CHANNEL_REC *channel)
+{
+ NICK_REC *next;
+
+ while (nick != NULL) {
+ next = nick->next;
+ nicklist_destroy(channel, nick);
+ nick = next;
+ }
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ g_hash_table_foreach(channel->nicks,
+ (GHFunc) nicklist_remove_hash, channel);
+ g_hash_table_destroy(channel->nicks);
+}
+
+static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
+{
+ NICK_REC *rec;
+ char *tmpnick;
+
+ tmpnick = g_strndup(nick, len);
+ rec = g_hash_table_lookup(channel->nicks, tmpnick);
+
+ if (rec != NULL) {
+ /* if there's multiple, get the one with identical case */
+ while (rec->next != NULL) {
+ if (strcmp(rec->nick, tmpnick) == 0)
+ break;
+ rec = rec->next;
+ }
+ }
+
+ g_free(tmpnick);
+ return rec;
+}
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
+{
+ const char *msgstart, *orignick;
+ int len, fullmatch;
+
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ if (channel != NULL && channel->server->nick_match_msg != NULL)
+ return channel->server->nick_match_msg(msg, nick);
+
+ /* first check for identical match */
+ len = strlen(nick);
+ if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len]))
+ return TRUE;
+
+ orignick = nick;
+ for (;;) {
+ nick = orignick;
+ msgstart = msg;
+ fullmatch = TRUE;
+
+ /* check if it matches for alphanumeric parts of nick */
+ while (*nick != '\0' && *msg != '\0') {
+ if (toupper(*nick) == toupper(*msg)) {
+ /* total match */
+ msg++;
+ } else if (isalnum(*msg) && !isalnum(*nick)) {
+ /* some strange char in your nick, pass it */
+ fullmatch = FALSE;
+ } else
+ break;
+
+ nick++;
+ }
+
+ if (msg != msgstart && !isalnumhigh(*msg)) {
+ /* at least some of the chars in line matched the
+ nick, and msg continue with non-alphanum character,
+ this might be for us.. */
+ if (*nick != '\0') {
+ /* remove the rest of the non-alphanum chars
+ from nick and check if it then matches. */
+ fullmatch = FALSE;
+ while (*nick != '\0' && !isalnum(*nick))
+ nick++;
+ }
+
+ if (*nick == '\0') {
+ /* yes, match! */
+ break;
+ }
+ }
+
+ /* no match. check if this is a message to multiple people
+ (like nick1,nick2: text) */
+ while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
+
+ if (*msg != ',') {
+ nick = orignick;
+ break;
+ }
+
+ msg++;
+ }
+
+ if (*nick != '\0')
+ return FALSE; /* didn't match */
+
+ if (fullmatch)
+ return TRUE; /* matched without fuzzyness */
+
+ /* matched with some fuzzyness .. check if there's an exact match
+ for some other nick in the same channel. */
+ return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
+}
+
+void nicklist_init(void)
+{
+ signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void nicklist_deinit(void)
+{
+ signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ module_uniq_destroy("NICK");
+}
--- /dev/null
+#ifndef __NICKLIST_H
+#define __NICKLIST_H
+
+/* Returns NICK_REC if it's nick, NULL if it isn't. */
+#define NICK(server) \
+ MODULE_CHECK_CAST(server, NICK_REC, type, "NICK")
+
+#define IS_NICK(server) \
+ (NICK(server) ? TRUE : FALSE)
+
+struct _NICK_REC {
+#include "nick-rec.h"
+};
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick);
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host);
+/* Remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick);
+/* Change nick */
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+ const char *new_nick);
+void nicklist_rename_unique(SERVER_REC *server,
+ void *old_nick_id, const char *old_nick,
+ void *new_nick_id, const char *new_nick);
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick);
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+ void *id);
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks that match the mask */
+GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel);
+/* Get all the nick records of `nick'. Returns channel, nick, channel, ... */
+GSList *nicklist_get_same(SERVER_REC *server, const char *nick);
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id);
+
+/* Update specified nick's status in server. */
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+ int gone, int ircop);
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+ int gone, int ircop);
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick);
+
+/* Nick record comparision for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2);
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick);
+
+void nicklist_init(void);
+void nicklist_deinit(void);
+
+#endif
--- /dev/null
+/*
+ nickmatch-cache.c : irssi
+
+ Copyright (C) 2001 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 "channels.h"
+#include "nicklist.h"
+
+#include "nickmatch-cache.h"
+
+static GSList *lists;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func)
+{
+ NICKMATCH_REC *rec;
+
+ rec = g_new0(NICKMATCH_REC, 1);
+ rec->func = func;
+
+ lists = g_slist_append(lists, rec);
+ return rec;
+}
+
+void nickmatch_deinit(NICKMATCH_REC *rec)
+{
+ lists = g_slist_remove(lists, rec);
+
+ g_hash_table_destroy(rec->nicks);
+ g_free(rec);
+}
+
+static void nickmatch_check_channel(CHANNEL_REC *channel, NICKMATCH_REC *rec)
+{
+ GSList *nicks, *tmp;
+
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *nick = tmp->data;
+
+ rec->func(rec->nicks, channel, nick);
+ }
+ g_slist_free(nicks);
+}
+
+void nickmatch_rebuild(NICKMATCH_REC *rec)
+{
+ if (rec->nicks != NULL)
+ g_hash_table_destroy(rec->nicks);
+
+ rec->nicks = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ g_slist_foreach(channels, (GFunc) nickmatch_check_channel, rec);
+}
+
+static void sig_nick_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ GSList *tmp;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+ NICKMATCH_REC *rec = tmp->data;
+
+ rec->func(rec->nicks, channel, nick);
+ }
+}
+
+static void sig_nick_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ GSList *tmp;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+ NICKMATCH_REC *rec = tmp->data;
+
+ g_hash_table_remove(rec->nicks, nick);
+ }
+}
+
+void nickmatch_cache_init(void)
+{
+ lists = NULL;
+ signal_add("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
+
+void nickmatch_cache_deinit(void)
+{
+ g_slist_foreach(lists, (GFunc) nickmatch_deinit, NULL);
+ g_slist_free(lists);
+
+ signal_remove("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
--- /dev/null
+#ifndef __NICKMATCH_CACHE_H
+#define __NICKMATCH_CACHE_H
+
+typedef void (*NICKMATCH_REBUILD_FUNC) (GHashTable *list,
+ CHANNEL_REC *channel, NICK_REC *nick);
+
+typedef struct {
+ GHashTable *nicks;
+ NICKMATCH_REBUILD_FUNC func;
+} NICKMATCH_REC;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func);
+void nickmatch_deinit(NICKMATCH_REC *rec);
+
+/* Calls rebuild function for all nicks in all channels.
+ This must be called soon after nickmatch_init(), before any nicklist
+ signals get sent. */
+void nickmatch_rebuild(NICKMATCH_REC *rec);
+
+#define nickmatch_find(rec, nick) \
+ g_hash_table_lookup((rec)->nicks, nick)
+
+void nickmatch_cache_init(void);
+void nickmatch_cache_deinit(void);
+
+#endif
--- /dev/null
+/*
+ pidwait.c :
+
+ Copyright (C) 1999-2000 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"
+
+#include <sys/wait.h>
+
+static GSList *pids;
+
+static unsigned int childcheck_tag;
+static int signal_pidwait;
+
+/* add a pid to wait list */
+void pidwait_add(int pid)
+{
+ pids = g_slist_append(pids, GINT_TO_POINTER(pid));
+}
+
+/* remove pid from wait list */
+void pidwait_remove(int pid)
+{
+ pids = g_slist_remove(pids, GINT_TO_POINTER(pid));
+}
+
+static int child_check(void)
+{
+ GSList *tmp, *next;
+ int status;
+
+ /* wait for each pid.. */
+ for (tmp = pids; tmp != NULL; tmp = next) {
+ int pid = GPOINTER_TO_INT(tmp->data);
+
+ next = tmp->next;
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ /* process terminated, remove from list */
+ signal_emit_id(signal_pidwait, 2, tmp->data,
+ GINT_TO_POINTER(status));
+ pids = g_slist_remove(pids, tmp->data);
+ }
+ }
+ return 1;
+}
+
+void pidwait_init(void)
+{
+ pids = NULL;
+ childcheck_tag = g_timeout_add(1000, (GSourceFunc) child_check, NULL);
+
+ signal_pidwait = signal_get_uniq_id("pidwait");
+}
+
+void pidwait_deinit(void)
+{
+ g_slist_free(pids);
+
+ g_source_remove(childcheck_tag);
+}
--- /dev/null
+#ifndef __PIDWAIT_H
+#define __PIDWAIT_H
+
+void pidwait_init(void);
+void pidwait_deinit(void);
+
+/* add a pid to wait list */
+void pidwait_add(int pid);
+/* remove pid from wait list */
+void pidwait_remove(int pid);
+
+#endif
--- /dev/null
+/*
+ queries.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+
+#include "servers.h"
+#include "queries.h"
+
+GSList *queries;
+
+void query_init(QUERY_REC *query, int automatic)
+{
+ g_return_if_fail(query != NULL);
+ g_return_if_fail(query->name != NULL);
+
+ queries = g_slist_append(queries, query);
+
+ MODULE_DATA_INIT(query);
+ query->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY");
+ if (query->server_tag != NULL) {
+ query->server = server_find_tag(query->server_tag);
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_append(query->server->queries, query);
+ }
+ }
+
+ signal_emit("query created", 2, query, GINT_TO_POINTER(automatic));
+}
+
+void query_destroy(QUERY_REC *query)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ if (query->destroying) return;
+ query->destroying = TRUE;
+
+ queries = g_slist_remove(queries, query);
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_remove(query->server->queries, query);
+ }
+ signal_emit("query destroyed", 1, query);
+
+ MODULE_DATA_DEINIT(query);
+ g_free_not_null(query->hilight_color);
+ g_free_not_null(query->server_tag);
+ g_free_not_null(query->address);
+ g_free(query->name);
+ g_free(query);
+}
+
+static QUERY_REC *query_find_server(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ if (server->query_find_func != NULL) {
+ /* use the server specific query find function */
+ return server->query_find_func(server, nick);
+ }
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+QUERY_REC *query_find(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ if (server != NULL)
+ return query_find_server(server, nick);
+
+ for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+void query_change_nick(QUERY_REC *query, const char *nick)
+{
+ char *oldnick;
+
+ g_return_if_fail(IS_QUERY(query));
+
+ oldnick = query->name;
+ query->name = g_strdup(nick);
+ signal_emit("query nick changed", 2, query, oldnick);
+ g_free(oldnick);
+}
+
+void query_change_address(QUERY_REC *query, const char *address)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ g_free_not_null(query->address);
+ query->address = g_strdup(address);
+ signal_emit("query address changed", 1, query);
+}
+
+void query_change_server(QUERY_REC *query, SERVER_REC *server)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_remove(query->server->queries, query);
+ }
+ if (server != NULL)
+ server->queries = g_slist_append(server->queries, query);
+
+ query->server = server;
+ signal_emit("query server changed", 1, query);
+}
+
+void queries_init(void)
+{
+}
+
+void queries_deinit(void)
+{
+ module_uniq_destroy("QUERY");
+}
--- /dev/null
+#ifndef __QUERIES_H
+#define __QUERIES_H
+
+#include "modules.h"
+
+/* Returns QUERY_REC if it's query, NULL if it isn't. */
+#define QUERY(query) \
+ MODULE_CHECK_CAST_MODULE(query, QUERY_REC, type, \
+ "WINDOW ITEM TYPE", "QUERY")
+
+#define IS_QUERY(query) \
+ (QUERY(query) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _QUERY_REC {
+#include "query-rec.h"
+};
+
+extern GSList *queries;
+
+void query_init(QUERY_REC *query, int automatic);
+void query_destroy(QUERY_REC *query);
+
+/* Find query by name, if `server' is NULL, search from all servers */
+QUERY_REC *query_find(SERVER_REC *server, const char *nick);
+
+void query_change_nick(QUERY_REC *query, const char *nick);
+void query_change_address(QUERY_REC *query, const char *address);
+void query_change_server(QUERY_REC *query, SERVER_REC *server);
+
+void queries_init(void);
+void queries_deinit(void);
+
+#endif
--- /dev/null
+/* QUERY_REC definition, used for inheritance */
+
+#include "window-item-rec.h"
+
+char *address;
+char *server_tag;
+unsigned int unwanted:1; /* TRUE if the other side closed or
+ some error occured (DCC chats!) */
+unsigned int destroying:1;
--- /dev/null
+/*
+ rawlog.c : irssi
+
+ Copyright (C) 1999-2000 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 "rawlog.h"
+#include "modules.h"
+#include "signals.h"
+#include "misc.h"
+#include "write-buffer.h"
+#include "settings.h"
+
+static int rawlog_lines;
+static int signal_rawlog;
+static int log_file_create_mode;
+
+RAWLOG_REC *rawlog_create(void)
+{
+ RAWLOG_REC *rec;
+
+ rec = g_new0(RAWLOG_REC, 1);
+ return rec;
+}
+
+void rawlog_destroy(RAWLOG_REC *rawlog)
+{
+ g_return_if_fail(rawlog != NULL);
+
+ g_slist_foreach(rawlog->lines, (GFunc) g_free, NULL);
+ g_slist_free(rawlog->lines);
+
+ if (rawlog->logging) {
+ write_buffer_flush();
+ close(rawlog->handle);
+ }
+ g_free(rawlog);
+}
+
+/* NOTE! str must be dynamically allocated and must not be freed after! */
+static void rawlog_add(RAWLOG_REC *rawlog, char *str)
+{
+ if (rawlog->nlines < rawlog_lines || rawlog_lines <= 2)
+ rawlog->nlines++;
+ else {
+ g_free(rawlog->lines->data);
+ rawlog->lines = g_slist_remove(rawlog->lines,
+ rawlog->lines->data);
+ }
+
+ if (rawlog->logging) {
+ write_buffer(rawlog->handle, str, strlen(str));
+ write_buffer(rawlog->handle, "\n", 1);
+ }
+
+ rawlog->lines = g_slist_append(rawlog->lines, str);
+ signal_emit_id(signal_rawlog, 2, rawlog, str);
+}
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf(">> %s", str));
+}
+
+void rawlog_output(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("<< %s", str));
+}
+
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("--> %s", str));
+}
+
+static void rawlog_dump(RAWLOG_REC *rawlog, int f)
+{
+ GSList *tmp;
+
+ for (tmp = rawlog->lines; tmp != NULL; tmp = tmp->next) {
+ write(f, tmp->data, strlen((char *) tmp->data));
+ write(f, "\n", 1);
+ }
+}
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path;
+
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(fname != NULL);
+
+ if (rawlog->logging)
+ return;
+
+ path = convert_home(fname);
+ rawlog->handle = open(path, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+ g_free(path);
+
+ rawlog_dump(rawlog, rawlog->handle);
+ rawlog->logging = rawlog->handle != -1;
+}
+
+void rawlog_close(RAWLOG_REC *rawlog)
+{
+ if (rawlog->logging) {
+ write_buffer_flush();
+ close(rawlog->handle);
+ rawlog->logging = 0;
+ }
+}
+
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path;
+ int f;
+
+ path = convert_home(fname);
+ f = open(path, O_WRONLY | O_APPEND | O_CREAT, log_file_create_mode);
+ g_free(path);
+
+ rawlog_dump(rawlog, f);
+ close(f);
+}
+
+void rawlog_set_size(int lines)
+{
+ rawlog_lines = lines;
+}
+
+static void read_settings(void)
+{
+ rawlog_set_size(settings_get_int("rawlog_lines"));
+ log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
+}
+
+void rawlog_init(void)
+{
+ signal_rawlog = signal_get_uniq_id("rawlog");
+
+ settings_add_int("history", "rawlog_lines", 200);
+ read_settings();
+
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void rawlog_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __RAWLOG_H
+#define __RAWLOG_H
+
+struct _RAWLOG_REC {
+ int logging;
+ int handle;
+
+ int nlines;
+ GSList *lines;
+};
+
+RAWLOG_REC *rawlog_create(void);
+void rawlog_destroy(RAWLOG_REC *rawlog);
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str);
+void rawlog_output(RAWLOG_REC *rawlog, const char *str);
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str);
+
+void rawlog_set_size(int lines);
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname);
+void rawlog_close(RAWLOG_REC *rawlog);
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname);
+
+void rawlog_init(void);
+void rawlog_deinit(void);
+
+#endif
--- /dev/null
+/* SERVER_CONNECT_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER CONNECT", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+/* if we're connecting via proxy, or just NULLs */
+char *proxy;
+int proxy_port;
+char *proxy_string, *proxy_password;
+
+unsigned short family; /* 0 = don't care, AF_INET or AF_INET6 */
+char *address;
+int port;
+char *chatnet;
+
+IPADDR *own_ip4, *own_ip6;
+
+char *password;
+char *nick;
+char *username;
+char *realname;
+
+/* when reconnecting, the old server status */
+unsigned int reconnection:1; /* we're trying to reconnect */
+char *channels;
+char *away_reason;
--- /dev/null
+/* SERVER_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+STRUCT_SERVER_CONNECT_REC *connrec;
+time_t connect_time; /* connection time */
+time_t real_connect_time; /* time when server replied that we really are connected */
+
+char *tag; /* tag name for addressing server */
+char *nick; /* current nick */
+
+unsigned int connected:1; /* connected to server */
+unsigned int connection_lost:1; /* Connection lost unintentionally */
+
+NET_SENDBUF_REC *handle;
+int readtag; /* input tag */
+
+/* for net_connect_nonblock() */
+GIOChannel *connect_pipe[2];
+int connect_tag;
+int connect_pid;
+
+/* For deciding if event should be handled internally */
+GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */
+GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */
+GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */
+
+RAWLOG_REC *rawlog;
+LINEBUF_REC *buffer; /* receive buffer */
+GHashTable *module_data;
+
+char *version; /* server version */
+char *away_reason;
+char *last_invite; /* channel where you were last invited */
+unsigned int server_operator:1;
+unsigned int usermode_away:1;
+unsigned int banned:1; /* not allowed to connect to this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+
+time_t lag_sent; /* 0 or time when last lag query was sent to server */
+time_t lag_last_check; /* last time we checked lag */
+int lag; /* server lag in milliseconds */
+
+GSList *channels;
+GSList *queries;
+
+/* -- support for multiple server types -- */
+
+/* -- must not be NULL: -- */
+/* join to a number of channels, channels are specified in `data' separated
+ with commas. there can exist other information after first space like
+ channel keys etc. */
+void (*channels_join)(SERVER_REC *server, const char *data, int automatic);
+/* returns true if `flag' indicates a nick flag (op/voice/halfop) */
+int (*isnickflag)(char flag);
+/* returns true if `data' indicates a channel */
+int (*ischannel)(const char *data);
+/* returns all nick flag characters in order op, voice, halfop. If some
+ of them aren't supported '\0' can be used. */
+const char *(*get_nick_flags)(void);
+/* send public or private message to server */
+void (*send_message)(SERVER_REC *server, const char *target, const char *msg);
+
+/* -- Default implementations are used if NULL -- */
+CHANNEL_REC *(*channel_find_func)(SERVER_REC *server, const char *name);
+QUERY_REC *(*query_find_func)(SERVER_REC *server, const char *nick);
+int (*mask_match_func)(const char *mask, const char *data);
+/* returns true if `msg' was meant for `nick' */
+int (*nick_match_msg)(const char *nick, const char *msg);
+
+#undef STRUCT_SERVER_CONNECT_REC
--- /dev/null
+int type; /* module_get_uniq_id("SERVER SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *chatnet;
+
+unsigned short family; /* 0 = default, AF_INET or AF_INET6 */
+char *address;
+int port;
+char *password;
+
+char *own_host; /* address to use when connecting this server */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
+
+time_t last_connect; /* to avoid reconnecting too fast.. */
+
+unsigned int autoconnect:1;
+unsigned int last_failed:1; /* if last connection attempt failed */
+unsigned int banned:1; /* if we're banned from this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+
+GHashTable *module_data;
--- /dev/null
+/*
+ servers-reconnect.c : irssi
+
+ Copyright (C) 1999-2000 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 "commands.h"
+#include "network.h"
+#include "signals.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+
+#include "settings.h"
+
+GSList *reconnects;
+static int last_reconnect_tag;
+static int reconnect_timeout_tag;
+static int reconnect_time;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+ g_free_not_null(conn->away_reason);
+ conn->away_reason = !server->usermode_away ? NULL :
+ g_strdup(server->away_reason);
+
+ signal_emit("server reconnect save status", 2, conn, server);
+}
+
+static void server_reconnect_add(SERVER_CONNECT_REC *conn,
+ time_t next_connect)
+{
+ RECONNECT_REC *rec;
+
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+ rec = g_new(RECONNECT_REC, 1);
+ rec->tag = ++last_reconnect_tag;
+ rec->conn = conn;
+ rec->next_connect = next_connect;
+
+ reconnects = g_slist_append(reconnects, rec);
+}
+
+void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn)
+{
+ g_return_if_fail(rec != NULL);
+
+ reconnects = g_slist_remove(reconnects, rec);
+
+ signal_emit("server reconnect remove", 1, rec);
+ if (free_conn) server_connect_free(rec->conn);
+ g_free(rec);
+
+ if (reconnects == NULL)
+ last_reconnect_tag = 0;
+}
+
+static int server_reconnect_timeout(void)
+{
+ SERVER_CONNECT_REC *conn;
+ GSList *list, *tmp;
+ time_t now;
+
+ /* If server_connect() removes the next reconnection in queue,
+ we're screwed. I don't think this should happen anymore, but just
+ to be sure we don't crash, do this safely. */
+ list = g_slist_copy(reconnects);
+ now = time(NULL);
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (g_slist_find(reconnects, rec) == NULL)
+ continue;
+
+ if (rec->next_connect <= now) {
+ conn = rec->conn;
+ server_reconnect_destroy(rec, FALSE);
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ }
+ }
+
+ g_slist_free(list);
+ return 1;
+}
+
+static void sserver_connect(SERVER_SETUP_REC *rec, SERVER_CONNECT_REC *conn)
+{
+ conn->family = rec->family;
+ conn->address = g_strdup(rec->address);
+ if (conn->port == 0) conn->port = rec->port;
+
+ server_setup_fill_reconn(conn, rec);
+ if (rec->last_connect > time(NULL)-reconnect_time) {
+ /* can't reconnect this fast, wait.. */
+ server_reconnect_add(conn, rec->last_connect+reconnect_time);
+ } else {
+ /* connect to server.. */
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ }
+}
+
+static SERVER_CONNECT_REC *
+server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
+{
+ SERVER_CONNECT_REC *dest;
+
+ dest = NULL;
+ signal_emit("server connect copy", 2, &dest, src);
+ g_return_val_if_fail(dest != NULL, NULL);
+
+ dest->type = module_get_uniq_id("SERVER CONNECT", 0);
+ dest->reconnection = src->reconnection;
+ dest->proxy = g_strdup(src->proxy);
+ dest->proxy_port = src->proxy_port;
+ dest->proxy_string = g_strdup(src->proxy_string);
+ dest->proxy_password = g_strdup(src->proxy_password);
+
+ if (connect_info) {
+ dest->family = src->family;
+ dest->address = g_strdup(src->address);
+ dest->port = src->port;
+ dest->password = g_strdup(src->password);
+ }
+
+ dest->chatnet = g_strdup(src->chatnet);
+ dest->nick = g_strdup(src->nick);
+ dest->username = g_strdup(src->username);
+ dest->realname = g_strdup(src->realname);
+
+ if (src->own_ip4 != NULL) {
+ dest->own_ip4 = g_new(IPADDR, 1);
+ memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR));
+ }
+ if (src->own_ip6 != NULL) {
+ dest->own_ip6 = g_new(IPADDR, 1);
+ memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR));
+ }
+
+ dest->channels = g_strdup(src->channels);
+ dest->away_reason = g_strdup(src->away_reason);
+
+ return dest;
+}
+
+#define server_should_reconnect(server) \
+ ((server)->connection_lost && ((server)->connrec->chatnet != NULL || \
+ (!(server)->banned && !(server)->dns_error)))
+
+#define sserver_connect_ok(rec, net) \
+ (!(rec)->banned && !(rec)->dns_error && (rec)->chatnet != NULL && \
+ g_strcasecmp((rec)->chatnet, (net)) == 0)
+
+static void sig_reconnect(SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ SERVER_SETUP_REC *sserver;
+ GSList *tmp;
+ int use_next, through;
+ time_t now;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ if (reconnect_time == -1 || !server_should_reconnect(server))
+ return;
+
+ conn = server_connect_copy_skeleton(server->connrec, FALSE);
+ g_return_if_fail(conn != NULL);
+
+ /* save the server status */
+ if (server->connected) {
+ conn->reconnection = TRUE;
+
+ reconnect_save_status(conn, server);
+ }
+
+ sserver = server_setup_find(server->connrec->address,
+ server->connrec->port);
+
+ if (sserver != NULL) {
+ /* save the last connection time/status */
+ sserver->last_connect = server->connect_time == 0 ?
+ time(NULL) : server->connect_time;
+ sserver->last_failed = !server->connected;
+ if (server->banned) sserver->banned = TRUE;
+ if (server->dns_error) sserver->dns_error = TRUE;
+ }
+
+ if (sserver == NULL || conn->chatnet == NULL) {
+ /* not in any chatnet, just reconnect back to same server */
+ conn->family = server->connrec->family;
+ conn->address = g_strdup(server->connrec->address);
+ conn->port = server->connrec->port;
+ conn->password = g_strdup(server->connrec->password);
+
+ if (server->connect_time != 0 &&
+ time(NULL)-server->connect_time > reconnect_time) {
+ /* there's been enough time since last connection,
+ reconnect back immediately */
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ } else {
+ /* reconnect later.. */
+ server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) :
+ server->connect_time) + reconnect_time);
+ }
+ return;
+ }
+
+ /* always try to first connect to the first on the list where we
+ haven't got unsuccessful connection attempts for the last half
+ an hour. */
+
+ now = time(NULL);
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (sserver_connect_ok(rec, conn->chatnet) &&
+ (!rec->last_connect || !rec->last_failed ||
+ rec->last_connect < now-FAILED_RECONNECT_WAIT)) {
+ if (rec == sserver)
+ conn->port = server->connrec->port;
+ sserver_connect(rec, conn);
+ return;
+ }
+ }
+
+ /* just try the next server in list */
+ use_next = through = FALSE;
+ for (tmp = setupservers; tmp != NULL; ) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (!use_next && server->connrec->port == rec->port &&
+ g_strcasecmp(rec->address, server->connrec->address) == 0)
+ use_next = TRUE;
+ else if (use_next && sserver_connect_ok(rec, conn->chatnet)) {
+ if (rec == sserver)
+ conn->port = server->connrec->port;
+ sserver_connect(rec, conn);
+ break;
+ }
+
+ if (tmp->next != NULL) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (through) {
+ /* shouldn't happen unless there's no servers in
+ this chatnet in setup.. */
+ server_connect_free(conn);
+ break;
+ }
+
+ tmp = setupservers;
+ use_next = through = TRUE;
+ }
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+ g_return_if_fail(IS_SERVER(server));
+ if (!server->connrec->reconnection)
+ return;
+
+ if (server->connrec->channels != NULL)
+ server->channels_join(server, server->connrec->channels, TRUE);
+}
+
+/* Remove all servers from reconnect list */
+/* SYNTAX: RMRECONNS */
+static void cmd_rmreconns(void)
+{
+ while (reconnects != NULL)
+ server_reconnect_destroy(reconnects->data, TRUE);
+}
+
+static RECONNECT_REC *reconnect_find_tag(int tag)
+{
+ GSList *tmp;
+
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (rec->tag == tag)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void reconnect_all(void)
+{
+ GSList *list;
+ SERVER_CONNECT_REC *conn;
+ RECONNECT_REC *rec;
+
+ /* first move reconnects to another list so if server_connect()
+ fails and goes to reconnection list again, we won't get stuck
+ here forever */
+ list = NULL;
+ while (reconnects != NULL) {
+ rec = reconnects->data;
+
+ list = g_slist_append(list, rec->conn);
+ server_reconnect_destroy(rec, FALSE);
+ }
+
+
+ while (list != NULL) {
+ conn = list->data;
+
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ list = g_slist_remove(list, conn);
+ }
+}
+
+/* SYNTAX: RECONNECT <tag> */
+static void cmd_reconnect(const char *data, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ RECONNECT_REC *rec;
+ int tag;
+
+ if (*data == '\0' && server != NULL) {
+ /* reconnect back to same server */
+ conn = server_connect_copy_skeleton(server->connrec, TRUE);
+
+ if (server->connected) {
+ reconnect_save_status(conn, server);
+ signal_emit("command disconnect", 2,
+ "* Reconnecting", server);
+ }
+
+ conn->reconnection = TRUE;
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+ return;
+ }
+
+ if (g_strcasecmp(data, "all") == 0) {
+ /* reconnect all servers in reconnect queue */
+ reconnect_all();
+ return;
+ }
+
+ if (*data == '\0') {
+ /* reconnect to first server in reconnection list */
+ if (reconnects == NULL)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+ rec = reconnects->data;
+ } else {
+ if (g_strncasecmp(data, "RECON-", 6) == 0)
+ data += 6;
+
+ tag = atoi(data);
+ rec = tag <= 0 ? NULL : reconnect_find_tag(tag);
+
+ if (rec == NULL) {
+ signal_emit("server reconnect not found", 1, data);
+ return;
+ }
+ }
+
+ conn = rec->conn;
+ server_reconnect_destroy(rec, FALSE);
+ CHAT_PROTOCOL(conn)->server_connect(conn);
+}
+
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+ RECONNECT_REC *rec;
+ int tag;
+
+ if (g_strncasecmp(data, "RECON-", 6) != 0)
+ return; /* handle only reconnection removing */
+
+ rec = sscanf(data+6, "%d", &tag) == 1 && tag > 0 ?
+ reconnect_find_tag(tag) : NULL;
+
+ if (rec == NULL)
+ signal_emit("server reconnect not found", 1, data);
+ else
+ server_reconnect_destroy(rec, TRUE);
+ signal_stop();
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+ GSList *tmp, *next;
+
+ for (tmp = reconnects; tmp != NULL; tmp = next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->conn->chat_type == proto->id)
+ server_reconnect_destroy(rec, TRUE);
+ }
+}
+
+static void read_settings(void)
+{
+ reconnect_time = settings_get_int("server_reconnect_time");
+}
+
+void servers_reconnect_init(void)
+{
+ settings_add_int("server", "server_reconnect_time", 300);
+
+ reconnects = NULL;
+ last_reconnect_tag = 0;
+
+ reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL);
+ read_settings();
+
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns);
+ command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect);
+ command_bind_first("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+}
+
+void servers_reconnect_deinit(void)
+{
+ g_source_remove(reconnect_timeout_tag);
+
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns);
+ command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+}
--- /dev/null
+#ifndef __SERVER_RECONNECT_H
+#define __SERVER_RECONNECT_H
+
+/* wait for half an hour before trying to reconnect to host where last
+ connection failed */
+#define FAILED_RECONNECT_WAIT (60*30)
+
+typedef struct {
+ int tag;
+ time_t next_connect;
+
+ SERVER_CONNECT_REC *conn;
+} RECONNECT_REC;
+
+extern GSList *reconnects;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server);
+void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn);
+
+void servers_reconnect_init(void);
+void servers_reconnect_deinit(void);
+
+#endif
--- /dev/null
+/*
+ server-redirect.c : irssi
+
+ Copyright (C) 1999-2000 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 "misc.h"
+
+#include "servers.h"
+#include "servers-redirect.h"
+
+static int redirect_group;
+
+static void server_eventtable_destroy(char *key, GSList *value)
+{
+ GSList *tmp;
+
+ g_free(key);
+
+ for (tmp = value; tmp != NULL; tmp = tmp->next) {
+ REDIRECT_REC *rec = tmp->data;
+
+ g_free_not_null(rec->arg);
+ g_free(rec->name);
+ g_free(rec);
+ }
+ g_slist_free(value);
+}
+
+static void server_eventgrouptable_destroy(gpointer key, GSList *value)
+{
+ g_slist_foreach(value, (GFunc) g_free, NULL);
+ g_slist_free(value);
+}
+
+static void server_cmdtable_destroy(char *key, REDIRECT_CMD_REC *value)
+{
+ g_free(key);
+
+ g_slist_foreach(value->events, (GFunc) g_free, NULL);
+ g_slist_free(value->events);
+ g_free(value);
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(IS_SERVER(server));
+
+ if (server->eventtable != NULL) {
+ g_hash_table_foreach(server->eventtable,
+ (GHFunc) server_eventtable_destroy, NULL);
+ g_hash_table_destroy(server->eventtable);
+ }
+
+ g_hash_table_foreach(server->eventgrouptable,
+ (GHFunc) server_eventgrouptable_destroy, NULL);
+ g_hash_table_destroy(server->eventgrouptable);
+
+ if (server->cmdtable != NULL) {
+ g_hash_table_foreach(server->cmdtable,
+ (GHFunc) server_cmdtable_destroy, NULL);
+ g_hash_table_destroy(server->cmdtable);
+ }
+}
+
+void server_redirect_initv(SERVER_REC *server, const char *command,
+ int last, GSList *list)
+{
+ REDIRECT_CMD_REC *rec;
+
+ g_return_if_fail(IS_SERVER(server));
+ g_return_if_fail(command != NULL);
+ g_return_if_fail(last > 0);
+
+ if (g_hash_table_lookup(server->cmdtable, command) != NULL) {
+ /* already in hash table. list of events SHOULD be the same. */
+ g_slist_foreach(list, (GFunc) g_free, NULL);
+ g_slist_free(list);
+ return;
+ }
+
+ rec = g_new(REDIRECT_CMD_REC, 1);
+ rec->last = last;
+ rec->events = list;
+ g_hash_table_insert(server->cmdtable, g_strdup(command), rec);
+}
+
+void server_redirect_init(SERVER_REC *server, const char *command,
+ int last, ...)
+{
+ va_list args;
+ GSList *list;
+ char *event;
+
+ va_start(args, last);
+ list = NULL;
+ while ((event = va_arg(args, gchar *)) != NULL)
+ list = g_slist_append(list, g_strdup(event));
+ va_end(args);
+
+ server_redirect_initv(server, command, last, list);
+}
+
+int server_redirect_single_event(SERVER_REC *server, const char *arg,
+ int last, int group, const char *event,
+ const char *signal, int argpos)
+{
+ REDIRECT_REC *rec;
+ GSList *list, *grouplist;
+ char *origkey;
+
+ g_return_val_if_fail(IS_SERVER(server), 0);
+ g_return_val_if_fail(event != NULL, 0);
+ g_return_val_if_fail(signal != NULL, 0);
+ g_return_val_if_fail(arg != NULL || argpos == -1, 0);
+
+ if (group == 0) group = ++redirect_group;
+
+ rec = g_new0(REDIRECT_REC, 1);
+ rec->arg = arg == NULL ? NULL : g_strdup(arg);
+ rec->argpos = argpos;
+ rec->name = g_strdup(signal);
+ rec->group = group;
+ rec->last = last;
+
+ if (g_hash_table_lookup_extended(server->eventtable, event,
+ (gpointer *) &origkey,
+ (gpointer *) &list)) {
+ g_hash_table_remove(server->eventtable, origkey);
+ } else {
+ list = NULL;
+ origkey = g_strdup(event);
+ }
+
+ grouplist = g_hash_table_lookup(server->eventgrouptable,
+ GINT_TO_POINTER(group));
+ if (grouplist != NULL) {
+ g_hash_table_remove(server->eventgrouptable,
+ GINT_TO_POINTER(group));
+ }
+
+ list = g_slist_append(list, rec);
+ grouplist = g_slist_append(grouplist, g_strdup(event));
+
+ g_hash_table_insert(server->eventtable, origkey, list);
+ g_hash_table_insert(server->eventgrouptable,
+ GINT_TO_POINTER(group), grouplist);
+
+ return group;
+}
+
+void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...)
+{
+ va_list args;
+ char *event, *signal;
+ int argpos, group;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ va_start(args, last);
+
+ group = 0;
+ while ((event = va_arg(args, gchar *)) != NULL) {
+ signal = va_arg(args, gchar *);
+ argpos = va_arg(args, gint);
+
+ group = server_redirect_single_event(server, arg, last > 0,
+ group, event, signal,
+ argpos);
+ last--;
+ }
+
+ va_end(args);
+}
+
+void server_redirect_default(SERVER_REC *server, const char *command)
+{
+ REDIRECT_CMD_REC *cmdrec;
+ REDIRECT_REC *rec;
+ GSList *events, *list, *grouplist;
+ char *event, *origkey;
+ int last;
+
+ g_return_if_fail(IS_SERVER(server));
+ g_return_if_fail(command != NULL);
+
+ if (server->cmdtable == NULL)
+ return; /* not connected yet */
+
+ cmdrec = g_hash_table_lookup(server->cmdtable, command);
+ if (cmdrec == NULL) return;
+
+ /* add all events used by command to eventtable and eventgrouptable */
+ redirect_group++; grouplist = NULL; last = cmdrec->last;
+ for (events = cmdrec->events; events != NULL; events = events->next) {
+ event = events->data;
+
+ if (g_hash_table_lookup_extended(server->eventtable, event,
+ (gpointer *) &origkey,
+ (gpointer *) &list)) {
+ g_hash_table_remove(server->eventtable, origkey);
+ } else {
+ list = NULL;
+ origkey = g_strdup(event);
+ }
+
+ rec = g_new0(REDIRECT_REC, 1);
+ rec->argpos = -1;
+ rec->name = g_strdup(event);
+ rec->group = redirect_group;
+ rec->last = last > 0;
+
+ grouplist = g_slist_append(grouplist, g_strdup(event));
+ list = g_slist_append(list, rec);
+ g_hash_table_insert(server->eventtable, origkey, list);
+
+ last--;
+ }
+
+ g_hash_table_insert(server->eventgrouptable,
+ GINT_TO_POINTER(redirect_group), grouplist);
+}
+
+void server_redirect_remove_next(SERVER_REC *server, const char *event,
+ GSList *item)
+{
+ REDIRECT_REC *rec;
+ GSList *grouplist, *list, *events, *tmp;
+ char *origkey;
+ int group;
+
+ g_return_if_fail(IS_SERVER(server));
+ g_return_if_fail(event != NULL);
+
+ if (!g_hash_table_lookup_extended(server->eventtable, event,
+ (gpointer *) &origkey,
+ (gpointer *) &list))
+ return;
+
+ rec = item == NULL ? list->data : item->data;
+ if (!rec->last) {
+ /* this wasn't last expected event */
+ return;
+ }
+ group = rec->group;
+
+ /* get list of events from this group */
+ grouplist = g_hash_table_lookup(server->eventgrouptable,
+ GINT_TO_POINTER(group));
+
+ /* remove all of them */
+ for (list = grouplist; list != NULL; list = list->next) {
+ char *event = list->data;
+
+ if (!g_hash_table_lookup_extended(server->eventtable, event,
+ (gpointer *) &origkey,
+ (gpointer *) &events)) {
+ g_warning("server_redirect_remove_next() : "
+ "event in eventgrouptable but not in "
+ "eventtable");
+ continue;
+ }
+
+ /* remove the right group */
+ for (tmp = events; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (rec->group == group)
+ break;
+ }
+
+ if (rec == NULL) {
+ g_warning("server_redirect_remove_next() : "
+ "event in eventgrouptable but not in "
+ "eventtable (group)");
+ continue;
+ }
+
+ g_free(event);
+
+ events = g_slist_remove(events, rec);
+ g_free_not_null(rec->arg);
+ g_free(rec->name);
+ g_free(rec);
+
+ /* update hash table */
+ g_hash_table_remove(server->eventtable, origkey);
+ if (events == NULL)
+ g_free(origkey);
+ else {
+ g_hash_table_insert(server->eventtable,
+ origkey, events);
+ }
+ }
+
+ g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group));
+ g_slist_free(grouplist);
+}
+
+GSList *server_redirect_getqueue(SERVER_REC *server, const char *event,
+ const char *args)
+{
+ REDIRECT_REC *rec;
+ GSList *list;
+ char **arglist;
+ int found;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+ g_return_val_if_fail(event != NULL, NULL);
+
+ list = g_hash_table_lookup(server->eventtable, event);
+
+ for (; list != NULL; list = list->next) {
+ rec = list->data;
+ if (rec->argpos == -1)
+ break;
+
+ if (rec->arg == NULL || args == NULL)
+ continue;
+
+ /* we need to check that the argument is right.. */
+ arglist = g_strsplit(args, " ", -1);
+ found = (strarray_length(arglist) > rec->argpos &&
+ find_substr(rec->arg, arglist[rec->argpos]));
+ g_strfreev(arglist);
+
+ if (found) break;
+ }
+
+ return list;
+}
+
+void servers_redirect_init(void)
+{
+ redirect_group = 0;
+
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
+
+void servers_redirect_deinit(void)
+{
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
--- /dev/null
+#ifndef __SERVERS_REDIRECT_H
+#define __SERVERS_REDIRECT_H
+
+typedef struct {
+ int last; /* number of "last" events at the start of the events list */
+ GSList *events; /* char* list of events */
+} REDIRECT_CMD_REC;
+
+typedef struct {
+ char *name; /* event name */
+
+ char *arg; /* argument for event we are expecting or NULL */
+ int argpos; /* argument position */
+
+ int group; /* group of events this belongs to */
+ int last; /* if this event is received, remove all the events in this group */
+}
+REDIRECT_REC;
+
+void server_redirect_init(SERVER_REC *server, const char *command, int last, ...);
+void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list);
+/* ... = char *event1, char *event2, ..., NULL */
+
+void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...);
+/* ... = char *event, char *callback_signal, int argpos, ..., NULL */
+
+int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group,
+ const char *event, const char *signal, int argpos);
+void server_redirect_default(SERVER_REC *server, const char *command);
+void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item);
+GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args);
+
+void servers_redirect_init(void);
+void servers_redirect_deinit(void);
+
+#endif
--- /dev/null
+/*
+ servers-setup.c : irssi
+
+ Copyright (C) 1999-2001 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 "network.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+
+GSList *setupservers;
+
+char *old_source_host;
+int source_host_ok; /* Use source_host_ip .. */
+IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+
+static void save_ips(IPADDR *ip4, IPADDR *ip6,
+ IPADDR **save_ip4, IPADDR **save_ip6)
+{
+ if (ip4->family == 0)
+ g_free_and_null(*save_ip4);
+ else {
+ if (*save_ip4 == NULL)
+ *save_ip4 = g_new(IPADDR, 1);
+ memcpy(*save_ip4, ip4, sizeof(IPADDR));
+ }
+
+ if (ip6->family == 0)
+ g_free_and_null(*save_ip6);
+ else {
+ if (*save_ip6 == NULL)
+ *save_ip6 = g_new(IPADDR, 1);
+ memcpy(*save_ip6, ip6, sizeof(IPADDR));
+ }
+}
+
+static void get_source_host_ip(void)
+{
+ const char *hostname;
+ IPADDR ip4, ip6;
+
+ if (source_host_ok)
+ return;
+
+ /* FIXME: This will block! */
+ hostname = settings_get_str("hostname");
+ source_host_ok = *hostname != '\0' &&
+ net_gethostbyname(hostname, &ip4, &ip6) == 0;
+
+ if (source_host_ok)
+ save_ips(&ip4, &ip6, &source_host_ip4, &source_host_ip6);
+ else {
+ g_free_and_null(source_host_ip4);
+ g_free_and_null(source_host_ip6);
+ }
+}
+
+static void conn_set_ip(SERVER_CONNECT_REC *conn, const char *own_host,
+ IPADDR **own_ip4, IPADDR **own_ip6)
+{
+ IPADDR ip4, ip6;
+
+ if (*own_ip4 == NULL && *own_ip6 == NULL) {
+ /* resolve the IP */
+ if (net_gethostbyname(own_host, &ip4, &ip6) == 0)
+ save_ips(&ip4, &ip6, own_ip4, own_ip6);
+ }
+
+ server_connect_own_ip_save(conn, *own_ip4, *own_ip6);
+}
+
+/* Fill information to connection from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+ if (sserver->own_host != NULL) {
+ conn_set_ip(conn, sserver->own_host,
+ &sserver->own_ip4, &sserver->own_ip6);
+ }
+
+ if (sserver->chatnet != NULL && conn->chatnet == NULL)
+ conn->chatnet = g_strdup(sserver->chatnet);
+
+ if (sserver->password != NULL && conn->password == NULL)
+ conn->password = g_strdup(sserver->password);
+
+ signal_emit("server setup fill reconn", 2, conn, sserver);
+}
+
+static void server_setup_fill(SERVER_CONNECT_REC *conn,
+ const char *address, int port)
+{
+ g_return_if_fail(conn != NULL);
+ g_return_if_fail(address != NULL);
+
+ conn->type = module_get_uniq_id("SERVER CONNECT", 0);
+
+ conn->address = g_strdup(address);
+ if (port > 0) conn->port = port;
+
+ if (!conn->nick) conn->nick = g_strdup(settings_get_str("nick"));
+ conn->username = g_strdup(settings_get_str("user_name"));
+ conn->realname = g_strdup(settings_get_str("real_name"));
+
+ /* proxy settings */
+ if (settings_get_bool("use_proxy")) {
+ conn->proxy = g_strdup(settings_get_str("proxy_address"));
+ conn->proxy_port = settings_get_int("proxy_port");
+ conn->proxy_string = g_strdup(settings_get_str("proxy_string"));
+ conn->proxy_password = g_strdup(settings_get_str("proxy_password"));
+ }
+
+ /* source IP */
+ if (source_host_ip4 != NULL) {
+ conn->own_ip4 = g_new(IPADDR, 1);
+ memcpy(conn->own_ip4, source_host_ip4, sizeof(IPADDR));
+ }
+ if (source_host_ip6 != NULL) {
+ conn->own_ip6 = g_new(IPADDR, 1);
+ memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR));
+ }
+}
+
+static void server_setup_fill_server(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+ sserver->last_connect = time(NULL);
+
+ if (sserver->family != 0 && conn->family == 0)
+ conn->family = sserver->family;
+ if (sserver->port > 0 && conn->port <= 0)
+ conn->port = sserver->port;
+ server_setup_fill_reconn(conn, sserver);
+
+ signal_emit("server setup fill server", 2, conn, sserver);
+}
+
+static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn,
+ CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ if (chatnet->nick) {
+ g_free(conn->nick);
+ conn->nick = g_strdup(chatnet->nick);;
+ }
+ if (chatnet->username) {
+ g_free(conn->username);
+ conn->username = g_strdup(chatnet->username);;
+ }
+ if (chatnet->realname) {
+ g_free(conn->realname);
+ conn->realname = g_strdup(chatnet->realname);;
+ }
+ if (chatnet->own_host != NULL) {
+ conn_set_ip(conn, chatnet->own_host,
+ &chatnet->own_ip4, &chatnet->own_ip6);
+ }
+
+ signal_emit("server setup fill chatnet", 2, conn, chatnet);
+}
+
+static SERVER_CONNECT_REC *
+create_addr_conn(int chat_type, const char *address, int port,
+ const char *chatnet, const char *password,
+ const char *nick)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_CONNECT_REC *conn;
+ SERVER_SETUP_REC *sserver;
+ CHATNET_REC *chatnetrec;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ sserver = server_setup_find(address, port);
+ if (sserver != NULL) {
+ if (chat_type < 0)
+ chat_type = sserver->chat_type;
+ else if (chat_type != sserver->chat_type)
+ sserver = NULL;
+ }
+
+ proto = chat_type >= 0 ? chat_protocol_find_id(chat_type) :
+ chat_protocol_get_default();
+
+ conn = proto->create_server_connect();
+ conn->chat_type = proto->id;
+ if (chatnet != NULL && *chatnet != '\0')
+ conn->chatnet = g_strdup(chatnet);
+
+ /* fill in the defaults */
+ server_setup_fill(conn, address, port);
+
+ /* fill the rest from chat network settings */
+ chatnetrec = chatnet != NULL ? chatnet_find(chatnet) :
+ (sserver == NULL || sserver->chatnet == NULL ? NULL :
+ chatnet_find(sserver->chatnet));
+ if (chatnetrec != NULL)
+ server_setup_fill_chatnet(conn, chatnetrec);
+
+ /* fill the information from setup */
+ if (sserver != NULL)
+ server_setup_fill_server(conn, sserver);
+
+ /* nick / password given in command line overrides all settings */
+ if (password && *password) {
+ g_free_not_null(conn->password);
+ conn->password = g_strdup(password);
+ }
+ if (nick && *nick) {
+ g_free_not_null(conn->nick);
+ conn->nick = g_strdup(nick);
+ }
+
+ signal_emit("server setup fill connect", 1, conn);
+ return conn;
+}
+
+/* Connect to server where last connect succeeded (or we haven't tried to
+ connect yet). If there's no such server, connect to server where we
+ haven't connected for the longest time */
+static SERVER_CONNECT_REC *
+create_chatnet_conn(const char *dest, int port,
+ const char *password, const char *nick)
+{
+ SERVER_SETUP_REC *bestrec;
+ GSList *tmp;
+ time_t now, besttime;
+
+ now = time(NULL);
+ bestrec = NULL; besttime = now;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (rec->chatnet == NULL ||
+ g_strcasecmp(rec->chatnet, dest) != 0)
+ continue;
+
+ if (!rec->last_failed) {
+ bestrec = rec;
+ break;
+ }
+
+ if (bestrec == NULL || besttime > rec->last_connect) {
+ bestrec = rec;
+ besttime = rec->last_connect;
+ }
+ }
+
+ return bestrec == NULL ? NULL :
+ create_addr_conn(bestrec->chat_type, bestrec->address, 0,
+ dest, NULL, nick);
+}
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *
+server_create_conn(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password,
+ const char *nick)
+{
+ SERVER_CONNECT_REC *rec;
+
+ g_return_val_if_fail(dest != NULL, NULL);
+
+ if (chatnet_find(dest) != NULL) {
+ rec = create_chatnet_conn(dest, port, password, nick);
+ if (rec != NULL)
+ return rec;
+ }
+
+ return create_addr_conn(chat_type, dest, port,
+ chatnet, password, nick);
+}
+
+/* Find matching server from setup. Try to find record with a same port,
+ but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port)
+{
+ SERVER_SETUP_REC *server;
+ GSList *tmp;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ server = NULL;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->address, address) == 0) {
+ server = rec;
+ if (rec->port == port)
+ break;
+ }
+ }
+
+ return server;
+}
+
+/* Find matching server from setup. Ports must match or NULL is returned. */
+SERVER_SETUP_REC *server_setup_find_port(const char *address, int port)
+{
+ SERVER_SETUP_REC *rec;
+
+ rec = server_setup_find(address, port);
+ return rec == NULL || rec->port != port ? NULL : rec;
+}
+
+static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node)
+{
+ SERVER_SETUP_REC *rec;
+ CHATNET_REC *chatnetrec;
+ char *server, *chatnet, *family;
+ int port;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ server = config_node_get_str(node, "address", NULL);
+ if (server == NULL)
+ return NULL;
+
+ port = config_node_get_int(node, "port", 0);
+ if (server_setup_find_port(server, port) != NULL) {
+ /* already exists - don't let it get there twice or
+ server reconnects will screw up! */
+ return NULL;
+ }
+
+ rec = NULL;
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+ if (chatnet == NULL) /* FIXME: remove this after .98... */ {
+ chatnet = config_node_get_str(node, "ircnet", NULL);
+ if (chatnet != NULL) {
+ iconfig_node_set_str(node, "chatnet", chatnet);
+ iconfig_node_set_str(node, "ircnet", NULL);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+ }
+ }
+
+ chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+ if (chatnetrec == NULL && chatnet != NULL) {
+ /* chat network not found, create it. */
+ chatnetrec = chat_protocol_get_default()->create_chatnet();
+ chatnetrec->chat_type = chat_protocol_get_default()->id;
+ chatnetrec->name = g_strdup(chatnet);
+ chatnet_create(chatnetrec);
+ }
+
+ family = config_node_get_str(node, "family", "");
+
+ rec = CHAT_PROTOCOL(chatnetrec)->create_server_setup();
+ rec->type = module_get_uniq_id("SERVER SETUP", 0);
+ rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+ rec->chatnet = chatnetrec == NULL ? NULL : g_strdup(chatnetrec->name);
+ rec->family = g_strcasecmp(family, "inet6") == 0 ? AF_INET6 :
+ (g_strcasecmp(family, "inet") == 0 ? AF_INET : 0);
+ rec->address = g_strdup(server);
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+ rec->port = port;
+ rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
+ rec->own_host = g_strdup(config_node_get_str(node, "own_host", NULL));
+
+ signal_emit("server setup read", 2, rec, node);
+
+ setupservers = g_slist_append(setupservers, rec);
+ return rec;
+}
+
+static void server_setup_save(SERVER_SETUP_REC *rec)
+{
+ CONFIG_NODE *parentnode, *node;
+ int index;
+
+ index = g_slist_index(setupservers, rec);
+
+ parentnode = iconfig_node_traverse("(servers", TRUE);
+ node = config_node_index(parentnode, index);
+ if (node == NULL)
+ node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_clear(node);
+ iconfig_node_set_str(node, "address", rec->address);
+ iconfig_node_set_str(node, "chatnet", rec->chatnet);
+
+ iconfig_node_set_int(node, "port", rec->port);
+ iconfig_node_set_str(node, "password", rec->password);
+ iconfig_node_set_str(node, "own_host", rec->own_host);
+
+ iconfig_node_set_str(node, "family",
+ rec->family == AF_INET6 ? "inet6" :
+ rec->family == AF_INET ? "inet" : NULL);
+
+ if (rec->autoconnect)
+ iconfig_node_set_bool(node, "autoconnect", TRUE);
+
+ signal_emit("server setup saved", 2, rec, node);
+}
+
+static void server_setup_remove_config(SERVER_SETUP_REC *rec)
+{
+ CONFIG_NODE *node;
+ int index;
+
+ node = iconfig_node_traverse("servers", FALSE);
+ if (node != NULL) {
+ index = g_slist_index(setupservers, rec);
+ iconfig_node_list_remove(node, index);
+ }
+}
+
+static void server_setup_destroy(SERVER_SETUP_REC *rec)
+{
+ setupservers = g_slist_remove(setupservers, rec);
+ signal_emit("server setup destroyed", 1, rec);
+
+ g_free_not_null(rec->own_host);
+ g_free_not_null(rec->own_ip4);
+ g_free_not_null(rec->own_ip6);
+ g_free_not_null(rec->chatnet);
+ g_free_not_null(rec->password);
+ g_free(rec->address);
+ g_free(rec);
+}
+
+void server_setup_add(SERVER_SETUP_REC *rec)
+{
+ rec->type = module_get_uniq_id("SERVER SETUP", 0);
+ if (g_slist_find(setupservers, rec) == NULL)
+ setupservers = g_slist_append(setupservers, rec);
+ server_setup_save(rec);
+}
+
+void server_setup_remove(SERVER_SETUP_REC *rec)
+{
+ server_setup_remove_config(rec);
+ server_setup_destroy(rec);
+}
+
+static void read_servers(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupservers != NULL)
+ server_setup_destroy(setupservers->data);
+
+ /* Read servers */
+ node = iconfig_node_traverse("servers", FALSE);
+ if (node != NULL) {
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ server_setup_read(tmp->data);
+ }
+}
+
+static void read_settings(void)
+{
+ if (old_source_host == NULL ||
+ strcmp(old_source_host, settings_get_str("hostname")) != 0) {
+ g_free_not_null(old_source_host);
+ old_source_host = g_strdup(settings_get_str("hostname"));
+
+ source_host_ok = FALSE;
+ get_source_host_ip();
+ }
+}
+
+void servers_setup_init(void)
+{
+ settings_add_str("server", "hostname", "");
+
+ settings_add_str("server", "nick", NULL);
+ settings_add_str("server", "user_name", NULL);
+ settings_add_str("server", "real_name", NULL);
+
+ settings_add_bool("proxy", "use_proxy", FALSE);
+ settings_add_str("proxy", "proxy_address", "");
+ settings_add_int("proxy", "proxy_port", 6667);
+ settings_add_str("proxy", "proxy_string", "CONNECT %s %d");
+ settings_add_str("proxy", "proxy_password", "");
+
+ setupservers = NULL;
+ source_host_ip4 = source_host_ip6 = NULL;
+ old_source_host = NULL;
+ read_settings();
+
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) read_servers);
+ signal_add("irssi init read settings", (SIGNAL_FUNC) read_servers);
+}
+
+void servers_setup_deinit(void)
+{
+ g_free_not_null(source_host_ip4);
+ g_free_not_null(source_host_ip6);
+ g_free_not_null(old_source_host);
+
+ while (setupservers != NULL)
+ server_setup_destroy(setupservers->data);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_servers);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_servers);
+
+ module_uniq_destroy("SERVER SETUP");
+}
--- /dev/null
+#ifndef __SERVERS_SETUP_H
+#define __SERVERS_SETUP_H
+
+#include "modules.h"
+
+#define SERVER_SETUP(server) \
+ MODULE_CHECK_CAST(server, SERVER_SETUP_REC, type, "SERVER SETUP")
+
+#define IS_SERVER_SETUP(server) \
+ (SERVER_SETUP(server) ? TRUE : FALSE)
+
+/* servers */
+struct _SERVER_SETUP_REC {
+#include "server-setup-rec.h"
+};
+
+extern GSList *setupservers;
+
+extern IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+extern int source_host_ok; /* Use source_host_ip .. */
+
+/* Fill reconnection specific information to connection
+ from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver);
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *
+server_create_conn(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password,
+ const char *nick);
+
+/* Find matching server from setup. Try to find record with a same port,
+ but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port);
+/* Find matching server from setup. Ports must match or NULL is returned. */
+SERVER_SETUP_REC *server_setup_find_port(const char *address, int port);
+
+void server_setup_add(SERVER_SETUP_REC *rec);
+void server_setup_remove(SERVER_SETUP_REC *rec);
+
+void servers_setup_init(void);
+void servers_setup_deinit(void);
+
+#endif
--- /dev/null
+/*
+ server.c : irssi
+
+ Copyright (C) 1999-2000 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 "commands.h"
+#include "line-split.h"
+#include "net-nonblock.h"
+#include "net-sendbuffer.h"
+#include "misc.h"
+#include "rawlog.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "servers-reconnect.h"
+#include "servers-redirect.h"
+#include "servers-setup.h"
+#include "channels.h"
+#include "queries.h"
+
+GSList *servers, *lookup_servers;
+
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg)
+{
+ g_return_if_fail(IS_SERVER(server));
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+
+ signal_emit("server connect failed", 2, server, msg);
+ if (server->connect_tag != -1)
+ g_source_remove(server->connect_tag);
+ if (server->handle != NULL)
+ net_sendbuffer_destroy(server->handle, TRUE);
+
+ if (server->connect_pipe[0] != NULL) {
+ g_io_channel_close(server->connect_pipe[0]);
+ g_io_channel_unref(server->connect_pipe[0]);
+ g_io_channel_close(server->connect_pipe[1]);
+ g_io_channel_unref(server->connect_pipe[1]);
+ }
+
+ MODULE_DATA_DEINIT(server);
+ server_connect_free(server->connrec);
+ g_free_not_null(server->nick);
+ g_free(server->tag);
+ g_free(server);
+}
+
+/* generate tag from server's address */
+static char *server_create_address_tag(const char *address)
+{
+ const char *start, *end;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ /* try to generate a reasonable server tag */
+ if (strchr(address, '.') == NULL) {
+ start = end = NULL;
+ } else if (g_strncasecmp(address, "irc", 3) == 0 ||
+ g_strncasecmp(address, "chat", 4) == 0) {
+ /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
+ end = strrchr(address, '.');
+ start = end-1;
+ while (start > address && *start != '.') start--;
+ } else {
+ /* efnet.cs.hut.fi -> efnet */
+ end = strchr(address, '.');
+ start = end;
+ }
+
+ if (start == end) start = address; else start++;
+ if (end == NULL) end = address + strlen(address);
+
+ return g_strndup(start, (int) (end-start));
+}
+
+/* create unique tag for server. prefer ircnet's name or
+ generate it from server's address */
+static char *server_create_tag(SERVER_CONNECT_REC *conn)
+{
+ GString *str;
+ char *tag;
+ int num;
+
+ g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
+
+ tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
+ g_strdup(conn->chatnet) :
+ server_create_address_tag(conn->address);
+
+ /* then just append numbers after tag until unused is found.. */
+ str = g_string_new(tag);
+ for (num = 2; server_find_tag(str->str) != NULL; num++)
+ g_string_sprintf(str, "%s%d", tag, num);
+ g_free(tag);
+
+ tag = str->str;
+ g_string_free(str, FALSE);
+ return tag;
+}
+
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server)
+{
+ server->connect_time = time(NULL);
+ server->rawlog = rawlog_create();
+
+ server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+ server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
+
+ servers = g_slist_append(servers, server);
+ signal_emit("server connected", 1, server);
+}
+
+static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
+{
+ int error;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ error = net_geterror(handle);
+ if (error != 0) {
+ server->connection_lost = TRUE;
+ server_connect_failed(server, g_strerror(error));
+ return;
+ }
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+
+ server_connect_finished(server);
+}
+
+static void server_connect_callback_readpipe(SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ RESOLVED_IP_REC iprec;
+ GIOChannel *handle;
+ IPADDR *ip, *own_ip;
+ const char *errormsg;
+ int port;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+
+ net_gethostbyname_return(server->connect_pipe[0], &iprec);
+
+ g_io_channel_close(server->connect_pipe[0]);
+ g_io_channel_unref(server->connect_pipe[0]);
+ g_io_channel_close(server->connect_pipe[1]);
+ g_io_channel_unref(server->connect_pipe[1]);
+
+ server->connect_pipe[0] = NULL;
+ server->connect_pipe[1] = NULL;
+
+ /* figure out if we should use IPv4 or v6 address */
+ ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 ||
+ (server->connrec->family == AF_INET && iprec.ip4.family != 0) ?
+ &iprec.ip4 : &iprec.ip6;
+ if (iprec.ip4.family != 0 && server->connrec->family == 0 &&
+ !settings_get_bool("resolve_prefer_ipv6"))
+ ip = &iprec.ip4;
+
+ conn = server->connrec;
+ port = conn->proxy != NULL ? conn->proxy_port : conn->port;
+ own_ip = ip == NULL ? NULL :
+ (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4);
+
+ if (ip != NULL)
+ signal_emit("server connecting", 2, server, ip);
+
+ handle = ip == NULL ? NULL : net_connect_ip(ip, port, own_ip);
+ if (handle == NULL) {
+ /* failed */
+ if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) {
+ /* IP wasn't found for the host, don't try to reconnect
+ back to this server */
+ server->dns_error = TRUE;
+ }
+
+ if (iprec.error == 0) {
+ /* connect() failed */
+ errormsg = g_strerror(errno);
+ } else {
+ /* gethostbyname() failed */
+ errormsg = iprec.errorstr != NULL ? iprec.errorstr :
+ "Host lookup failed";
+ }
+ server->connection_lost = TRUE;
+ server_connect_failed(server, errormsg);
+ g_free_not_null(iprec.errorstr);
+ return;
+ }
+
+ server->handle = net_sendbuffer_create(handle, 0);
+ server->connect_tag =
+ g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ,
+ (GInputFunction) server_connect_callback_init,
+ server);
+}
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ MODULE_DATA_INIT(server);
+ server->type = module_get_uniq_id("SERVER", 0);
+
+ server->nick = g_strdup(server->connrec->nick);
+ if (server->connrec->username == NULL || *server->connrec->username == '\0') {
+ g_free_not_null(server->connrec->username);
+
+ server->connrec->username = g_get_user_name();
+ if (*server->connrec->username == '\0') server->connrec->username = "-";
+ server->connrec->username = g_strdup(server->connrec->username);
+ }
+ if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
+ g_free_not_null(server->connrec->realname);
+
+ server->connrec->realname = g_get_real_name();
+ if (*server->connrec->realname == '\0') server->connrec->realname = "-";
+ server->connrec->realname = g_strdup(server->connrec->realname);
+ }
+
+ server->tag = server_create_tag(server->connrec);
+}
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server)
+{
+ const char *connect_address;
+ int fd[2];
+
+ g_return_val_if_fail(server != NULL, FALSE);
+ if (server->connrec->port <= 0) return FALSE;
+
+ server_connect_init(server);
+
+ if (pipe(fd) != 0) {
+ g_warning("server_connect(): pipe() failed.");
+ g_free(server->tag);
+ g_free(server->nick);
+ return FALSE;
+ }
+
+ server->connect_pipe[0] = g_io_channel_unix_new(fd[0]);
+ server->connect_pipe[1] = g_io_channel_unix_new(fd[1]);
+
+ connect_address = server->connrec->proxy != NULL ?
+ server->connrec->proxy : server->connrec->address;
+ server->connect_pid =
+ net_gethostbyname_nonblock(connect_address,
+ server->connect_pipe[1]);
+ server->connect_tag =
+ g_input_add(server->connect_pipe[0], G_INPUT_READ,
+ (GInputFunction) server_connect_callback_readpipe,
+ server);
+
+ lookup_servers = g_slist_append(lookup_servers, server);
+
+ signal_emit("server looking", 1, server);
+ return TRUE;
+}
+
+static int server_remove_channels(SERVER_REC *server)
+{
+ GSList *tmp;
+ int found;
+
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ found = FALSE;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ channel->server = NULL;
+ channel_destroy(channel);
+ found = TRUE;
+ }
+
+ while (server->queries != NULL)
+ query_change_server(server->queries->data, NULL);
+
+ g_slist_free(server->channels);
+ g_slist_free(server->queries);
+
+ return found;
+}
+
+void server_disconnect(SERVER_REC *server)
+{
+ int chans;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ if (server->connect_tag != -1) {
+ /* still connecting to server.. */
+ if (server->connect_pid != -1)
+ net_disconnect_nonblock(server->connect_pid);
+ server_connect_failed(server, NULL);
+ return;
+ }
+
+ servers = g_slist_remove(servers, server);
+
+ signal_emit("server disconnected", 1, server);
+
+ /* close all channels */
+ chans = server_remove_channels(server);
+
+ if (server->handle != NULL) {
+ if (!chans || server->connection_lost)
+ net_sendbuffer_destroy(server->handle, TRUE);
+ else {
+ /* we were on some channels, try to let the server
+ disconnect so that our quit message is guaranteed
+ to get displayed */
+ net_disconnect_later(net_sendbuffer_handle(server->handle));
+ net_sendbuffer_destroy(server->handle, FALSE);
+ }
+ server->handle = NULL;
+ }
+
+ if (server->readtag > 0)
+ g_source_remove(server->readtag);
+
+ MODULE_DATA_DEINIT(server);
+ server_connect_free(server->connrec);
+ rawlog_destroy(server->rawlog);
+ line_split_free(server->buffer);
+ g_free_not_null(server->version);
+ g_free_not_null(server->away_reason);
+ g_free(server->nick);
+ g_free(server->tag);
+ g_free(server);
+}
+
+SERVER_REC *server_find_tag(const char *tag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(tag != NULL, NULL);
+ if (*tag == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (g_strcasecmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (g_strcasecmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+SERVER_REC *server_find_chatnet(const char *chatnet)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(chatnet != NULL, NULL);
+ if (*chatnet == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (server->connrec->chatnet != NULL &&
+ g_strcasecmp(server->connrec->chatnet, chatnet) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+void server_connect_free(SERVER_CONNECT_REC *conn)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+ signal_emit("server connect free", 1, conn);
+ g_free_not_null(conn->proxy);
+ g_free_not_null(conn->proxy_string);
+ g_free_not_null(conn->proxy_password);
+
+ g_free_not_null(conn->address);
+ g_free_not_null(conn->chatnet);
+
+ g_free_not_null(conn->own_ip4);
+ g_free_not_null(conn->own_ip6);
+
+ g_free_not_null(conn->password);
+ g_free_not_null(conn->nick);
+ g_free_not_null(conn->username);
+ g_free_not_null(conn->realname);
+
+ g_free_not_null(conn->channels);
+ g_free_not_null(conn->away_reason);
+ g_free(conn);
+}
+
+void server_change_nick(SERVER_REC *server, const char *nick)
+{
+ g_free(server->connrec->nick);
+ g_free(server->nick);
+ server->connrec->nick = g_strdup(nick);
+ server->nick = g_strdup(nick);
+
+ signal_emit("server nick changed", 1, server);
+}
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+ IPADDR *ip4, IPADDR *ip6)
+{
+ if (ip4 == NULL || ip4->family == 0)
+ g_free_and_null(conn->own_ip4);
+ if (ip6 == NULL || ip6->family == 0)
+ g_free_and_null(conn->own_ip6);
+
+ if (ip4 != NULL && ip4->family != 0) {
+ /* IPv4 address was found */
+ if (conn->own_ip4 == NULL)
+ conn->own_ip4 = g_new0(IPADDR, 1);
+ memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
+ }
+
+ if (ip6 != NULL && ip6->family != 0) {
+ /* IPv6 address was found */
+ if (conn->own_ip6 == NULL)
+ conn->own_ip6 = g_new0(IPADDR, 1);
+ memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
+ }
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+ GHashTable *optlist,
+ SERVER_REC *defserver)
+{
+ SERVER_REC *server;
+ GSList *list, *tmp, *next;
+
+ /* get all the options, then remove the known ones. there should
+ be only one left - the server tag. */
+ list = hashtable_get_keys(optlist);
+ if (cmd != NULL) {
+ for (tmp = list; tmp != NULL; tmp = next) {
+ char *option = tmp->data;
+ next = tmp->next;
+
+ if (command_have_option(cmd, option))
+ list = g_slist_remove(list, option);
+ }
+ }
+
+ if (list == NULL)
+ return defserver;
+
+ server = server_find_tag(list->data);
+ if (server == NULL || list->next != NULL) {
+ /* unknown option (not server tag) */
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+ server == NULL ? list->data : list->next->data);
+ signal_stop();
+
+ server = NULL;
+ }
+
+ g_slist_free(list);
+ return server;
+}
+
+static void disconnect_servers(GSList *servers, int chat_type)
+{
+ GSList *tmp, *next;
+
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ SERVER_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->chat_type == chat_type)
+ server_disconnect(rec);
+ }
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+ disconnect_servers(servers, proto->id);
+ disconnect_servers(lookup_servers, proto->id);
+}
+
+void servers_init(void)
+{
+ settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
+ lookup_servers = servers = NULL;
+
+ signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+ servers_reconnect_init();
+ servers_redirect_init();
+ servers_setup_init();
+}
+
+void servers_deinit(void)
+{
+ signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+ servers_setup_deinit();
+ servers_redirect_deinit();
+ servers_reconnect_deinit();
+
+ module_uniq_destroy("SERVER");
+ module_uniq_destroy("SERVER CONNECT");
+}
--- /dev/null
+#ifndef __SERVERS_H
+#define __SERVERS_H
+
+#include "modules.h"
+
+/* Returns SERVER_REC if it's server, NULL if it isn't. */
+#define SERVER(server) \
+ MODULE_CHECK_CAST(server, SERVER_REC, type, "SERVER")
+
+/* Returns SERVER_CONNECT_REC if it's server connection, NULL if it isn't. */
+#define SERVER_CONNECT(conn) \
+ MODULE_CHECK_CAST(conn, SERVER_CONNECT_REC, type, "SERVER CONNECT")
+
+#define IS_SERVER(server) \
+ (SERVER(server) ? TRUE : FALSE)
+
+#define IS_SERVER_CONNECT(conn) \
+ (SERVER_CONNECT(conn) ? TRUE : FALSE)
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+struct _SERVER_CONNECT_REC {
+#include "server-connect-rec.h"
+};
+
+#define STRUCT_SERVER_CONNECT_REC SERVER_CONNECT_REC
+struct _SERVER_REC {
+#include "server-rec.h"
+};
+
+extern GSList *servers, *lookup_servers;
+
+void servers_init(void);
+void servers_deinit(void);
+
+/* Disconnect from server */
+void server_disconnect(SERVER_REC *server);
+
+SERVER_REC *server_find_tag(const char *tag);
+SERVER_REC *server_find_chatnet(const char *chatnet);
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server);
+void server_connect_free(SERVER_CONNECT_REC *conn);
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server);
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server);
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg);
+
+/* Change your nick */
+void server_change_nick(SERVER_REC *server, const char *nick);
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+ IPADDR *ip4, IPADDR *ip6);
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+ GHashTable *optlist,
+ SERVER_REC *defserver);
+
+#endif
--- /dev/null
+/*
+ settings.c : Irssi settings
+
+ Copyright (C) 1999 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 "commands.h"
+#include "misc.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+#include "default-config.h"
+
+#include <signal.h>
+
+CONFIG_REC *mainconfig;
+
+static GString *last_errors;
+static char *last_config_error_msg;
+static GSList *last_invalid_modules;
+static int fe_initialized;
+static int config_changed; /* FIXME: remove after .98 (unless needed again) */
+
+static GHashTable *settings;
+static int timeout_tag;
+
+static int config_last_modifycounter;
+static time_t config_last_mtime;
+static long config_last_size;
+static unsigned int config_last_checksum;
+
+static SETTINGS_REC *settings_find(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) {
+ g_warning("settings_get_default_str(%s) : "
+ "unknown setting", key);
+ return NULL;
+ }
+
+ return rec;
+}
+
+const char *settings_get_str(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *setnode, *node;
+
+ rec = settings_find(key);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ setnode = iconfig_node_traverse("settings", FALSE);
+ if (setnode == NULL)
+ return rec->def;
+
+ node = config_node_section(setnode, rec->module, -1);
+ return node == NULL ? rec->def :
+ config_node_get_str(node, key, rec->def);
+}
+
+int settings_get_int(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *setnode, *node;
+ int def;
+
+ rec = settings_find(key);
+ g_return_val_if_fail(rec != NULL, 0);
+ def = GPOINTER_TO_INT(rec->def);
+
+ setnode = iconfig_node_traverse("settings", FALSE);
+ if (setnode == NULL)
+ return def;
+
+ node = config_node_section(setnode, rec->module, -1);
+ return node == NULL ? def :
+ config_node_get_int(node, key, def);
+}
+
+int settings_get_bool(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *setnode, *node;
+ int def;
+
+ rec = settings_find(key);
+ g_return_val_if_fail(rec != NULL, 0);
+ def = GPOINTER_TO_INT(rec->def);
+
+ setnode = iconfig_node_traverse("settings", FALSE);
+ if (setnode == NULL)
+ return def;
+
+ node = config_node_section(setnode, rec->module, -1);
+ return node == NULL ? def :
+ config_node_get_bool(node, key, def);
+}
+
+void settings_add_str_module(const char *module, const char *section,
+ const char *key, const char *def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->module = g_strdup(module);
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = def == NULL ? NULL : g_strdup(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+void settings_add_int_module(const char *module, const char *section,
+ const char *key, int def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->module = g_strdup(module);
+ rec->type = SETTING_TYPE_INT;
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = GINT_TO_POINTER(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+void settings_add_bool_module(const char *module, const char *section,
+ const char *key, int def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->module = g_strdup(module);
+ rec->type = SETTING_TYPE_BOOLEAN;
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = GINT_TO_POINTER(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+static void settings_destroy(SETTINGS_REC *rec)
+{
+ if (rec->type == SETTING_TYPE_STRING)
+ g_free_not_null(rec->def);
+ g_free(rec->module);
+ g_free(rec->section);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+void settings_remove(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) return;
+
+ g_hash_table_remove(settings, key);
+ settings_destroy(rec);
+}
+
+static int settings_remove_hash(const char *key, SETTINGS_REC *rec,
+ const char *module)
+{
+ if (strcmp(rec->module, module) == 0) {
+ settings_destroy(rec);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void settings_remove_module(const char *module)
+{
+ g_hash_table_foreach_remove(settings,
+ (GHRFunc) settings_remove_hash,
+ (void *) module);
+}
+
+static CONFIG_NODE *settings_get_node(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ node = iconfig_node_traverse("settings", TRUE);
+ return config_node_section(node, rec->module, NODE_TYPE_BLOCK);
+}
+
+void settings_set_str(const char *key, const char *value)
+{
+ iconfig_node_set_str(settings_get_node(key), key, value);
+}
+
+void settings_set_int(const char *key, int value)
+{
+ iconfig_node_set_int(settings_get_node(key), key, value);
+}
+
+void settings_set_bool(const char *key, int value)
+{
+ iconfig_node_set_bool(settings_get_node(key), key, value);
+}
+
+int settings_get_type(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, -1);
+
+ rec = g_hash_table_lookup(settings, key);
+ return rec == NULL ? -1 : rec->type;
+}
+
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key)
+{
+ g_return_val_if_fail(key != NULL, NULL);
+
+ return g_hash_table_lookup(settings, key);
+}
+
+static void sig_init_finished(void)
+{
+ fe_initialized = TRUE;
+ if (last_errors != NULL) {
+ signal_emit("settings errors", 1, last_errors->str);
+ g_string_free(last_errors, TRUE);
+ }
+
+ if (last_config_error_msg != NULL) {
+ signal_emit("gui dialog", 2, "error", last_config_error_msg);
+ g_free_and_null(last_config_error_msg);
+ }
+
+ if (config_changed) {
+ /* some backwards compatibility changes were made to
+ config file, reload it */
+ signal_emit("setup changed", 0);
+ }
+}
+
+/* FIXME: remove after 0.7.98 - only for backward compatibility */
+static void settings_move(SETTINGS_REC *rec, char *value)
+{
+ CONFIG_NODE *setnode, *node;
+
+ setnode = iconfig_node_traverse("settings", TRUE);
+ node = config_node_section(setnode, rec->module, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_str(node, rec->key, value);
+ iconfig_node_set_str(setnode, rec->key, NULL);
+
+ config_changed = TRUE;
+}
+
+static void settings_clean_invalid_module(const char *module)
+{
+ CONFIG_NODE *node;
+ SETTINGS_REC *set;
+ GSList *tmp, *next;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ if (node == NULL) return;
+
+ node = config_node_section(node, module, -1);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = next) {
+ CONFIG_NODE *subnode = tmp->data;
+ next = tmp->next;
+
+ set = g_hash_table_lookup(settings, subnode->key);
+ if (set == NULL || strcmp(set->module, module) != 0)
+ iconfig_node_remove(node, subnode);
+ }
+}
+
+/* remove all invalid settings from config file. works only with the
+ modules that have already called settings_check() */
+void settings_clean_invalid(void)
+{
+ while (last_invalid_modules != NULL) {
+ char *module = last_invalid_modules->data;
+
+ settings_clean_invalid_module(module);
+
+ g_free(module);
+ last_invalid_modules =
+ g_slist_remove(last_invalid_modules, module);
+ }
+}
+
+/* verify that all settings in config file for `module' are actually found
+ from /SET list */
+void settings_check_module(const char *module)
+{
+ SETTINGS_REC *set;
+ CONFIG_NODE *node;
+ GString *errors;
+ GSList *tmp, *next;
+ int count;
+
+ g_return_if_fail(module != NULL);
+
+ node = iconfig_node_traverse("settings", FALSE);
+ if (node != NULL) {
+ /* FIXME: remove after 0.7.98 */
+ for (tmp = node->value; tmp != NULL; tmp = next) {
+ CONFIG_NODE *node = tmp->data;
+
+ next = tmp->next;
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+ set = g_hash_table_lookup(settings, node->key);
+ if (set != NULL)
+ settings_move(set, node->value);
+ }
+ }
+ node = node == NULL ? NULL : config_node_section(node, module, -1);
+ if (node == NULL) return;
+
+ errors = g_string_new(NULL);
+ g_string_sprintf(errors, "Unknown settings in configuration "
+ "file for module %s:", module);
+
+ count = 0;
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ set = g_hash_table_lookup(settings, node->key);
+ if (set == NULL || strcmp(set->module, module) != 0) {
+ g_string_sprintfa(errors, " %s", node->key);
+ count++;
+ }
+ }
+ if (count > 0) {
+ if (gslist_find_icase_string(last_invalid_modules,
+ module) == NULL) {
+ /* mark this module having invalid settings */
+ last_invalid_modules =
+ g_slist_append(last_invalid_modules,
+ g_strdup(module));
+ }
+ if (fe_initialized)
+ signal_emit("settings errors", 1, errors->str);
+ else {
+ if (last_errors == NULL)
+ last_errors = g_string_new(NULL);
+ else
+ g_string_append_c(last_errors, '\n');
+ g_string_append(last_errors, errors->str);
+ }
+ }
+ g_string_free(errors, TRUE);
+}
+
+static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2)
+{
+ return strcmp(v1->section, v2->section);
+}
+
+static void settings_hash_get(const char *key, SETTINGS_REC *rec,
+ GSList **list)
+{
+ *list = g_slist_insert_sorted(*list, rec,
+ (GCompareFunc) settings_compare);
+}
+
+GSList *settings_get_sorted(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list);
+ return list;
+}
+
+void sig_term(int n)
+{
+ /* if we get SIGTERM after this, just die instead of coming back here. */
+ signal(SIGTERM, SIG_DFL);
+
+ /* quit from all servers too.. */
+ signal_emit("command quit", 1, "");
+
+ /* and die */
+ raise(SIGTERM);
+}
+
+/* Yes, this is my own stupid checksum generator, some "real" algorithm
+ would be nice but would just take more space without much real benefit */
+static unsigned int file_checksum(const char *fname)
+{
+ char buf[512];
+ int f, ret, n;
+ unsigned int checksum = 0;
+
+ f = open(fname, O_RDONLY);
+ if (f == -1) return 0;
+
+ n = 0;
+ while ((ret = read(f, buf, sizeof(buf))) > 0) {
+ while (ret-- > 0)
+ checksum += buf[ret] << ((n++ & 3)*8);
+ }
+ close(f);
+ return checksum;
+}
+
+static void irssi_config_save_state(const char *fname)
+{
+ struct stat statbuf;
+
+ g_return_if_fail(fname != NULL);
+
+ if (stat(fname, &statbuf) != 0)
+ return;
+
+ /* save modify time, file size and checksum */
+ config_last_mtime = statbuf.st_mtime;
+ config_last_size = statbuf.st_size;
+ config_last_checksum = file_checksum(fname);
+}
+
+int irssi_config_is_changed(const char *fname)
+{
+ struct stat statbuf;
+
+ if (fname == NULL)
+ fname = mainconfig->fname;
+
+ if (stat(fname, &statbuf) != 0)
+ return FALSE;
+
+ return config_last_mtime != statbuf.st_mtime &&
+ (config_last_size != statbuf.st_size ||
+ config_last_checksum != file_checksum(fname));
+}
+
+static CONFIG_REC *parse_configfile(const char *fname)
+{
+ CONFIG_REC *config;
+ struct stat statbuf;
+ const char *path;
+ char *real_fname;
+
+ real_fname = fname != NULL ? g_strdup(fname) :
+ g_strdup_printf("%s"G_DIR_SEPARATOR_S".irssi"
+ G_DIR_SEPARATOR_S"config", g_get_home_dir());
+
+ if (stat(real_fname, &statbuf) == 0)
+ path = real_fname;
+ else {
+ /* user configuration file not found, use the default one
+ from sysconfdir */
+ path = SYSCONFDIR"/irssi/config";
+ if (stat(path, &statbuf) != 0) {
+ /* no configuration file in sysconfdir ..
+ use the build-in configuration */
+ path = NULL;
+ }
+ }
+
+ config = config_open(path, -1);
+ if (config == NULL) {
+ last_config_error_msg =
+ g_strdup_printf("Error opening configuration file %s: %s",
+ path, g_strerror(errno));
+ config = config_open(NULL, -1);
+ }
+
+ if (path != NULL)
+ config_parse(config);
+ else
+ config_parse_data(config, default_config, "internal");
+
+ config_change_file_name(config, real_fname, 0660);
+ irssi_config_save_state(real_fname);
+ g_free(real_fname);
+ return config;
+}
+
+static void init_configfile(void)
+{
+ struct stat statbuf;
+ char *str;
+
+ str = g_strdup_printf("%s"G_DIR_SEPARATOR_S".irssi", g_get_home_dir());
+ if (stat(str, &statbuf) != 0) {
+ /* ~/.irssi not found, create it. */
+ if (mkpath(str, 0700) != 0) {
+ g_error("Couldn't create %s directory", str);
+ }
+ } else if (!S_ISDIR(statbuf.st_mode)) {
+ g_error("%s is not a directory.\n"
+ "You should remove it with command: rm ~/.irssi", str);
+ }
+ g_free(str);
+
+ mainconfig = parse_configfile(NULL);
+ config_last_modifycounter = mainconfig->modifycounter;
+
+ /* any errors? */
+ if (config_last_error(mainconfig) != NULL) {
+ last_config_error_msg =
+ g_strdup_printf("Ignored errors in configuration "
+ "file:\n%s",
+ config_last_error(mainconfig));
+ }
+
+ signal(SIGTERM, sig_term);
+}
+
+int settings_reread(const char *fname)
+{
+ CONFIG_REC *tempconfig;
+ char *str;
+
+ if (fname == NULL) fname = "~/.irssi/config";
+
+ str = convert_home(fname);
+ tempconfig = parse_configfile(str);
+ g_free(str);
+
+ if (tempconfig == NULL) {
+ signal_emit("gui dialog", 2, "error", g_strerror(errno));
+ return FALSE;
+ }
+
+ if (config_last_error(tempconfig) != NULL) {
+ str = g_strdup_printf("Errors in configuration file:\n%s",
+ config_last_error(tempconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+
+ config_close(tempconfig);
+ return FALSE;
+ }
+
+ config_close(mainconfig);
+ mainconfig = tempconfig;
+ config_last_modifycounter = mainconfig->modifycounter;
+
+ signal_emit("setup changed", 0);
+ signal_emit("setup reread", 0);
+ return TRUE;
+}
+
+int settings_save(const char *fname)
+{
+ char *str;
+ int error;
+
+ if (fname == NULL)
+ fname = mainconfig->fname;
+
+ error = config_write(mainconfig, fname, 0660) != 0;
+ irssi_config_save_state(fname);
+ config_last_modifycounter = mainconfig->modifycounter;
+ if (error) {
+ str = g_strdup_printf("Couldn't save configuration file: %s",
+ config_last_error(mainconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+ }
+ return !error;
+}
+
+static void sig_autosave(void)
+{
+ char *fname, *str;
+
+ if (!settings_get_bool("settings_autosave") ||
+ config_last_modifycounter == mainconfig->modifycounter)
+ return;
+
+ if (!irssi_config_is_changed(NULL))
+ settings_save(NULL);
+ else {
+ fname = g_strconcat(mainconfig->fname, ".autosave", NULL);
+ str = g_strdup_printf("Configuration file was modified "
+ "while irssi was running. Saving "
+ "configuration to file '%s' instead. "
+ "Use /SAVE or /RELOAD to get rid of "
+ "this message.", fname);
+ signal_emit("gui dialog", 2, "warning", str);
+ g_free(str);
+
+ settings_save(fname);
+ g_free(fname);
+ }
+}
+
+void settings_init(void)
+{
+ settings = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ last_errors = NULL;
+ last_config_error_msg = NULL;
+ last_invalid_modules = NULL;
+ fe_initialized = FALSE;
+ config_changed = FALSE;
+
+ config_last_mtime = 0;
+ config_last_modifycounter = 0;
+ init_configfile();
+
+ settings_add_bool("misc", "settings_autosave", TRUE);
+ timeout_tag = g_timeout_add(1000*60*60, (GSourceFunc) sig_autosave, NULL);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+ signal_add("gui exit", (SIGNAL_FUNC) sig_autosave);
+}
+
+static void settings_hash_free(const char *key, SETTINGS_REC *rec)
+{
+ settings_destroy(rec);
+}
+
+void settings_deinit(void)
+{
+ g_source_remove(timeout_tag);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+ signal_remove("gui exit", (SIGNAL_FUNC) sig_autosave);
+
+ g_slist_foreach(last_invalid_modules, (GFunc) g_free, NULL);
+ g_slist_free(last_invalid_modules);
+
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL);
+ g_hash_table_destroy(settings);
+
+ if (mainconfig != NULL) config_close(mainconfig);
+}
--- /dev/null
+#ifndef __SETTINGS_H
+#define __SETTINGS_H
+
+enum {
+ SETTING_TYPE_STRING,
+ SETTING_TYPE_INT,
+ SETTING_TYPE_BOOLEAN
+};
+
+typedef struct {
+ char *module;
+ int type;
+ char *key;
+ char *section;
+ void *def;
+} SETTINGS_REC;
+
+/* macros for handling the default Irssi configuration */
+#define iconfig_get_str(a, b, c) config_get_str(mainconfig, a, b, c)
+#define iconfig_get_int(a, b, c) config_get_int(mainconfig, a, b, c)
+#define iconfig_get_bool(a, b, c) config_get_bool(mainconfig, a, b, c)
+#define iconfig_list_find(a, b, c, d) config_list_find(mainconfig, a, b, c, d)
+
+#define iconfig_set_str(a, b, c) config_set_str(mainconfig, a, b, c)
+#define iconfig_set_int(a, b, c) config_set_int(mainconfig, a, b, c)
+#define iconfig_set_bool(a, b, c) config_set_bool(mainconfig, a, b, c)
+
+#define iconfig_node_traverse(a, b) config_node_traverse(mainconfig, a, b)
+#define iconfig_node_set_str(a, b, c) config_node_set_str(mainconfig, a, b, c)
+#define iconfig_node_set_int(a, b, c) config_node_set_int(mainconfig, a, b, c)
+#define iconfig_node_set_bool(a, b, c) config_node_set_bool(mainconfig, a, b, c)
+#define iconfig_node_list_remove(a, b) config_node_list_remove(mainconfig, a, b)
+#define iconfig_node_remove(a, b) config_node_remove(mainconfig, a, b)
+#define iconfig_node_clear(a) config_node_clear(mainconfig, a)
+#define iconfig_node_add_list(a, b) config_node_add_list(mainconfig, a, b)
+
+extern CONFIG_REC *mainconfig;
+
+/* Functions for handling the "settings" node of Irssi configuration */
+const char *settings_get_str(const char *key);
+int settings_get_int(const char *key);
+int settings_get_bool(const char *key);
+
+/* Functions to add/remove settings */
+void settings_add_str_module(const char *module, const char *section,
+ const char *key, const char *def);
+void settings_add_int_module(const char *module, const char *section,
+ const char *key, int def);
+void settings_add_bool_module(const char *module, const char *section,
+ const char *key, int def);
+void settings_remove(const char *key);
+void settings_remove_module(const char *module);
+
+#define settings_add_str(section, key, def) \
+ settings_add_str_module(MODULE_NAME, section, key, def)
+#define settings_add_int(section, key, def) \
+ settings_add_int_module(MODULE_NAME, section, key, def)
+#define settings_add_bool(section, key, def) \
+ settings_add_bool_module(MODULE_NAME, section, key, def)
+
+void settings_set_str(const char *key, const char *value);
+void settings_set_int(const char *key, int value);
+void settings_set_bool(const char *key, int value);
+
+/* Get the type (SETTING_TYPE_xxx) of `key' */
+int settings_get_type(const char *key);
+/* Get all settings sorted by section. Free the result with g_slist_free() */
+GSList *settings_get_sorted(void);
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key);
+
+/* verify that all settings in config file for `module' are actually found
+ from /SET list */
+void settings_check_module(const char *module);
+#define settings_check() settings_check_module(MODULE_NAME)
+
+/* remove all invalid settings from config file. works only with the
+ modules that have already called settings_check() */
+void settings_clean_invalid(void);
+
+/* if `fname' is NULL, the default is used */
+int settings_reread(const char *fname);
+int settings_save(const char *fname);
+int irssi_config_is_changed(const char *fname);
+
+void settings_init(void);
+void settings_deinit(void);
+
+#endif
--- /dev/null
+/*
+ signals.c : irssi
+
+ Copyright (C) 1999 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 "../common.h"
+#include "signals.h"
+#include "modules.h"
+
+#define SIGNAL_LISTS 3
+
+typedef struct {
+ int id; /* signal id */
+
+ int emitting; /* signal is being emitted */
+ int altered; /* some signal functions are marked as NULL */
+ int stop_emit; /* this signal was stopped */
+
+ GPtrArray *modulelist[SIGNAL_LISTS]; /* list of what signals belong
+ to which module */
+ GPtrArray *siglist[SIGNAL_LISTS]; /* signal lists */
+} SIGNAL_REC;
+
+#define signal_is_emitlist_empty(a) \
+ (!(a)->siglist[0] && !(a)->siglist[1] && !(a)->siglist[2])
+
+static GMemChunk *signals_chunk;
+static GHashTable *signals;
+static SIGNAL_REC *current_emitted_signal;
+
+void signal_add_to(const char *module, int pos,
+ const char *signal, SIGNAL_FUNC func)
+{
+ g_return_if_fail(signal != NULL);
+
+ signal_add_to_id(module, pos, signal_get_uniq_id(signal), func);
+}
+
+/* bind a signal */
+void signal_add_to_id(const char *module, int pos,
+ int signal_id, SIGNAL_FUNC func)
+{
+ SIGNAL_REC *rec;
+
+ g_return_if_fail(signal_id >= 0);
+ g_return_if_fail(func != NULL);
+ g_return_if_fail(pos >= 0 && pos < SIGNAL_LISTS);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec == NULL) {
+ rec = g_mem_chunk_alloc0(signals_chunk);
+ rec->id = signal_id;
+ g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), rec);
+ }
+
+ if (rec->siglist[pos] == NULL) {
+ rec->siglist[pos] = g_ptr_array_new();
+ rec->modulelist[pos] = g_ptr_array_new();
+ }
+
+ g_ptr_array_add(rec->siglist[pos], (void *) func);
+ g_ptr_array_add(rec->modulelist[pos], (void *) module);
+}
+
+/* Destroy the whole signal */
+static void signal_destroy(int signal_id)
+{
+ SIGNAL_REC *rec;
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL) {
+ /* remove whole signal from memory */
+ g_hash_table_remove(signals, GINT_TO_POINTER(signal_id));
+ g_free(rec);
+ }
+}
+
+static int signal_list_find(GPtrArray *array, void *data)
+{
+ unsigned int n;
+
+ for (n = 0; n < array->len; n++) {
+ if (g_ptr_array_index(array, n) == data)
+ return n;
+ }
+
+ return -1;
+}
+
+static void signal_remove_from_list(SIGNAL_REC *rec, int signal_id,
+ int list, int index)
+{
+ if (rec->emitting) {
+ g_ptr_array_index(rec->siglist[list], index) = NULL;
+ rec->altered = TRUE;
+ } else {
+ g_ptr_array_remove_index(rec->siglist[list], index);
+ g_ptr_array_remove_index(rec->modulelist[list], index);
+ if (signal_is_emitlist_empty(rec))
+ signal_destroy(signal_id);
+ }
+}
+
+/* Remove signal from emit lists */
+static int signal_remove_from_lists(SIGNAL_REC *rec, int signal_id,
+ SIGNAL_FUNC func)
+{
+ int n, index;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ index = signal_list_find(rec->siglist[n], (void *) func);
+ if (index != -1) {
+ /* remove the function from emit list */
+ signal_remove_from_list(rec, signal_id, n, index);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void signal_remove_id(int signal_id, SIGNAL_FUNC func)
+{
+ SIGNAL_REC *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_from_lists(rec, signal_id, func);
+}
+
+/* unbind signal */
+void signal_remove(const char *signal, SIGNAL_FUNC func)
+{
+ g_return_if_fail(signal != NULL);
+
+ signal_remove_id(signal_get_uniq_id(signal), func);
+}
+
+/* Remove all NULL functions from signal list */
+static void signal_list_clean(SIGNAL_REC *rec)
+{
+ int n, index;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ for (index = rec->siglist[n]->len-1; index >= 0; index--) {
+ if (g_ptr_array_index(rec->siglist[n], index) == NULL) {
+ g_ptr_array_remove_index(rec->siglist[n], index);
+ g_ptr_array_remove_index(rec->modulelist[n], index);
+ }
+ }
+ }
+}
+
+static int signal_emit_real(SIGNAL_REC *rec, gconstpointer *arglist)
+{
+ SIGNAL_REC *prev_emitted_signal;
+ SIGNAL_FUNC func;
+ int n, index, stopped, stop_emit_count;
+
+ /* signal_stop_by_name("signal"); signal_emit("signal", ...);
+ fails if we compare rec->stop_emit against 0. */
+ stop_emit_count = rec->stop_emit;
+
+ stopped = FALSE;
+ rec->emitting++;
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ /* run signals in emit lists */
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ for (index = rec->siglist[n]->len-1; index >= 0; index--) {
+ func = (SIGNAL_FUNC) g_ptr_array_index(rec->siglist[n], index);
+
+ if (func != NULL) {
+ prev_emitted_signal = current_emitted_signal;
+ current_emitted_signal = rec;
+#if SIGNAL_MAX_ARGUMENTS != 6
+# error SIGNAL_MAX_ARGS changed - update code
+#endif
+ func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5]);
+ current_emitted_signal = prev_emitted_signal;
+ }
+
+ if (rec->stop_emit != stop_emit_count) {
+ stopped = TRUE;
+ rec->stop_emit--;
+ n = SIGNAL_LISTS;
+ break;
+ }
+ }
+ }
+ rec->emitting--;
+
+ if (!rec->emitting) {
+ if (rec->stop_emit != 0) {
+ /* signal_stop() used too many times */
+ rec->stop_emit = 0;
+ }
+ if (rec->altered) {
+ signal_list_clean(rec);
+ rec->altered = FALSE;
+ }
+ }
+
+ return stopped;
+}
+
+static int signal_emitv_id(int signal_id, int params, va_list va)
+{
+ gconstpointer arglist[SIGNAL_MAX_ARGUMENTS];
+ SIGNAL_REC *rec;
+ int n;
+
+ g_return_val_if_fail(signal_id >= 0, FALSE);
+ g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
+
+ for (n = 0; n < SIGNAL_MAX_ARGUMENTS; n++)
+ arglist[n] = n >= params ? NULL : va_arg(va, gconstpointer);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL && signal_emit_real(rec, arglist))
+ return TRUE;
+
+ return rec != NULL;
+}
+
+int signal_emit(const char *signal, int params, ...)
+{
+ va_list va;
+ int signal_id, ret;
+
+ /* get arguments */
+ signal_id = signal_get_uniq_id(signal);
+
+ va_start(va, params);
+ ret = signal_emitv_id(signal_id, params, va);
+ va_end(va);
+
+ return ret;
+}
+
+int signal_emit_id(int signal_id, int params, ...)
+{
+ va_list va;
+ int ret;
+
+ /* get arguments */
+ va_start(va, params);
+ ret = signal_emitv_id(signal_id, params, va);
+ va_end(va);
+
+ return ret;
+}
+
+/* stop the current ongoing signal emission */
+void signal_stop(void)
+{
+ SIGNAL_REC *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 *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;
+
+ 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;
+
+ 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 *rec,
+ const char *module)
+{
+ unsigned int index;
+ int signal_id, list;
+
+ signal_id = GPOINTER_TO_INT(signal);
+
+ for (list = 0; list < SIGNAL_LISTS; list++) {
+ if (rec->modulelist[list] == NULL)
+ continue;
+
+ for (index = 0; index < rec->modulelist[list]->len; index++) {
+ if (g_strcasecmp(g_ptr_array_index(rec->modulelist[list], index), module) == 0)
+ signal_remove_from_list(rec, signal_id, list, index);
+ }
+ }
+}
+
+/* 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_remove_module, (void *) module);
+}
+
+void signals_init(void)
+{
+ signals_chunk = g_mem_chunk_new("signals", sizeof(SIGNAL_REC),
+ sizeof(SIGNAL_REC)*200, G_ALLOC_AND_FREE);
+ signals = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+}
+
+static void signal_free(void *key, SIGNAL_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] != NULL) {
+ g_ptr_array_free(rec->siglist[n], TRUE);
+ g_ptr_array_free(rec->modulelist[n], TRUE);
+ }
+ }
+
+ g_mem_chunk_free(signals_chunk, rec);
+ current_emitted_signal = NULL;
+}
+
+void signals_deinit(void)
+{
+ g_hash_table_foreach(signals, (GHFunc) signal_free, NULL);
+ g_hash_table_destroy(signals);
+
+ module_uniq_destroy("signals");
+ g_mem_chunk_destroy(signals_chunk);
+}
--- /dev/null
+#ifndef __SIGNAL_H
+#define __SIGNAL_H
+
+#define SIGNAL_MAX_ARGUMENTS 6
+typedef void (*SIGNAL_FUNC) (gconstpointer, gconstpointer,
+ gconstpointer, gconstpointer,
+ gconstpointer, gconstpointer);
+
+void signals_init(void);
+void signals_deinit(void);
+
+/* signal name -> ID */
+#define signal_get_uniq_id(signal) \
+ module_get_uniq_id_str("signals", signal)
+/* signal ID -> name */
+#define signal_get_id_str(signal_id) \
+ module_find_id_str("signals", signal_id)
+
+/* bind a signal */
+void signal_add_to(const char *module, int pos,
+ const char *signal, SIGNAL_FUNC func);
+void signal_add_to_id(const char *module, int pos,
+ int signal, SIGNAL_FUNC func);
+#define signal_add(a, b) signal_add_to(MODULE_NAME, 1, a, b)
+#define signal_add_first(a, b) signal_add_to(MODULE_NAME, 0, a, b)
+#define signal_add_last(a, b) signal_add_to(MODULE_NAME, 2, a, b)
+
+/* unbind signal */
+void signal_remove(const char *signal, SIGNAL_FUNC func);
+void signal_remove_id(int signal_id, SIGNAL_FUNC func);
+
+/* emit signal */
+int signal_emit(const char *signal, int params, ...);
+int signal_emit_id(int signal_id, int params, ...);
+
+/* stop the current ongoing signal emission */
+void signal_stop(void);
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal);
+
+/* return the name of the signal that is currently being emitted */
+const char *signal_get_emitted(void);
+/* return the ID of the signal that is currently being emitted */
+int signal_get_emitted_id(void);
+/* return TRUE if specified signal was stopped */
+int signal_is_stopped(int signal_id);
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module);
+
+#endif
--- /dev/null
+/*
+ special-vars.c : irssi
+
+ Copyright (C) 2000 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 "special-vars.h"
+#include "expandos.h"
+#include "settings.h"
+#include "misc.h"
+
+#define ALIGN_RIGHT 0x01
+#define ALIGN_CUT 0x02
+#define ALIGN_PAD 0x04
+
+#define isvarchar(c) \
+ (isalnum(c) || (c) == '_')
+
+static SPECIAL_HISTORY_FUNC history_func = NULL;
+
+static char *get_argument(char **cmd, char **arglist)
+{
+ GString *str;
+ char *ret;
+ int max, arg, argcount;
+
+ arg = 0;
+ max = -1;
+
+ argcount = strarray_length(arglist);
+
+ if (**cmd == '*') {
+ /* get all arguments */
+ } else if (**cmd == '~') {
+ /* get last argument */
+ arg = max = argcount-1;
+ } else {
+ if (isdigit(**cmd)) {
+ /* first argument */
+ arg = max = (**cmd)-'0';
+ (*cmd)++;
+ }
+
+ if (**cmd == '-') {
+ /* get more than one argument */
+ (*cmd)++;
+ if (!isdigit(**cmd))
+ max = -1; /* get all the rest */
+ else {
+ max = (**cmd)-'0';
+ (*cmd)++;
+ }
+ }
+ (*cmd)--;
+ }
+
+ str = g_string_new(NULL);
+ while (arg < argcount && (arg <= max || max == -1)) {
+ g_string_append(str, arglist[arg]);
+ g_string_append_c(str, ' ');
+ arg++;
+ }
+ if (str->len > 0) g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *get_internal_setting(const char *key, int type, int *free_ret)
+{
+ switch (type) {
+ case SETTING_TYPE_BOOLEAN:
+ return settings_get_bool(key) ? "yes" : "no";
+ case SETTING_TYPE_INT:
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", settings_get_int(key));
+ case SETTING_TYPE_STRING:
+ return (char *) settings_get_str(key);
+ }
+
+ return NULL;
+}
+
+static char *get_long_variable_value(const char *key, SERVER_REC *server,
+ void *item, int *free_ret)
+{
+ EXPANDO_FUNC func;
+ char *ret;
+ int type;
+
+ *free_ret = FALSE;
+
+ /* expando? */
+ func = expando_find_long(key);
+ if (func != NULL)
+ return func(server, item, free_ret);
+
+ /* internal setting? */
+ type = settings_get_type(key);
+ if (type != -1)
+ return get_internal_setting(key, type, free_ret);
+
+ /* environment variable? */
+ ret = g_getenv(key);
+ if (ret != NULL)
+ return ret;
+
+ return NULL;
+}
+
+static char *get_long_variable(char **cmd, SERVER_REC *server,
+ void *item, int *free_ret, int getname)
+{
+ char *start, *var, *ret;
+
+ /* get variable name */
+ start = *cmd;
+ while (isvarchar((*cmd)[1])) (*cmd)++;
+
+ var = g_strndup(start, (int) (*cmd-start)+1);
+ if (getname) {
+ *free_ret = TRUE;
+ return var;
+ }
+ ret = get_long_variable_value(var, server, item, free_ret);
+ g_free(var);
+ return ret;
+}
+
+/* return the value of the variable found from `cmd'.
+ if 'getname' is TRUE, return the name of the variable instead it's value */
+static char *get_variable(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used,
+ int getname)
+{
+ EXPANDO_FUNC func;
+
+ if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') {
+ /* argument */
+ *free_ret = TRUE;
+ if (arg_used != NULL) *arg_used = TRUE;
+ return getname ? g_strdup_printf("%c", **cmd) :
+ get_argument(cmd, arglist);
+ }
+
+ if (isalpha(**cmd) && isvarchar((*cmd)[1])) {
+ /* long variable name.. */
+ return get_long_variable(cmd, server, item, free_ret, getname);
+ }
+
+ /* single character variable. */
+ if (getname) {
+ *free_ret = TRUE;
+ return g_strdup_printf("%c", **cmd);
+ }
+ *free_ret = FALSE;
+ func = expando_find_char(**cmd);
+ return func == NULL ? NULL : func(server, item, free_ret);
+}
+
+static char *get_history(char **cmd, void *item, int *free_ret)
+{
+ char *start, *text, *ret;
+
+ /* get variable name */
+ start = ++(*cmd);
+ while (**cmd != '\0' && **cmd != '!') (*cmd)++;
+
+ if (history_func == NULL)
+ ret = NULL;
+ else {
+ text = g_strndup(start, (int) (*cmd-start)+1);
+ ret = history_func(text, item, free_ret);
+ g_free(text);
+ }
+
+ if (**cmd == '\0') (*cmd)--;
+ return ret;
+}
+
+static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used,
+ int flags)
+{
+ char command, *value, *p;
+ int len;
+
+ if (**cmd == '!') {
+ /* find text from command history */
+ if (flags & PARSE_FLAG_GETNAME)
+ return "!";
+
+ return get_history(cmd, item, free_ret);
+ }
+
+ command = 0;
+ if (**cmd == '#' || **cmd == '@') {
+ command = **cmd;
+ if ((*cmd)[1] != '\0')
+ (*cmd)++;
+ else {
+ /* default to $* */
+ char *temp_cmd = "*";
+
+ if (flags & PARSE_FLAG_GETNAME)
+ return "*";
+
+ *free_ret = TRUE;
+ return get_argument(&temp_cmd, arglist);
+ }
+ }
+
+ value = get_variable(cmd, server, item, arglist, free_ret,
+ arg_used, flags & PARSE_FLAG_GETNAME);
+
+ if (flags & PARSE_FLAG_GETNAME)
+ return value;
+
+ if (command == '#') {
+ /* number of words */
+ if (value == NULL || *value == '\0') {
+ if (value != NULL && *free_ret) {
+ g_free(value);
+ *free_ret = FALSE;
+ }
+ return "0";
+ }
+
+ len = 1;
+ for (p = value; *p != '\0'; p++) {
+ if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
+ len++;
+ }
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ if (command == '@') {
+ /* number of characters */
+ if (value == NULL) return "0";
+
+ len = strlen(value);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ return value;
+}
+
+/* get alignment arguments (inside the []) */
+static int get_alignment_args(char **data, int *align, int *flags, char *pad)
+{
+ char *str;
+
+ *align = 0;
+ *flags = ALIGN_CUT|ALIGN_PAD;
+ *pad = ' ';
+
+ /* '!' = don't cut, '-' = right padding */
+ str = *data;
+ while (*str != '\0' && *str != ']' && !isdigit(*str)) {
+ if (*str == '!')
+ *flags &= ~ALIGN_CUT;
+ else if (*str == '-')
+ *flags |= ALIGN_RIGHT;
+ else if (*str == '.')
+ *flags &= ~ALIGN_PAD;
+ str++;
+ }
+ if (!isdigit(*str))
+ return FALSE; /* expecting number */
+
+ /* get the alignment size */
+ while (isdigit(*str)) {
+ *align = (*align) * 10 + (*str-'0');
+ str++;
+ }
+
+ /* get the pad character */
+ while (*str != '\0' && *str != ']') {
+ *pad = *str;
+ str++;
+ }
+
+ if (*str++ != ']') return FALSE;
+
+ *data = str;
+ return TRUE;
+}
+
+/* return the aligned text */
+static char *get_alignment(const char *text, int align, int flags, char pad)
+{
+ GString *str;
+ char *ret;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ str = g_string_new(text);
+
+ /* cut */
+ if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
+ g_string_truncate(str, align);
+
+ /* add pad characters */
+ if (flags & ALIGN_PAD) {
+ while (str->len < align) {
+ if (flags & ALIGN_RIGHT)
+ g_string_prepend_c(str, pad);
+ else
+ g_string_append_c(str, pad);
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used, int flags)
+{
+ static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
+ char command, *value;
+
+ char align_pad;
+ int align, align_flags;
+
+ char *nest_value;
+ int brackets, nest_free;
+
+ *free_ret = FALSE;
+
+ command = **cmd; (*cmd)++;
+ switch (command) {
+ case '[':
+ /* alignment */
+ if (!get_alignment_args(cmd, &align, &align_flags,
+ &align_pad) || **cmd == '\0') {
+ (*cmd)--;
+ return NULL;
+ }
+ break;
+ default:
+ command = 0;
+ (*cmd)--;
+ }
+
+ nest_free = FALSE; nest_value = NULL;
+ if (**cmd == '(') {
+ /* subvariable */
+ int toplevel = nested_orig_cmd == NULL;
+
+ if (toplevel) nested_orig_cmd = cmd;
+ (*cmd)++;
+ if (**cmd != '$') {
+ /* ... */
+ nest_value = *cmd;
+ } else {
+ (*cmd)++;
+ nest_value = parse_special(cmd, server, item, arglist,
+ &nest_free, arg_used,
+ flags);
+ }
+
+ while ((*nested_orig_cmd)[1] != '\0') {
+ (*nested_orig_cmd)++;
+ if (**nested_orig_cmd == ')')
+ break;
+ }
+ cmd = &nest_value;
+
+ if (toplevel) nested_orig_cmd = NULL;
+ }
+
+ if (**cmd != '{')
+ brackets = FALSE;
+ else {
+ /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
+ (*cmd)++;
+ brackets = TRUE;
+ }
+
+ value = get_special_value(cmd, server, item, arglist,
+ free_ret, arg_used, flags);
+ if (**cmd == '\0')
+ g_error("parse_special() : buffer overflow!");
+
+ if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
+ *arg_used = TRUE;
+
+ if (brackets) {
+ while (**cmd != '}' && (*cmd)[1] != '\0')
+ (*cmd)++;
+ }
+
+ if (nest_free) g_free(nest_value);
+
+ if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
+ /* alignment */
+ char *p;
+
+ if (value == NULL) return "";
+
+ p = get_alignment(value, align, align_flags, align_pad);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return p;
+ }
+
+ return value;
+}
+
+static void gstring_append_escaped(GString *str, const char *text)
+{
+ while (*text != '\0') {
+ if (*text == '%')
+ g_string_append_c(str, '%');
+ g_string_append_c(str, *text);
+ text++;
+ }
+}
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+ const char *data, int *arg_used, int flags)
+{
+ char code, **arglist, *ret;
+ GString *str;
+ int need_free;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+ g_return_val_if_fail(data != NULL, NULL);
+
+ /* create the argument list */
+ arglist = g_strsplit(data, " ", -1);
+
+ if (arg_used != NULL) *arg_used = FALSE;
+ code = 0;
+ str = g_string_new(NULL);
+ while (*cmd != '\0') {
+ if (code == '\\'){
+ switch (*cmd) {
+ case 't':
+ g_string_append_c(str, '\t');
+ break;
+ case 'n':
+ g_string_append_c(str, '\n');
+ break;
+ default:
+ g_string_append_c(str, *cmd);
+ }
+ code = 0;
+ } else if (code == '$') {
+ char *ret;
+
+ ret = parse_special((char **) &cmd, server, item,
+ arglist, &need_free, arg_used,
+ flags);
+ if (ret != NULL) {
+ if ((flags & PARSE_FLAG_ESCAPE_VARS) == 0)
+ g_string_append(str, ret);
+ else
+ gstring_append_escaped(str, ret);
+ if (need_free) g_free(ret);
+ }
+ code = 0;
+ } else {
+ if (*cmd == '\\' || *cmd == '$')
+ code = *cmd;
+ else
+ g_string_append_c(str, *cmd);
+ }
+
+ cmd++;
+ }
+ g_strfreev(arglist);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+#define is_split_char(str, start) \
+ ((str)[0] == ';' && ((start) == (str) || \
+ ((str)[-1] != '\\' && (str)[-1] != '$')))
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+ SERVER_REC *server, void *item)
+{
+ const char *cmdchars;
+ char *orig, *str, *start, *ret;
+ int arg_used, arg_used_ever;
+ GSList *commands;
+
+ commands = NULL;
+ arg_used_ever = FALSE;
+ cmdchars = settings_get_str("cmdchars");
+
+ /* get a list of all the commands to run */
+ orig = start = str = g_strdup(cmd);
+ do {
+ if (is_split_char(str, start))
+ *str++ = '\0';
+ else if (*str != '\0') {
+ str++;
+ continue;
+ }
+
+ ret = parse_special_string(start, server, item,
+ data, &arg_used, 0);
+ if (arg_used) arg_used_ever = TRUE;
+
+ if (strchr(cmdchars, *ret) == NULL) {
+ /* no command char - let's put it there.. */
+ char *old = ret;
+
+ ret = g_strdup_printf("%c%s", *cmdchars, old);
+ g_free(old);
+ }
+ commands = g_slist_append(commands, ret);
+ start = str;
+ } while (*start != '\0');
+
+ /* run the command, if no arguments were ever used, append all of them
+ after each command */
+ while (commands != NULL) {
+ ret = commands->data;
+
+ if (!arg_used_ever && *data != '\0') {
+ char *old = ret;
+
+ ret = g_strconcat(old, " ", data, NULL);
+ g_free(old);
+ }
+ signal_emit("send command", 3, ret, server, item);
+
+ g_free(ret);
+ commands = g_slist_remove(commands, commands->data);
+ }
+ g_free(orig);
+}
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func)
+{
+ history_func = func;
+}
+
+static void special_vars_signals_do(const char *text, int funccount,
+ SIGNAL_FUNC *funcs, int bind)
+{
+ char *ret;
+ int need_free;
+
+ while (*text != '\0') {
+ if (*text == '\\' && text[1] != '\0') {
+ text += 2;
+ } else if (*text == '$' && text[1] != '\0') {
+ text++;
+ ret = parse_special((char **) &text, NULL, NULL,
+ NULL, &need_free, NULL,
+ PARSE_FLAG_GETNAME);
+ if (ret != NULL) {
+ if (bind)
+ expando_bind(ret, funccount, funcs);
+ else
+ expando_unbind(ret, funccount, funcs);
+ if (need_free) g_free(ret);
+ }
+
+ }
+ else text++;
+ }
+}
+
+void special_vars_add_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs)
+{
+ special_vars_signals_do(text, funccount, funcs, TRUE);
+}
+
+void special_vars_remove_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs)
+{
+ special_vars_signals_do(text, funccount, funcs, FALSE);
+}
--- /dev/null
+#ifndef __SPECIAL_VARS_H
+#define __SPECIAL_VARS_H
+
+#include "signals.h"
+
+#define PARSE_FLAG_GETNAME 0x01 /* return argument name instead of it's value */
+#define PARSE_FLAG_ISSET_ANY 0x02 /* arg_used field specifies that at least one of the $variables was non-empty */
+#define PARSE_FLAG_ESCAPE_VARS 0x04 /* if any arguments/variables contain % chars, escape them with another % */
+
+typedef char* (*SPECIAL_HISTORY_FUNC)
+ (const char *text, void *item, int *free_ret);
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used, int flags);
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+ const char *data, int *arg_used, int flags);
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+ SERVER_REC *server, void *item);
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func);
+
+void special_vars_add_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs);
+void special_vars_remove_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs);
+
+#endif
--- /dev/null
+#ifndef __WINDOW_ITEM_DEF_H
+#define __WINDOW_ITEM_DEF_H
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _WI_ITEM_REC {
+#include "window-item-rec.h"
+};
+
+#endif
--- /dev/null
+/* WI_ITEM_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("CHANNEL/QUERY/xxx", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+GHashTable *module_data;
+
+void *window;
+STRUCT_SERVER_REC *server;
+char *name;
+
+time_t createtime;
+int data_level;
+char *hilight_color;
+
+#undef STRUCT_SERVER_REC
--- /dev/null
+/*
+ write-buffer.c : irssi
+
+ Copyright (C) 2001 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 "commands.h"
+#include "settings.h"
+#include "write-buffer.h"
+
+#define BUFFER_BLOCK_SIZE 2048
+
+typedef struct {
+ char *active_block;
+ int active_block_pos;
+
+ GSList *blocks;
+} BUFFER_REC;
+
+static GSList *empty_blocks;
+static GHashTable *buffers;
+static int block_count;
+
+static int write_buffer_max_blocks;
+static int timeout_tag;
+
+static void write_buffer_new_block(BUFFER_REC *rec)
+{
+ char *block;
+
+ if (empty_blocks == NULL)
+ block = g_malloc(BUFFER_BLOCK_SIZE);
+ else {
+ block = empty_blocks->data;
+ empty_blocks = g_slist_remove(empty_blocks, block);
+ }
+
+ block_count++;
+ rec->active_block = block;
+ rec->active_block_pos = 0;
+ rec->blocks = g_slist_append(rec->blocks, block);
+}
+
+int write_buffer(int handle, const void *data, int size)
+{
+ BUFFER_REC *rec;
+ const char *cdata = data;
+ int next_size;
+
+ if (write_buffer_max_blocks <= 0) {
+ /* no write buffer */
+ return write(handle, data, size);
+ }
+
+ if (size <= 0)
+ return size;
+
+ rec = g_hash_table_lookup(buffers, GINT_TO_POINTER(handle));
+ if (rec == NULL) {
+ rec = g_new0(BUFFER_REC, 1);
+ write_buffer_new_block(rec);
+ g_hash_table_insert(buffers, GINT_TO_POINTER(handle), rec);
+ }
+
+ while (size > 0) {
+ if (rec->active_block_pos == BUFFER_BLOCK_SIZE)
+ write_buffer_new_block(rec);
+
+ next_size = size < BUFFER_BLOCK_SIZE-rec->active_block_pos ?
+ size : BUFFER_BLOCK_SIZE-rec->active_block_pos;
+ memcpy(rec->active_block+rec->active_block_pos,
+ cdata, next_size);
+
+ rec->active_block_pos += next_size;
+ cdata += next_size;
+ size -= next_size;
+ }
+
+ if (block_count > write_buffer_max_blocks)
+ write_buffer_flush();
+
+ return size;
+}
+
+static int write_buffer_flush_rec(void *handlep, BUFFER_REC *rec)
+{
+ GSList *tmp;
+ int handle, size;
+
+ handle = GPOINTER_TO_INT(handlep);
+ for (tmp = rec->blocks; tmp != NULL; tmp = tmp->next) {
+ size = tmp->data != rec->active_block ? BUFFER_BLOCK_SIZE :
+ rec->active_block_pos;
+ write(handle, tmp->data, size);
+ }
+
+ empty_blocks = g_slist_concat(empty_blocks, rec->blocks);
+ g_free(rec);
+ return TRUE;
+}
+
+void write_buffer_flush(void)
+{
+ g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+ g_slist_free(empty_blocks);
+ empty_blocks = NULL;
+
+ g_hash_table_foreach_remove(buffers,
+ (GHRFunc) write_buffer_flush_rec, NULL);
+ block_count = 0;
+}
+
+static void read_settings(void)
+{
+ int msecs;
+
+ if (timeout_tag != -1)
+ g_source_remove(timeout_tag);
+
+ write_buffer_flush();
+
+ write_buffer_max_blocks = settings_get_int("write_buffer_kb") *
+ 1024/BUFFER_BLOCK_SIZE;
+
+ if (settings_get_int("write_buffer_mins") > 0) {
+ msecs = settings_get_int("write_buffer_mins")*60*1000;
+ timeout_tag = g_timeout_add(msecs,
+ (GSourceFunc) write_buffer_flush,
+ NULL);
+ }
+}
+
+static void cmd_flushbuffer(void)
+{
+ write_buffer_flush();
+}
+
+void write_buffer_init(void)
+{
+ settings_add_int("misc", "write_buffer_mins", 0);
+ settings_add_int("misc", "write_buffer_kb", 0);
+
+ buffers = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ empty_blocks = NULL;
+ block_count = 0;
+
+ timeout_tag = -1;
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ command_bind("flushbuffer", NULL, (SIGNAL_FUNC) cmd_flushbuffer);
+}
+
+void write_buffer_deinit(void)
+{
+ if (timeout_tag != -1)
+ g_source_remove(timeout_tag);
+
+ write_buffer_flush();
+ g_hash_table_destroy(buffers);
+
+ g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+ g_slist_free(empty_blocks);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ command_unbind("flushbuffer", (SIGNAL_FUNC) cmd_flushbuffer);
+}
--- /dev/null
+#ifndef __WRITE_BUFFER_H
+#define __WRITE_BUFFER_H
+
+int write_buffer(int handle, const void *data, int size);
+void write_buffer_flush(void);
+
+void write_buffer_init(void);
+void write_buffer_deinit(void);
+
+#endif
--- /dev/null
+noinst_LIBRARIES = libfe_common_core.a
+
+INCLUDES = \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_common_core_a_SOURCES = \
+ autorun.c \
+ chat-completion.c \
+ command-history.c \
+ completion.c \
+ fe-channels.c \
+ fe-common-core.c \
+ fe-core-commands.c \
+ fe-exec.c \
+ fe-expandos.c \
+ fe-help.c \
+ fe-ignore.c \
+ fe-ignore-messages.c \
+ fe-log.c \
+ fe-messages.c \
+ fe-modules.c \
+ fe-queries.c \
+ fe-server.c \
+ fe-settings.c \
+ formats.c \
+ hilight-text.c \
+ keyboard.c \
+ module-formats.c \
+ printtext.c \
+ themes.c \
+ translation.c \
+ window-activity.c \
+ window-commands.c \
+ window-items.c \
+ windows-layout.c \
+ fe-windows.c
+
+noinst_HEADERS = \
+ command-history.h \
+ chat-completion.h \
+ completion.h \
+ fe-channels.h \
+ fe-common-core.h \
+ fe-exec.h \
+ fe-messages.h \
+ fe-queries.h \
+ formats.h \
+ hilight-text.h \
+ keyboard.h \
+ module-formats.h \
+ module.h \
+ printtext.h \
+ themes.h \
+ translation.h \
+ window-items.h \
+ windows-layout.h \
+ fe-windows.h
--- /dev/null
+/*
+ autorun.c : irssi
+
+ Copyright (C) 1999-2000 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 "line-split.h"
+#include "special-vars.h"
+
+#include "fe-windows.h"
+
+static void sig_autorun(void)
+{
+ char tmpbuf[1024], *str, *path;
+ LINEBUF_REC *buffer = NULL;
+ int f, ret, recvlen;
+
+ /* open ~/.irssi/startup and run all commands in it */
+ path = g_strdup_printf("%s/.irssi/startup", g_get_home_dir());
+ f = open(path, O_RDONLY);
+ g_free(path);
+ if (f == -1) {
+ /* file not found */
+ return;
+ }
+
+ do {
+ recvlen = read(f, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, &str, &buffer);
+ if (ret > 0) eval_special_string(str, "", active_win->active_server, active_win->active);
+ } while (ret > 0);
+ line_split_free(buffer);
+
+ close(f);
+}
+
+void autorun_init(void)
+{
+ signal_add_last("irssi init finished", (SIGNAL_FUNC) sig_autorun);
+}
+
+void autorun_deinit(void)
+{
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_autorun);
+}
--- /dev/null
+/*
+ chat-completion.c : irssi
+
+ Copyright (C) 1999-2000 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 "commands.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "chatnets.h"
+#include "servers-setup.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "queries.h"
+#include "nicklist.h"
+
+#include "completion.h"
+#include "window-items.h"
+
+static int keep_privates_count, keep_publics_count;
+static int completion_lowercase;
+static const char *completion_char, *cmdchars;
+static GSList *global_lastmsgs;
+static int completion_auto, completion_strict;
+
+#define SERVER_LAST_MSG_ADD(server, nick) \
+ last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \
+ nick, TRUE, keep_privates_count)
+
+#define CHANNEL_LAST_MSG_ADD(channel, nick, own) \
+ last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \
+ nick, own, keep_publics_count)
+
+static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick)
+{
+ while (list != NULL) {
+ LAST_MSG_REC *rec = list->data;
+
+ if (g_strcasecmp(rec->nick, nick) == 0)
+ return rec;
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+static void last_msg_dec_owns(GSList *list)
+{
+ LAST_MSG_REC *rec;
+
+ while (list != NULL) {
+ rec = list->data;
+ if (rec->own) rec->own--;
+
+ list = list->next;
+ }
+}
+
+static void last_msg_add(GSList **list, const char *nick, int own, int max)
+{
+ LAST_MSG_REC *rec;
+
+ rec = last_msg_find(*list, nick);
+ if (rec != NULL) {
+ /* msg already exists, update it */
+ *list = g_slist_remove(*list, rec);
+ if (own)
+ rec->own = max;
+ else if (rec->own)
+ rec->own--;
+ } else {
+ rec = g_new(LAST_MSG_REC, 1);
+ rec->nick = g_strdup(nick);
+
+ if ((int)g_slist_length(*list) == max) {
+ *list = g_slist_remove(*list,
+ g_slist_last(*list)->data);
+ }
+
+ rec->own = own ? max : 0;
+ }
+ rec->time = time(NULL);
+
+ last_msg_dec_owns(*list);
+
+ *list = g_slist_prepend(*list, rec);
+}
+
+static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
+{
+ *list = g_slist_remove(*list, rec);
+
+ g_free(rec->nick);
+ g_free(rec);
+}
+
+void completion_last_message_add(const char *nick)
+{
+ g_return_if_fail(nick != NULL);
+
+ last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count);
+}
+
+void completion_last_message_remove(const char *nick)
+{
+ LAST_MSG_REC *rec;
+
+ g_return_if_fail(nick != NULL);
+
+ rec = last_msg_find(global_lastmsgs, nick);
+ if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec);
+}
+
+void completion_last_message_rename(const char *oldnick, const char *newnick)
+{
+ LAST_MSG_REC *rec;
+
+ g_return_if_fail(oldnick != NULL);
+ g_return_if_fail(newnick != NULL);
+
+ rec = last_msg_find(global_lastmsgs, oldnick);
+ if (rec != NULL) {
+ g_free(rec->nick);
+ rec->nick = g_strdup(newnick);
+ }
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ CHANNEL_REC *channel;
+ int own;
+
+ channel = channel_find(server, target);
+ if (channel != NULL) {
+ own = nick_match_msg(channel, msg, server->nick);
+ CHANNEL_LAST_MSG_ADD(channel, nick, own);
+ }
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ SERVER_LAST_MSG_ADD(server, nick);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nick;
+ char *p, *msgnick;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+ if (target == NULL) return;
+
+ channel = channel_find(server, target);
+ if (channel == NULL)
+ return;
+
+ /* channel msg - if first word in line is nick,
+ add it to lastmsgs */
+ p = strchr(msg, ' ');
+ if (p != NULL && p != msg) {
+ msgnick = g_strndup(msg, (int) (p-msg));
+ nick = nicklist_find(channel, msgnick);
+ if (nick == NULL && msgnick[1] != '\0') {
+ /* probably ':' or ',' or some other
+ char after nick, try without it */
+ msgnick[strlen(msgnick)-1] = '\0';
+ nick = nicklist_find(channel, msgnick);
+ }
+ g_free(msgnick);
+ if (nick != NULL && nick != channel->ownnick)
+ CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE);
+ }
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(target != NULL);
+
+ if (target != NULL && query_find(server, target) == NULL)
+ SERVER_LAST_MSG_ADD(server, target);
+}
+
+static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ LAST_MSG_REC *rec;
+
+ mchannel = MODULE_DATA(channel);
+ rec = last_msg_find(mchannel->lastmsgs, nick->nick);
+ if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec);
+}
+
+static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick,
+ const char *oldnick)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ LAST_MSG_REC *rec;
+
+ mchannel = MODULE_DATA(channel);
+ rec = last_msg_find(mchannel->lastmsgs, oldnick);
+ if (rec != NULL) {
+ g_free(rec->nick);
+ rec->nick = g_strdup(nick->nick);
+ }
+}
+
+static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2)
+{
+ return m1->time < m2->time ? 1 : -1;
+}
+
+/* Complete /MSG from specified server, or from
+ global_lastmsgs if server is NULL */
+static void completion_msg_server(GSList **list, SERVER_REC *server,
+ const char *nick, const char *prefix)
+{
+ LAST_MSG_REC *msg;
+ GSList *tmp;
+ int len;
+
+ g_return_if_fail(nick != NULL);
+
+ len = strlen(nick);
+ tmp = server == NULL ? global_lastmsgs :
+ ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs;
+ for (; tmp != NULL; tmp = tmp->next) {
+ LAST_MSG_REC *rec = tmp->data;
+
+ if (len != 0 && g_strncasecmp(rec->nick, nick, len) != 0)
+ continue;
+
+ msg = g_new(LAST_MSG_REC, 1);
+ msg->time = rec->time;
+ msg->nick = prefix == NULL || *prefix == '\0' ?
+ g_strdup(rec->nick) :
+ g_strconcat(prefix, " ", rec->nick, NULL);
+ *list = g_slist_insert_sorted(*list, msg,
+ (GCompareFunc) last_msg_cmp);
+ }
+}
+
+/* convert list of LAST_MSG_REC's to list of char* nicks. */
+static GList *convert_msglist(GSList *msglist)
+{
+ GList *list;
+
+ list = NULL;
+ while (msglist != NULL) {
+ LAST_MSG_REC *rec = msglist->data;
+
+ list = g_list_append(list, rec->nick);
+ msglist = g_slist_remove(msglist, rec);
+ g_free(rec);
+ }
+
+ return list;
+}
+
+/* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
+static GList *completion_msg(SERVER_REC *win_server,
+ SERVER_REC *find_server,
+ const char *nick, const char *prefix)
+{
+ GSList *tmp, *list;
+ char *newprefix;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+ if (servers == NULL) return NULL;
+
+ list = NULL;
+ if (find_server != NULL) {
+ completion_msg_server(&list, find_server, nick, prefix);
+ return convert_msglist(list);
+ }
+
+ completion_msg_server(&list, NULL, nick, prefix);
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ if (rec == win_server)
+ newprefix = g_strdup(prefix);
+ else {
+ newprefix = prefix == NULL ?
+ g_strdup_printf("-%s", rec->tag) :
+ g_strdup_printf("%s -%s", prefix, rec->tag);
+ }
+
+ completion_msg_server(&list, rec, nick, newprefix);
+ g_free_not_null(newprefix);
+ }
+
+ return convert_msglist(list);
+}
+
+static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel,
+ const char *nick, const char *suffix)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ GSList *tmp;
+ GList *ownlist;
+ char *str;
+ int len;
+
+ /* go through the last x nicks who have said something in the channel.
+ nicks of all the "own messages" are placed before others */
+ ownlist = NULL;
+ len = strlen(nick);
+ mchannel = MODULE_DATA(channel);
+ for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) {
+ LAST_MSG_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->nick, nick, len) == 0 &&
+ glist_find_icase_string(*outlist, rec->nick) == NULL) {
+ str = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase) g_strdown(str);
+ if (rec->own)
+ ownlist = g_list_append(ownlist, str);
+ else
+ *outlist = g_list_append(*outlist, str);
+ }
+ }
+
+ *outlist = g_list_concat(ownlist, *outlist);
+}
+
+static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
+ const char *nick,
+ const char *suffix)
+{
+ GSList *nicks, *tmp;
+ GList *list;
+ char *tnick, *str, *in, *out;
+ int len, str_len, tmplen;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ list = NULL;
+
+ /* get all nicks from current channel, strip non alnum chars,
+ compare again and add to completion list on matching */
+ len = strlen(nick);
+ nicks = nicklist_getnicks(channel);
+
+ str_len = 80; str = g_malloc(str_len+1);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ tmplen = strlen(rec->nick);
+ if (tmplen > str_len) {
+ str_len = tmplen*2;
+ str = g_realloc(str, str_len+1);
+ }
+
+ /* remove non alnum chars from nick */
+ in = rec->nick; out = str;
+ while (*in != '\0') {
+ if (isalnum(*in))
+ *out++ = *in;
+ in++;
+ }
+ *out = '\0';
+
+ /* add to list if 'cleaned' nick matches */
+ if (g_strncasecmp(str, nick, len) == 0) {
+ tnick = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase)
+ g_strdown(tnick);
+
+ if (glist_find_icase_string(list, tnick) == NULL)
+ list = g_list_append(list, tnick);
+ else
+ g_free(tnick);
+ }
+
+ }
+ g_free(str);
+ g_slist_free(nicks);
+
+ return list;
+}
+
+static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick,
+ const char *suffix)
+{
+ GSList *nicks, *tmp;
+ GList *list;
+ char *str;
+ int len;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+ if (*nick == '\0') return NULL;
+
+ if (suffix != NULL && *suffix == '\0')
+ suffix = NULL;
+
+ /* put first the nicks who have recently said something */
+ list = NULL;
+ complete_from_nicklist(&list, channel, nick, suffix);
+
+ /* and add the rest of the nicks too */
+ len = strlen(nick);
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->nick, nick, len) == 0 &&
+ rec != channel->ownnick) {
+ str = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase)
+ g_strdown(str);
+ if (glist_find_icase_string(list, str) == NULL)
+ list = g_list_append(list, str);
+ else
+ g_free(str);
+ }
+ }
+ g_slist_free(nicks);
+
+ /* remove non alphanum chars from nick and search again in case
+ list is still NULL ("foo<tab>" would match "_foo_" f.e.) */
+ if (!completion_strict)
+ list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix));
+ return list;
+}
+
+/* append all strings in list2 to list1 that already aren't there and
+ free list2 */
+static GList *completion_joinlist(GList *list1, GList *list2)
+{
+ GList *old;
+
+ old = list2;
+ while (list2 != NULL) {
+ if (!glist_find_icase_string(list1, list2->data))
+ list1 = g_list_append(list1, list2->data);
+ else
+ g_free(list2->data);
+
+ list2 = list2->next;
+ }
+
+ g_list_free(old);
+ return list1;
+}
+
+GList *completion_get_channels(SERVER_REC *server, const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+ g_return_val_if_fail(*word != '\0', NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ /* first get the joined channels */
+ tmp = server == NULL ? NULL : server->channels;
+ for (; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->name, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->name));
+ }
+
+ /* get channels from setup */
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->name, word, len) == 0 &&
+ glist_find_icase_string(list, rec->name) == NULL)
+ list = g_list_append(list, g_strdup(rec->name));
+
+ }
+
+ return list;
+}
+
+static void complete_window_nicks(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart)
+{
+ CHANNEL_REC *channel;
+ GList *tmplist;
+ GSList *tmp;
+ const char *nicksuffix;
+
+ nicksuffix = *linestart != '\0' ? NULL : completion_char;
+
+ channel = CHANNEL(window->active);
+
+ /* first the active channel */
+ if (channel != NULL) {
+ tmplist = completion_channel_nicks(channel, word, nicksuffix);
+ *list = completion_joinlist(*list, tmplist);
+ }
+
+ if (nicksuffix != NULL) {
+ /* completing nick at the start of line - probably answering
+ to some other nick, don't even try to complete from
+ non-active channels */
+ return;
+ }
+
+ /* then the rest */
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ channel = CHANNEL(tmp->data);
+ if (channel != NULL && tmp->data != window->active) {
+ tmplist = completion_channel_nicks(channel, word,
+ nicksuffix);
+ *list = completion_joinlist(*list, tmplist);
+ }
+ }
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart)
+{
+ SERVER_REC *server;
+ CHANNEL_REC *channel;
+ QUERY_REC *query;
+ char *prefix;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(linestart != NULL);
+
+ server = window->active_server;
+ if (server == NULL && servers != NULL)
+ server = servers->data;
+
+ if (server != NULL && server->ischannel(word)) {
+ /* probably completing a channel name */
+ *list = completion_get_channels(window->active_server, word);
+ return;
+ }
+
+ server = window->active_server;
+ if (server == NULL || !server->connected)
+ return;
+
+ if (*linestart == '\0' && *word == '\0') {
+ /* pressed TAB at the start of line - add /MSG */
+ prefix = g_strdup_printf("%cmsg", *cmdchars);
+ *list = completion_msg(server, NULL, "", prefix);
+ if (*list == NULL)
+ *list = g_list_append(*list, g_strdup(prefix));
+ g_free(prefix);
+
+ signal_stop();
+ return;
+ }
+
+ channel = CHANNEL(window->active);
+ query = QUERY(window->active);
+ if (channel == NULL && query != NULL &&
+ g_strncasecmp(word, query->name, strlen(word)) == 0) {
+ /* completion in query */
+ *list = g_list_append(*list, g_strdup(query->name));
+ } else if (channel != NULL) {
+ /* nick completion .. we could also be completing a nick
+ after /MSG from nicks in channel */
+ complete_window_nicks(list, window, word, linestart);
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+static SERVER_REC *line_get_server(const char *line)
+{
+ SERVER_REC *server;
+ char *tag, *ptr;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ if (*line != '-') return NULL;
+
+ /* -option found - should be server tag */
+ tag = g_strdup(line+1);
+ ptr = strchr(tag, ' ');
+ if (ptr != NULL) *ptr = '\0';
+
+ server = server_find_tag(tag);
+
+ g_free(tag);
+ return server;
+}
+
+static void sig_complete_msg(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ SERVER_REC *server, *msgserver;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ server = window->active_server;
+ if (server == NULL || !server->connected)
+ return;
+
+ msgserver = line_get_server(line);
+ *list = completion_msg(server, msgserver, word, NULL);
+ if (*list != NULL) signal_stop();
+}
+
+GList *completion_get_chatnets(const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ CHATNET_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->name, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->name));
+ }
+
+ return list;
+}
+
+GList *completion_get_servers(const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->address, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->address));
+ }
+
+ return list;
+}
+
+static void sig_complete_connect(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_chatnets(word);
+ *list = g_list_concat(*list, completion_get_servers(word));
+ if (*list != NULL) signal_stop();
+}
+
+/* expand \n, \t and \\ */
+static char *expand_escapes(const char *line, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ char *ptr, *ret;
+
+ ret = ptr = g_malloc(strlen(line)+1);
+ for (; *line != '\0'; line++) {
+ if (*line != '\\') {
+ *ptr++ = *line;
+ continue;
+ }
+
+ line++;
+ if (*line == '\0') {
+ *ptr++ = '\\';
+ break;
+ }
+
+ switch (*line) {
+ case 'n':
+ /* newline .. we need to send another "send text"
+ event to handle it (or actually the text before
+ the newline..) */
+ *ptr = '\0';
+ signal_emit("send text", 3, ret, server, item);
+ ptr = ret;
+ break;
+ case 't':
+ *ptr++ = '\t';
+ break;
+ case '\\':
+ *ptr++ = '\\';
+ break;
+ default:
+ *ptr++ = '\\';
+ *ptr++ = *line;
+ break;
+ }
+ }
+
+ *ptr = '\0';
+ return ret;
+}
+
+static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *channel;
+ GList *comp;
+ char *line, *str, *ptr, comp_char;
+
+ g_return_if_fail(data != NULL);
+ if (item == NULL) return;
+
+ line = settings_get_bool("expand_escapes") ?
+ expand_escapes(data, server, item) : g_strdup(data);
+ comp_char = *completion_char;
+
+ /* check for automatic nick completion */
+ ptr = NULL;
+ comp = NULL;
+ channel = CHANNEL(item);
+
+ if (completion_auto && channel != NULL && comp_char != '\0') {
+ ptr = strchr(line, comp_char);
+ if (ptr != NULL) {
+ *ptr++ = '\0';
+ if (nicklist_find(channel, line) == NULL) {
+ comp = completion_channel_nicks(channel,
+ line, NULL);
+ }
+ }
+ }
+
+ str = g_strdup_printf(ptr == NULL ? "%s %s" : "%s %s%c%s", item->name,
+ comp != NULL ? (char *) comp->data : line,
+ comp_char, ptr);
+ signal_emit("command msg", 3, str, server, item);
+
+ g_free(str);
+ g_free(line);
+
+ if (comp != NULL) {
+ g_list_foreach(comp, (GFunc) g_free, NULL);
+ g_list_free(comp);
+ }
+
+ signal_stop();
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ while (mserver->lastmsgs)
+ last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data);
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ MODULE_CHANNEL_REC *mchannel;
+
+ g_return_if_fail(channel != NULL);
+
+ mchannel = MODULE_DATA(channel);
+ while (mchannel->lastmsgs != NULL) {
+ last_msg_destroy(&mchannel->lastmsgs,
+ mchannel->lastmsgs->data);
+ }
+}
+
+static void read_settings(void)
+{
+ keep_privates_count = settings_get_int("completion_keep_privates");
+ keep_publics_count = settings_get_int("completion_keep_publics");
+ completion_lowercase = settings_get_bool("completion_nicks_lowercase");
+ completion_char = settings_get_str("completion_char");
+ cmdchars = settings_get_str("cmdchars");
+ completion_auto = settings_get_bool("completion_auto");
+ completion_strict = settings_get_bool("completion_strict");
+}
+
+void chat_completion_init(void)
+{
+ settings_add_str("completion", "completion_char", ":");
+ settings_add_bool("completion", "completion_auto", FALSE);
+ settings_add_int("completion", "completion_keep_publics", 50);
+ settings_add_int("completion", "completion_keep_privates", 10);
+ settings_add_bool("completion", "expand_escapes", FALSE);
+ settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
+ settings_add_bool("completion", "completion_strict", FALSE);
+
+ read_settings();
+ signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+ signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+ signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+ signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+ signal_add("send text", (SIGNAL_FUNC) event_text);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void chat_completion_deinit(void)
+{
+ while (global_lastmsgs != NULL)
+ last_msg_destroy(&global_lastmsgs, global_lastmsgs->data);
+
+ signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+ signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+ signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+ signal_remove("send text", (SIGNAL_FUNC) event_text);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __CHAT_COMPLETION_H
+#define __CHAT_COMPLETION_H
+
+void completion_last_message_add(const char *nick);
+void completion_last_message_remove(const char *nick);
+void completion_last_message_rename(const char *oldnick, const char *newnick);
+
+#endif
--- /dev/null
+/*
+ command-history.c : irssi
+
+ Copyright (C) 1999 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 "misc.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+
+/* command history */
+static GList *history, *history_pos;
+static int history_lines, history_over_counter;
+static int window_history;
+
+void command_history_add(WINDOW_REC *window, const char *text)
+{
+ GList **phistory, *link;
+ int *phistory_lines;
+
+ g_return_if_fail(text != NULL);
+
+ if (window_history) {
+ /* window specific command history */
+ phistory = &window->history;
+ phistory_lines = &window->history_lines;
+ } else {
+ /* global command history */
+ phistory = &history;
+ phistory_lines = &history_lines;
+ }
+
+ if (settings_get_int("max_command_history") < 1 || *phistory_lines < settings_get_int("max_command_history"))
+ (*phistory_lines)++;
+ else {
+ link = *phistory;
+ g_free(link->data);
+ *phistory = g_list_remove_link(*phistory, link);
+ g_list_free_1(link);
+ }
+
+ *phistory = g_list_append(*phistory, g_strdup(text));
+}
+
+const char *command_history_prev(WINDOW_REC *window, const char *text)
+{
+ GList *pos, **phistory_pos;
+ int *phistory_over_counter;
+
+ if (window_history) {
+ phistory_pos = &window->history_pos;
+ phistory_over_counter = &window->history_over_counter;
+ } else {
+ phistory_pos = &history_pos;
+ phistory_over_counter = &history_over_counter;
+ }
+
+ pos = *phistory_pos;
+ if (*phistory_pos != NULL) {
+ *phistory_pos = (*phistory_pos)->prev;
+ if (*phistory_pos == NULL)
+ (*phistory_over_counter)++;
+ } else {
+ *phistory_pos = g_list_last(window_history ?
+ window->history : history);
+ }
+
+ if (*text != '\0' &&
+ (pos == NULL || strcmp(pos->data, text) != 0)) {
+ /* save the old entry to history */
+ command_history_add(window, text);
+ }
+
+ return *phistory_pos == NULL ? "" : (*phistory_pos)->data;
+}
+
+const char *command_history_next(WINDOW_REC *window, const char *text)
+{
+ GList *pos, **phistory_pos;
+ int *phistory_over_counter;
+
+ if (window_history) {
+ phistory_pos = &window->history_pos;
+ phistory_over_counter = &window->history_over_counter;
+ } else {
+ phistory_pos = &history_pos;
+ phistory_over_counter = &history_over_counter;
+ }
+
+ pos = *phistory_pos;
+
+ if (pos != NULL)
+ *phistory_pos = (*phistory_pos)->next;
+ else if (*phistory_over_counter > 0) {
+ (*phistory_over_counter)--;
+ *phistory_pos = window_history ? window->history : history;
+ }
+
+ if (*text != '\0' &&
+ (pos == NULL || strcmp(pos->data, text) != 0)) {
+ /* save the old entry to history */
+ command_history_add(window, text);
+ }
+ return *phistory_pos == NULL ? "" : (*phistory_pos)->data;
+}
+
+void command_history_clear_pos(WINDOW_REC *window)
+{
+ window->history_over_counter = 0;
+ window->history_pos = NULL;
+ history_over_counter = 0;
+ history_pos = NULL;
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+ g_list_foreach(window->history, (GFunc) g_free, NULL);
+ g_list_free(window->history);
+}
+
+static char *special_history_func(const char *text, void *item, int *free_ret)
+{
+ WINDOW_REC *window;
+ GList *tmp;
+ char *findtext, *ret;
+
+ window = item == NULL ? active_win : window_item_window(item);
+
+ findtext = g_strdup_printf("*%s*", text);
+ ret = NULL;
+
+ tmp = window_history ? window->history : history;
+ for (; tmp != NULL; tmp = tmp->next) {
+ const char *line = tmp->data;
+
+ if (match_wildcards(findtext, line)) {
+ *free_ret = TRUE;
+ ret = g_strdup(line);
+ }
+ }
+ g_free(findtext);
+
+ return ret;
+}
+
+static void read_settings(void)
+{
+ window_history = settings_get_bool("window_history");
+}
+
+void command_history_init(void)
+{
+ settings_add_int("history", "max_command_history", 100);
+ settings_add_bool("history", "window_history", FALSE);
+
+ special_history_func_set(special_history_func);
+
+ history_lines = 0; history_over_counter = 0;
+ history = NULL; history_pos = NULL;
+
+ read_settings();
+ signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void command_history_deinit(void)
+{
+ signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ g_list_foreach(history, (GFunc) g_free, NULL);
+ g_list_free(history);
+}
--- /dev/null
+#ifndef __COMMAND_HISTORY_H
+#define __COMMAND_HISTORY_H
+
+#include "fe-windows.h"
+
+void command_history_init(void);
+void command_history_deinit(void);
+
+void command_history_add(WINDOW_REC *window, const char *text, int prepend);
+
+const char *command_history_prev(WINDOW_REC *window, const char *text);
+const char *command_history_next(WINDOW_REC *window, const char *text);
+
+void command_history_clear_pos(WINDOW_REC *window);
+
+#endif
--- /dev/null
+/*
+ completion.c : irssi
+
+ Copyright (C) 2000 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 "commands.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "completion.h"
+
+#define wordreplace_find(word) \
+ iconfig_list_find("replaces", "text", word, "replace")
+
+#define completion_find(completion) \
+ iconfig_list_find("completions", "short", completion, "long")
+
+static GList *complist; /* list of commands we're currently completing */
+static char *last_line;
+static int last_want_space, last_line_pos;
+
+#define isseparator_notspace(c) \
+ ((c) == ',')
+
+#define isseparator(c) \
+ (isspace((int) (c)) || isseparator_notspace(c))
+
+void chat_completion_init(void);
+void chat_completion_deinit(void);
+
+/* Return whole word at specified position in string */
+char *get_word_at(const char *str, int pos, char **startpos)
+{
+ const char *start, *end;
+
+ g_return_val_if_fail(str != NULL, NULL);
+ g_return_val_if_fail(pos >= 0, NULL);
+
+ /* get previous word if char at `pos' is space */
+ start = str+pos;
+ while (start > str && isseparator(start[-1])) start--;
+
+ end = start;
+ while (start > str && !isseparator(start[-1])) start--;
+ while (*end != '\0' && !isseparator(*end)) end++;
+ while (*end != '\0' && isseparator_notspace(*end)) end++;
+
+ *startpos = (char *) start;
+ return g_strndup(start, (int) (end-start));
+}
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos)
+{
+ GString *result;
+ const char *replace;
+ char *word, *wordstart, *ret;
+ int startpos;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(pos != NULL, NULL);
+
+ word = get_word_at(line, *pos, &wordstart);
+ startpos = (int) (wordstart-line);
+
+ result = g_string_new(line);
+ g_string_erase(result, startpos, strlen(word));
+
+ /* check for words in autocompletion list */
+ replace = wordreplace_find(word);
+ if (replace == NULL) {
+ ret = NULL;
+ g_string_free(result, TRUE);
+ } else {
+ *pos = startpos+strlen(replace);
+
+ g_string_insert(result, startpos, replace);
+ ret = result->str;
+ g_string_free(result, FALSE);
+ }
+
+ g_free(word);
+ return ret;
+}
+
+static void free_completions(void)
+{
+ complist = g_list_first(complist);
+
+ g_list_foreach(complist, (GFunc) g_free, NULL);
+ g_list_free(complist);
+ complist = NULL;
+
+ g_free_and_null(last_line);
+}
+
+/* manual word completion - called when TAB is pressed */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos)
+{
+ static int startpos = 0, wordlen = 0;
+
+ GString *result;
+ char *word, *wordstart, *linestart, *ret;
+ int want_space;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(pos != NULL, NULL);
+
+ if (complist != NULL && *pos == last_line_pos &&
+ strcmp(line, last_line) == 0) {
+ /* complete from old list */
+ complist = complist->next != NULL ? complist->next :
+ g_list_first(complist);
+ want_space = last_want_space;
+ } else {
+ /* get new completion list */
+ free_completions();
+
+ /* get the word we want to complete */
+ word = get_word_at(line, *pos, &wordstart);
+ startpos = (int) (wordstart-line);
+ wordlen = strlen(word);
+
+ /* get the start of line until the word we're completing */
+ if (isseparator(*line)) {
+ /* empty space at the start of line */
+ if (wordstart == line)
+ wordstart += strlen(wordstart);
+ } else {
+ while (wordstart > line && isseparator(wordstart[-1]))
+ wordstart--;
+ }
+ linestart = g_strndup(line, (int) (wordstart-line));
+
+ /* completions usually add space after the word, that makes
+ things a bit harder. When continuing a completion
+ "/msg nick1 "<tab> we have to cycle to nick2, etc.
+ BUT if we start completion with "/msg "<tab>, we don't
+ want to complete the /msg word, but instead complete empty
+ word with /msg being in linestart. */
+ if (*pos > 0 && line[*pos-1] == ' ') {
+ char *old;
+
+ old = linestart;
+ linestart = *linestart == '\0' ?
+ g_strdup(word) :
+ g_strconcat(linestart, " ", word, NULL);
+ g_free(old);
+
+ g_free(word);
+ word = g_strdup("");
+ startpos = strlen(linestart)+1;
+ wordlen = 0;
+ }
+
+ want_space = TRUE;
+ signal_emit("complete word", 5, &complist, window, word, linestart, &want_space);
+ last_want_space = want_space;
+
+ g_free(linestart);
+ g_free(word);
+ }
+
+ if (complist == NULL)
+ return NULL;
+
+ /* word completed */
+ *pos = startpos+strlen(complist->data);
+
+ /* replace the word in line - we need to return
+ a full new line */
+ result = g_string_new(line);
+ g_string_erase(result, startpos, wordlen);
+ g_string_insert(result, startpos, complist->data);
+
+ if (want_space) {
+ if (!isseparator(result->str[*pos]))
+ g_string_insert_c(result, *pos, ' ');
+ (*pos)++;
+ }
+
+ wordlen = strlen(complist->data);
+ last_line_pos = *pos;
+ g_free_not_null(last_line);
+ last_line = g_strdup(result->str);
+
+ ret = result->str;
+ g_string_free(result, FALSE);
+ return ret;
+}
+
+GList *list_add_file(GList *list, const char *name)
+{
+ struct stat statbuf;
+ char *fname;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ fname = convert_home(name);
+ if (stat(fname, &statbuf) == 0) {
+ list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) :
+ g_strconcat(name, G_DIR_SEPARATOR_S, NULL));
+ }
+
+ g_free(fname);
+ return list;
+}
+
+GList *filename_complete(const char *path)
+{
+ GList *list;
+ DIR *dirp;
+ struct dirent *dp;
+ char *realpath, *dir, *basename, *name;
+ int len;
+
+ g_return_val_if_fail(path != NULL, NULL);
+
+ list = NULL;
+
+ /* get directory part of the path - expand ~/ */
+ realpath = convert_home(path);
+ dir = g_dirname(realpath);
+ g_free(realpath);
+
+ /* open directory for reading */
+ dirp = opendir(dir);
+ g_free(dir);
+ if (dirp == NULL) return NULL;
+
+ dir = g_dirname(path);
+ if (*dir == G_DIR_SEPARATOR && dir[1] == '\0')
+ *dir = '\0'; /* completing file in root directory */
+ basename = g_basename(path);
+ len = strlen(basename);
+
+ /* add all files in directory to completion list */
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.') {
+ if (dp->d_name[1] == '\0' ||
+ (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
+ continue; /* skip . and .. */
+
+ if (basename[0] != '.')
+ continue;
+ }
+
+ if (len == 0 || strncmp(dp->d_name, basename, len) == 0) {
+ name = g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name);
+ list = list_add_file(list, name);
+ g_free(name);
+ }
+ }
+ closedir(dirp);
+
+ g_free(dir);
+ return list;
+}
+
+static GList *completion_get_settings(const char *key)
+{
+ GList *complist;
+ GSList *tmp, *sets;
+ int len;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ sets = settings_get_sorted();
+
+ len = strlen(key);
+ complist = NULL;
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->key, key, len) == 0)
+ complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
+ }
+ g_slist_free(sets);
+ return complist;
+}
+
+static GList *completion_get_bool_settings(const char *key)
+{
+ GList *complist;
+ GSList *tmp, *sets;
+ int len;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ sets = settings_get_sorted();
+
+ len = strlen(key);
+ complist = NULL;
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if (rec->type == SETTING_TYPE_BOOLEAN &&
+ g_strncasecmp(rec->key, key, len) == 0)
+ complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp);
+ }
+ g_slist_free(sets);
+ return complist;
+}
+
+static GList *completion_get_aliases(const char *alias, char cmdchar)
+{
+ CONFIG_NODE *node;
+ GList *complist;
+ GSList *tmp;
+ char *word;
+ int len;
+
+ g_return_val_if_fail(alias != NULL, NULL);
+
+ /* get list of aliases from mainconfig */
+ node = iconfig_node_traverse("aliases", FALSE);
+ tmp = node == NULL ? NULL : node->value;
+
+ len = strlen(alias);
+ complist = NULL;
+ for (; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (g_strncasecmp(node->key, alias, len) == 0) {
+ word = g_strdup_printf("%c%s", cmdchar, node->key);
+ /* add matching alias to completion list, aliases will
+ be appended after command completions and kept in
+ uppercase to show it's an alias */
+ if (glist_find_icase_string(complist, word) == NULL)
+ complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
+ else
+ g_free(word);
+ }
+ }
+ return complist;
+}
+
+static GList *completion_get_commands(const char *cmd, char cmdchar)
+{
+ GList *complist;
+ GSList *tmp;
+ char *word;
+ int len;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ len = strlen(cmd);
+ complist = NULL;
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (strchr(rec->cmd, ' ') != NULL)
+ continue;
+
+ if (g_strncasecmp(rec->cmd, cmd, len) == 0) {
+ word = cmdchar == '\0' ? g_strdup(rec->cmd) :
+ g_strdup_printf("%c%s", cmdchar, rec->cmd);
+ if (glist_find_icase_string(complist, word) == NULL)
+ complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp);
+ else
+ g_free(word);
+ }
+ }
+ return complist;
+}
+
+static GList *completion_get_subcommands(const char *cmd)
+{
+ GList *complist;
+ GSList *tmp;
+ char *spacepos;
+ int len, skip;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ /* get the number of chars to skip at the start of command. */
+ spacepos = strrchr(cmd, ' ');
+ skip = spacepos == NULL ? strlen(cmd)+1 :
+ ((int) (spacepos-cmd) + 1);
+
+ len = strlen(cmd);
+ complist = NULL;
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if ((int)strlen(rec->cmd) < len)
+ continue;
+
+ if (strchr(rec->cmd+len, ' ') != NULL)
+ continue;
+
+ if (g_strncasecmp(rec->cmd, cmd, len) == 0)
+ complist = g_list_insert_sorted(complist, g_strdup(rec->cmd+skip), (GCompareFunc) g_istr_cmp);
+ }
+ return complist;
+}
+
+GList *completion_get_options(const char *cmd, const char *option)
+{
+ COMMAND_REC *rec;
+ GList *list;
+ char **tmp;
+ int len;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+ g_return_val_if_fail(option != NULL, NULL);
+
+ rec = command_find(cmd);
+ if (rec == NULL || rec->options == NULL) return NULL;
+
+ list = NULL;
+ len = strlen(option);
+ for (tmp = rec->options; *tmp != NULL; tmp++) {
+ const char *optname = *tmp + iscmdtype(**tmp);
+
+ if (len == 0 || g_strncasecmp(optname, option, len) == 0)
+ list = g_list_append(list, g_strconcat("-", optname, NULL));
+ }
+
+ return list;
+}
+
+/* split the line to command and arguments */
+static char *line_get_command(const char *line, char **args, int aliases)
+{
+ const char *ptr, *cmdargs;
+ char *cmd, *checkcmd;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(args != NULL, NULL);
+
+ cmd = checkcmd = NULL; *args = "";
+ cmdargs = NULL; ptr = line;
+
+ do {
+ ptr = strchr(ptr, ' ');
+ if (ptr == NULL) {
+ checkcmd = g_strdup(line);
+ cmdargs = "";
+ } else {
+ checkcmd = g_strndup(line, (int) (ptr-line));
+
+ while (isspace(*ptr)) ptr++;
+ cmdargs = ptr;
+ }
+
+ if (aliases ? !alias_find(checkcmd) :
+ !command_find(checkcmd)) {
+ /* not found, use the previous */
+ g_free(checkcmd);
+ break;
+ }
+
+ /* found, check if it has subcommands */
+ g_free_not_null(cmd);
+ if (!aliases)
+ cmd = checkcmd;
+ else {
+ cmd = g_strdup(alias_find(checkcmd));
+ g_free(checkcmd);
+ }
+ *args = (char *) cmdargs;
+ } while (ptr != NULL);
+
+ return cmd;
+}
+
+static char *expand_aliases(const char *line)
+{
+ char *cmd, *args, *ret;
+
+ g_return_val_if_fail(line != NULL, NULL);
+
+ cmd = line_get_command(line, &args, TRUE);
+ if (cmd == NULL) return g_strdup(line);
+ if (*args == '\0') return cmd;
+
+ ret = g_strconcat(cmd, " ", args, NULL);
+ g_free(cmd);
+ return ret;
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart, int *want_space)
+{
+ const char *newword, *cmdchars;
+ char *signal, *cmd, *args, *line;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(linestart != NULL);
+
+ /* check against "completion words" list */
+ newword = completion_find(word);
+ if (newword != NULL) {
+ *list = g_list_append(*list, g_strdup(newword));
+
+ signal_stop();
+ return;
+ }
+
+ /* command completion? */
+ cmdchars = settings_get_str("cmdchars");
+ if (strchr(cmdchars, *word) && *linestart == '\0') {
+ /* complete /command */
+ *list = completion_get_commands(word+1, *word);
+
+ /* complete aliases, too */
+ *list = g_list_concat(*list,
+ completion_get_aliases(word+1, *word));
+
+ if (*list != NULL) signal_stop();
+ return;
+ }
+
+ /* check only for /command completions from now on */
+ cmdchars = strchr(cmdchars, *linestart);
+ if (cmdchars == NULL) return;
+
+ /* check if there's aliases */
+ line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
+ expand_aliases(linestart+1);
+
+ cmd = line_get_command(line, &args, FALSE);
+ if (cmd == NULL) {
+ g_free(line);
+ return;
+ }
+
+ /* we're completing -option? */
+ if (*word == '-') {
+ *list = completion_get_options(cmd, word+1);
+ g_free(cmd);
+ g_free(line);
+ return;
+ }
+
+ /* complete parameters */
+ signal = g_strconcat("complete command ", cmd, NULL);
+ signal_emit(signal, 5, list, window, word, args, want_space);
+
+ if (command_have_sub(line)) {
+ /* complete subcommand */
+ g_free(cmd);
+ cmd = g_strconcat(line, " ", word, NULL);
+ *list = g_list_concat(completion_get_subcommands(cmd), *list);
+
+ if (*list != NULL) signal_stop();
+ }
+
+ g_free(signal);
+ g_free(cmd);
+
+ g_free(line);
+}
+
+static void sig_complete_set(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') return;
+
+ *list = completion_get_settings(word);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_toggle(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') return;
+
+ *list = completion_get_bool_settings(word);
+ if (*list != NULL) signal_stop();
+}
+
+/* first argument of command is file name - complete it */
+static void sig_complete_filename(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') return;
+
+ *list = filename_complete(word);
+ if (*list != NULL) {
+ *want_space = FALSE;
+ signal_stop();
+ }
+}
+
+/* first argument of command is .. command :) (/HELP command) */
+static void sig_complete_command(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ char *cmd;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0') {
+ /* complete base command */
+ *list = completion_get_commands(word, '\0');
+ } else if (command_have_sub(line)) {
+ /* complete subcommand */
+ cmd = g_strconcat(line, " ", word, NULL);
+ *list = completion_get_subcommands(cmd);
+ g_free(cmd);
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+void completion_init(void)
+{
+ complist = NULL;
+ last_line = NULL; last_line_pos = -1;
+
+ chat_completion_init();
+
+ signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set);
+ signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+ signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command run", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command);
+}
+
+void completion_deinit(void)
+{
+ free_completions();
+
+ chat_completion_deinit();
+
+ signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set);
+ signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+ signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command run", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command);
+}
+
+
--- /dev/null
+#ifndef __COMPLETION_H
+#define __COMPLETION_H
+
+#include "window-items.h"
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos);
+/* manual word completion - called when TAB is pressed */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos);
+
+GList *filename_complete(const char *path);
+
+void completion_init(void);
+void completion_deinit(void);
+
+#endif
--- /dev/null
+/*
+ fe-channels.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "channels.h"
+#include "channels-setup.h"
+#include "nicklist.h"
+
+#include "fe-windows.h"
+#include "fe-channels.h"
+#include "window-items.h"
+#include "printtext.h"
+
+static void signal_channel_created(CHANNEL_REC *channel, void *automatic)
+{
+ if (window_item_window(channel) == NULL) {
+ window_item_create((WI_ITEM_REC *) channel,
+ GPOINTER_TO_INT(automatic));
+ }
+}
+
+static void signal_channel_created_curwin(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ window_item_add(active_win, (WI_ITEM_REC *) channel, FALSE);
+}
+
+static void signal_channel_destroyed(CHANNEL_REC *channel)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(channel != NULL);
+
+ window = window_item_window((WI_ITEM_REC *) channel);
+ if (window == NULL)
+ return;
+
+ window_item_destroy((WI_ITEM_REC *) channel);
+
+ if (channel->joined && !channel->left &&
+ channel->server != NULL) {
+ /* kicked out from channel */
+ window_bind_add(window, channel->server->tag,
+ channel->name);
+ } else if (!channel->joined || channel->left)
+ window_auto_destroy(window);
+}
+
+static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *channel;
+
+ g_return_if_fail(window != NULL);
+
+ channel = CHANNEL(item);
+ if (channel != NULL) channel_destroy(channel);
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+ WINDOW_REC *window;
+ GSList *tmp;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ window = window_item_window((WI_ITEM_REC *) channel);
+ window_bind_add(window, server->tag, channel->name);
+ }
+}
+
+static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ g_return_if_fail(window != NULL);
+ if (item == NULL) return;
+
+ if (g_slist_length(window->items) > 1 && IS_CHANNEL(item)) {
+ printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE,
+ TXT_TALKING_IN, item->name);
+ signal_stop();
+ }
+}
+
+static void cmd_wjoin_pre(const char *data)
+{
+ GHashTable *optlist;
+ char *nick;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "join", &optlist, &nick))
+ return;
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_add("channel created",
+ (SIGNAL_FUNC) signal_channel_created_curwin);
+ }
+ cmd_params_free(free_arg);
+}
+
+static void cmd_join(const char *data, SERVER_REC *server)
+{
+ WINDOW_REC *window;
+ CHANNEL_REC *channel;
+
+ if (strchr(data, ' ') != NULL || strchr(data, ',') != NULL)
+ return;
+
+ channel = channel_find(server, data);
+ if (channel == NULL)
+ return;
+
+ window = window_item_window(channel);
+
+ if (window == active_win) {
+ /* channel is in active window, set it active */
+ window_item_set_active(active_win,
+ (WI_ITEM_REC *) channel);
+ } else {
+ /* notify user how to move the channel to active
+ window. this was used to be done automatically
+ but it just confused everyone who did it
+ accidentally */
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_CHANNEL_MOVE_NOTIFY, channel->name,
+ window->refnum);
+ }
+}
+
+static void cmd_wjoin_post(const char *data)
+{
+ GHashTable *optlist;
+ char *nick;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "join", &optlist, &nick))
+ return;
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_remove("channel created",
+ (SIGNAL_FUNC) signal_channel_created_curwin);
+ }
+ cmd_params_free(free_arg);
+}
+
+static void cmd_channel_list_joined(void)
+{
+ CHANNEL_REC *channel;
+ GString *nicks;
+ GSList *nicklist, *tmp, *ntmp;
+
+ if (channels == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_IN_CHANNELS);
+ return;
+ }
+
+ /* print active channel */
+ channel = CHANNEL(active_win->active);
+ if (channel != NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CURRENT_CHANNEL, channel->name);
+
+ /* print list of all channels, their modes, server tags and nicks */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_HEADER);
+ for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+ channel = tmp->data;
+
+ nicklist = nicklist_getnicks(channel);
+ nicks = g_string_new(NULL);
+ for (ntmp = nicklist; ntmp != NULL; ntmp = ntmp->next) {
+ NICK_REC *rec = ntmp->data;
+
+ g_string_sprintfa(nicks, "%s ", rec->nick);
+ }
+
+ if (nicks->len > 1) g_string_truncate(nicks, nicks->len-1);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_LINE,
+ channel->name, channel->mode, channel->server->tag, nicks->str);
+
+ g_slist_free(nicklist);
+ g_string_free(nicks, TRUE);
+ }
+}
+
+/* SYNTAX: CHANNEL LIST */
+static void cmd_channel_list(void)
+{
+ GString *str;
+ GSList *tmp;
+
+ str = g_string_new(NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_HEADER);
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ g_string_truncate(str, 0);
+ if (rec->autojoin)
+ g_string_append(str, "autojoin, ");
+ if (rec->botmasks != NULL && *rec->botmasks != '\0')
+ g_string_sprintfa(str, "bots: %s, ", rec->botmasks);
+ if (rec->autosendcmd != NULL && *rec->autosendcmd != '\0')
+ g_string_sprintfa(str, "botcmd: %s, ", rec->autosendcmd);
+
+ if (str->len > 2) g_string_truncate(str, str->len-2);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_LINE,
+ rec->name, rec->chatnet == NULL ? "" : rec->chatnet,
+ rec->password == NULL ? "" : rec->password, str->str);
+ }
+ g_string_free(str, TRUE);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_FOOTER);
+}
+
+static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ if (*data == '\0')
+ cmd_channel_list_joined();
+ else
+ command_runsub("channel", data, server, item);
+}
+
+/* SYNTAX: CHANNEL ADD [-auto | -noauto] [-bots <masks>] [-botcmd <command>]
+ <channel> <chatnet> [<password>] */
+static void cmd_channel_add(const char *data)
+{
+ GHashTable *optlist;
+ CHATNET_REC *chatnetrec;
+ CHANNEL_SETUP_REC *rec;
+ char *botarg, *botcmdarg, *chatnet, *channel, *password;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+ "channel add", &optlist, &channel, &chatnet, &password))
+ return;
+
+ if (*chatnet == '\0' || *channel == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chatnetrec = chatnet_find(chatnet);
+ if (chatnetrec == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_CHATNET, chatnet);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ botarg = g_hash_table_lookup(optlist, "bots");
+ botcmdarg = g_hash_table_lookup(optlist, "botcmd");
+
+ rec = channel_setup_find(channel, chatnet);
+ if (rec == NULL) {
+ rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+ rec->name = g_strdup(channel);
+ rec->chatnet = g_strdup(chatnet);
+ } else {
+ if (g_hash_table_lookup(optlist, "bots")) g_free_and_null(rec->botmasks);
+ if (g_hash_table_lookup(optlist, "botcmd")) g_free_and_null(rec->autosendcmd);
+ if (*password != '\0') g_free_and_null(rec->password);
+ }
+ if (g_hash_table_lookup(optlist, "auto")) rec->autojoin = TRUE;
+ if (g_hash_table_lookup(optlist, "noauto")) rec->autojoin = FALSE;
+ if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg);
+ if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg);
+ if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
+
+ signal_emit("channel add fill", 2, rec, optlist);
+
+ channel_setup_create(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CHANSETUP_ADDED, channel, chatnet);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CHANNEL REMOVE <channel> <chatnet> */
+static void cmd_channel_remove(const char *data)
+{
+ CHANNEL_SETUP_REC *rec;
+ char *chatnet, *channel;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 2, &channel, &chatnet))
+ return;
+ if (*chatnet == '\0' || *channel == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ rec = channel_setup_find(channel, chatnet);
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet);
+ channel_setup_remove(rec);
+ }
+ cmd_params_free(free_arg);
+}
+
+static int get_nick_length(void *data)
+{
+ return strlen(((NICK_REC *) data)->nick);
+}
+
+static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
+{
+ WINDOW_REC *window;
+ TEXT_DEST_REC dest;
+ GString *str;
+ GSList *tmp;
+ char *format, *stripped;
+ char *linebuf, nickmode[2] = { 0, 0 };
+ int *columns, cols, rows, last_col_rows, col, row, max_width;
+ int item_extra, linebuf_size;
+
+ window = window_find_closest(channel->server, channel->name,
+ MSGLEVEL_CLIENTCRAP);
+ max_width = window->width;
+
+ /* get the length of item extra stuff ("[ ] ") */
+ format = format_get_text(MODULE_NAME, NULL,
+ channel->server, channel->name,
+ TXT_NAMES_NICK, " ", "");
+ stripped = strip_codes(format);
+ item_extra = strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+
+ if (settings_get_int("names_max_width") > 0 &&
+ max_width > settings_get_int("names_max_width"))
+ max_width = settings_get_int("names_max_width");
+
+ /* remove width of timestamp from max_width */
+ format_create_dest(&dest, channel->server, channel->name,
+ MSGLEVEL_CLIENTCRAP, NULL);
+ format = format_get_line_start(current_theme, &dest, time(NULL));
+ if (format != NULL) {
+ stripped = strip_codes(format);
+ max_width -= strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+ }
+
+ /* calculate columns */
+ cols = get_max_column_count(nicklist, get_nick_length, max_width,
+ settings_get_int("names_max_columns"),
+ item_extra, 3, &columns, &rows);
+ nicklist = columns_sort_list(nicklist, rows);
+
+ /* rows in last column */
+ last_col_rows = rows-(cols*rows-g_slist_length(nicklist));
+ if (last_col_rows == 0)
+ last_col_rows = rows;
+
+ str = g_string_new(NULL);
+ linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size);
+
+ col = 0; row = 0;
+ for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ nickmode[0] = rec->op ? '@' : rec->voice ? '+' : ' ';
+
+ if (linebuf_size < columns[col]-item_extra+1) {
+ linebuf_size = (columns[col]-item_extra+1)*2;
+ linebuf = g_realloc(linebuf, linebuf_size);
+ }
+ memset(linebuf, ' ', columns[col]-item_extra);
+ linebuf[columns[col]-item_extra] = '\0';
+ memcpy(linebuf, rec->nick, strlen(rec->nick));
+
+ format = format_get_text(MODULE_NAME, NULL,
+ channel->server, channel->name,
+ TXT_NAMES_NICK, nickmode, linebuf);
+ g_string_append(str, format);
+ g_free(format);
+
+ if (++col == cols) {
+ printtext(channel->server, channel->name,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ g_string_truncate(str, 0);
+ col = 0; row++;
+
+ if (row == last_col_rows)
+ cols--;
+ }
+ }
+
+ if (str->len != 0) {
+ printtext(channel->server, channel->name,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ }
+
+ g_slist_free(nicklist);
+ g_string_free(str, TRUE);
+ g_free_not_null(columns);
+ g_free(linebuf);
+}
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags)
+{
+ NICK_REC *nick;
+ GSList *tmp, *nicklist, *sorted;
+ int nicks, normal, voices, ops;
+
+ nicks = normal = voices = ops = 0;
+ nicklist = nicklist_getnicks(channel);
+ sorted = NULL;
+
+ /* sort the nicklist */
+ for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+ nick = tmp->data;
+
+ nicks++;
+ if (nick->op) {
+ ops++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_OPS) == 0)
+ continue;
+ } else if (nick->halfop) {
+ if ((flags & CHANNEL_NICKLIST_FLAG_HALFOPS) == 0)
+ continue;
+ } else if (nick->voice) {
+ voices++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_VOICES) == 0)
+ continue;
+ } else {
+ normal++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_NORMAL) == 0)
+ continue;
+ }
+
+ sorted = g_slist_insert_sorted(sorted, nick, (GCompareFunc)
+ nicklist_compare);
+ }
+ g_slist_free(nicklist);
+
+ /* display the nicks */
+ printformat(channel->server, channel->name,
+ MSGLEVEL_CRAP, TXT_NAMES, channel->name, "");
+ display_sorted_nicks(channel, sorted);
+ g_slist_free(sorted);
+
+ printformat(channel->server, channel->name,
+ MSGLEVEL_CRAP, TXT_ENDOFNAMES,
+ channel->name, nicks, ops, voices, normal);
+}
+
+/* SYNTAX: NAMES [-ops -halfops -voices -normal] [<channels> | **] */
+static void cmd_names(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *chanrec;
+ GHashTable *optlist;
+ GString *unknowns;
+ char *channel, **channels, **tmp;
+ int flags;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "names", &optlist, &channel))
+ return;
+
+ if (strcmp(channel, "*") == 0 || *channel == '\0') {
+ if (!IS_CHANNEL(item))
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ channel = item->name;
+ }
+
+ flags = 0;
+ if (g_hash_table_lookup(optlist, "ops") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_OPS;
+ if (g_hash_table_lookup(optlist, "halfops") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_HALFOPS;
+ if (g_hash_table_lookup(optlist, "voices") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_VOICES;
+ if (g_hash_table_lookup(optlist, "normal") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_NORMAL;
+
+ if (flags == 0) flags = CHANNEL_NICKLIST_FLAG_ALL;
+
+ unknowns = g_string_new(NULL);
+
+ channels = g_strsplit(channel, ",", -1);
+ for (tmp = channels; *tmp != NULL; tmp++) {
+ chanrec = channel_find(server, *tmp);
+ if (chanrec == NULL)
+ g_string_sprintfa(unknowns, "%s,", *tmp);
+ else {
+ fe_channels_nicklist(chanrec, flags);
+ signal_stop();
+ }
+ }
+ g_strfreev(channels);
+
+ if (unknowns->len > 1)
+ g_string_truncate(unknowns, unknowns->len-1);
+
+ if (unknowns->len > 0 && strcmp(channel, unknowns->str) != 0)
+ signal_emit("command names", 3, unknowns->str, server, item);
+ g_string_free(unknowns, TRUE);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CYCLE [<channel>] [<message>] */
+static void cmd_cycle(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *chanrec;
+ char *channame, *msg, *joindata;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN,
+ item, &channame, &msg))
+ return;
+ if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ joindata = chanrec->get_join_data(chanrec);
+ window_bind_add(window_item_window(chanrec),
+ chanrec->server->tag, chanrec->name);
+ channel_destroy(chanrec);
+
+ server->channels_join(server, joindata, FALSE);
+ g_free(joindata);
+
+ cmd_params_free(free_arg);
+}
+
+void fe_channels_init(void)
+{
+ settings_add_bool("lookandfeel", "autoclose_windows", TRUE);
+ settings_add_int("lookandfeel", "names_max_columns", 6);
+ settings_add_int("lookandfeel", "names_max_width", 0);
+
+ signal_add("channel created", (SIGNAL_FUNC) signal_channel_created);
+ signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
+ signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+ signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+ signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+
+ command_bind_first("join", NULL, (SIGNAL_FUNC) cmd_wjoin_pre);
+ command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+ command_bind_last("join", NULL, (SIGNAL_FUNC) cmd_wjoin_post);
+ command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel);
+ command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add);
+ command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove);
+ command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list);
+ command_bind("names", NULL, (SIGNAL_FUNC) cmd_names);
+ command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle);
+
+ command_set_options("channel add", "auto noauto -bots -botcmd");
+ command_set_options("names", "ops halfops voices normal");
+ command_set_options("join", "window");
+}
+
+void fe_channels_deinit(void)
+{
+ signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
+ signal_remove("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+ signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+
+ command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_pre);
+ command_unbind("join", (SIGNAL_FUNC) cmd_join);
+ command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_post);
+ command_unbind("channel", (SIGNAL_FUNC) cmd_channel);
+ command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add);
+ command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove);
+ command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list);
+ command_unbind("names", (SIGNAL_FUNC) cmd_names);
+ command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle);
+}
--- /dev/null
+#ifndef __FE_CHANNELS_H
+#define __FE_CHANNELS_H
+
+#define CHANNEL_NICKLIST_FLAG_OPS 0x01
+#define CHANNEL_NICKLIST_FLAG_HALFOPS 0x02
+#define CHANNEL_NICKLIST_FLAG_VOICES 0x04
+#define CHANNEL_NICKLIST_FLAG_NORMAL 0x08
+#define CHANNEL_NICKLIST_FLAG_ALL 0x0f
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags);
+
+void fe_channels_init(void);
+void fe_channels_deinit(void);
+
+#endif
--- /dev/null
+/*
+ fe-common-core.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "args.h"
+#include "misc.h"
+#include "levels.h"
+#include "settings.h"
+
+#include "channels.h"
+#include "servers-setup.h"
+
+#include "fe-queries.h"
+#include "hilight-text.h"
+#include "command-history.h"
+#include "completion.h"
+#include "keyboard.h"
+#include "printtext.h"
+#include "formats.h"
+#include "themes.h"
+#include "translation.h"
+#include "fe-channels.h"
+#include "fe-windows.h"
+#include "window-items.h"
+#include "windows-layout.h"
+
+#include <signal.h>
+
+static char *autocon_server;
+static char *autocon_password;
+static int autocon_port;
+static int no_autoconnect;
+static char *cmdline_nick;
+static char *cmdline_hostname;
+
+void autorun_init(void);
+void autorun_deinit(void);
+
+void fe_core_log_init(void);
+void fe_core_log_deinit(void);
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+void fe_expandos_init(void);
+void fe_expandos_deinit(void);
+
+void fe_help_init(void);
+void fe_help_deinit(void);
+
+void fe_ignore_init(void);
+void fe_ignore_deinit(void);
+
+void fe_ignore_messages_init(void);
+void fe_ignore_messages_deinit(void);
+
+void fe_log_init(void);
+void fe_log_deinit(void);
+
+void fe_messages_init(void);
+void fe_messages_deinit(void);
+
+void fe_modules_init(void);
+void fe_modules_deinit(void);
+
+void fe_server_init(void);
+void fe_server_deinit(void);
+
+void fe_settings_init(void);
+void fe_settings_deinit(void);
+
+void window_activity_init(void);
+void window_activity_deinit(void);
+
+void window_commands_init(void);
+void window_commands_deinit(void);
+
+void fe_core_commands_init(void);
+void fe_core_commands_deinit(void);
+
+static void sig_connected(SERVER_REC *server)
+{
+ MODULE_DATA_SET(server, g_new0(MODULE_SERVER_REC, 1));
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+ g_free(MODULE_DATA(server));
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+ MODULE_DATA_SET(channel, g_new0(MODULE_CHANNEL_REC, 1));
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ g_free(MODULE_DATA(channel));
+}
+
+void fe_common_core_init(void)
+{
+ static struct poptOption options[] = {
+ { "connect", 'c', POPT_ARG_STRING, &autocon_server, 0, "Automatically connect to server/ircnet", "SERVER" },
+ { "password", 'w', POPT_ARG_STRING, &autocon_password, 0, "Autoconnect password", "SERVER" },
+ { "port", 'p', POPT_ARG_INT, &autocon_port, 0, "Autoconnect port", "PORT" },
+ { "noconnect", '!', POPT_ARG_NONE, &no_autoconnect, 0, "Disable autoconnecting", NULL },
+ { "nick", 'n', POPT_ARG_STRING, &cmdline_nick, 0, "Specify nick to use", NULL },
+ { "hostname", 'h', POPT_ARG_STRING, &cmdline_hostname, 0, "Specify host name to use", NULL },
+ { NULL, '\0', 0, NULL }
+ };
+
+ autocon_server = NULL;
+ autocon_password = NULL;
+ autocon_port = 6667;
+ no_autoconnect = FALSE;
+ cmdline_nick = NULL;
+ cmdline_hostname = NULL;
+ args_register(options);
+
+ settings_add_bool("lookandfeel", "timestamps", TRUE);
+ settings_add_bool("lookandfeel", "msgs_timestamps", FALSE);
+ settings_add_int("lookandfeel", "timestamp_timeout", 0);
+
+ settings_add_bool("lookandfeel", "bell_beeps", FALSE);
+ settings_add_str("lookandfeel", "beep_msg_level", "");
+ settings_add_bool("lookandfeel", "beep_when_window_active", TRUE);
+ settings_add_bool("lookandfeel", "beep_when_away", TRUE);
+
+ settings_add_bool("lookandfeel", "hide_text_style", FALSE);
+ settings_add_bool("lookandfeel", "hide_server_tags", FALSE);
+
+ settings_add_bool("lookandfeel", "use_status_window", TRUE);
+ settings_add_bool("lookandfeel", "use_msgs_window", FALSE);
+
+ themes_init();
+ theme_register(fecommon_core_formats);
+
+ autorun_init();
+ command_history_init();
+ completion_init();
+ keyboard_init();
+ printtext_init();
+ formats_init();
+#ifndef WIN32
+ fe_exec_init();
+#endif
+ fe_expandos_init();
+ fe_help_init();
+ fe_ignore_init();
+ fe_log_init();
+ fe_modules_init();
+ fe_server_init();
+ fe_settings_init();
+ translation_init();
+ windows_init();
+ window_activity_init();
+ window_commands_init();
+ window_items_init();
+ windows_layout_init();
+ fe_core_commands_init();
+
+ fe_channels_init();
+ fe_queries_init();
+
+ fe_messages_init();
+ hilight_text_init();
+ fe_ignore_messages_init();
+
+ settings_check();
+
+ signal_add_first("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void fe_common_core_deinit(void)
+{
+ autorun_deinit();
+ hilight_text_deinit();
+ command_history_deinit();
+ completion_deinit();
+ keyboard_deinit();
+ printtext_deinit();
+ formats_deinit();
+#ifndef WIN32
+ fe_exec_deinit();
+#endif
+ fe_expandos_deinit();
+ fe_help_deinit();
+ fe_ignore_deinit();
+ fe_log_deinit();
+ fe_modules_deinit();
+ fe_server_deinit();
+ fe_settings_deinit();
+ translation_deinit();
+ windows_deinit();
+ window_activity_deinit();
+ window_commands_deinit();
+ window_items_deinit();
+ windows_layout_deinit();
+ fe_core_commands_deinit();
+
+ fe_channels_deinit();
+ fe_queries_deinit();
+
+ fe_messages_deinit();
+ fe_ignore_messages_init();
+
+ theme_unregister();
+ themes_deinit();
+
+ signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void glog_func(const char *log_domain, GLogLevelFlags log_level,
+ const char *message)
+{
+ const char *reason;
+
+ switch (log_level) {
+ case G_LOG_LEVEL_WARNING:
+ reason = "warning";
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ reason = "critical";
+ break;
+ default:
+ reason = "error";
+ break;
+ }
+
+ if (windows == NULL)
+ fprintf(stderr, "GLib %s: %s", reason, message);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_GLIB_ERROR, reason, message);
+ }
+}
+
+static void create_windows(void)
+{
+ WINDOW_REC *window;
+
+ windows_layout_restore();
+ if (windows != NULL)
+ return;
+
+ if (settings_get_bool("use_status_window")) {
+ window = window_create(NULL, TRUE);
+ window_set_name(window, "(status)");
+ window_set_level(window, MSGLEVEL_ALL ^
+ (settings_get_bool("use_msgs_window") ?
+ (MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS) : 0));
+ }
+
+ if (settings_get_bool("use_msgs_window")) {
+ window = window_create(NULL, TRUE);
+ window_set_name(window, "(msgs)");
+ window_set_level(window, MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS);
+ }
+
+ if (windows == NULL) {
+ /* we have to have at least one window.. */
+ window = window_create(NULL, TRUE);
+ }
+}
+
+static void autoconnect_servers(void)
+{
+ GSList *tmp, *chatnets;
+ char *str;
+
+ if (autocon_server != NULL) {
+ /* connect to specified server */
+ str = g_strdup_printf(autocon_password == NULL ? "%s %d" : "%s %d %s",
+ autocon_server, autocon_port, autocon_password);
+ signal_emit("command connect", 1, str);
+ g_free(str);
+ return;
+ }
+
+ if (no_autoconnect) {
+ /* don't autoconnect */
+ return;
+ }
+
+ /* connect to autoconnect servers */
+ chatnets = NULL;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (rec->autoconnect &&
+ (rec->chatnet == NULL ||
+ gslist_find_icase_string(chatnets, rec->chatnet) == NULL)) {
+ if (rec->chatnet != NULL)
+ chatnets = g_slist_append(chatnets, rec->chatnet);
+
+ str = g_strdup_printf("%s %d", rec->address, rec->port);
+ signal_emit("command connect", 1, str);
+ g_free(str);
+ }
+ }
+
+ g_slist_free(chatnets);
+}
+
+void fe_common_core_finish_init(void)
+{
+ signal_emit("irssi init read settings", 0);
+
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ if (cmdline_nick != NULL) {
+ /* override nick found from setup */
+ settings_set_str("nick", cmdline_nick);
+ }
+
+ if (cmdline_hostname != NULL) {
+ /* override host name found from setup */
+ settings_set_str("hostname", cmdline_hostname);
+ }
+
+ create_windows();
+
+ /* _after_ windows are created.. */
+ g_log_set_handler(G_LOG_DOMAIN,
+ (GLogLevelFlags) (G_LOG_LEVEL_CRITICAL |
+ G_LOG_LEVEL_WARNING),
+ (GLogFunc) glog_func, NULL);
+
+ autoconnect_servers();
+}
--- /dev/null
+#ifndef __FE_COMMON_CORE_H
+#define __FE_COMMON_CORE_H
+
+void fe_common_core_init(void);
+void fe_common_core_deinit(void);
+void fe_common_core_finish_init(void);
+
+#endif
--- /dev/null
+/*
+ fe-core-commands.c : irssi
+
+ Copyright (C) 1999-2001 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "line-split.h"
+#include "settings.h"
+#include "irssi-version.h"
+
+#include "fe-windows.h"
+#include "printtext.h"
+
+#define PASTE_CHECK_SPEED 200 /* 0.2 sec */
+
+static int ret_texts[] = {
+ TXT_OPTION_UNKNOWN,
+ TXT_OPTION_AMBIGUOUS,
+ TXT_OPTION_MISSING_ARG,
+ TXT_COMMAND_UNKNOWN,
+ TXT_COMMAND_AMBIGUOUS,
+ -1,
+ TXT_NOT_ENOUGH_PARAMS,
+ TXT_NOT_CONNECTED,
+ TXT_NOT_JOINED,
+ TXT_CHAN_NOT_FOUND,
+ TXT_CHAN_NOT_SYNCED,
+ TXT_NOT_GOOD_IDEA
+};
+
+/* keep the whole command line here temporarily. we need it in
+ "default command" event handler, but there we don't know if the start of
+ the line had one or two command chars, and which one.. */
+static const char *current_cmdline;
+static int hide_output;
+
+static GTimeVal time_command_last, time_command_now;
+static int last_command_cmd, command_cmd;
+
+/* SYNTAX: ECHO [-current] [-window <name>] [-level <level>] <text> */
+static void cmd_echo(const char *data, void *server, WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+ GHashTable *optlist;
+ char *msg, *levelstr, *winname;
+ void *free_arg;
+ int level;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "echo", &optlist, &msg))
+ return;
+
+ levelstr = g_hash_table_lookup(optlist, "level");
+ level = levelstr == NULL ? 0 :
+ level2bits(g_hash_table_lookup(optlist, "level"));
+ if (level == 0) level = MSGLEVEL_CRAP;
+
+ winname = g_hash_table_lookup(optlist, "window");
+ window = winname == NULL ? NULL :
+ is_numeric(winname, '\0') ?
+ window_find_refnum(atoi(winname)) :
+ window_find_item(NULL, winname);
+ if (window == NULL) window = active_win;
+
+ printtext_window(window, level, "%s", msg);
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: VERSION */
+static void cmd_version(char *data)
+{
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "Client: "PACKAGE" " IRSSI_VERSION);
+ }
+}
+
+/* SYNTAX: CAT <file> */
+static void cmd_cat(const char *data)
+{
+ LINEBUF_REC *buffer = NULL;
+ char *fname, *fposstr;
+ char tmpbuf[1024], *str;
+ void *free_arg;
+ int f, ret, recvlen, fpos;
+
+ if (!cmd_get_params(data, &free_arg, 2, &fname, &fposstr))
+ return;
+
+ fname = convert_home(fname);
+ fpos = atoi(fposstr);
+ cmd_params_free(free_arg);
+
+ f = open(fname, O_RDONLY);
+ g_free(fname);
+
+ if (f == -1) {
+ /* file not found */
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "%s", g_strerror(errno));
+ return;
+ }
+
+ lseek(f, fpos, SEEK_SET);
+ do {
+ recvlen = read(f, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, &str, &buffer);
+ if (ret > 0)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str);
+ } while (ret > 0);
+ line_split_free(buffer);
+
+ close(f);
+}
+
+/* SYNTAX: BEEP */
+static void cmd_beep(void)
+{
+ signal_emit("beep", 0);
+}
+
+static void sig_stop(void)
+{
+ signal_stop();
+}
+
+static void event_command(const char *data)
+{
+ const char *cmdchar;
+
+ /* save current command line */
+ current_cmdline = data;
+
+ /* for detecting if we're pasting text */
+ time_command_last = time_command_now;
+ last_command_cmd = command_cmd;
+
+ g_get_current_time(&time_command_now);
+ command_cmd = strchr(settings_get_str("cmdchars"), *data) != NULL;
+
+ /* /^command hides the output of the command */
+ cmdchar = strchr(settings_get_str("cmdchars"), *data);
+ if (cmdchar != NULL && (data[1] == '^' ||
+ (data[1] == *cmdchar && data[2] == '^'))) {
+ hide_output = TRUE;
+ signal_add_first("print starting", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print format", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print text stripped", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print text", (SIGNAL_FUNC) sig_stop);
+ }
+}
+
+static void event_command_last(const char *data)
+{
+ if (hide_output) {
+ hide_output = FALSE;
+ signal_remove("print starting", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print format", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print text stripped", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print text", (SIGNAL_FUNC) sig_stop);
+ }
+}
+
+static void event_default_command(const char *data, void *server,
+ WI_ITEM_REC *item)
+{
+ const char *cmdchars, *ptr;
+ char *cmd, *p;
+ long diff;
+
+ cmdchars = settings_get_str("cmdchars");
+
+ ptr = data;
+ while (*ptr != '\0' && *ptr != ' ') {
+ if (strchr(cmdchars, *ptr)) {
+ /* command character inside command .. we probably
+ want to send this text to channel. for example
+ when pasting a path /usr/bin/xxx. */
+ signal_emit("send text", 3, current_cmdline, server, item);
+ return;
+ }
+ ptr++;
+ }
+
+ /* maybe we're copy+pasting text? check how long it was since the
+ last line */
+ diff = get_timeval_diff(&time_command_now, &time_command_last);
+ if (item != NULL && !last_command_cmd && diff < PASTE_CHECK_SPEED) {
+ signal_emit("send text", 3, current_cmdline, active_win->active_server, active_win->active);
+ command_cmd = FALSE;
+ return;
+ }
+
+ /* get the command part of the line, send "error command" signal */
+ cmd = g_strdup(data);
+ p = strchr(cmd, ' ');
+ if (p != NULL) *p = '\0';
+
+ signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd);
+
+ g_free(cmd);
+}
+
+static void event_cmderror(void *errorp, const char *arg)
+{
+ int error;
+
+ error = GPOINTER_TO_INT(errorp);
+ if (error == CMDERR_ERRNO) {
+ /* errno is special */
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", g_strerror(errno));
+ } else {
+ /* others */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error + -CMDERR_OPTION_UNKNOWN], arg);
+ }
+}
+
+static void event_list_subcommands(const char *command)
+{
+ GSList *tmp;
+ GString *str;
+ int len;
+
+ str = g_string_new(NULL);
+
+ len = strlen(command);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->cmd, command, len) == 0 &&
+ rec->cmd[len] == ' ' &&
+ strchr(rec->cmd+len+1, ' ') == NULL) {
+ g_string_sprintfa(str, "%s ", rec->cmd+len+1);
+ }
+ }
+
+ if (str->len != 0) {
+ g_string_truncate(str, str->len-1);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str->str);
+ }
+
+ g_string_free(str, TRUE);
+}
+
+void fe_core_commands_init(void)
+{
+ hide_output = FALSE;
+
+ command_cmd = FALSE;
+ memset(&time_command_now, 0, sizeof(GTimeVal));
+
+ command_bind("echo", NULL, (SIGNAL_FUNC) cmd_echo);
+ command_bind("version", NULL, (SIGNAL_FUNC) cmd_version);
+ command_bind("cat", NULL, (SIGNAL_FUNC) cmd_cat);
+ command_bind("beep", NULL, (SIGNAL_FUNC) cmd_beep);
+
+ signal_add("send command", (SIGNAL_FUNC) event_command);
+ signal_add_last("send command", (SIGNAL_FUNC) event_command_last);
+ signal_add("default command", (SIGNAL_FUNC) event_default_command);
+ signal_add("error command", (SIGNAL_FUNC) event_cmderror);
+ signal_add("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+
+ command_set_options("echo", "current +level +window");
+}
+
+void fe_core_commands_deinit(void)
+{
+ command_unbind("echo", (SIGNAL_FUNC) cmd_echo);
+ command_unbind("version", (SIGNAL_FUNC) cmd_version);
+ command_unbind("cat", (SIGNAL_FUNC) cmd_cat);
+ command_unbind("beep", (SIGNAL_FUNC) cmd_beep);
+
+ signal_remove("send command", (SIGNAL_FUNC) event_command);
+ signal_remove("send command", (SIGNAL_FUNC) event_command_last);
+ signal_remove("default command", (SIGNAL_FUNC) event_default_command);
+ signal_remove("error command", (SIGNAL_FUNC) event_cmderror);
+ signal_remove("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+}
--- /dev/null
+/*
+ fe-exec.c : irssi
+
+ Copyright (C) 2000 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 "commands.h"
+#include "pidwait.h"
+#include "line-split.h"
+#include "net-sendbuffer.h"
+#include "misc.h"
+#include "levels.h"
+
+#include "printtext.h"
+#include "fe-exec.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+#include <signal.h>
+#include <sys/wait.h>
+
+static GSList *processes;
+static int signal_exec_input;
+
+static EXEC_WI_REC *exec_wi_create(WINDOW_REC *window, PROCESS_REC *rec)
+{
+ EXEC_WI_REC *item;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ item = g_new0(EXEC_WI_REC, 1);
+ item->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "EXEC");
+ item->name = rec->name != NULL ?
+ g_strdup_printf("%%%s", rec->name) :
+ g_strdup_printf("%%%d", rec->id);
+
+ item->window = window;
+ item->createtime = time(NULL);
+ item->process = rec;
+
+ MODULE_DATA_INIT(item);
+ window_item_add(window, (WI_ITEM_REC *) item, FALSE);
+ return item;
+}
+
+static void exec_wi_destroy(EXEC_WI_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ if (rec->destroying) return;
+ rec->destroying = TRUE;
+
+ if (window_item_window((WI_ITEM_REC *) rec) != NULL)
+ window_item_destroy((WI_ITEM_REC *) rec);
+
+ MODULE_DATA_DEINIT(rec);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+static int process_get_new_id(void)
+{
+ PROCESS_REC *rec;
+ GSList *tmp;
+ int id;
+
+ id = 0;
+ tmp = processes;
+ while (tmp != NULL) {
+ rec = tmp->data;
+
+ if (id != rec->id) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ id++;
+ tmp = processes;
+ }
+
+ return id;
+}
+
+static PROCESS_REC *process_find_pid(int pid)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(pid > 0, NULL);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->pid == pid)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static PROCESS_REC *process_find_id(int id, int verbose)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(id != -1, NULL);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->id == id)
+ return rec;
+ }
+
+ if (verbose) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process id: %d", id);
+ }
+
+ return NULL;
+}
+
+static PROCESS_REC *process_find(const char *name, int verbose)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (*name == '%' && is_numeric(name+1, 0))
+ return process_find_id(atoi(name+1), verbose);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->name != NULL && strcmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ if (verbose) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process name: %s", name);
+ }
+
+ return NULL;
+}
+
+static void process_destroy(PROCESS_REC *rec, int status)
+{
+ processes = g_slist_remove(processes, rec);
+
+ signal_emit("exec remove", 2, rec, GINT_TO_POINTER(status));
+
+ if (rec->read_tag != -1)
+ g_source_remove(rec->read_tag);
+ if (rec->target_item != NULL)
+ exec_wi_destroy(rec->target_item);
+
+ line_split_free(rec->databuf);
+ g_io_channel_close(rec->in);
+ g_io_channel_unref(rec->in);
+ net_sendbuffer_destroy(rec->out, TRUE);
+
+ g_free_not_null(rec->name);
+ g_free_not_null(rec->target);
+ g_free(rec->args);
+ g_free(rec);
+}
+
+static void processes_killall(int signum)
+{
+ GSList *tmp;
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ kill(rec->pid, signum);
+ }
+}
+
+static int signal_name_to_id(const char *name)
+{
+ /* check only the few most common signals, too much job to check
+ them all. if we sometimes want more, procps-sources/proc/sig.c
+ would be useful for copypasting */
+ if (g_strcasecmp(name, "hup") == 0)
+ return SIGHUP;
+ if (g_strcasecmp(name, "int") == 0)
+ return SIGINT;
+ if (g_strcasecmp(name, "term") == 0)
+ return SIGTERM;
+ if (g_strcasecmp(name, "kill") == 0)
+ return SIGKILL;
+ if (g_strcasecmp(name, "usr1") == 0)
+ return SIGUSR1;
+ if (g_strcasecmp(name, "usr2") == 0)
+ return SIGUSR2;
+ return -1;
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+static int cmd_options_get_signal(const char *cmd,
+ GHashTable *optlist)
+{
+ GSList *list, *tmp, *next;
+ char *signame;
+ int signum;
+
+ /* get all the options, then remove the known ones. there should
+ be only one left - the signal */
+ list = hashtable_get_keys(optlist);
+ if (cmd != NULL) {
+ for (tmp = list; tmp != NULL; tmp = next) {
+ char *option = tmp->data;
+ next = tmp->next;
+
+ if (command_have_option(cmd, option))
+ list = g_slist_remove(list, option);
+ }
+ }
+
+ if (list == NULL)
+ return -1;
+
+ signame = list->data;
+ signum = -1;
+
+ signum = is_numeric(signame, 0) ? atol(signame) :
+ signal_name_to_id(signame);
+
+ if (signum == -1 || list->next != NULL) {
+ /* unknown option (not a signal) */
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+ signum == -1 ? list->data : list->next->data);
+ signal_stop();
+ return -2;
+ }
+
+ g_slist_free(list);
+ return signum;
+}
+
+static void exec_show_list(void)
+{
+ GSList *tmp;
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "%d (%s): %s", rec->id, rec->name, rec->args);
+ }
+}
+
+static void process_exec(PROCESS_REC *rec, const char *cmd)
+{
+ const char *shell_args[4] = { "/bin/sh", "-c", NULL, NULL };
+ char **args;
+ int in[2], out[2];
+ int n;
+
+ if (pipe(in) == -1)
+ return;
+ if (pipe(out) == -1)
+ return;
+
+ shell_args[2] = cmd;
+ rec->pid = fork();
+ if (rec->pid == -1) {
+ /* error */
+ close(in[0]); close(in[1]);
+ close(out[0]); close(out[1]);
+ return;
+ }
+
+ if (rec->pid != 0) {
+ /* parent process */
+ GIOChannel *outio = g_io_channel_unix_new(in[1]);
+
+ rec->in = g_io_channel_unix_new(out[0]);
+ rec->out = net_sendbuffer_create(outio, 0);
+
+ close(out[1]);
+ close(in[0]);
+ pidwait_add(rec->pid);
+ return;
+ }
+
+ /* child process, try to clean up everything */
+ setsid();
+ setuid(getuid());
+ setgid(getgid());
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_DFL);
+
+ putenv("TERM=tty");
+
+ /* set stdin, stdout and stderr */
+ dup2(in[0], STDIN_FILENO);
+ dup2(out[1], STDOUT_FILENO);
+ dup2(out[1], STDERR_FILENO);
+
+ /* don't let child see our files */
+ for (n = 3; n < 256; n++)
+ close(n);
+
+ if (rec->shell) {
+ execvp(shell_args[0], (char **) shell_args);
+
+ fprintf(stderr, "Exec: /bin/sh: %s\n", g_strerror(errno));
+ } else {
+ args = g_strsplit(cmd, " ", -1);
+ execvp(args[0], args);
+
+ fprintf(stderr, "Exec: %s: %s\n", args[0], g_strerror(errno));
+ }
+
+ _exit(-1);
+}
+
+static void sig_exec_input_reader(PROCESS_REC *rec)
+{
+ char tmpbuf[512], *str;
+ unsigned int recvlen;
+ int err, ret;
+
+ g_return_if_fail(rec != NULL);
+
+ recvlen = 0;
+ err = g_io_channel_read(rec->in, tmpbuf,
+ sizeof(tmpbuf), &recvlen);
+ if (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
+ recvlen = -1;
+
+ do {
+ ret = line_split(tmpbuf, recvlen, &str, &rec->databuf);
+ if (ret == -1) {
+ /* link to terminal closed? */
+ g_source_remove(rec->read_tag);
+ rec->read_tag = -1;
+ break;
+ }
+
+ if (ret > 0) {
+ signal_emit_id(signal_exec_input, 2, rec, str);
+ if (recvlen > 0) recvlen = 0;
+ }
+ } while (ret > 0);
+}
+
+static void handle_exec(const char *args, GHashTable *optlist,
+ WI_ITEM_REC *item)
+{
+ PROCESS_REC *rec;
+ char *target;
+ int notice, signum, interactive;
+
+ /* check that there's no unknown options. we allowed them
+ because signals can be used as options, but there should be
+ only one unknown option: the signal name/number. */
+ signum = cmd_options_get_signal("exec", optlist);
+ if (signum == -2)
+ return;
+
+ if (*args == '\0') {
+ exec_show_list();
+ return;
+ }
+
+ target = NULL;
+ notice = FALSE;
+
+ if (g_hash_table_lookup(optlist, "in") != NULL) {
+ rec = process_find(g_hash_table_lookup(optlist, "in"), TRUE);
+ if (rec != NULL) {
+ net_sendbuffer_send(rec->out, args, strlen(args));
+ net_sendbuffer_send(rec->out, "\n", 1);
+ }
+ return;
+ }
+
+ /* check if args is a process ID or name. if it's ID but not found,
+ complain about it and fail immediately */
+ rec = process_find(args, *args == '%');
+ if (*args == '%' && rec == NULL)
+ return;
+
+ /* common options */
+ if (g_hash_table_lookup(optlist, "out") != NULL) {
+ /* redirect output to active channel/query */
+ if (item == NULL)
+ cmd_return_error(CMDERR_NOT_JOINED);
+ target = item->name;
+ } else if (g_hash_table_lookup(optlist, "msg") != NULL) {
+ /* redirect output to /msg <nick> */
+ target = g_hash_table_lookup(optlist, "msg");
+ } else if (g_hash_table_lookup(optlist, "notice") != NULL) {
+ target = g_hash_table_lookup(optlist, "notice");
+ notice = TRUE;
+ }
+
+ /* options that require process ID/name as argument */
+ if (rec == NULL &&
+ (signum != -1 || g_hash_table_lookup(optlist, "close") != NULL)) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process name: %s", args);
+ return;
+ }
+ if (g_hash_table_lookup(optlist, "close") != NULL) {
+ /* forcibly close the process */
+ process_destroy(rec, -1);
+ return;
+ }
+
+ if (signum != -1) {
+ /* send a signal to process */
+ kill(rec->pid, signum);
+ return;
+ }
+
+ interactive = g_hash_table_lookup(optlist, "interactive") != NULL;
+ if (*args == '%') {
+ /* do something to already existing process */
+ char *name;
+
+ if (target != NULL) {
+ /* redirect output to target */
+ g_free_and_null(rec->target);
+ rec->target = g_strdup(target);
+ rec->notice = notice;
+ }
+
+ name = g_hash_table_lookup(optlist, "name");
+ if (name != NULL) {
+ /* change window name */
+ g_free_not_null(rec->name);
+ rec->name = *name == '\0' ? NULL : g_strdup(name);
+ } else if (target == NULL &&
+ (rec->target_item == NULL || interactive)) {
+ /* no parameters given,
+ redirect output to the active window */
+ g_free_and_null(rec->target);
+ rec->target_win = active_win;
+
+ if (rec->target_item != NULL) {
+ exec_wi_destroy(rec->target_item);
+ rec->target_item = NULL;
+ }
+
+ if (interactive) {
+ rec->target_item =
+ exec_wi_create(active_win, rec);
+ }
+ }
+ return;
+ }
+
+ /* starting a new process */
+ rec = g_new0(PROCESS_REC, 1);
+ rec->pid = -1;
+ rec->shell = g_hash_table_lookup(optlist, "nosh") == NULL;
+
+ process_exec(rec, args);
+ if (rec->pid == -1) {
+ /* pipe() or fork() failed */
+ g_free(rec);
+ cmd_return_error(CMDERR_ERRNO);
+ }
+
+ rec->id = process_get_new_id();
+ rec->target = g_strdup(target);
+ rec->target_win = active_win;
+ rec->args = g_strdup(args);
+ rec->notice = notice;
+ rec->silent = g_hash_table_lookup(optlist, "-") != NULL;
+ rec->name = g_strdup(g_hash_table_lookup(optlist, "name"));
+
+ rec->read_tag = g_input_add(rec->in, G_INPUT_READ,
+ (GInputFunction) sig_exec_input_reader,
+ rec);
+ processes = g_slist_append(processes, rec);
+
+ if (rec->target == NULL && interactive)
+ rec->target_item = exec_wi_create(active_win, rec);
+
+ signal_emit("exec new", 1, rec);
+}
+
+/* SYNTAX: EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>]
+ [-name <name>] <cmd line>
+ EXEC -out | -window | -msg <target> | -notice <target> |
+ -close | -<signal> <id>
+ EXEC -in <id> <text to send to process> */
+static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ char *args;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "exec", &optlist, &args)) {
+ handle_exec(args, optlist, item);
+ cmd_params_free(free_arg);
+ }
+}
+
+static void sig_pidwait(void *pid, void *statusp)
+{
+ PROCESS_REC *rec;
+ int status = GPOINTER_TO_INT(statusp);
+
+ rec = process_find_pid(GPOINTER_TO_INT(pid));
+ if (rec == NULL) return;
+
+ /* process exited */
+ if (!rec->silent) {
+ if (WIFSIGNALED(status)) {
+ status = WTERMSIG(status);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "process %d (%s) terminated with signal %d (%s)",
+ rec->id, rec->args,
+ status, g_strsignal(status));
+ } else {
+ status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "process %d (%s) terminated with return code %d",
+ rec->id, rec->args, status);
+ }
+ }
+ process_destroy(rec, status);
+}
+
+static void sig_exec_input(PROCESS_REC *rec, const char *text)
+{
+ WI_ITEM_REC *item;
+ SERVER_REC *server;
+ char *str;
+
+ item = NULL;
+ server = NULL;
+
+ if (rec->target != NULL) {
+ item = window_item_find(NULL, rec->target);
+ server = item != NULL ? item->server :
+ active_win->active_server;
+
+ str = g_strconcat(rec->target, " ", text, NULL);
+ signal_emit(rec->notice ? "command notice" : "command msg",
+ 3, str, server, item);
+ g_free(str);
+ } else if (rec->target_item != NULL) {
+ printtext(NULL, rec->target_item->name, MSGLEVEL_CLIENTCRAP,
+ "%s", text);
+ } else {
+ printtext_window(rec->target_win, MSGLEVEL_CLIENTCRAP,
+ "%s", text);
+ }
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ /* window is being closed, if there's any /exec targets for it,
+ change them to active window. */
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->target_win == window)
+ rec->target_win = active_win;
+ }
+}
+
+static void sig_window_item_destroyed(WINDOW_REC *window, EXEC_WI_REC *item)
+{
+ if (IS_EXEC_WI(item)) {
+ item->process->target_item = NULL;
+ exec_wi_destroy(item);
+ }
+}
+
+static void event_text(const char *data, SERVER_REC *server, EXEC_WI_REC *item)
+{
+ if (!IS_EXEC_WI(item)) return;
+
+ net_sendbuffer_send(item->process->out, data, strlen(data));
+ net_sendbuffer_send(item->process->out, "\n", 1);
+ signal_stop();
+}
+
+void fe_exec_init(void)
+{
+ command_bind("exec", NULL, (SIGNAL_FUNC) cmd_exec);
+ command_set_options("exec", "!- interactive nosh +name out +msg +notice +in window close");
+
+ signal_exec_input = signal_get_uniq_id("exec input");
+ signal_add("pidwait", (SIGNAL_FUNC) sig_pidwait);
+ signal_add("exec input", (SIGNAL_FUNC) sig_exec_input);
+ signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_add("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed);
+ signal_add_first("send text", (SIGNAL_FUNC) event_text);
+}
+
+void fe_exec_deinit(void)
+{
+ if (processes != NULL) {
+ processes_killall(SIGTERM);
+ sleep(1);
+ processes_killall(SIGKILL);
+
+ while (processes != NULL)
+ process_destroy(processes->data, -1);
+ }
+
+ command_unbind("exec", (SIGNAL_FUNC) cmd_exec);
+
+ signal_remove("pidwait", (SIGNAL_FUNC) sig_pidwait);
+ signal_remove("exec input", (SIGNAL_FUNC) sig_exec_input);
+ signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_remove("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed);
+ signal_remove("send text", (SIGNAL_FUNC) event_text);
+}
--- /dev/null
+#ifndef __FE_EXEC_H
+#define __FE_EXEC_H
+
+#include "fe-windows.h"
+
+#define EXEC_WI(query) \
+ MODULE_CHECK_CAST_MODULE(query, EXEC_WI_REC, type, \
+ "WINDOW ITEM TYPE", "EXEC")
+
+#define IS_EXEC_WI(query) \
+ (EXEC_WI(query) ? TRUE : FALSE)
+
+typedef struct PROCESS_REC PROCESS_REC;
+
+#define STRUCT_SERVER_REC void
+typedef struct {
+#include "window-item-rec.h"
+ PROCESS_REC *process;
+ unsigned int destroying:1;
+} EXEC_WI_REC;
+
+struct PROCESS_REC {
+ int id;
+ char *name;
+ char *args;
+
+ int pid;
+ GIOChannel *in;
+ NET_SENDBUF_REC *out;
+ LINEBUF_REC *databuf;
+ int read_tag;
+
+ char *target; /* send text with /msg <target> ... */
+ WINDOW_REC *target_win; /* print text to this window */
+ EXEC_WI_REC *target_item; /* print text to this exec window item */
+
+ unsigned int shell:1; /* start the program via /bin/sh */
+ unsigned int notice:1; /* send text with /notice, not /msg if target is set */
+ unsigned int silent:1; /* don't print "process exited with level xx" */
+};
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+#endif
--- /dev/null
+/*
+ fe-expandos.c : irssi
+
+ Copyright (C) 2000 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 "expandos.h"
+#include "fe-windows.h"
+
+/* Window ref# */
+static char *expando_winref(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", active_win->refnum);
+}
+
+/* Window name */
+static char *expando_winname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return active_win->name;
+}
+
+void fe_expandos_init(void)
+{
+ expando_create("winref", expando_winref,
+ "window changed", EXPANDO_ARG_NONE,
+ "window refnum changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("winname", expando_winname,
+ "window name changed", EXPANDO_ARG_WINDOW, NULL);
+}
+
+void fe_expandos_deinit(void)
+{
+ expando_destroy("winref", expando_winref);
+ expando_destroy("winname", expando_winname);
+}
--- /dev/null
+/*
+ fe-help.c : irssi
+
+ Copyright (C) 1999-2001 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 "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "line-split.h"
+#include "settings.h"
+
+#include "printtext.h"
+#include "formats.h"
+
+static int commands_equal(COMMAND_REC *rec, COMMAND_REC *rec2)
+{
+ if (rec->category == NULL && rec2->category != NULL)
+ return -1;
+ if (rec2->category == NULL && rec->category != NULL)
+ return 1;
+
+ return strcmp(rec->cmd, rec2->cmd);
+}
+
+static int get_cmd_length(void *data)
+{
+ return strlen(((COMMAND_REC *) data)->cmd);
+}
+
+static void help_category(GSList *cmdlist, int items)
+{
+ WINDOW_REC *window;
+ TEXT_DEST_REC dest;
+ GString *str;
+ GSList *tmp;
+ int *columns, cols, rows, col, row, last_col_rows, max_width;
+ char *linebuf, *format, *stripped;
+
+ window = window_find_closest(NULL, NULL, MSGLEVEL_CLIENTCRAP);
+ max_width = window->width;
+
+ /* remove width of timestamp from max_width */
+ format_create_dest(&dest, NULL, NULL, MSGLEVEL_CLIENTCRAP, NULL);
+ format = format_get_line_start(current_theme, &dest, time(NULL));
+ if (format != NULL) {
+ stripped = strip_codes(format);
+ max_width -= strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+ }
+
+ /* calculate columns */
+ cols = get_max_column_count(cmdlist, get_cmd_length,
+ max_width, 6, 1, 3, &columns, &rows);
+ cmdlist = columns_sort_list(cmdlist, rows);
+
+ /* rows in last column */
+ last_col_rows = rows-(cols*rows-g_slist_length(cmdlist));
+ if (last_col_rows == 0)
+ last_col_rows = rows;
+
+ str = g_string_new(NULL);
+ linebuf = g_malloc(max_width+1);
+
+ col = 0; row = 0;
+ for (tmp = cmdlist; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ memset(linebuf, ' ', columns[col]);
+ linebuf[columns[col]] = '\0';
+ memcpy(linebuf, rec->cmd, strlen(rec->cmd));
+ g_string_append(str, linebuf);
+
+ if (++col == cols) {
+ printtext(NULL, NULL,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ g_string_truncate(str, 0);
+ col = 0; row++;
+
+ if (row == last_col_rows)
+ cols--;
+ }
+ }
+ if (str->len != 0)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str->str);
+
+ g_slist_free(cmdlist);
+ g_string_free(str, TRUE);
+ g_free(columns);
+ g_free(linebuf);
+}
+
+static int show_help_file(const char *file)
+{
+ const char *helppath;
+ char tmpbuf[1024], *str, *path;
+ LINEBUF_REC *buffer = NULL;
+ int f, ret, recvlen;
+
+ helppath = settings_get_str("help_path");
+
+ /* helpdir/command or helpdir/category/command */
+ path = g_strdup_printf("%s/%s", helppath, file);
+ f = open(path, O_RDONLY);
+ g_free(path);
+
+ if (f == -1)
+ return FALSE;
+
+ /* just print to screen whatever is in the file */
+ do {
+ recvlen = read(f, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, &str, &buffer);
+ if (ret > 0) {
+ str = g_strconcat("%|", str, NULL);
+ printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, str);
+ g_free(str);
+ }
+ }
+ while (ret > 0);
+ line_split_free(buffer);
+
+ close(f);
+ return TRUE;
+}
+
+static void show_help(const char *data)
+{
+ COMMAND_REC *rec, *last;
+ GSList *tmp, *cmdlist;
+ int items, findlen;
+ int header, found, fullmatch;
+
+ g_return_if_fail(data != NULL);
+
+ /* sort the commands list */
+ commands = g_slist_sort(commands, (GCompareFunc) commands_equal);
+
+ /* print command, sort by category */
+ cmdlist = NULL; last = NULL; header = FALSE; fullmatch = FALSE;
+ items = 0; findlen = strlen(data); found = FALSE;
+ for (tmp = commands; tmp != NULL; last = rec, tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (last != NULL && rec->category != NULL &&
+ (last->category == NULL ||
+ strcmp(rec->category, last->category) != 0)) {
+ /* category changed */
+ if (items > 0) {
+ if (!header) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "Irssi commands:");
+ header = TRUE;
+ }
+ if (last->category != NULL) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "");
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s:", last->category);
+ }
+ help_category(cmdlist, items);
+ }
+
+ g_slist_free(cmdlist); cmdlist = NULL;
+ items = 0;
+ }
+
+ if (last != NULL && g_strcasecmp(rec->cmd, last->cmd) == 0)
+ continue; /* don't display same command twice */
+
+ if ((int)strlen(rec->cmd) >= findlen &&
+ g_strncasecmp(rec->cmd, data, findlen) == 0) {
+ if (rec->cmd[findlen] == '\0') {
+ fullmatch = TRUE;
+ found = TRUE;
+ break;
+ }
+ else if (strchr(rec->cmd+findlen+1, ' ') == NULL) {
+ /* not a subcommand (and matches the query) */
+ items++;
+ cmdlist = g_slist_append(cmdlist, rec);
+ found = TRUE;
+ }
+ }
+ }
+
+ if ((!found || fullmatch) && !show_help_file(data)) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "No help for %s", data);
+ }
+
+ if (*data != '\0' && data[strlen(data)-1] != ' ' &&
+ command_have_sub(data)) {
+ char *cmd;
+
+ cmd = g_strconcat(data, " ", NULL);
+ show_help(cmd);
+ g_free(cmd);
+ }
+
+ if (items != 0) {
+ /* display the last category */
+ if (!header) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "Irssi commands:");
+ header = TRUE;
+ }
+
+ if (last->category != NULL) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "");
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "%s:", last->category);
+ }
+ help_category(cmdlist, items);
+ g_slist_free(cmdlist);
+ }
+}
+
+/* SYNTAX: HELP [<command>] */
+static void cmd_help(const char *data)
+{
+ char *cmd, *ptr;
+
+ cmd = g_strdup(data);
+ ptr = cmd+strlen(cmd);
+ while (ptr[-1] == ' ') ptr--; *ptr = '\0';
+
+ show_help(cmd);
+ g_free(cmd);
+}
+
+void fe_help_init(void)
+{
+ settings_add_str("misc", "help_path", HELPDIR);
+ command_bind("help", NULL, (SIGNAL_FUNC) cmd_help);
+}
+
+void fe_help_deinit(void)
+{
+ command_unbind("help", (SIGNAL_FUNC) cmd_help);
+}
--- /dev/null
+/*
+ fe-ignore-messages.c : irssi
+
+ Copyright (C) 2000 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 "levels.h"
+#include "ignore.h"
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ if (ignore_check(server, nick, address, target, msg, MSGLEVEL_PUBLIC))
+ signal_stop();
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address, NULL, msg, MSGLEVEL_MSGS))
+ signal_stop();
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_JOINS))
+ signal_stop();
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ const char *reason)
+{
+ if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_PARTS))
+ signal_stop();
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
+ signal_stop();
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *kicker,
+ const char *address, const char *reason)
+{
+ if (ignore_check(server, kicker, address,
+ channel, reason, MSGLEVEL_KICKS))
+ signal_stop();
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ if (ignore_check(server, oldnick, address,
+ NULL, NULL, MSGLEVEL_NICKS) ||
+ ignore_check(server, newnick, address,
+ NULL, NULL, MSGLEVEL_NICKS))
+ signal_stop();
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ if (ignore_check(server, oldnick, address, NULL, NULL, MSGLEVEL_NICKS))
+ signal_stop();
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ if (*channel == '\0' ||
+ ignore_check(server, nick, address,
+ channel, NULL, MSGLEVEL_INVITES))
+ signal_stop();
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+ const char *topic,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address,
+ channel, topic, MSGLEVEL_TOPICS))
+ signal_stop();
+}
+
+void fe_ignore_messages_init(void)
+{
+ signal_add_first("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add_first("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_add_first("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_add_first("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_add_first("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_add_first("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_add_first("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_add_first("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_add_first("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
+
+void fe_ignore_messages_deinit(void)
+{
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
--- /dev/null
+/*
+ fe-ignore.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+
+#include "servers.h"
+#include "ignore.h"
+#include "printtext.h"
+
+static char *ignore_get_key(IGNORE_REC *rec)
+{
+ char *chans, *ret;
+
+ if (rec->channels == NULL)
+ return g_strdup(rec->mask != NULL ? rec->mask : "*" );
+
+ chans = g_strjoinv(",", rec->channels);
+ if (rec->mask == NULL) return chans;
+
+ ret = g_strdup_printf("%s %s", rec->mask, chans);
+ g_free(chans);
+ return ret;
+}
+
+static void ignore_print(int index, IGNORE_REC *rec)
+{
+ GString *options;
+ char *key, *levels;
+
+ key = ignore_get_key(rec);
+ levels = bits2level(rec->level);
+
+ options = g_string_new(NULL);
+ if (rec->exception) g_string_sprintfa(options, "-except ");
+ if (rec->regexp) g_string_sprintfa(options, "-regexp ");
+ if (rec->fullword) g_string_sprintfa(options, "-full ");
+ if (rec->replies) g_string_sprintfa(options, "-replies ");
+ if (options->len > 1) g_string_truncate(options, options->len-1);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_IGNORE_LINE, index,
+ key != NULL ? key : "",
+ levels != NULL ? levels : "", options->str);
+ g_string_free(options, TRUE);
+ g_free(key);
+ g_free(levels);
+}
+
+static void cmd_ignore_show(void)
+{
+ GSList *tmp;
+ int index;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_HEADER);
+ index = 1;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next, index++) {
+ IGNORE_REC *rec = tmp->data;
+
+ ignore_print(index, rec);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_FOOTER);
+}
+
+/* SYNTAX: IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+ [-channels <channel>] [-time <secs>] <mask> [<levels>]
+ IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+ [-time <secs>] <channels> [<levels>] */
+static void cmd_ignore(const char *data)
+{
+ GHashTable *optlist;
+ IGNORE_REC *rec;
+ char *patternarg, *chanarg, *mask, *levels, *timestr;
+ char **channels;
+ void *free_arg;
+ int new_ignore;
+
+ if (*data == '\0') {
+ cmd_ignore_show();
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST,
+ "ignore", &optlist, &mask, &levels))
+ return;
+
+ patternarg = g_hash_table_lookup(optlist, "pattern");
+ chanarg = g_hash_table_lookup(optlist, "channels");
+
+ if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (*levels == '\0') levels = "ALL";
+
+ if (active_win->active_server != NULL &&
+ active_win->active_server->ischannel(mask)) {
+ chanarg = mask;
+ mask = NULL;
+ }
+ channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
+ g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1);
+
+ rec = ignore_find(NULL, mask, channels);
+ new_ignore = rec == NULL;
+
+ if (rec == NULL) {
+ rec = g_new0(IGNORE_REC, 1);
+
+ rec->mask = mask == NULL || *mask == '\0' ||
+ strcmp(mask, "*") == 0 ? NULL : g_strdup(mask);
+ rec->channels = channels;
+ } else {
+ g_free_and_null(rec->pattern);
+ g_strfreev(channels);
+ }
+
+ rec->level = combine_level(rec->level, levels);
+ rec->pattern = (patternarg == NULL || *patternarg == '\0') ?
+ NULL : g_strdup(patternarg);
+ rec->exception = g_hash_table_lookup(optlist, "except") != NULL;
+ rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+ rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+ rec->replies = g_hash_table_lookup(optlist, "replies") != NULL;
+ timestr = g_hash_table_lookup(optlist, "time");
+ if (timestr != NULL)
+ rec->unignore_time = time(NULL)+atoi(timestr);
+
+ if (rec->level == 0) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED,
+ rec->mask == NULL ? "*" : rec->mask);
+ }
+
+ if (new_ignore)
+ ignore_add_rec(rec);
+ else
+ ignore_update_rec(rec);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNIGNORE <id>|<mask> */
+static void cmd_unignore(const char *data)
+{
+ IGNORE_REC *rec;
+ GSList *tmp;
+
+ if (*data == '\0')
+ cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (is_numeric(data, ' ')) {
+ /* with index number */
+ tmp = g_slist_nth(ignores, atoi(data)-1);
+ rec = tmp == NULL ? NULL : tmp->data;
+ } else {
+ /* with mask */
+ const char *chans[2] = { "*", NULL };
+
+ if (active_win->active_server != NULL &&
+ active_win->active_server->ischannel(data)) {
+ chans[0] = data;
+ data = NULL;
+ }
+ rec = ignore_find("*", data, (char **) chans);
+ }
+
+ if (rec != NULL) {
+ rec->level = 0;
+ ignore_update_rec(rec);
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_IGNORE_NOT_FOUND, data);
+ }
+}
+
+static void sig_ignore_created(IGNORE_REC *rec)
+{
+ char *key, *levels;
+
+ key = ignore_get_key(rec);
+ levels = bits2level(rec->level);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_IGNORED, key, levels);
+ g_free(key);
+ g_free(levels);
+}
+
+static void sig_ignore_destroyed(IGNORE_REC *rec)
+{
+ char *key;
+
+ key = ignore_get_key(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED, key);
+ g_free(key);
+}
+
+void fe_ignore_init(void)
+{
+ command_bind("ignore", NULL, (SIGNAL_FUNC) cmd_ignore);
+ command_bind("unignore", NULL, (SIGNAL_FUNC) cmd_unignore);
+
+ signal_add("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+ signal_add("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+ signal_add("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+
+ command_set_options("ignore", "regexp full except replies -time -pattern -channels");
+}
+
+void fe_ignore_deinit(void)
+{
+ command_unbind("ignore", (SIGNAL_FUNC) cmd_ignore);
+ command_unbind("unignore", (SIGNAL_FUNC) cmd_unignore);
+
+ signal_remove("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+ signal_remove("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+ signal_remove("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+}
--- /dev/null
+/*
+ fe-log.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "servers.h"
+#include "levels.h"
+#include "misc.h"
+#include "log.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "formats.h"
+#include "themes.h"
+#include "printtext.h"
+
+/* close autologs after 5 minutes of inactivity */
+#define AUTOLOG_INACTIVITY_CLOSE (60*5)
+
+#define LOG_DIR_CREATE_MODE 0770
+
+static int autolog_level;
+static int autoremove_tag;
+static const char *autolog_path;
+
+static THEME_REC *log_theme;
+static int skip_next_printtext;
+static const char *log_theme_name;
+
+static void log_add_targets(LOG_REC *log, const char *targets, const char *tag)
+{
+ char **tmp, **items;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(targets != NULL);
+
+ items = g_strsplit(targets, " ", -1);
+
+ for (tmp = items; *tmp != NULL; tmp++)
+ log_item_add(log, LOG_ITEM_TARGET, *tmp, tag);
+
+ g_strfreev(items);
+}
+
+/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-window] [-<server tag>]
+ [-targets <targets>] <fname> [<levels>] */
+static void cmd_log_open(const char *data)
+{
+ SERVER_REC *server;
+ GHashTable *optlist;
+ char *targetarg, *fname, *levels, *servertag;
+ void *free_arg;
+ char window[MAX_INT_STRLEN];
+ LOG_REC *log;
+ int level;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_OPTIONS,
+ "log open", &optlist, &fname, &levels))
+ return;
+ if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ level = level2bits(levels);
+ log = log_create_rec(fname, level != 0 ? level : MSGLEVEL_ALL);
+
+ /* -<server tag> */
+ server = cmd_options_get_server("log open", optlist, NULL);
+ servertag = server == NULL ? NULL : server->tag;
+
+ if (g_hash_table_lookup(optlist, "window")) {
+ /* log by window ref# */
+ ltoa(window, active_win->refnum);
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window,
+ servertag);
+ } else {
+ targetarg = g_hash_table_lookup(optlist, "targets");
+ if (targetarg != NULL && *targetarg != '\0')
+ log_add_targets(log, targetarg, servertag);
+ }
+
+ if (g_hash_table_lookup(optlist, "autoopen"))
+ log->autoopen = TRUE;
+
+ log_update(log);
+
+ if (log->handle == -1 && g_hash_table_lookup(optlist, "noopen") == NULL) {
+ /* start logging */
+ if (log_start_logging(log)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_LOG_OPENED, fname);
+ } else {
+ log_close(log);
+ }
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static LOG_REC *log_find_from_data(const char *data)
+{
+ GSList *tmp;
+
+ if (!is_numeric(data, ' '))
+ return log_find(data);
+
+ /* with index number */
+ tmp = g_slist_nth(logs, atoi(data)-1);
+ return tmp == NULL ? NULL : tmp->data;
+}
+
+/* SYNTAX: LOG CLOSE <id>|<file> */
+static void cmd_log_close(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+ else {
+ log_close(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+ }
+}
+
+/* SYNTAX: LOG START <id>|<file> */
+static void cmd_log_start(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log != NULL) {
+ log_start_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, data);
+ }
+}
+
+/* SYNTAX: LOG STOP <id>|<file> */
+static void cmd_log_stop(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log == NULL || log->handle == -1)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+ else {
+ log_stop_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+ }
+}
+
+static char *log_items_get_list(LOG_REC *log)
+{
+ GSList *tmp;
+ GString *str;
+ char *ret;
+
+ g_return_val_if_fail(log != NULL, NULL);
+ g_return_val_if_fail(log->items != NULL, NULL);
+
+ str = g_string_new(NULL);
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ LOG_ITEM_REC *rec = tmp->data;
+
+ g_string_sprintfa(str, "%s, ", rec->name);
+ }
+ g_string_truncate(str, str->len-2);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static void cmd_log_list(void)
+{
+ GSList *tmp;
+ char *levelstr, *items;
+ int index;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_HEADER);
+ for (tmp = logs, index = 1; tmp != NULL; tmp = tmp->next, index++) {
+ LOG_REC *rec = tmp->data;
+
+ levelstr = bits2level(rec->level);
+ items = rec->items == NULL ? NULL :
+ log_items_get_list(rec);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST,
+ index, rec->fname, items != NULL ? items : "",
+ levelstr, rec->autoopen ? " -autoopen" : "");
+
+ g_free_not_null(items);
+ g_free(levelstr);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_FOOTER);
+}
+
+static void cmd_log(const char *data, SERVER_REC *server, void *item)
+{
+ if (*data == '\0')
+ cmd_log_list();
+ else
+ command_runsub("log", data, server, item);
+}
+
+static LOG_REC *logs_find_item(int type, const char *item,
+ const char *servertag, LOG_ITEM_REC **ret_item)
+{
+ LOG_ITEM_REC *logitem;
+ GSList *tmp;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *log = tmp->data;
+
+ logitem = log_item_find(log, type, item, servertag);
+ if (logitem != NULL) {
+ if (ret_item != NULL) *ret_item = logitem;
+ return log;
+ }
+ }
+
+ return NULL;
+}
+
+/* SYNTAX: WINDOW LOG on|off|toggle [<filename>] */
+static void cmd_window_log(const char *data)
+{
+ LOG_REC *log;
+ char *set, *fname, window[MAX_INT_STRLEN];
+ void *free_arg;
+ int open_log, close_log;
+
+ if (!cmd_get_params(data, &free_arg, 2, &set, &fname))
+ return;
+
+ ltoa(window, active_win->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+ open_log = close_log = FALSE;
+ if (g_strcasecmp(set, "ON") == 0)
+ open_log = TRUE;
+ else if (g_strcasecmp(set, "OFF") == 0) {
+ close_log = TRUE;
+ } else if (g_strcasecmp(set, "TOGGLE") == 0) {
+ open_log = log == NULL;
+ close_log = log != NULL;
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_TOGGLE);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (open_log && log == NULL) {
+ /* irc.log.<windowname> or irc.log.Window<ref#> */
+ fname = *fname != '\0' ? g_strdup(fname) :
+ g_strdup_printf("~/irc.log.%s%s",
+ active_win->name != NULL ? active_win->name : "Window",
+ active_win->name != NULL ? "" : window);
+ log = log_create_rec(fname, MSGLEVEL_ALL);
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+ log_update(log);
+ g_free(fname);
+ }
+
+ if (open_log && log != NULL) {
+ log_start_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, log->fname);
+ } else if (close_log && log != NULL && log->handle != -1) {
+ log_stop_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, log->fname);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* Create log file entry to window, but don't start logging */
+/* SYNTAX: WINDOW LOGFILE <file> */
+static void cmd_window_logfile(const char *data)
+{
+ LOG_REC *log;
+ char window[MAX_INT_STRLEN];
+
+ ltoa(window, active_win->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+ if (log != NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE_LOGGING);
+ return;
+ }
+
+ log = log_create_rec(data, MSGLEVEL_ALL);
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+ log_update(log);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE, data);
+}
+
+/* window's refnum changed - update the logs to log the new window refnum */
+static void sig_window_refnum_changed(WINDOW_REC *window, gpointer old_refnum)
+{
+ char winnum[MAX_INT_STRLEN];
+ LOG_REC *log;
+ LOG_ITEM_REC *item;
+
+ ltoa(winnum, GPOINTER_TO_INT(old_refnum));
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, winnum, NULL, &item);
+
+ if (log != NULL) {
+ ltoa(winnum, window->refnum);
+
+ g_free(item->name);
+ item->name = g_strdup(winnum);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ LOG_ITEM_REC *logitem;
+ GSList *tmp, *next;
+
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *log = tmp->data;
+ next = tmp->next;
+
+ if (!log->temp || log->items == NULL)
+ continue;
+
+ logitem = log->items->data;
+ if (logitem->type == LOG_ITEM_TARGET &&
+ g_strcasecmp(logitem->servertag, server->tag) == 0)
+ log_close(log);
+ }
+}
+
+static void autologs_close_all(void)
+{
+ GSList *tmp, *next;
+
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->temp) log_close(rec);
+ }
+}
+
+static void autolog_open(SERVER_REC *server, const char *target)
+{
+ LOG_REC *log;
+ char *fname, *dir, *fixed_target, *tag;
+
+ tag = server == NULL ? NULL : server->tag;
+ log = logs_find_item(LOG_ITEM_TARGET, target, tag, NULL);
+ if (log != NULL && !log->failed) {
+ log_start_logging(log);
+ return;
+ }
+
+ /* '/' -> '_' - don't even accidentally try to log to
+ #../../../file if you happen to join to such channel.. */
+ fixed_target = g_strdup(target);
+ replace_chars(fixed_target, '/', '_');
+ fname = parse_special_string(autolog_path, server, NULL,
+ fixed_target, NULL, 0);
+ g_free(fixed_target);
+
+ if (log_find(fname) == NULL) {
+ log = log_create_rec(fname, autolog_level);
+ log_item_add(log, LOG_ITEM_TARGET, target, tag);
+
+ dir = g_dirname(log->real_fname);
+ mkpath(dir, LOG_DIR_CREATE_MODE);
+ g_free(dir);
+
+ log->temp = TRUE;
+ log_update(log);
+ log_start_logging(log);
+ }
+ g_free(fname);
+}
+
+static void autolog_open_check(SERVER_REC *server, const char *target,
+ int level)
+{
+ char **targets, **tmp;
+
+ if (level == MSGLEVEL_PARTS || /* FIXME: kind of a kludge, but we don't want to reopen logs when we're parting the channel with /WINDOW CLOSE.. */
+ (autolog_level & level) == 0 || target == NULL || *target == '\0')
+ return;
+
+ /* there can be multiple targets separated with comma */
+ targets = g_strsplit(target, ",", -1);
+ for (tmp = targets; *tmp != NULL; tmp++)
+ autolog_open(server, *tmp);
+ g_strfreev(targets);
+}
+
+static void log_single_line(WINDOW_REC *window, void *server,
+ const char *target, int level, const char *text)
+{
+ char windownum[MAX_INT_STRLEN];
+ char **targets, **tmp;
+ LOG_REC *log;
+
+ /* save to log created with /WINDOW LOG */
+ ltoa(windownum, window->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM,
+ windownum, NULL, NULL);
+ if (log != NULL) log_write_rec(log, text, level);
+
+ if (target == NULL)
+ log_file_write(server, NULL, level, text, FALSE);
+ else {
+ /* there can be multiple items separated with comma */
+ targets = g_strsplit(target, ",", -1);
+ for (tmp = targets; *tmp != NULL; tmp++)
+ log_file_write(server, *tmp, level, text, FALSE);
+ g_strfreev(targets);
+ }
+}
+
+static void log_line(WINDOW_REC *window, SERVER_REC *server,
+ const char *target, int level, const char *text)
+{
+ char **lines, **tmp;
+
+ if (level == MSGLEVEL_NEVER)
+ return;
+
+ /* let autolog open the log records */
+ autolog_open_check(server, target, level);
+
+ if (logs == NULL)
+ return;
+
+ /* text may contain one or more lines, log wants to eat them one
+ line at a time */
+ lines = g_strsplit(text, "\n", -1);
+ for (tmp = lines; *tmp != NULL; tmp++)
+ log_single_line(window, server, target, level, *tmp);
+ g_strfreev(lines);
+}
+
+static void sig_printtext_stripped(TEXT_DEST_REC *dest, const char *text)
+{
+ if (skip_next_printtext) {
+ skip_next_printtext = FALSE;
+ return;
+ }
+
+ log_line(dest->window, dest->server, dest->target,
+ dest->level, text);
+}
+
+static void sig_print_format(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, void *formatnum, char **args)
+{
+ char *str, *stripped, *linestart, *tmp;
+
+ if (log_theme == NULL) {
+ /* theme isn't loaded for some reason (/reload destroys it),
+ reload it. */
+ log_theme = theme_load(log_theme_name);
+ if (log_theme == NULL) return;
+ }
+
+ if (theme == log_theme)
+ return;
+
+ str = format_get_text_theme_charargs(log_theme, module, dest,
+ GPOINTER_TO_INT(formatnum), args);
+ skip_next_printtext = TRUE;
+
+ if (*str != '\0') {
+ /* add the line start format */
+ linestart = format_get_level_tag(log_theme, dest);
+ tmp = str;
+ str = format_add_linestart(tmp, linestart);
+ g_free_not_null(linestart);
+ g_free(tmp);
+
+ /* strip colors from text, log it. */
+ stripped = strip_codes(str);
+ log_line(dest->window, dest->server, dest->target,
+ dest->level, stripped);
+ g_free(stripped);
+ }
+ g_free(str);
+
+}
+
+static int sig_autoremove(void)
+{
+ SERVER_REC *server;
+ LOG_ITEM_REC *logitem;
+ GSList *tmp, *next;
+ time_t removetime;
+
+ removetime = time(NULL)-AUTOLOG_INACTIVITY_CLOSE;
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *log = tmp->data;
+
+ next = tmp->next;
+
+ if (!log->temp || log->last > removetime || log->items == NULL)
+ continue;
+
+ /* Close only logs with private messages */
+ logitem = log->items->data;
+ if (logitem->servertag == NULL)
+ continue;
+
+ server = server_find_tag(logitem->servertag);
+ if (logitem->type == LOG_ITEM_TARGET &&
+ server != NULL && !server->ischannel(logitem->name))
+ log_close(log);
+ }
+ return 1;
+}
+
+static void sig_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ LOG_REC *log;
+
+ log = logs_find_item(LOG_ITEM_TARGET, item->name,
+ item->server == NULL ? NULL :
+ item->server->tag, NULL);
+ if (log != NULL && log->temp)
+ log_close(log);
+}
+
+static void sig_log_locked(LOG_REC *log)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_LOG_LOCKED, log->fname);
+}
+
+static void sig_log_create_failed(LOG_REC *log)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_LOG_CREATE_FAILED, log->fname, g_strerror(errno));
+}
+
+static void sig_awaylog_show(LOG_REC *log, gpointer pmsgs, gpointer pfilepos)
+{
+ char *str;
+ int msgs, filepos;
+
+ msgs = GPOINTER_TO_INT(pmsgs);
+ filepos = GPOINTER_TO_INT(pfilepos);
+
+ if (msgs == 0)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_NO_AWAY_MSGS, log->fname);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_AWAY_MSGS, log->fname, msgs);
+
+ str = g_strdup_printf("\"%s\" %d", log->fname, filepos);
+ signal_emit("command cat", 1, str);
+ g_free(str);
+ }
+}
+
+static void sig_theme_destroyed(THEME_REC *theme)
+{
+ if (theme == log_theme)
+ log_theme = NULL;
+}
+
+static void read_settings(void)
+{
+ int old_autolog = autolog_level;
+
+ autolog_path = settings_get_str("autolog_path");
+ autolog_level = !settings_get_bool("autolog") ? 0 :
+ level2bits(settings_get_str("autolog_level"));
+
+ if (old_autolog && !autolog_level)
+ autologs_close_all();
+
+ /* write to log files with different theme? */
+ if (log_theme_name != NULL)
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+ log_theme_name = settings_get_str("log_theme");
+ if (*log_theme_name == '\0')
+ log_theme_name = NULL;
+ else
+ signal_add("print format", (SIGNAL_FUNC) sig_print_format);
+
+ log_theme = log_theme_name == NULL ? NULL :
+ theme_load(log_theme_name);
+}
+
+void fe_log_init(void)
+{
+ autoremove_tag = g_timeout_add(60000, (GSourceFunc) sig_autoremove, NULL);
+ skip_next_printtext = FALSE;
+
+ settings_add_str("log", "autolog_path", "~/irclogs/$tag/$0.log");
+ settings_add_str("log", "autolog_level", "all -crap -clientcrap -ctcps");
+ settings_add_bool("log", "autolog", FALSE);
+ settings_add_str("log", "log_theme", "");
+
+ autolog_level = 0;
+ log_theme_name = NULL;
+ read_settings();
+
+ command_bind("log", NULL, (SIGNAL_FUNC) cmd_log);
+ command_bind("log open", NULL, (SIGNAL_FUNC) cmd_log_open);
+ command_bind("log close", NULL, (SIGNAL_FUNC) cmd_log_close);
+ command_bind("log start", NULL, (SIGNAL_FUNC) cmd_log_start);
+ command_bind("log stop", NULL, (SIGNAL_FUNC) cmd_log_stop);
+ command_bind("window log", NULL, (SIGNAL_FUNC) cmd_window_log);
+ command_bind("window logfile", NULL, (SIGNAL_FUNC) cmd_window_logfile);
+ signal_add_first("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped);
+ signal_add("window item destroy", (SIGNAL_FUNC) sig_window_item_destroy);
+ signal_add("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("log locked", (SIGNAL_FUNC) sig_log_locked);
+ signal_add("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+ signal_add("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+ signal_add("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_set_options("log open", "noopen autoopen -targets window");
+}
+
+void fe_log_deinit(void)
+{
+ g_source_remove(autoremove_tag);
+ if (log_theme_name != NULL)
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+
+ command_unbind("log", (SIGNAL_FUNC) cmd_log);
+ command_unbind("log open", (SIGNAL_FUNC) cmd_log_open);
+ command_unbind("log close", (SIGNAL_FUNC) cmd_log_close);
+ command_unbind("log start", (SIGNAL_FUNC) cmd_log_start);
+ command_unbind("log stop", (SIGNAL_FUNC) cmd_log_stop);
+ command_unbind("window log", (SIGNAL_FUNC) cmd_window_log);
+ command_unbind("window logfile", (SIGNAL_FUNC) cmd_window_logfile);
+ signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped);
+ signal_remove("window item destroy", (SIGNAL_FUNC) sig_window_item_destroy);
+ signal_remove("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("log locked", (SIGNAL_FUNC) sig_log_locked);
+ signal_remove("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+ signal_remove("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+ signal_remove("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+/*
+ fe-messages.c : irssi
+
+ Copyright (C) 2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "window-items.h"
+#include "fe-queries.h"
+#include "channels.h"
+#include "nicklist.h"
+#include "hilight-text.h"
+#include "ignore.h"
+#include "printtext.h"
+
+#define ishighalnum(c) ((unsigned char) (c) >= 128 || isalnum(c))
+
+static GHashTable *printnicks;
+
+/* convert _underlined_ and *bold* words (and phrases) to use real
+ underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text)
+{
+ GString *str;
+ char *ret;
+ int pos;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ str = g_string_new(text);
+
+ for (pos = 0; pos < str->len; pos++) {
+ char type, *bgn, *end;
+
+ bgn = str->str + pos;
+
+ if (*bgn == '*')
+ type = 2; /* bold */
+ else if (*bgn == '_')
+ type = 31; /* underlined */
+ else
+ continue;
+
+ /* check that the beginning marker starts a word, and
+ that the matching end marker ends a word */
+ if ((pos > 0 && !isspace(bgn[-1])) || !ishighalnum(bgn[1]))
+ continue;
+ if ((end = strchr(bgn+1, *bgn)) == NULL)
+ continue;
+ if (!ishighalnum(end[-1]) || ishighalnum(end[1]) ||
+ end[1] == type || end[1] == '*' || end[1] == '_')
+ continue;
+
+ if (IS_CHANNEL(item)) {
+ /* check that this isn't a _nick_, we don't want to
+ use emphasis on them. */
+ int found;
+ char c;
+
+ c = end[1];
+ end[1] = '\0';
+ found = nicklist_find(CHANNEL(item), bgn) != NULL;
+ end[1] = c;
+ if (found) continue;
+ }
+
+ /* allow only *word* emphasis, not *multiple words* */
+ if (!settings_get_bool("emphasis_multiword")) {
+ char *c;
+ for (c = bgn+1; c != end; c++) {
+ if (!ishighalnum(*c))
+ break;
+ }
+ if (c != end) continue;
+ }
+
+ if (settings_get_bool("emphasis_replace")) {
+ *bgn = *end = type;
+ pos += (end-bgn);
+ } else {
+ g_string_insert_c(str, pos, type);
+ pos += (end - bgn) + 2;
+ g_string_insert_c(str, pos++, type);
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick)
+{
+ NICK_REC *nickrec;
+ char *emptystr;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ if (!settings_get_bool("show_nickmode"))
+ return "";
+
+ emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
+
+ nickrec = channel == NULL ? NULL :
+ nicklist_find(channel, nick);
+ return nickrec == NULL ? emptystr :
+ (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr));
+}
+
+static char *channel_get_nickmode_rec(NICK_REC *nickrec)
+{
+ char *emptystr;
+
+ if (!settings_get_bool("show_nickmode"))
+ return "";
+
+ emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
+
+ return nickrec == NULL ? emptystr :
+ (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr));
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target, NICK_REC *nickrec)
+{
+ CHANNEL_REC *chanrec;
+ const char *nickmode, *printnick;
+ int for_me, print_channel, level;
+ char *color, *freemsg = NULL;
+
+ /* NOTE: this may return NULL if some channel is just closed with
+ /WINDOW CLOSE and server still sends the few last messages */
+ chanrec = channel_find(server, target);
+ if (nickrec == NULL && chanrec != NULL)
+ nickrec = nicklist_find(chanrec, nick);
+
+ for_me = nick_match_msg(chanrec, msg, server->nick);
+ color = for_me ? NULL :
+ hilight_match_nick(server, target, nick, address, MSGLEVEL_PUBLIC, msg);
+
+ print_channel = chanrec == NULL ||
+ !window_item_is_active((WI_ITEM_REC *) chanrec);
+ if (!print_channel && settings_get_bool("print_active_channel") &&
+ window_item_window((WI_ITEM_REC *) chanrec)->items->next != NULL)
+ print_channel = TRUE;
+
+ level = MSGLEVEL_PUBLIC | (for_me || color != NULL ?
+ MSGLEVEL_HILIGHT : MSGLEVEL_NOHILIGHT);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg);
+
+ /* get nick mode & nick what to print the msg with
+ (in case there's multiple identical nicks) */
+ nickmode = channel_get_nickmode_rec(nickrec);
+ printnick = nickrec == NULL ? nick :
+ g_hash_table_lookup(printnicks, nickrec);
+ if (printnick == NULL)
+ printnick = nick;
+
+ if (!print_channel) {
+ /* message to active channel in window */
+ if (color != NULL) {
+ /* highlighted nick */
+ printformat(server, target, level,
+ TXT_PUBMSG_HILIGHT,
+ color, printnick, msg, nickmode);
+ } else {
+ printformat(server, target, level,
+ for_me ? TXT_PUBMSG_ME : TXT_PUBMSG,
+ printnick, msg, nickmode);
+ }
+ } else {
+ /* message to not existing/active channel */
+ if (color != NULL) {
+ /* highlighted nick */
+ printformat(server, target, level,
+ TXT_PUBMSG_HILIGHT_CHANNEL,
+ color, printnick, target, msg, nickmode);
+ } else {
+ printformat(server, target, level,
+ for_me ? TXT_PUBMSG_ME_CHANNEL :
+ TXT_PUBMSG_CHANNEL,
+ printnick, target, msg, nickmode);
+ }
+ }
+
+ g_free_not_null(freemsg);
+ g_free_not_null(color);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ QUERY_REC *query;
+ char *freemsg = NULL;
+
+ query = query_find(server, nick);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+ printformat(server, nick, MSGLEVEL_MSGS,
+ query == NULL ? TXT_MSG_PRIVATE :
+ TXT_MSG_PRIVATE_QUERY, nick, address, msg);
+
+ g_free_not_null(freemsg);
+}
+
+static void print_own_channel_message(SERVER_REC *server, CHANNEL_REC *channel,
+ const char *target, const char *msg)
+{
+ WINDOW_REC *window;
+ const char *nickmode;
+ char *freemsg = NULL;
+ int print_channel;
+
+ nickmode = channel_get_nickmode(channel, server->nick);
+
+ window = channel == NULL ? NULL :
+ window_item_window((WI_ITEM_REC *) channel);
+
+ print_channel = window == NULL ||
+ window->active != (WI_ITEM_REC *) channel;
+
+ if (!print_channel && settings_get_bool("print_active_channel") &&
+ window != NULL && g_slist_length(window->items) > 1)
+ print_channel = TRUE;
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) channel, msg);
+
+ if (!print_channel) {
+ printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG, server->nick, msg, nickmode);
+ } else {
+ printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode);
+ }
+
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+ const char *target)
+{
+ CHANNEL_REC *channel;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+
+ channel = channel_find(server, target);
+ print_own_channel_message(server, channel, target, msg);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ QUERY_REC *query;
+ char *freemsg = NULL;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+
+ if (target == NULL) {
+ /* this should only happen if some special target failed and
+ we should display some error message. currently the special
+ targets are only ',' and '.'. */
+ g_return_if_fail(strcmp(origtarget, ",") == 0 ||
+ strcmp(origtarget, ".") == 0);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ *origtarget == ',' ? TXT_NO_MSGS_GOT :
+ TXT_NO_MSGS_SENT);
+ signal_stop();
+ return;
+ }
+
+ query = privmsg_get_query(server, target, TRUE, MSGLEVEL_MSGS);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+ printformat(server, target,
+ MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ query == NULL ? TXT_OWN_MSG_PRIVATE :
+ TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick);
+
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ printformat(server, channel, MSGLEVEL_JOINS,
+ TXT_JOIN, nick, address, channel);
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ const char *reason)
+{
+ printformat(server, channel, MSGLEVEL_PARTS,
+ TXT_PART, nick, address, channel, reason);
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ WINDOW_REC *window;
+ GString *chans;
+ GSList *tmp, *windows;
+ char *print_channel;
+ int once, count;
+
+ if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
+ return;
+
+ print_channel = NULL;
+ once = settings_get_bool("show_quit_once");
+
+ count = 0; windows = NULL;
+ chans = g_string_new(NULL);
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (!nicklist_find(rec, nick))
+ continue;
+
+ if (ignore_check(server, nick, address, rec->name,
+ reason, MSGLEVEL_QUITS)) {
+ count++;
+ continue;
+ }
+
+ if (print_channel == NULL ||
+ active_win->active == (WI_ITEM_REC *) rec)
+ print_channel = rec->name;
+
+ if (once)
+ g_string_sprintfa(chans, "%s,", rec->name);
+ else {
+ window = window_item_window((WI_ITEM_REC *) rec);
+ if (g_slist_find(windows, window) == NULL) {
+ windows = g_slist_append(windows, window);
+ printformat(server, rec->name, MSGLEVEL_QUITS,
+ TXT_QUIT, nick, address, reason,
+ rec->name);
+ }
+ }
+ count++;
+ }
+ g_slist_free(windows);
+
+ if (!once) {
+ /* check if you had query with the nick and
+ display the quit there too */
+ QUERY_REC *query = query_find(server, nick);
+ if (query != NULL) {
+ printformat(server, nick, MSGLEVEL_QUITS,
+ TXT_QUIT, nick, address, reason, "");
+ }
+ }
+
+ if (once || count == 0) {
+ if (chans->len > 0)
+ g_string_truncate(chans, chans->len-1);
+ printformat(server, print_channel, MSGLEVEL_QUITS,
+ count <= 1 ? TXT_QUIT : TXT_QUIT_ONCE,
+ nick, address, reason, chans->str);
+ }
+ g_string_free(chans, TRUE);
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *kicker,
+ const char *address, const char *reason)
+{
+ printformat(server, channel, MSGLEVEL_KICKS,
+ TXT_KICK, nick, channel, kicker, reason);
+}
+
+static void print_nick_change_channel(SERVER_REC *server, const char *channel,
+ const char *newnick, const char *oldnick,
+ const char *address,
+ int ownnick)
+{
+ if (ignore_check(server, oldnick, address,
+ channel, newnick, MSGLEVEL_NICKS))
+ return;
+
+ printformat(server, channel, MSGLEVEL_NICKS,
+ ownnick ? TXT_YOUR_NICK_CHANGED : TXT_NICK_CHANGED,
+ oldnick, newnick, channel);
+}
+
+static void print_nick_change(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address,
+ int ownnick)
+{
+ GSList *tmp, *windows;
+ int msgprint;
+
+ msgprint = FALSE;
+
+ /* Print to each channel/query where the nick is.
+ Don't print more than once to the same window. */
+ windows = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+ WINDOW_REC *window =
+ window_item_window((WI_ITEM_REC *) channel);
+
+ if (nicklist_find(channel, newnick) == NULL ||
+ g_slist_find(windows, window) != NULL)
+ continue;
+
+ windows = g_slist_append(windows, window);
+ print_nick_change_channel(server, channel->name, newnick,
+ oldnick, address, ownnick);
+ msgprint = TRUE;
+ }
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *query = tmp->data;
+ WINDOW_REC *window =
+ window_item_window((WI_ITEM_REC *) query);
+
+ if (g_strcasecmp(query->name, oldnick) != 0 ||
+ g_slist_find(windows, window) != NULL)
+ continue;
+
+ windows = g_slist_append(windows, window);
+ print_nick_change_channel(server, query->name, newnick,
+ oldnick, address, ownnick);
+ msgprint = TRUE;
+ }
+ g_slist_free(windows);
+
+ if (!msgprint && ownnick) {
+ printformat(server, NULL, MSGLEVEL_NICKS,
+ TXT_YOUR_NICK_CHANGED, oldnick, newnick, "");
+ }
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ print_nick_change(server, newnick, oldnick, address, FALSE);
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ print_nick_change(server, newnick, oldnick, address, TRUE);
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ char *str;
+
+ str = show_lowascii(channel);
+ printformat(server, NULL, MSGLEVEL_INVITES,
+ TXT_INVITE, nick, str);
+ g_free(str);
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+ const char *topic,
+ const char *nick, const char *address)
+{
+ printformat(server, channel, MSGLEVEL_TOPICS,
+ *topic != '\0' ? TXT_NEW_TOPIC : TXT_TOPIC_UNSET,
+ nick, channel, topic);
+}
+
+static int printnick_exists(NICK_REC *first, NICK_REC *ignore,
+ const char *nick)
+{
+ char *printnick;
+
+ while (first != NULL) {
+ if (first != ignore) {
+ printnick = g_hash_table_lookup(printnicks, first);
+ if (printnick != NULL && strcmp(printnick, nick) == 0)
+ return TRUE;
+ }
+
+ first = first->next;
+ }
+
+ return FALSE;
+}
+
+static NICK_REC *printnick_find_original(NICK_REC *nick)
+{
+ while (nick != NULL) {
+ if (g_hash_table_lookup(printnicks, nick) == NULL)
+ return nick;
+
+ nick = nick->next;
+ }
+
+ return NULL;
+}
+
+static void sig_nicklist_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *firstnick;
+ GString *newnick;
+ char *nickhost, *p;
+ int n;
+
+ if (nick->host == NULL)
+ return;
+
+ firstnick = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (firstnick->next == NULL)
+ return;
+
+ if (nick == channel->ownnick) {
+ /* own nick is being added, might be a nick change and
+ someone else having the original nick already in use.. */
+ nick = printnick_find_original(firstnick->next);
+ if (nick == NULL)
+ return; /* nope, we have it */
+ }
+
+ /* identical nick already exists, have to change it somehow.. */
+ p = strchr(nick->host, '@');
+ if (p == NULL) p = nick->host; else p++;
+
+ nickhost = g_strdup_printf("%s@%s", nick->nick, p);
+ p = strchr(nickhost+strlen(nick->nick), '.');
+ if (p != NULL) *p = '\0';
+
+ if (!printnick_exists(firstnick, nick, nickhost)) {
+ /* use nick@host */
+ g_hash_table_insert(printnicks, nick, nickhost);
+ return;
+ }
+
+ newnick = g_string_new(NULL);
+ n = 2;
+ do {
+ g_string_sprintf(newnick, "%s%d", nickhost, n);
+ n++;
+ } while (printnick_exists(firstnick, nick, newnick->str));
+
+ g_hash_table_insert(printnicks, nick, newnick->str);
+ g_string_free(newnick, FALSE);
+ g_free(nickhost);
+}
+
+static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ char *nickname;
+
+ nickname = g_hash_table_lookup(printnicks, nick);
+ if (nickname != NULL) {
+ g_free(nickname);
+ g_hash_table_remove(printnicks, nick);
+ }
+}
+
+static void sig_nicklist_changed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ sig_nicklist_remove(channel, nick);
+ sig_nicklist_new(channel, nick);
+}
+
+static void sig_channel_joined(CHANNEL_REC *channel)
+{
+ NICK_REC *nick;
+ char *nickname;
+
+ /* channel->ownnick is set at this point - check if our own nick
+ has been changed, if it was set it back to the original nick and
+ change the previous original to something else */
+
+ nickname = g_hash_table_lookup(printnicks, channel->ownnick);
+ if (nickname == NULL)
+ return;
+
+ g_free(nickname);
+ g_hash_table_remove(printnicks, channel->ownnick);
+
+ /* our own nick is guaranteed to be the first in list */
+ nick = channel->ownnick->next;
+ while (nick != NULL) {
+ if (g_hash_table_lookup(printnicks, nick) == NULL) {
+ sig_nicklist_new(channel, nick);
+ break;
+ }
+ nick = nick->next;
+ }
+}
+
+static void g_hash_free_value(void *key, void *value)
+{
+ g_free(value);
+}
+
+void fe_messages_init(void)
+{
+ printnicks = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ settings_add_bool("lookandfeel", "emphasis", TRUE);
+ settings_add_bool("lookandfeel", "emphasis_replace", FALSE);
+ settings_add_bool("lookandfeel", "emphasis_multiword", FALSE);
+ settings_add_bool("lookandfeel", "show_nickmode", TRUE);
+ settings_add_bool("lookandfeel", "show_nickmode_empty", TRUE);
+ settings_add_bool("lookandfeel", "print_active_channel", FALSE);
+ settings_add_bool("lookandfeel", "show_quit_once", FALSE);
+
+ signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_add("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_add("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_add("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_add("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_add("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_add("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_add("message topic", (SIGNAL_FUNC) sig_message_topic);
+
+ signal_add("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+ signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
+
+void fe_messages_deinit(void)
+{
+ g_hash_table_foreach(printnicks, (GHFunc) g_hash_free_value, NULL);
+ g_hash_table_destroy(printnicks);
+
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+
+ signal_remove("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+ signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
--- /dev/null
+#ifndef __FE_MESSAGES_H
+#define __FE_MESSAGES_H
+
+/* convert _underlined_ and *bold* words (and phrases) to use real
+ underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text);
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick);
+
+#endif
--- /dev/null
+/*
+ fe-common-core.c : irssi
+
+ Copyright (C) 1999-2000 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 "modules.h"
+#include "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "chat-protocols.h"
+
+#include "printtext.h"
+
+static void sig_module_error(void *number, const char *module,
+ const char *data)
+{
+ switch (GPOINTER_TO_INT(number)) {
+ case MODULE_ERROR_ALREADY_LOADED:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_ALREADY_LOADED, module);
+ break;
+ case MODULE_ERROR_LOAD:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_LOAD_ERROR, module, data);
+ break;
+ case MODULE_ERROR_INVALID:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_INVALID, module);
+ break;
+ }
+}
+
+static void sig_module_loaded(MODULE_REC *rec)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_MODULE_LOADED, rec->name);
+}
+
+static void sig_module_unloaded(MODULE_REC *rec)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_MODULE_UNLOADED, rec->name);
+}
+
+static void cmd_load_list(void)
+{
+ GSList *tmp;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_HEADER);
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ MODULE_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_MODULE_LINE, rec->name);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_FOOTER);
+}
+
+static char **module_prefixes_get(void)
+{
+ GSList *tmp;
+ char **list, *name;
+ int count;
+
+ list = g_new(char *, 2 + 2*g_slist_length(chat_protocols));
+ list[0] = "fe";
+
+ count = 1;
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ name = g_strdup(rec->name);
+ g_strdown(name);
+
+ list[count++] = name;
+ list[count++] = g_strconcat("fe_", name, NULL);
+ }
+ list[count] = NULL;
+
+ return list;
+}
+
+static void module_prefixes_free(char **list)
+{
+ char **pos = list+1;
+
+ while (*pos != NULL) {
+ g_free(*pos);
+ pos++;
+ }
+ g_free(list);
+}
+
+/* SYNTAX: LOAD <module> */
+static void cmd_load(const char *data)
+{
+#ifdef HAVE_GMODULE
+ char **module_prefixes;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0')
+ cmd_load_list();
+ else {
+ module_prefixes = module_prefixes_get();
+ module_load(data, module_prefixes);
+ module_prefixes_free(module_prefixes);
+ }
+#else
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Dynamic modules loading not supported");
+#endif
+}
+
+/* SYNTAX: UNLOAD <module> */
+static void cmd_unload(const char *data)
+{
+ MODULE_REC *rec;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ rec = module_find(data);
+ if (rec != NULL) module_unload(rec);
+}
+
+void fe_modules_init(void)
+{
+ signal_add("module error", (SIGNAL_FUNC) sig_module_error);
+ signal_add("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+ signal_add("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+ command_bind("load", NULL, (SIGNAL_FUNC) cmd_load);
+ command_bind("unload", NULL, (SIGNAL_FUNC) cmd_unload);
+}
+
+void fe_modules_deinit(void)
+{
+ signal_remove("module error", (SIGNAL_FUNC) sig_module_error);
+ signal_remove("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+ signal_remove("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+ command_unbind("load", (SIGNAL_FUNC) cmd_load);
+ command_unbind("unload", (SIGNAL_FUNC) cmd_unload);
+}
--- /dev/null
+/*
+ fe-queries.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "queries.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "printtext.h"
+
+static int queryclose_tag, query_auto_close, querycreate_level;
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+ int own, int level)
+{
+ QUERY_REC *query;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ query = query_find(server, nick);
+ if (query == NULL && (querycreate_level & level) != 0 &&
+ (!own || settings_get_bool("autocreate_own_query"))) {
+ query = CHAT_PROTOCOL(server)->
+ query_create(server->tag, nick, TRUE);
+ }
+
+ return query;
+}
+
+static void signal_query_created(QUERY_REC *query, gpointer automatic)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ if (window_item_window(query) == NULL) {
+ window_item_create((WI_ITEM_REC *) query,
+ GPOINTER_TO_INT(automatic));
+ printformat(query->server, query->name, MSGLEVEL_CLIENTNOTICE,
+ TXT_QUERY_STARTED, query->name);
+ }
+}
+
+static void signal_query_created_curwin(QUERY_REC *query)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ window_item_add(active_win, (WI_ITEM_REC *) query, FALSE);
+}
+
+static void signal_query_destroyed(QUERY_REC *query)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(IS_QUERY(query));
+
+ window = window_item_window((WI_ITEM_REC *) query);
+ if (window != NULL) {
+ window_item_destroy((WI_ITEM_REC *) query);
+
+ if (!query->unwanted)
+ window_auto_destroy(window);
+ }
+}
+
+static void signal_query_server_changed(QUERY_REC *query)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(query != NULL);
+
+ window = window_item_window((WI_ITEM_REC *) query);
+ if (window->active == (WI_ITEM_REC *) query)
+ window_change_server(window, query->server);
+}
+
+static void signal_query_nick_changed(QUERY_REC *query, const char *oldnick)
+{
+ g_return_if_fail(query != NULL);
+
+ signal_emit("window item changed", 2,
+ window_item_window((WI_ITEM_REC *) query), query);
+}
+
+static void signal_window_item_server_changed(WINDOW_REC *window,
+ QUERY_REC *query)
+{
+ if (IS_QUERY(query)) {
+ g_free_and_null(query->server_tag);
+ if (query->server != NULL)
+ query->server_tag = g_strdup(query->server->tag);
+ }
+}
+
+static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ QUERY_REC *query;
+
+ g_return_if_fail(window != NULL);
+
+ query = QUERY(item);
+ if (query != NULL) query_destroy(query);
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+ GSList *tmp;
+
+ if (!IS_SERVER(server))
+ return;
+
+ /* check if there's any queries without server */
+ for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (rec->server == NULL &&
+ (rec->server_tag == NULL ||
+ g_strcasecmp(rec->server_tag, server->tag) == 0)) {
+ window_item_change_server((WI_ITEM_REC *) rec, server);
+ server->queries = g_slist_append(server->queries, rec);
+ }
+ }
+}
+
+static void cmd_window_server(const char *data)
+{
+ SERVER_REC *server;
+ QUERY_REC *query;
+
+ g_return_if_fail(data != NULL);
+
+ server = server_find_tag(data);
+ query = QUERY(active_win->active);
+ if (server == NULL || query == NULL)
+ return;
+
+ /* /WINDOW SERVER used in a query window */
+ query_change_server(query, server);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_QUERY_SERVER_CHANGED,
+ query->name, server->tag);
+ signal_stop();
+}
+
+/* SYNTAX: UNQUERY [<nick>] */
+static void cmd_unquery(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ QUERY_REC *query;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ /* remove current query */
+ query = QUERY(item);
+ if (query == NULL) return;
+ } else {
+ query = query_find(server, data);
+ if (query == NULL) {
+ printformat(server, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_NO_QUERY, data);
+ return;
+ }
+ }
+
+ query_destroy(query);
+}
+
+/* SYNTAX: QUERY [-window] [-<server tag>] <nick> [<message>] */
+static void cmd_query(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ QUERY_REC *query;
+ char *nick, *msg;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ /* remove current query */
+ cmd_unquery("", server, item);
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+ PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS,
+ "query", &optlist, &nick, &msg))
+ return;
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ server = cmd_options_get_server("query", optlist, server);
+ if (server == NULL) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*nick != '=' && (server == NULL || !server->connected))
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_add("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+ }
+
+ query = query_find(server, nick);
+ if (query == NULL)
+ CHAT_PROTOCOL(server)->query_create(server->tag, nick, FALSE);
+ else {
+ /* query already exists */
+ WINDOW_REC *window = window_item_window(query);
+
+ if (window == active_win) {
+ /* query is in active window, set it active */
+ window_item_set_active(active_win,
+ (WI_ITEM_REC *) query);
+ } else {
+ /* notify user how to move the query to active
+ window. this was used to be done automatically
+ but it just confused everyone who did it
+ accidentally */
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_QUERY_MOVE_NOTIFY, query->name,
+ window->refnum);
+ }
+ }
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_remove("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+ }
+
+ if (*msg != '\0') {
+ /* FIXME: we'll need some function that does both
+ of these. and separate the , and . target handling
+ from own_private messagge.. */
+ server->send_message(server, nick, msg);
+
+ signal_emit("message own_private", 4,
+ server, msg, nick, nick);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static int window_has_query(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(window != NULL, FALSE);
+
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ if (IS_QUERY(tmp->data))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void sig_window_changed(WINDOW_REC *window, WINDOW_REC *old_window)
+{
+ if (query_auto_close <= 0)
+ return;
+
+ /* reset the window's last_line timestamp so that query doesn't get
+ closed immediately after switched to the window, or after changed
+ to some other window from it */
+ if (window != NULL && window_has_query(window))
+ window->last_line = time(NULL);
+ if (old_window != NULL && window_has_query(old_window))
+ old_window->last_line = time(NULL);
+}
+
+static int sig_query_autoclose(void)
+{
+ WINDOW_REC *window;
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+ for (tmp = queries; tmp != NULL; tmp = next) {
+ QUERY_REC *rec = tmp->data;
+
+ next = tmp->next;
+ window = window_item_window((WI_ITEM_REC *) rec);
+ if (window != active_win && rec->data_level == 0 &&
+ now-window->last_line > query_auto_close)
+ query_destroy(rec);
+ }
+ return 1;
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ /* create query window if needed */
+ privmsg_get_query(server, nick, FALSE, MSGLEVEL_MSGS);
+}
+
+static void read_settings(void)
+{
+ querycreate_level = level2bits(settings_get_str("autocreate_query_level"));
+ query_auto_close = settings_get_int("autoclose_query");
+ if (query_auto_close > 0 && queryclose_tag == -1)
+ queryclose_tag = g_timeout_add(5000, (GSourceFunc) sig_query_autoclose, NULL);
+ else if (query_auto_close <= 0 && queryclose_tag != -1) {
+ g_source_remove(queryclose_tag);
+ queryclose_tag = -1;
+ }
+}
+
+void fe_queries_init(void)
+{
+ settings_add_str("lookandfeel", "autocreate_query_level", "MSGS DCCMSGS");
+ settings_add_bool("lookandfeel", "autocreate_own_query", TRUE);
+ settings_add_int("lookandfeel", "autoclose_query", 0);
+
+ queryclose_tag = -1;
+ read_settings();
+
+ signal_add("query created", (SIGNAL_FUNC) signal_query_created);
+ signal_add("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+ signal_add("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+ signal_add("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+ signal_add("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+ signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+ signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("query", NULL, (SIGNAL_FUNC) cmd_query);
+ command_bind("unquery", NULL, (SIGNAL_FUNC) cmd_unquery);
+ command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+
+ command_set_options("query", "window");
+}
+
+void fe_queries_deinit(void)
+{
+ if (queryclose_tag != -1) g_source_remove(queryclose_tag);
+
+ signal_remove("query created", (SIGNAL_FUNC) signal_query_created);
+ signal_remove("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+ signal_remove("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+ signal_remove("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+ signal_remove("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+ signal_remove("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("query", (SIGNAL_FUNC) cmd_query);
+ command_unbind("unquery", (SIGNAL_FUNC) cmd_unquery);
+ command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+}
--- /dev/null
+#ifndef __FE_QUERIES_H
+#define __FE_QUERIES_H
+
+#include "queries.h"
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+ int own, int level);
+
+void fe_queries_init(void);
+void fe_queries_deinit(void);
+
+#endif
--- /dev/null
+/*
+ fe-server.c : irssi
+
+ Copyright (C) 1999-2001 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 "commands.h"
+#include "network.h"
+#include "levels.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "chatnets.h"
+#include "servers.h"
+#include "servers-setup.h"
+#include "servers-reconnect.h"
+
+#include "module-formats.h"
+#include "printtext.h"
+
+static void print_servers(void)
+{
+ GSList *tmp;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LIST,
+ rec->tag, rec->connrec->address, rec->connrec->port,
+ rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick);
+ }
+}
+
+static void print_lookup_servers(void)
+{
+ GSList *tmp;
+ for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LOOKUP_LIST,
+ rec->tag, rec->connrec->address, rec->connrec->port,
+ rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick);
+ }
+}
+
+static void print_reconnects(void)
+{
+ GSList *tmp;
+ char *tag, *next_connect;
+ int left;
+
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+ SERVER_CONNECT_REC *conn = rec->conn;
+
+ tag = g_strdup_printf("RECON-%d", rec->tag);
+ left = rec->next_connect-time(NULL);
+ next_connect = g_strdup_printf("%02d:%02d", left/60, left%60);
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_RECONNECT_LIST,
+ tag, conn->address, conn->port,
+ conn->chatnet == NULL ? "" : conn->chatnet,
+ conn->nick, next_connect);
+ g_free(next_connect);
+ g_free(tag);
+ }
+}
+
+static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist)
+{
+ CHAT_PROTOCOL_REC *rec;
+ SERVER_SETUP_REC *server;
+ char *chatnet;
+
+ rec = chat_protocol_find_net(optlist);
+ if (rec == NULL)
+ rec = chat_protocol_get_default();
+ else {
+ chatnet = g_hash_table_lookup(optlist, rec->chatnet);
+ if (chatnet_find(chatnet) == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_CHATNET, chatnet);
+ return NULL;
+ }
+ }
+
+ server = rec->create_server_setup();
+ server->chat_type = rec->id;
+ return server;
+}
+
+static void cmd_server_add(const char *data)
+{
+ GHashTable *optlist;
+ SERVER_SETUP_REC *rec;
+ char *addr, *portstr, *password, *value;
+ void *free_arg;
+ int port;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+ "server add", &optlist, &addr, &portstr, &password))
+ return;
+
+ if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ port = *portstr == '\0' ? 6667 : atoi(portstr);
+
+ rec = server_setup_find_port(addr, port);
+ if (rec == NULL) {
+ rec = create_server_setup(optlist);
+ if (rec == NULL) {
+ cmd_params_free(free_arg);
+ return;
+ }
+ rec->address = g_strdup(addr);
+ rec->port = port;
+ } else {
+ value = g_hash_table_lookup(optlist, "port");
+ if (value != NULL && *value != '\0') rec->port = atoi(value);
+
+ if (*password != '\0') g_free_and_null(rec->password);
+ if (g_hash_table_lookup(optlist, "host")) {
+ g_free_and_null(rec->own_host);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+ }
+
+ if (g_hash_table_lookup(optlist, "6"))
+ rec->family = AF_INET6;
+ else if (g_hash_table_lookup(optlist, "4"))
+ rec->family = AF_INET;
+
+ if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE;
+ if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE;
+
+ if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
+ value = g_hash_table_lookup(optlist, "host");
+ if (value != NULL && *value != '\0') {
+ rec->own_host = g_strdup(value);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+
+ signal_emit("server add fill", 2, rec, optlist);
+
+ server_setup_add(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_SETUPSERVER_ADDED, addr, port);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: SERVER REMOVE <address> [<port>] */
+static void cmd_server_remove(const char *data)
+{
+ SERVER_SETUP_REC *rec;
+ char *addr, *port;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 2, &addr, &port))
+ return;
+ if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*port == '\0')
+ rec = server_setup_find(addr, -1);
+ else
+ rec = server_setup_find_port(addr, atoi(port));
+
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND, addr, port);
+ else {
+ server_setup_remove(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_REMOVED, addr, port);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_server(const char *data, SERVER_REC *server, void *item)
+{
+ GHashTable *optlist;
+ char *addr;
+ void *free_arg;
+
+ if (*data == '\0') {
+ if (servers == NULL && lookup_servers == NULL &&
+ reconnects == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_NO_CONNECTED_SERVERS);
+ } else {
+ print_servers();
+ print_lookup_servers();
+ print_reconnects();
+ }
+
+ signal_stop();
+ return;
+ }
+
+ if (g_strncasecmp(data, "add ", 4) == 0 ||
+ g_strncasecmp(data, "remove ", 7) == 0 ||
+ g_strcasecmp(data, "list") == 0 ||
+ g_strncasecmp(data, "list ", 5) == 0) {
+ command_runsub("server", data, server, item);
+ signal_stop();
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "connect", &optlist, &addr))
+ return;
+
+ if (*addr == '\0' || strcmp(addr, "+") == 0)
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (*addr == '+') window_create(NULL, FALSE);
+
+ cmd_params_free(free_arg);
+}
+
+static void sig_server_looking(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOOKING_UP, server->connrec->address);
+}
+
+static void sig_server_connecting(SERVER_REC *server, IPADDR *ip)
+{
+ char ipaddr[MAX_IP_LEN];
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(ip != NULL);
+
+ net_ip2host(ip, ipaddr);
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONNECTING,
+ server->connrec->address, ipaddr, server->connrec->port);
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_ESTABLISHED, server->connrec->address);
+}
+
+static void sig_connect_failed(SERVER_REC *server, gchar *msg)
+{
+ g_return_if_fail(server != NULL);
+
+ if (msg == NULL) {
+ /* no message so this wasn't unexpected fail - send
+ connection_lost message instead */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_LOST, server->connrec->address);
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_CANT_CONNECT, server->connrec->address, server->connrec->port, msg);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_LOST, server->connrec->address);
+}
+
+static void sig_server_quit(SERVER_REC *server, const char *msg)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_SERVER_QUIT, server->connrec->address, msg);
+}
+
+static void sig_server_lag_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_LAG_DISCONNECTED, server->connrec->address, time(NULL)-server->lag_sent);
+}
+
+static void sig_server_reconnect_removed(RECONNECT_REC *reconnect)
+{
+ g_return_if_fail(reconnect != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_RECONNECT_REMOVED, reconnect->conn->address, reconnect->conn->port,
+ reconnect->conn->chatnet == NULL ? "" : reconnect->conn->chatnet);
+}
+
+static void sig_server_reconnect_not_found(const char *tag)
+{
+ g_return_if_fail(tag != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_RECONNECT_NOT_FOUND, tag);
+}
+
+static void sig_chat_protocol_unknown(const char *protocol)
+{
+ g_return_if_fail(protocol != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_UNKNOWN_CHAT_PROTOCOL, protocol);
+}
+
+void fe_server_init(void)
+{
+ command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+ command_bind("server add", NULL, (SIGNAL_FUNC) cmd_server_add);
+ command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove);
+ command_set_options("server add", "4 6 auto noauto -host -port");
+
+ signal_add("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting);
+ signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_connect_failed);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+ signal_add("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected);
+ signal_add("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed);
+ signal_add("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found);
+
+ signal_add("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
+
+void fe_server_deinit(void)
+{
+ command_unbind("server", (SIGNAL_FUNC) cmd_server);
+ command_unbind("server add", (SIGNAL_FUNC) cmd_server_add);
+ command_unbind("server remove", (SIGNAL_FUNC) cmd_server_remove);
+
+ signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_remove("server connecting", (SIGNAL_FUNC) sig_server_connecting);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+ signal_remove("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected);
+ signal_remove("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed);
+ signal_remove("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found);
+
+ signal_remove("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
--- /dev/null
+/*
+ fe-settings.c : irssi
+
+ Copyright (C) 1999 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "servers.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "levels.h"
+#include "printtext.h"
+#include "keyboard.h"
+
+static void set_print(SETTINGS_REC *rec)
+{
+ const char *value;
+ char value_int[MAX_INT_STRLEN];
+
+ switch (rec->type) {
+ case SETTING_TYPE_BOOLEAN:
+ value = settings_get_bool(rec->key) ? "ON" : "OFF";
+ break;
+ case SETTING_TYPE_INT:
+ ltoa(value_int, settings_get_int(rec->key));
+ value = value_int;
+ break;
+ case SETTING_TYPE_STRING:
+ value = settings_get_str(rec->key);
+ break;
+ default:
+ value = "";
+ }
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s = %s", rec->key, value);
+}
+
+static void set_boolean(const char *key, const char *value)
+{
+ if (g_strcasecmp(value, "ON") == 0)
+ settings_set_bool(key, TRUE);
+ else if (g_strcasecmp(value, "OFF") == 0)
+ settings_set_bool(key, FALSE);
+ else if (g_strcasecmp(value, "TOGGLE") == 0)
+ settings_set_bool(key, !settings_get_bool(key));
+ else
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_NOT_TOGGLE);
+}
+
+/* SYNTAX: SET [-clear] [<key> [<value>]] */
+static void cmd_set(char *data)
+{
+ GHashTable *optlist;
+ GSList *sets, *tmp;
+ const char *last_section;
+ char *key, *value;
+ void *free_arg;
+ int found, clear;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "set", &optlist, &key, &value))
+ return;
+
+ clear = g_hash_table_lookup(optlist, "clear") != NULL;
+
+ last_section = ""; found = 0;
+ sets = settings_get_sorted();
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if (((clear || *value != '\0') && g_strcasecmp(rec->key, key) != 0) ||
+ (*value == '\0' && *key != '\0' && stristr(rec->key, key) == NULL))
+ continue;
+
+ if (strcmp(last_section, rec->section) != 0) {
+ /* print section */
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%_[ %s ]", rec->section);
+ last_section = rec->section;
+ }
+
+ if (clear || *value != '\0') {
+ /* change the setting */
+ switch (rec->type) {
+ case SETTING_TYPE_BOOLEAN:
+ if (clear)
+ settings_set_bool(key, FALSE);
+ else
+ set_boolean(key, value);
+ break;
+ case SETTING_TYPE_INT:
+ settings_set_int(key, clear ? 0 : atoi(value));
+ break;
+ case SETTING_TYPE_STRING:
+ settings_set_str(key, clear ? "" : value);
+ break;
+ }
+ signal_emit("setup changed", 0);
+ }
+
+ set_print(rec);
+ found = TRUE;
+
+ if (clear || *value != '\0')
+ break;
+ }
+ g_slist_free(sets);
+
+ if (!found)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %s", key);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: TOGGLE <key> [on|off|toggle] */
+static void cmd_toggle(const char *data)
+{
+ char *key, *value;
+ void *free_arg;
+ int type;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &key, &value))
+ return;
+
+ if (*key == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ type = settings_get_type(key);
+ if (type == -1)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %_%s", key);
+ else if (type != SETTING_TYPE_BOOLEAN)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Setting %_%s%_ isn't boolean, use /SET", key);
+ else {
+ set_boolean(key, *value != '\0' ? value : "TOGGLE");
+ set_print(settings_get_record(key));
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static int config_key_compare(CONFIG_NODE *node1, CONFIG_NODE *node2)
+{
+ return g_strcasecmp(node1->key, node2->key);
+}
+
+static void show_aliases(const char *alias)
+{
+ CONFIG_NODE *node;
+ GSList *tmp, *list;
+ int aliaslen;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_HEADER);
+
+ node = iconfig_node_traverse("aliases", FALSE);
+ tmp = node == NULL ? NULL : node->value;
+
+ /* first get the list of aliases sorted */
+ list = NULL;
+ aliaslen = strlen(alias);
+ for (; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (aliaslen != 0 && g_strncasecmp(node->key, alias, aliaslen) != 0)
+ continue;
+
+ list = g_slist_insert_sorted(list, node, (GCompareFunc) config_key_compare);
+ }
+
+ /* print the aliases */
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_LINE,
+ node->key, node->value);
+ }
+ g_slist_free(list);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_FOOTER);
+}
+
+static void alias_remove(const char *alias)
+{
+ if (iconfig_get_str("aliases", alias, NULL) == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_NOT_FOUND, alias);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_REMOVED, alias);
+ iconfig_set_str("aliases", alias, NULL);
+ }
+}
+
+/* SYNTAX: ALIAS [[-]<alias> [<command>]] */
+static void cmd_alias(const char *data)
+{
+ char *alias, *value;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &alias, &value))
+ return;
+
+ if (*alias == '-') {
+ if (alias[1] != '\0') alias_remove(alias+1);
+ } else if (*alias == '\0' || *value == '\0')
+ show_aliases(alias);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_ADDED, alias);
+ iconfig_set_str("aliases", alias, value);
+ }
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNALIAS <alias> */
+static void cmd_unalias(const char *data)
+{
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ alias_remove(data);
+}
+
+/* SYNTAX: RELOAD [<file>] */
+static void cmd_reload(const char *data)
+{
+ char *fname;
+
+ fname = *data != '\0' ? g_strdup(data) :
+ g_strdup_printf("%s/.irssi/config", g_get_home_dir());
+ if (settings_reread(fname)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_RELOADED, fname);
+ }
+ g_free(fname);
+}
+
+static void settings_save_fe(const char *fname)
+{
+ if (settings_save(fname)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_SAVED, fname);
+ }
+}
+
+static void settings_save_confirm(const char *line, char *fname)
+{
+ if (line[0] == 'Y')
+ settings_save_fe(fname);
+ g_free(fname);
+}
+
+/* SYNTAX: SAVE [<file>] */
+static void cmd_save(const char *data)
+{
+ char *format;
+
+ if (*data == '\0')
+ data = mainconfig->fname;
+
+ if (!irssi_config_is_changed(data)) {
+ settings_save_fe(data);
+ return;
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_MODIFIED, data);
+
+ format = format_get_text(MODULE_NAME, NULL, NULL, NULL,
+ TXT_OVERWRITE_CONFIG);
+ keyboard_entry_redirect((SIGNAL_FUNC) settings_save_confirm,
+ format, 0, g_strdup(data));
+ g_free(format);
+}
+
+static void settings_clean_confirm(const char *line)
+{
+ if (line[0] == 'Y')
+ settings_clean_invalid();
+}
+
+static void sig_settings_errors(const char *msg)
+{
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", msg);
+ keyboard_entry_redirect((SIGNAL_FUNC) settings_clean_confirm,
+ "Remove unknown settings from config file (Y/n)?",
+ 0, NULL);
+}
+
+void fe_settings_init(void)
+{
+ command_bind("set", NULL, (SIGNAL_FUNC) cmd_set);
+ command_bind("toggle", NULL, (SIGNAL_FUNC) cmd_toggle);
+ command_bind("alias", NULL, (SIGNAL_FUNC) cmd_alias);
+ command_bind("unalias", NULL, (SIGNAL_FUNC) cmd_unalias);
+ command_bind("reload", NULL, (SIGNAL_FUNC) cmd_reload);
+ command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+ command_set_options("set", "clear");
+
+ signal_add("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
+
+void fe_settings_deinit(void)
+{
+ command_unbind("set", (SIGNAL_FUNC) cmd_set);
+ command_unbind("toggle", (SIGNAL_FUNC) cmd_toggle);
+ command_unbind("alias", (SIGNAL_FUNC) cmd_alias);
+ command_unbind("unalias", (SIGNAL_FUNC) cmd_unalias);
+ command_unbind("reload", (SIGNAL_FUNC) cmd_reload);
+ command_unbind("save", (SIGNAL_FUNC) cmd_save);
+
+ signal_remove("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
--- /dev/null
+/*
+ windows.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "servers.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "printtext.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+GSList *windows; /* first in the list is the active window,
+ next is the last active, etc. */
+WINDOW_REC *active_win;
+
+static int daytag;
+static int daycheck; /* 0 = don't check, 1 = time is 00:00, check,
+ 2 = time is 00:00, already checked */
+
+static int window_get_new_refnum(void)
+{
+ WINDOW_REC *win;
+ GSList *tmp;
+ int refnum;
+
+ refnum = 1;
+ tmp = windows;
+ while (tmp != NULL) {
+ win = tmp->data;
+
+ if (refnum != win->refnum) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ refnum++;
+ tmp = windows;
+ }
+
+ return refnum;
+}
+
+WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic)
+{
+ WINDOW_REC *rec;
+
+ rec = g_new0(WINDOW_REC, 1);
+ rec->refnum = window_get_new_refnum();
+
+ windows = g_slist_prepend(windows, rec);
+ signal_emit("window created", 2, rec, GINT_TO_POINTER(automatic));
+
+ if (item != NULL) window_item_add(rec, item, automatic);
+ if (windows->next == NULL || !automatic || settings_get_bool("window_auto_change")) {
+ if (automatic && windows->next != NULL)
+ signal_emit("window changed automatic", 1, rec);
+ window_set_active(rec);
+ }
+ return rec;
+}
+
+/* removed_refnum was removed from the windows list, pack the windows so
+ there won't be any holes. If there is any holes after removed_refnum,
+ leave the windows behind it alone. */
+static void windows_pack(int removed_refnum)
+{
+ WINDOW_REC *window;
+ int refnum;
+
+ for (refnum = removed_refnum+1;; refnum++) {
+ window = window_find_refnum(refnum);
+ if (window == NULL || window->sticky_refnum)
+ break;
+
+ window_set_refnum(window, refnum-1);
+ }
+}
+
+void window_destroy(WINDOW_REC *window)
+{
+ g_return_if_fail(window != NULL);
+
+ if (window->destroying) return;
+ window->destroying = TRUE;
+ windows = g_slist_remove(windows, window);
+
+ if (active_win == window && windows != NULL) {
+ active_win = NULL; /* it's corrupted */
+ window_set_active(windows->data);
+ }
+
+ while (window->items != NULL)
+ window_item_destroy(window->items->data);
+
+ if (settings_get_bool("windows_auto_renumber"))
+ windows_pack(window->refnum);
+
+ signal_emit("window destroyed", 1, window);
+
+ while (window->bound_items != NULL)
+ window_bind_destroy(window, window->bound_items->data);
+
+ g_free_not_null(window->hilight_color);
+ g_free_not_null(window->servertag);
+ g_free_not_null(window->theme_name);
+ g_free_not_null(window->name);
+ g_free(window);
+}
+
+void window_auto_destroy(WINDOW_REC *window)
+{
+ if (settings_get_bool("autoclose_windows") && windows->next != NULL &&
+ window->items == NULL && window->level == 0)
+ window_destroy(window);
+}
+
+void window_set_active(WINDOW_REC *window)
+{
+ WINDOW_REC *old_window;
+
+ if (window == active_win)
+ return;
+
+ old_window = active_win;
+ active_win = window;
+ if (active_win != NULL) {
+ windows = g_slist_remove(windows, active_win);
+ windows = g_slist_prepend(windows, active_win);
+ }
+
+ if (active_win != NULL)
+ signal_emit("window changed", 2, active_win, old_window);
+}
+
+void window_change_server(WINDOW_REC *window, void *server)
+{
+ window->active_server = server;
+ signal_emit("window server changed", 2, window, server);
+}
+
+void window_set_refnum(WINDOW_REC *window, int refnum)
+{
+ GSList *tmp;
+ int old_refnum;
+
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(refnum >= 1);
+ if (window->refnum == refnum) return;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum == refnum) {
+ rec->refnum = window->refnum;
+ signal_emit("window refnum changed", 2, rec, GINT_TO_POINTER(refnum));
+ break;
+ }
+ }
+
+ old_refnum = window->refnum;
+ window->refnum = refnum;
+ signal_emit("window refnum changed", 2, window, GINT_TO_POINTER(old_refnum));
+}
+
+void window_set_name(WINDOW_REC *window, const char *name)
+{
+ g_free_not_null(window->name);
+ window->name = g_strdup(name);
+
+ signal_emit("window name changed", 1, window);
+}
+
+void window_set_level(WINDOW_REC *window, int level)
+{
+ g_return_if_fail(window != NULL);
+
+ window->level = level;
+ signal_emit("window level changed", 1, window);
+}
+
+/* return active item's name, or if none is active, window's name */
+char *window_get_active_name(WINDOW_REC *window)
+{
+ g_return_val_if_fail(window != NULL, NULL);
+
+ if (window->active != NULL)
+ return window->active->name;
+
+ return window->name;
+}
+
+WINDOW_REC *window_find_level(void *server, int level)
+{
+ WINDOW_REC *match;
+ GSList *tmp;
+
+ match = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if ((server == NULL || rec->active_server == server) &&
+ (rec->level & level)) {
+ if (server == NULL || rec->active_server == server)
+ return rec;
+ match = rec;
+ }
+ }
+
+ return match;
+}
+
+WINDOW_REC *window_find_closest(void *server, const char *name, int level)
+{
+ WINDOW_REC *window;
+ WI_ITEM_REC *item;
+
+ /* match by name */
+ item = name == NULL ? NULL :
+ window_item_find(server, name);
+ if (item != NULL)
+ return window_item_window(item);
+
+ /* match by level */
+ if (level != MSGLEVEL_HILIGHT)
+ level &= ~(MSGLEVEL_HILIGHT | MSGLEVEL_NOHILIGHT);
+ window = window_find_level(server, level);
+ if (window != NULL) return window;
+
+ /* match by level - ignore server */
+ window = window_find_level(NULL, level);
+ if (window != NULL) return window;
+
+ /* fallback to active */
+ return active_win;
+}
+
+WINDOW_REC *window_find_refnum(int refnum)
+{
+ GSList *tmp;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum == refnum)
+ return rec;
+ }
+
+ return NULL;
+}
+
+WINDOW_REC *window_find_name(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->name != NULL && g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name)
+{
+ WINDOW_REC *rec;
+ WI_ITEM_REC *item;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = window_find_name(name);
+ if (rec != NULL) return rec;
+
+ item = server == NULL ? NULL :
+ window_item_find(server, name);
+ if (item == NULL && server == NULL) {
+ /* not found from the active server - any server? */
+ item = window_item_find(NULL, name);
+ }
+
+ if (item == NULL) {
+ char *chan;
+
+ /* still nothing? maybe user just left the # in front of
+ channel, try again with it.. */
+ chan = g_strdup_printf("#%s", name);
+ item = server == NULL ? NULL :
+ window_item_find(server, chan);
+ if (item == NULL) item = window_item_find(NULL, chan);
+ g_free(chan);
+ }
+
+ if (item == NULL)
+ return 0;
+
+ return window_item_window(item);
+}
+
+int window_refnum_prev(int refnum, int wrap)
+{
+ GSList *tmp;
+ int prev, max;
+
+ max = prev = -1;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum < refnum && (prev == -1 || rec->refnum > prev))
+ prev = rec->refnum;
+ if (wrap && (max == -1 || rec->refnum > max))
+ max = rec->refnum;
+ }
+
+ return prev != -1 ? prev : max;
+}
+
+int window_refnum_next(int refnum, int wrap)
+{
+ GSList *tmp;
+ int min, next;
+
+ min = next = -1;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum > refnum && (next == -1 || rec->refnum < next))
+ next = rec->refnum;
+ if (wrap && (min == -1 || rec->refnum < min))
+ min = rec->refnum;
+ }
+
+ return next != -1 ? next : min;
+}
+
+int windows_refnum_last(void)
+{
+ GSList *tmp;
+ int max;
+
+ max = -1;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum > max)
+ max = rec->refnum;
+ }
+
+ return max;
+}
+
+static int window_refnum_cmp(WINDOW_REC *w1, WINDOW_REC *w2)
+{
+ return w1->refnum < w2->refnum ? -1 : 1;
+}
+
+GSList *windows_get_sorted(void)
+{
+ GSList *tmp, *sorted;
+
+ sorted = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ sorted = g_slist_insert_sorted(sorted, rec, (GCompareFunc)
+ window_refnum_cmp);
+ }
+
+ return sorted;
+}
+
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+ const char *name)
+{
+ WINDOW_BIND_REC *rec;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(servertag != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = g_new0(WINDOW_BIND_REC, 1);
+ rec->name = g_strdup(name);
+ rec->servertag = g_strdup(servertag);
+
+ window->bound_items = g_slist_append(window->bound_items, rec);
+ return rec;
+}
+
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec)
+{
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(rec != NULL);
+
+ window->bound_items = g_slist_remove(window->bound_items, rec);
+
+ g_free(rec->servertag);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+ const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(servertag != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0 &&
+ g_strcasecmp(rec->servertag, servertag) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+void window_bind_remove_unsticky(WINDOW_REC *window)
+{
+ GSList *tmp, *next;
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (!rec->sticky)
+ window_bind_destroy(window, rec);
+ }
+}
+
+static void sig_server_looking(SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ /* Try to keep some server assigned to windows..
+ Also change active window's server if the window is empty */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if ((rec->servertag == NULL ||
+ g_strcasecmp(rec->servertag, server->tag) == 0) &&
+ (rec->active_server == NULL ||
+ (rec == active_win && rec->items == NULL)))
+ window_change_server(rec, server);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ GSList *tmp;
+ SERVER_REC *new_server;
+
+ g_return_if_fail(server != NULL);
+
+ new_server = servers == NULL ? NULL : servers->data;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->active_server == server) {
+ window_change_server(rec, rec->servertag != NULL ?
+ NULL : new_server);
+ }
+ }
+}
+
+static void sig_print_text(void)
+{
+ GSList *tmp;
+ char month[100];
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ if (strftime(month, sizeof(month), "%b", tm) <= 0)
+ month[0] = '\0';
+
+ if (tm->tm_hour != 0 || tm->tm_min != 0)
+ return;
+
+ daycheck = 2;
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+ /* day changed, print notice about it to every window */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ printformat_window(tmp->data, MSGLEVEL_NEVER, TXT_DAYCHANGE,
+ tm->tm_mday, tm->tm_mon+1,
+ 1900+tm->tm_year, month);
+ }
+}
+
+static int sig_check_daychange(void)
+{
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+
+ if (daycheck == 1 && tm->tm_hour == 0 && tm->tm_min == 0) {
+ sig_print_text();
+ return TRUE;
+ }
+
+ if (tm->tm_hour != 23 || tm->tm_min != 59) {
+ daycheck = 0;
+ return TRUE;
+ }
+
+ /* time is 23:59 */
+ if (daycheck == 0) {
+ daycheck = 1;
+ signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+ }
+ return TRUE;
+}
+
+static void read_settings(void)
+{
+ if (daytag != -1) {
+ g_source_remove(daytag);
+ daytag = -1;
+ }
+
+ if (settings_get_bool("timestamps"))
+ daytag = g_timeout_add(30000, (GSourceFunc) sig_check_daychange, NULL);
+}
+
+void windows_init(void)
+{
+ active_win = NULL;
+ daycheck = 0; daytag = -1;
+ settings_add_bool("lookandfeel", "window_auto_change", FALSE);
+ settings_add_bool("lookandfeel", "windows_auto_renumber", TRUE);
+
+ read_settings();
+ signal_add("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void windows_deinit(void)
+{
+ if (daytag != -1) g_source_remove(daytag);
+ if (daycheck == 1) signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+ signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __WINDOWS_H
+#define __WINDOWS_H
+
+#include "servers.h"
+
+#define STRUCT_SERVER_REC SERVER_REC
+#include "window-item-def.h"
+
+enum {
+ DATA_LEVEL_NONE = 0,
+ DATA_LEVEL_TEXT,
+ DATA_LEVEL_MSG,
+ DATA_LEVEL_HILIGHT
+};
+
+typedef struct {
+ char *servertag;
+ char *name;
+ unsigned int sticky:1;
+} WINDOW_BIND_REC;
+
+typedef struct {
+ int refnum;
+ char *name;
+
+ int width, height;
+
+ GSList *items;
+ WI_ITEM_REC *active;
+ SERVER_REC *active_server;
+ char *servertag; /* active_server must be either NULL or have this tag (unless there's items in this window) */
+
+ int level; /* message level */
+ GSList *bound_items; /* list of WINDOW_BIND_RECs */
+
+ unsigned int sticky_refnum:1;
+ unsigned int destroying:1;
+
+ /* window-specific command line history */
+ GList *history, *history_pos;
+ int history_lines, history_over_counter;
+
+ int data_level; /* current data level */
+ char *hilight_color; /* current hilight color in %format */
+
+ time_t last_timestamp; /* When was last timestamp printed */
+ time_t last_line; /* When was last line printed */
+
+ char *theme_name; /* active theme in window, NULL = default */
+ void *theme; /* THEME_REC */
+
+ void *gui_data;
+} WINDOW_REC;
+
+extern GSList *windows;
+extern WINDOW_REC *active_win;
+
+WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic);
+void window_destroy(WINDOW_REC *window);
+
+void window_auto_destroy(WINDOW_REC *window);
+
+void window_set_active(WINDOW_REC *window);
+void window_change_server(WINDOW_REC *window, void *server);
+
+void window_set_refnum(WINDOW_REC *window, int refnum);
+void window_set_name(WINDOW_REC *window, const char *name);
+void window_set_level(WINDOW_REC *window, int level);
+
+/* return active item's name, or if none is active, window's name */
+char *window_get_active_name(WINDOW_REC *window);
+
+WINDOW_REC *window_find_level(void *server, int level);
+WINDOW_REC *window_find_closest(void *server, const char *name, int level);
+WINDOW_REC *window_find_refnum(int refnum);
+WINDOW_REC *window_find_name(const char *name);
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name);
+
+int window_refnum_prev(int refnum, int wrap);
+int window_refnum_next(int refnum, int wrap);
+int windows_refnum_last(void);
+
+GSList *windows_get_sorted(void);
+
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+ const char *name);
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec);
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+ const char *name);
+void window_bind_remove_unsticky(WINDOW_REC *window);
+
+void windows_init(void);
+void windows_deinit(void);
+
+#endif
--- /dev/null
+/*
+ formats.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "signals.h"
+#include "special-vars.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "fe-windows.h"
+#include "formats.h"
+#include "themes.h"
+#include "translation.h"
+
+static const char *format_backs = "04261537";
+static const char *format_fores = "kbgcrmyw";
+static const char *format_boldfores = "KBGCRMYW";
+
+static int signal_gui_print_text;
+static int hide_text_style, hide_server_tags;
+
+static int timestamps, msgs_timestamps;
+static int timestamp_timeout;
+
+int format_find_tag(const char *module, const char *tag)
+{
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ if (formats == NULL)
+ return -1;
+
+ for (n = 0; formats[n].def != NULL; n++) {
+ if (formats[n].tag != NULL &&
+ g_strcasecmp(formats[n].tag, tag) == 0)
+ return n;
+ }
+
+ return -1;
+}
+
+int format_expand_styles(GString *out, char format)
+{
+ char *p;
+
+ switch (format) {
+ case 'U':
+ /* Underline on/off */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_UNDERLINE);
+ break;
+ case '9':
+ case '_':
+ /* bold on/off */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BOLD);
+ break;
+ case '8':
+ /* reverse */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_REVERSE);
+ break;
+ case '%':
+ g_string_append_c(out, '%');
+ break;
+ case ':':
+ /* Newline */
+ g_string_append_c(out, '\n');
+ break;
+ case '|':
+ /* Indent here */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_INDENT);
+ break;
+ case 'F':
+ /* blink */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BLINK);
+ break;
+ case 'n':
+ case 'N':
+ /* default color */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_DEFAULTS);
+ break;
+ default:
+ /* check if it's a background color */
+ p = strchr(format_backs, format);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ g_string_append_c(out, (char) ((int) (p-format_backs)+'0'));
+ break;
+ }
+
+ /* check if it's a foreground color */
+ if (format == 'p') format = 'm';
+ p = strchr(format_fores, format);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) ((int) (p-format_fores)+'0'));
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ break;
+ }
+
+ /* check if it's a bold foreground color */
+ if (format == 'P') format = 'M';
+ p = strchr(format_boldfores, format);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) (8+(int) (p-format_boldfores)+'0'));
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ break;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void format_read_arglist(va_list va, FORMAT_REC *format,
+ char **arglist, int arglist_size,
+ char *buffer, int buffer_size)
+{
+ int num, len, bufpos;
+
+ g_return_if_fail(format->params < arglist_size);
+
+ bufpos = 0;
+ arglist[format->params] = NULL;
+ for (num = 0; num < format->params; num++) {
+ switch (format->paramtypes[num]) {
+ case FORMAT_STRING:
+ arglist[num] = (char *) va_arg(va, char *);
+ if (arglist[num] == NULL) {
+ g_warning("format_read_arglist() : parameter %d is NULL", num);
+ arglist[num] = "";
+ }
+ break;
+ case FORMAT_INT: {
+ int d = (int) va_arg(va, int);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%d", d);
+ bufpos += len+1;
+ break;
+ }
+ case FORMAT_LONG: {
+ long l = (long) va_arg(va, long);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%ld", l);
+ bufpos += len+1;
+ break;
+ }
+ case FORMAT_FLOAT: {
+ double f = (double) va_arg(va, double);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%0.2f", f);
+ bufpos += len+1;
+ break;
+ }
+ }
+ }
+}
+
+void format_create_dest(TEXT_DEST_REC *dest,
+ void *server, const char *target,
+ int level, WINDOW_REC *window)
+{
+ dest->server = server;
+ dest->target = target;
+ dest->level = level;
+ dest->window = window != NULL ? window :
+ window_find_closest(server, target, level);
+
+ dest->hilight_priority = 0;
+ dest->hilight_color = NULL;
+}
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str)
+{
+ GString *tmp;
+ int len;
+
+ g_return_val_if_fail(str != NULL, 0);
+
+ tmp = g_string_new(NULL);
+ len = 0;
+ while (*str != '\0') {
+ if (*str == '%' && str[1] != '\0') {
+ str++;
+ if (*str != '%' && format_expand_styles(tmp, *str)) {
+ str++;
+ continue;
+ }
+
+ /* %% or unknown %code, written as-is */
+ if (*str != '%')
+ len++;
+ }
+
+ len++;
+ str++;
+ }
+
+ g_string_free(tmp, TRUE);
+ return len;
+}
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. Like strip_real_length(), except this
+ handles %codes. */
+int format_real_length(const char *str, int len)
+{
+ GString *tmp;
+ const char *start;
+
+ g_return_val_if_fail(str != NULL, 0);
+ g_return_val_if_fail(len >= 0, 0);
+
+ start = str;
+ tmp = g_string_new(NULL);
+ while (*str != '\0' && len > 0) {
+ if (*str == '%' && str[1] != '\0') {
+ str++;
+ if (*str != '%' && format_expand_styles(tmp, *str)) {
+ str++;
+ continue;
+ }
+
+ /* %% or unknown %code, written as-is */
+ if (*str != '%') {
+ if (--len == 0)
+ break;
+ }
+ }
+
+ len--;
+ str++;
+ }
+
+ g_string_free(tmp, TRUE);
+ return (int) (str-start);
+}
+
+char *format_string_expand(const char *text)
+{
+ GString *out;
+ char code, *ret;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ out = g_string_new(NULL);
+
+ code = 0;
+ while (*text != '\0') {
+ if (code == '%') {
+ /* color code */
+ if (!format_expand_styles(out, *text)) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *text);
+ }
+ code = 0;
+ } else {
+ if (*text == '%')
+ code = *text;
+ else
+ g_string_append_c(out, *text);
+ }
+
+ text++;
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+static char *format_get_text_args(TEXT_DEST_REC *dest,
+ const char *text, char **arglist)
+{
+ GString *out;
+ char code, *ret;
+ int need_free;
+
+ out = g_string_new(NULL);
+
+ code = 0;
+ while (*text != '\0') {
+ if (code == '%') {
+ /* color code */
+ if (!format_expand_styles(out, *text)) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *text);
+ }
+ code = 0;
+ } else if (code == '$') {
+ /* argument */
+ char *ret;
+
+ ret = parse_special((char **) &text,
+ active_win == NULL ? NULL :
+ active_win->active_server,
+ active_win == NULL ? NULL :
+ active_win->active, arglist,
+ &need_free, NULL, 0);
+
+ if (ret != NULL) {
+ /* string shouldn't end with \003 or it could
+ mess up the next one or two characters */
+ int diff;
+ int len = strlen(ret);
+ while (len > 0 && ret[len-1] == 3) len--;
+ diff = strlen(ret)-len;
+
+ g_string_append(out, ret);
+ if (diff > 0)
+ g_string_truncate(out, out->len-diff);
+ if (need_free) g_free(ret);
+ }
+ code = 0;
+ } else {
+ if (*text == '%' || *text == '$')
+ code = *text;
+ else
+ g_string_append_c(out, *text);
+ }
+
+ text++;
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum, ...)
+{
+ va_list va;
+ char *str;
+
+ if (theme == NULL) {
+ theme = dest->window->theme == NULL ? current_theme :
+ dest->window->theme;
+ }
+
+ va_start(va, formatnum);
+ str = format_get_text_theme_args(theme, module, dest, formatnum, va);
+ va_end(va);
+
+ return str;
+}
+
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ va_list va)
+{
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ return format_get_text_theme_charargs(theme, module, dest,
+ formatnum, arglist);
+}
+
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ char **args)
+{
+ MODULE_THEME_REC *module_theme;
+ char *text;
+
+ module_theme = g_hash_table_lookup(theme->modules, module);
+ if (module_theme == NULL)
+ return NULL;
+
+ text = module_theme->expanded_formats[formatnum];
+ return format_get_text_args(dest, text, args);
+}
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+ void *server, const char *target,
+ int formatnum, ...)
+{
+ TEXT_DEST_REC dest;
+ THEME_REC *theme;
+ va_list va;
+ char *str;
+
+ format_create_dest(&dest, server, target, 0, window);
+ theme = dest.window->theme == NULL ? current_theme :
+ dest.window->theme;
+
+ va_start(va, formatnum);
+ str = format_get_text_theme_args(theme, module, &dest, formatnum, va);
+ va_end(va);
+
+ return str;
+}
+
+/* add `linestart' to start of each line in `text'. `text' may contain
+ multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart)
+{
+ GString *str;
+ char *ret;
+
+ if (linestart == NULL)
+ return g_strdup(text);
+
+ if (strchr(text, '\n') == NULL)
+ return g_strconcat(linestart, text, NULL);
+
+ str = g_string_new(linestart);
+ while (*text != '\0') {
+ g_string_append_c(str, *text);
+ if (*text == '\n')
+ g_string_append(str, linestart);
+ text++;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+#define LINE_START_IRSSI_LEVEL \
+ (MSGLEVEL_CLIENTERROR | MSGLEVEL_CLIENTNOTICE)
+
+#define NOT_LINE_START_LEVEL \
+ (MSGLEVEL_NEVER | MSGLEVEL_LASTLOG | MSGLEVEL_CLIENTCRAP | \
+ MSGLEVEL_MSGS | MSGLEVEL_PUBLIC | MSGLEVEL_DCC | MSGLEVEL_DCCMSGS | \
+ MSGLEVEL_ACTIONS | MSGLEVEL_NOTICES | MSGLEVEL_SNOTES | MSGLEVEL_CTCPS)
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+ int format;
+
+ if (dest->level & LINE_START_IRSSI_LEVEL)
+ format = TXT_LINE_START_IRSSI;
+ else if ((dest->level & NOT_LINE_START_LEVEL) == 0)
+ format = TXT_LINE_START;
+ else
+ return NULL;
+
+ return format_get_text_theme(theme, MODULE_NAME, dest, format);
+}
+
+#define show_timestamp(level) \
+ ((level & (MSGLEVEL_NEVER|MSGLEVEL_LASTLOG)) == 0 && \
+ (timestamps || (msgs_timestamps && ((level) & MSGLEVEL_MSGS))))
+
+static char *get_timestamp(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+ struct tm *tm;
+ int diff;
+
+ if (!show_timestamp(dest->level))
+ return NULL;
+
+ if (timestamp_timeout > 0) {
+ diff = t - dest->window->last_timestamp;
+ dest->window->last_timestamp = t;
+ if (diff < timestamp_timeout)
+ return NULL;
+ }
+
+ tm = localtime(&t);
+ return format_get_text_theme(theme, MODULE_NAME, dest, TXT_TIMESTAMP,
+ tm->tm_year+1900,
+ tm->tm_mon+1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static char *get_server_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+ SERVER_REC *server;
+ int count = 0;
+
+ server = dest->server;
+
+ if (server == NULL || hide_server_tags ||
+ (dest->window->active != NULL &&
+ dest->window->active->server == server))
+ return NULL;
+
+ if (servers != NULL) {
+ count++;
+ if (servers->next != NULL)
+ count++;
+ }
+ if (count < 2 && lookup_servers != NULL) {
+ count++;
+ if (lookup_servers->next != NULL)
+ count++;
+ }
+
+ return count < 2 ? NULL :
+ format_get_text_theme(theme, MODULE_NAME, dest,
+ TXT_SERVERTAG, server->tag);
+}
+
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+ char *timestamp, *servertag;
+ char *linestart;
+
+ timestamp = get_timestamp(theme, dest, t);
+ servertag = get_server_tag(theme, dest);
+
+ if (timestamp == NULL && servertag == NULL)
+ return NULL;
+
+ linestart = g_strconcat(timestamp != NULL ? timestamp : "",
+ servertag, NULL);
+
+ g_free_not_null(timestamp);
+ g_free_not_null(servertag);
+ return linestart;
+}
+
+void format_newline(WINDOW_REC *window)
+{
+ g_return_if_fail(window != NULL);
+
+ signal_emit_id(signal_gui_print_text, 6, window,
+ GINT_TO_POINTER(-1), GINT_TO_POINTER(-1),
+ GINT_TO_POINTER(PRINTFLAG_NEWLINE),
+ "", GINT_TO_POINTER(-1));
+}
+
+/* parse ANSI color string */
+static char *get_ansi_color(THEME_REC *theme, char *str,
+ int *fg_ret, int *bg_ret, int *flags_ret)
+{
+ static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+ char *start;
+ int fg, bg, flags, num;
+
+ if (*str != '[')
+ return str;
+ start = str++;
+
+ fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret;
+ bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret;
+ flags = flags_ret == NULL ? 0 : *flags_ret;
+
+ num = 0;
+ for (;; str++) {
+ if (*str == '\0') return start;
+
+ if (isdigit((int) *str)) {
+ num = num*10 + (*str-'0');
+ continue;
+ }
+
+ if (*str != ';' && *str != 'm')
+ return start;
+
+ switch (num) {
+ case 0:
+ /* reset colors back to default */
+ fg = theme->default_color;
+ bg = -1;
+ flags &= ~PRINTFLAG_INDENT;
+ break;
+ case 1:
+ /* hilight */
+ flags |= PRINTFLAG_BOLD;
+ break;
+ case 5:
+ /* blink */
+ flags |= PRINTFLAG_BLINK;
+ break;
+ case 7:
+ /* reverse */
+ flags |= PRINTFLAG_REVERSE;
+ break;
+ default:
+ if (num >= 30 && num <= 37)
+ fg = (fg & 0xf8) | ansitab[num-30];
+ if (num >= 40 && num <= 47) {
+ if (bg == -1) bg = 0;
+ bg = (bg & 0xf8) | ansitab[num-40];
+ }
+ break;
+ }
+ num = 0;
+
+ if (*str == 'm') {
+ if (fg_ret != NULL) *fg_ret = fg;
+ if (bg_ret != NULL) *bg_ret = bg;
+ if (flags_ret != NULL) *flags_ret = flags;
+
+ str++;
+ break;
+ }
+ }
+
+ return str;
+}
+
+/* parse MIRC color string */
+static void get_mirc_color(const char **str, int *fg_ret, int *bg_ret)
+{
+ int fg, bg;
+
+ fg = fg_ret == NULL ? -1 : *fg_ret;
+ bg = bg_ret == NULL ? -1 : *bg_ret;
+
+ if (!isdigit((int) **str) && **str != ',') {
+ fg = -1;
+ bg = -1;
+ } else {
+ /* foreground color */
+ if (**str != ',') {
+ fg = **str-'0';
+ (*str)++;
+ if (isdigit((int) **str)) {
+ fg = fg*10 + (**str-'0');
+ (*str)++;
+ }
+ }
+ if (**str == ',') {
+ /* background color */
+ (*str)++;
+ if (!isdigit((int) **str))
+ bg = -1;
+ else {
+ bg = **str-'0';
+ (*str)++;
+ if (isdigit((int) **str)) {
+ bg = bg*10 + (**str-'0');
+ (*str)++;
+ }
+ }
+ }
+ }
+
+ if (fg_ret) *fg_ret = fg;
+ if (bg_ret) *bg_ret = bg;
+}
+
+#define IS_COLOR_CODE(c) \
+ ((c) == 2 || (c) == 3 || (c) == 4 || (c) == 6 || (c) == 7 || \
+ (c) == 15 || (c) == 22 || (c) == 27 || (c) == 31)
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+ int *last_color_pos, int *last_color_len)
+{
+ const char *start = str;
+
+ if (last_color_pos != NULL)
+ *last_color_pos = -1;
+ if (last_color_len != NULL)
+ *last_color_len = -1;
+
+ while (*str != '\0') {
+ if (*str == 3) {
+ const char *mircstart = str;
+
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ str++;
+ get_mirc_color(&str, NULL, NULL);
+ if (last_color_len != NULL)
+ *last_color_len = (int) (str-mircstart);
+
+ } else if (*str == 4 && str[1] != '\0') {
+ if (str[1] < FORMAT_STYLE_SPECIAL && str[2] != '\0') {
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ if (last_color_len != NULL)
+ *last_color_len = 3;
+ str++;
+ } else if (str[1] == FORMAT_STYLE_DEFAULTS) {
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ if (last_color_len != NULL)
+ *last_color_len = 2;
+ }
+ str += 2;
+ } else {
+ if (!IS_COLOR_CODE(*str)) {
+ if (len-- == 0)
+ break;
+ }
+ str++;
+ }
+ }
+
+ return (int) (str-start);
+}
+
+char *strip_codes(const char *input)
+{
+ const char *p;
+ char *str, *out;
+
+ out = str = g_strdup(input);
+ for (p = input; *p != '\0'; p++) {
+ if (*p == 3) {
+ p++;
+
+ /* mirc color */
+ get_mirc_color(&p, NULL, NULL);
+ p--;
+ continue;
+ }
+
+ if (*p == 4 && p[1] != '\0') {
+ if (p[1] >= FORMAT_STYLE_SPECIAL) {
+ p++;
+ continue;
+ }
+
+ /* irssi color */
+ if (p[2] != '\0') {
+ p += 2;
+ continue;
+ }
+ }
+
+ if (!IS_COLOR_CODE(*p))
+ *out++ = *p;
+ }
+
+ *out = '\0';
+ return str;
+}
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text)
+{
+ char *dup, *str, *ptr, type;
+ int fgcolor, bgcolor;
+ int flags;
+
+ dup = str = g_strdup(text);
+
+ flags = 0; fgcolor = -1; bgcolor = -1;
+ while (*str != '\0') {
+ type = '\0';
+ for (ptr = str; *ptr != '\0'; ptr++) {
+ if (IS_COLOR_CODE(*ptr) || *ptr == '\n') {
+ type = *ptr;
+ *ptr++ = '\0';
+ break;
+ }
+
+ *ptr = (char) translation_in[(int) (unsigned char) *ptr];
+ }
+
+ if (type == 7) {
+ /* bell */
+ if (settings_get_bool("bell_beeps"))
+ signal_emit("beep", 0);
+ }
+
+ if (*str != '\0') {
+ /* send the text to gui handler */
+ signal_emit_id(signal_gui_print_text, 6, dest->window,
+ GINT_TO_POINTER(fgcolor),
+ GINT_TO_POINTER(bgcolor),
+ GINT_TO_POINTER(flags), str,
+ dest->level);
+ flags &= ~PRINTFLAG_INDENT;
+ }
+
+ if (type == '\n')
+ format_newline(dest->window);
+
+ if (*ptr == '\0')
+ break;
+
+ switch (type)
+ {
+ case 2:
+ /* bold */
+ if (!hide_text_style)
+ flags ^= PRINTFLAG_BOLD;
+ break;
+ case 3:
+ /* MIRC color */
+ get_mirc_color((const char **) &ptr,
+ hide_text_style ? NULL : &fgcolor,
+ hide_text_style ? NULL : &bgcolor);
+ if (!hide_text_style)
+ flags |= PRINTFLAG_MIRC_COLOR;
+ break;
+ case 4:
+ /* user specific colors */
+ flags &= ~PRINTFLAG_MIRC_COLOR;
+ switch (*ptr) {
+ case FORMAT_STYLE_BLINK:
+ flags ^= PRINTFLAG_BLINK;
+ break;
+ case FORMAT_STYLE_UNDERLINE:
+ flags ^= PRINTFLAG_UNDERLINE;
+ break;
+ case FORMAT_STYLE_BOLD:
+ flags ^= PRINTFLAG_BOLD;
+ break;
+ case FORMAT_STYLE_REVERSE:
+ flags ^= PRINTFLAG_REVERSE;
+ break;
+ case FORMAT_STYLE_INDENT:
+ flags |= PRINTFLAG_INDENT;
+ break;
+ case FORMAT_STYLE_DEFAULTS:
+ fgcolor = bgcolor = -1;
+ flags &= PRINTFLAG_INDENT;
+ break;
+ default:
+ if (*ptr != FORMAT_COLOR_NOCHANGE) {
+ fgcolor = (unsigned char) *ptr-'0';
+ if (fgcolor <= 7)
+ flags &= ~PRINTFLAG_BOLD;
+ else {
+ /* bold */
+ if (fgcolor != 8) fgcolor -= 8;
+ flags |= PRINTFLAG_BOLD;
+ }
+ }
+ ptr++;
+ if (*ptr != FORMAT_COLOR_NOCHANGE)
+ bgcolor = *ptr-'0';
+ }
+ ptr++;
+ break;
+ case 6:
+ /* blink */
+ if (!hide_text_style)
+ flags ^= PRINTFLAG_BLINK;
+ break;
+ case 15:
+ /* remove all styling */
+ fgcolor = bgcolor = -1;
+ flags &= PRINTFLAG_INDENT;
+ break;
+ case 22:
+ /* reverse */
+ if (!hide_text_style)
+ flags ^= PRINTFLAG_REVERSE;
+ break;
+ case 31:
+ /* underline */
+ if (!hide_text_style)
+ flags ^= PRINTFLAG_UNDERLINE;
+ break;
+ case 27:
+ /* ansi color code */
+ ptr = get_ansi_color(dest->window == NULL || dest->window->theme == NULL ?
+ current_theme : dest->window->theme,
+ ptr,
+ hide_text_style ? NULL : &fgcolor,
+ hide_text_style ? NULL : &bgcolor,
+ hide_text_style ? NULL : &flags);
+ break;
+ }
+
+ str = ptr;
+ }
+
+ g_free(dup);
+}
+
+static void read_settings(void)
+{
+ hide_server_tags = settings_get_bool("hide_server_tags");
+ hide_text_style = settings_get_bool("hide_text_style");
+ timestamps = settings_get_bool("timestamps");
+ timestamp_timeout = settings_get_int("timestamp_timeout");
+ msgs_timestamps = settings_get_bool("msgs_timestamps");
+}
+
+void formats_init(void)
+{
+ signal_gui_print_text = signal_get_uniq_id("gui print text");
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void formats_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __FORMATS_H
+#define __FORMATS_H
+
+#include "themes.h"
+#include "fe-windows.h"
+
+#define PRINTFLAG_BOLD 0x01
+#define PRINTFLAG_REVERSE 0x02
+#define PRINTFLAG_UNDERLINE 0x04
+#define PRINTFLAG_BLINK 0x08
+#define PRINTFLAG_MIRC_COLOR 0x10
+#define PRINTFLAG_INDENT 0x20
+#define PRINTFLAG_NEWLINE 0x40
+
+#define MAX_FORMAT_PARAMS 10
+#define DEFAULT_FORMAT_ARGLIST_SIZE 200
+
+enum {
+ FORMAT_STRING,
+ FORMAT_INT,
+ FORMAT_LONG,
+ FORMAT_FLOAT
+};
+
+struct _FORMAT_REC {
+ char *tag;
+ char *def;
+
+ int params;
+ int paramtypes[MAX_FORMAT_PARAMS];
+};
+
+typedef struct {
+ WINDOW_REC *window;
+ SERVER_REC *server;
+ const char *target;
+ int level;
+
+ int hilight_priority;
+ char *hilight_color;
+} TEXT_DEST_REC;
+
+int format_find_tag(const char *module, const char *tag);
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str);
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. Like strip_real_length(), except this
+ handles %codes. */
+int format_real_length(const char *str, int len);
+
+char *format_string_expand(const char *text);
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+ void *server, const char *target,
+ int formatnum, ...);
+
+/* good size for buffer is DEFAULT_FORMAT_ARGLIST_SIZE */
+void format_read_arglist(va_list va, FORMAT_REC *format,
+ char **arglist, int arglist_size,
+ char *buffer, int buffer_size);
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum, ...);
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ va_list va);
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ char **args);
+
+/* add `linestart' to start of each line in `text'. `text' may contain
+ multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart);
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest);
+
+/* return timestamp + server tag */
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t);
+
+
+/* "private" functions for printtext */
+void format_create_dest(TEXT_DEST_REC *dest,
+ void *server, const char *target,
+ int level, WINDOW_REC *window);
+
+void format_newline(WINDOW_REC *window);
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+ int *last_color_pos, int *last_color_len);
+
+/* strip all color (etc.) codes from `input'.
+ Returns newly allocated string. */
+char *strip_codes(const char *input);
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text);
+
+#define FORMAT_COLOR_NOCHANGE ('0'-1) /* don't change this, at least hilighting depends this value */
+
+#define FORMAT_STYLE_SPECIAL 0x60
+#define FORMAT_STYLE_BLINK (0x01 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_UNDERLINE (0x02 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_BOLD (0x03 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_REVERSE (0x04 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_INDENT (0x05 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_DEFAULTS (0x06 + FORMAT_STYLE_SPECIAL)
+int format_expand_styles(GString *out, char format);
+
+void formats_init(void);
+void formats_deinit(void);
+
+#endif
--- /dev/null
+/*
+ hilight-text.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "servers.h"
+#include "channels.h"
+#include "nicklist.h"
+
+#include "hilight-text.h"
+#include "nickmatch-cache.h"
+#include "printtext.h"
+#include "formats.h"
+
+static NICKMATCH_REC *nickmatch;
+static HILIGHT_REC *next_nick_hilight, *next_line_hilight;
+static int next_hilight_start, next_hilight_end;
+static int never_hilight_level, default_hilight_level;
+GSList *hilights;
+
+static void reset_cache(void)
+{
+ GSList *tmp;
+
+ never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (never_hilight_level & rec->level)
+ never_hilight_level &= ~rec->level;
+ }
+
+ nickmatch_rebuild(nickmatch);
+}
+
+static void hilight_add_config(HILIGHT_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(rec != NULL);
+
+ node = iconfig_node_traverse("(hilights", TRUE);
+ node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_str(node, "text", rec->text);
+ if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level);
+ if (rec->color) iconfig_node_set_str(node, "color", rec->color);
+ if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color);
+ if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority);
+ iconfig_node_set_bool(node, "nick", rec->nick);
+ iconfig_node_set_bool(node, "word", rec->word);
+ if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE);
+ if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+ if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+
+ if (rec->channels != NULL && *rec->channels != NULL) {
+ node = config_node_section(node, "channels", NODE_TYPE_LIST);
+ iconfig_node_add_list(node, rec->channels);
+ }
+}
+
+static void hilight_remove_config(HILIGHT_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(rec != NULL);
+
+ node = iconfig_node_traverse("hilights", FALSE);
+ if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec));
+}
+
+static void hilight_destroy(HILIGHT_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+#ifdef HAVE_REGEX_H
+ if (rec->regexp_compiled) regfree(&rec->preg);
+#endif
+ if (rec->channels != NULL) g_strfreev(rec->channels);
+ g_free_not_null(rec->color);
+ g_free_not_null(rec->act_color);
+ g_free(rec->text);
+ g_free(rec);
+}
+
+static void hilights_destroy_all(void)
+{
+ g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL);
+ g_slist_free(hilights);
+ hilights = NULL;
+}
+
+static void hilight_remove(HILIGHT_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ hilight_remove_config(rec);
+ hilights = g_slist_remove(hilights, rec);
+ hilight_destroy(rec);
+}
+
+static HILIGHT_REC *hilight_find(const char *text, char **channels)
+{
+ GSList *tmp;
+ char **chan;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->text, text) != 0)
+ continue;
+
+ if ((channels == NULL && rec->channels == NULL))
+ return rec; /* no channels - ok */
+
+ if (channels != NULL && strcmp(*channels, "*") == 0)
+ return rec; /* ignore channels */
+
+ if (channels == NULL || rec->channels == NULL)
+ continue; /* other doesn't have channels */
+
+ if (strarray_length(channels) != strarray_length(rec->channels))
+ continue; /* different amount of channels */
+
+ /* check that channels match */
+ for (chan = channels; *chan != NULL; chan++) {
+ if (strarray_find(rec->channels, *chan) == -1)
+ break;
+ }
+
+ if (*chan == NULL)
+ return rec; /* channels ok */
+ }
+
+ return NULL;
+}
+
+static int hilight_match_text(HILIGHT_REC *rec, const char *text,
+ int *match_beg, int *match_end)
+{
+ char *match;
+
+ if (rec->regexp) {
+#ifdef HAVE_REGEX_H
+ regmatch_t rmatch[1];
+
+ if (rec->regexp_compiled &&
+ regexec(&rec->preg, text, 1, rmatch, 0) == 0) {
+ if (rmatch[0].rm_so > 0 &&
+ match_beg != NULL && match_end != NULL) {
+ *match_beg = rmatch[0].rm_so;
+ *match_end = rmatch[0].rm_eo;
+ }
+ return TRUE;
+ }
+#endif
+ } else {
+ match = rec->fullword ?
+ stristr_full(text, rec->text) :
+ stristr(text, rec->text);
+ if (match != NULL) {
+ if (match_beg != NULL && match_end != NULL) {
+ *match_beg = (int) (match-text);
+ *match_end = *match_beg + strlen(rec->text);
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+#define hilight_match_level(rec, level) \
+ (level & (((rec)->level != 0 ? rec->level : default_hilight_level)))
+
+#define hilight_match_channel(rec, channel) \
+ ((rec)->channels == NULL || ((channel) != NULL && \
+ strarray_find((rec)->channels, (channel)) != -1))
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *str,
+ int *match_beg, int *match_end)
+{
+ GSList *tmp;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+
+ g_return_val_if_fail(str != NULL, NULL);
+
+ if ((never_hilight_level & level) == level)
+ return NULL;
+
+ if (nick != NULL) {
+ /* check nick mask hilights */
+ chanrec = channel_find(server, channel);
+ nickrec = chanrec == NULL ? NULL :
+ nicklist_find(chanrec, nick);
+ if (nickrec != NULL) {
+ HILIGHT_REC *rec;
+
+ if (nickrec->host == NULL)
+ nicklist_set_host(chanrec, nickrec, address);
+
+ rec = nickmatch_find(nickmatch, nickrec);
+ if (rec != NULL && hilight_match_level(rec, level))
+ return rec;
+ }
+ }
+
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (!rec->nickmask && hilight_match_level(rec, level) &&
+ hilight_match_channel(rec, channel) &&
+ hilight_match_text(rec, str, match_beg, match_end))
+ return rec;
+ }
+
+ return NULL;
+}
+
+static char *hilight_get_act_color(HILIGHT_REC *rec)
+{
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return g_strdup(rec->act_color != NULL ? rec->act_color :
+ rec->color != NULL ? rec->color :
+ settings_get_str("hilight_act_color"));
+}
+
+static char *hilight_get_color(HILIGHT_REC *rec)
+{
+ const char *color;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ color = rec->color != NULL ? rec->color :
+ settings_get_str("hilight_color");
+
+ return format_string_expand(color);
+}
+
+static void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec)
+{
+ dest->level |= MSGLEVEL_HILIGHT;
+
+ if (rec->priority > 0)
+ dest->hilight_priority = rec->priority;
+
+ dest->hilight_color = hilight_get_act_color(rec);
+}
+
+static void sig_print_text_stripped(TEXT_DEST_REC *dest, const char *str)
+{
+ HILIGHT_REC *hilight;
+
+ g_return_if_fail(str != NULL);
+
+ if (next_nick_hilight != NULL) {
+ if (!next_nick_hilight->nick) {
+ /* non-nick hilight wanted */
+ hilight = next_nick_hilight;
+ next_nick_hilight = NULL;
+ if (!hilight_match_text(hilight, str,
+ &next_hilight_start,
+ &next_hilight_end)) {
+ next_hilight_start = 0;
+ next_hilight_end = strlen(str);
+ }
+ } else {
+ /* nick is highlighted, just set priority */
+ hilight_update_text_dest(dest, next_nick_hilight);
+ next_nick_hilight = NULL;
+ return;
+ }
+ } else {
+ if (dest->level & (MSGLEVEL_NOHILIGHT|MSGLEVEL_HILIGHT))
+ return;
+
+ hilight = hilight_match(dest->server, dest->target,
+ NULL, NULL, dest->level, str,
+ &next_hilight_start,
+ &next_hilight_end);
+ }
+
+ if (hilight != NULL) {
+ /* update the level / hilight info */
+ hilight_update_text_dest(dest, hilight);
+
+ next_line_hilight = hilight;
+ }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *str)
+{
+ char *color, *newstr;
+ int next_hilight_len;
+
+ if (next_line_hilight == NULL)
+ return;
+
+ color = hilight_get_color(next_line_hilight);
+ next_hilight_len = next_hilight_end-next_hilight_start;
+
+ if (!next_line_hilight->word) {
+ /* hilight whole line */
+ char *tmp = strip_codes(str);
+ newstr = g_strconcat(color, tmp, NULL);
+ g_free(tmp);
+ } else {
+ /* hilight part of the line */
+ GString *tmp;
+ char *middle, *lastcolor;
+ int pos, color_pos, color_len;
+
+ tmp = g_string_new(NULL);
+
+ /* start of the line */
+ pos = strip_real_length(str, next_hilight_start, NULL, NULL);
+ g_string_append(tmp, str);
+ g_string_truncate(tmp, pos);
+
+ /* color */
+ g_string_append(tmp, color);
+
+ /* middle of the line, stripped */
+ middle = strip_codes(str+pos);
+ pos = tmp->len;
+ g_string_append(tmp, middle);
+ g_string_truncate(tmp, pos+next_hilight_len);
+ g_free(middle);
+
+ /* end of the line */
+ pos = strip_real_length(str, next_hilight_end,
+ &color_pos, &color_len);
+ if (color_pos > 0)
+ lastcolor = g_strndup(str+color_pos, color_len);
+ else {
+ /* no colors in line, change back to default */
+ lastcolor = g_malloc0(3);
+ lastcolor[0] = 4;
+ lastcolor[1] = FORMAT_STYLE_DEFAULTS;
+ }
+ g_string_append(tmp, lastcolor);
+ g_string_append(tmp, str+pos);
+ g_free(lastcolor);
+
+ newstr = tmp->str;
+ g_string_free(tmp, FALSE);
+ }
+
+ next_line_hilight = NULL;
+ signal_emit("print text", 2, dest, newstr);
+
+ g_free(color);
+ g_free(newstr);
+
+ signal_stop();
+}
+
+char *hilight_match_nick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *msg)
+{
+ HILIGHT_REC *rec;
+ char *color;
+
+ rec = hilight_match(server, channel, nick, address,
+ level, msg, NULL, NULL);
+ color = rec == NULL || !rec->nick ? NULL :
+ hilight_get_color(rec);
+
+ next_nick_hilight = rec;
+ return color;
+}
+
+static void read_hilight_config(void)
+{
+ CONFIG_NODE *node;
+ HILIGHT_REC *rec;
+ GSList *tmp;
+ char *text, *color;
+
+ hilights_destroy_all();
+
+ node = iconfig_node_traverse("hilights", FALSE);
+ if (node == NULL) {
+ reset_cache();
+ return;
+ }
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ text = config_node_get_str(node, "text", NULL);
+ if (text == NULL || *text == '\0')
+ continue;
+
+ rec = g_new0(HILIGHT_REC, 1);
+ hilights = g_slist_append(hilights, rec);
+
+ rec->text = g_strdup(text);
+
+ color = config_node_get_str(node, "color", NULL);
+ rec->color = color == NULL || *color == '\0' ? NULL :
+ g_strdup(color);
+
+ color = config_node_get_str(node, "act_color", NULL);
+ rec->act_color = color == NULL || *color == '\0' ? NULL :
+ g_strdup(color);
+
+ rec->level = config_node_get_int(node, "level", 0);
+ rec->priority = config_node_get_int(node, "priority", 0);
+ rec->nick = config_node_get_bool(node, "nick", TRUE);
+ rec->word = config_node_get_bool(node, "word", TRUE);
+
+ rec->nickmask = config_node_get_bool(node, "mask", FALSE);
+ rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+ rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+
+#ifdef HAVE_REGEX_H
+ rec->regexp_compiled = !rec->regexp ? FALSE :
+ regcomp(&rec->preg, rec->text,
+ REG_EXTENDED|REG_ICASE) == 0;
+#endif
+
+ node = config_node_section(node, "channels", -1);
+ if (node != NULL) rec->channels = config_node_get_list(node);
+ }
+
+ reset_cache();
+}
+
+static void hilight_print(int index, HILIGHT_REC *rec)
+{
+ char *chans, *levelstr;
+
+ chans = rec->channels == NULL ? NULL :
+ g_strjoinv(",", rec->channels);
+ levelstr = rec->level == 0 ? NULL :
+ bits2level(rec->level);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_HILIGHT_LINE, index, rec->text,
+ chans != NULL ? chans : "",
+ levelstr != NULL ? levelstr : "",
+ rec->nickmask ? " -mask" : "",
+ rec->fullword ? " -full" : "",
+ rec->regexp ? " -regexp" : "");
+ g_free_not_null(chans);
+ g_free_not_null(levelstr);
+}
+
+static void cmd_hilight_show(void)
+{
+ GSList *tmp;
+ int index;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_HEADER);
+ index = 1;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) {
+ HILIGHT_REC *rec = tmp->data;
+
+ hilight_print(index, rec);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_FOOTER);
+}
+
+/* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -regexp]
+ [-color <color>] [-actcolor <color>] [-level <level>]
+ [-channels <channels>] <text> */
+static void cmd_hilight(const char *data)
+{
+ GHashTable *optlist;
+ HILIGHT_REC *rec;
+ char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text;
+ char **channels;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ cmd_hilight_show();
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "hilight", &optlist, &text))
+ return;
+
+ chanarg = g_hash_table_lookup(optlist, "channels");
+ levelarg = g_hash_table_lookup(optlist, "level");
+ priorityarg = g_hash_table_lookup(optlist, "priority");
+ colorarg = g_hash_table_lookup(optlist, "color");
+ actcolorarg = g_hash_table_lookup(optlist, "actcolor");
+
+ if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
+ g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1);
+
+ rec = hilight_find(text, channels);
+ if (rec == NULL) {
+ rec = g_new0(HILIGHT_REC, 1);
+
+ /* default to nick/word hilighting */
+ rec->nick = TRUE;
+ rec->word = TRUE;
+
+ rec->text = g_strdup(text);
+ rec->channels = channels;
+ } else {
+ g_strfreev(channels);
+
+ hilight_remove_config(rec);
+ hilights = g_slist_remove(hilights, rec);
+ }
+
+ rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 :
+ level2bits(replace_chars(levelarg, ',', ' '));
+ rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg);
+
+ if (g_hash_table_lookup(optlist, "line") != NULL) {
+ rec->word = FALSE;
+ rec->nick = FALSE;
+ }
+
+ if (g_hash_table_lookup(optlist, "word") != NULL) {
+ rec->word = TRUE;
+ rec->nick = FALSE;
+ }
+
+ if (g_hash_table_lookup(optlist, "nick") != NULL)
+ rec->nick = TRUE;
+
+ rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL;
+ rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+ rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+
+ if (colorarg != NULL) {
+ if (*colorarg != '\0')
+ rec->color = g_strdup(colorarg);
+ else
+ g_free_and_null(rec->color);
+ }
+ if (actcolorarg != NULL) {
+ if (*actcolorarg != '\0')
+ rec->act_color = g_strdup(actcolorarg);
+ else
+ g_free_and_null(rec->act_color);
+ }
+
+#ifdef HAVE_REGEX_H
+ if (rec->regexp_compiled)
+ regfree(&rec->preg);
+ rec->regexp_compiled = !rec->regexp ? FALSE :
+ regcomp(&rec->preg, rec->text, REG_EXTENDED|REG_ICASE) == 0;
+#endif
+
+ hilights = g_slist_append(hilights, rec);
+ hilight_add_config(rec);
+
+ hilight_print(g_slist_index(hilights, rec)+1, rec);
+ cmd_params_free(free_arg);
+
+ reset_cache();
+}
+
+/* SYNTAX: DEHILIGHT <id>|<mask> */
+static void cmd_dehilight(const char *data)
+{
+ HILIGHT_REC *rec;
+ GSList *tmp;
+
+ if (is_numeric(data, ' ')) {
+ /* with index number */
+ tmp = g_slist_nth(hilights, atoi(data)-1);
+ rec = tmp == NULL ? NULL : tmp->data;
+ } else {
+ /* with mask */
+ char *chans[2] = { "*", NULL };
+ rec = hilight_find(data, chans);
+ }
+
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_NOT_FOUND, data);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text);
+ hilight_remove(rec);
+ reset_cache();
+ }
+}
+
+static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+ NICK_REC *nick)
+{
+ GSList *tmp;
+ HILIGHT_REC *match;
+ char *nickmask;
+ int len, best_match;
+
+ if (nick->host == NULL)
+ return; /* don't check until host is known */
+
+ nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+
+ best_match = 0; match = NULL;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (rec->nickmask &&
+ hilight_match_channel(rec, channel->name) &&
+ match_wildcards(rec->text, nickmask)) {
+ len = strlen(rec->text);
+ if (best_match < len) {
+ best_match = len;
+ match = rec;
+ }
+ }
+ }
+ g_free_not_null(nickmask);
+
+ if (match != NULL)
+ g_hash_table_insert(list, nick, match);
+}
+
+static void read_settings(void)
+{
+ default_hilight_level = level2bits(settings_get_str("hilight_level"));
+}
+
+void hilight_text_init(void)
+{
+ settings_add_str("lookandfeel", "hilight_color", "%Y");
+ settings_add_str("lookandfeel", "hilight_act_color", "%M");
+ settings_add_str("lookandfeel", "hilight_level", "PUBLIC DCCMSGS");
+
+ next_nick_hilight = NULL;
+ next_line_hilight = NULL;
+
+ read_settings();
+
+ nickmatch = nickmatch_init(hilight_nick_cache);
+ read_hilight_config();
+
+ signal_add_first("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
+ signal_add_first("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight);
+ command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight);
+ command_set_options("hilight", "-color -actcolor -level -priority -channels nick word line mask full regexp");
+}
+
+void hilight_text_deinit(void)
+{
+ hilights_destroy_all();
+ nickmatch_deinit(nickmatch);
+
+ signal_remove("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped);
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight);
+ command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight);
+}
--- /dev/null
+#ifndef __HILIGHT_TEXT_H
+#define __HILIGHT_TEXT_H
+
+#ifdef HAVE_REGEX_H
+# include <regex.h>
+#endif
+
+typedef struct {
+ char *text;
+
+ char **channels; /* if non-NULL, check the text only from these channels */
+ int level; /* match only messages with this level, 0=default */
+ char *color; /* if starts with number, \003 is automatically
+ inserted before it. */
+ char *act_color; /* color for window activity */
+ int priority;
+
+ unsigned int nick:1; /* hilight only nick if possible */
+ unsigned int word:1; /* hilight only word, not full line */
+
+ unsigned int nickmask:1; /* `text' is a nick mask */
+ unsigned int fullword:1; /* match `text' only for full words */
+ unsigned int regexp:1; /* `text' is a regular expression */
+#ifdef HAVE_REGEX_H
+ unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */
+ regex_t preg;
+#endif
+} HILIGHT_REC;
+
+extern GSList *hilights;
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *str,
+ int *match_beg, int *match_end);
+
+char *hilight_match_nick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *msg);
+
+void hilight_text_init(void);
+void hilight_text_deinit(void);
+
+#endif
--- /dev/null
+/*
+ keyboard.c : irssi
+
+ Copyright (C) 1999-2001 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "keyboard.h"
+#include "fe-windows.h"
+#include "printtext.h"
+
+GSList *keyinfos;
+static GHashTable *keys, *default_keys;
+
+/* A cache of some sort for key presses that generate a single char only.
+ If the key isn't used, used_keys[key] is zero. */
+static char used_keys[256];
+
+/* contains list of all key bindings of which command is "key" -
+ this can be used to check fast if some command queue exists or not.
+ Format is _always_ in key1-key2-key3 format (like ^W-^N,
+ not ^W^N) */
+static GTree *key_states;
+/* List of all key combo names */
+static GSList *key_combos;
+static int key_config_frozen;
+
+struct KEYBOARD_REC {
+ /* example:
+ /BIND ^[ key meta
+ /BIND meta-O key meta2
+ /BIND meta-[ key meta2
+
+ /BIND meta2-C key right
+ /BIND ^W-meta-right /echo ^W Meta-right key pressed
+
+ When ^W Meta-Right is pressed, the full char combination
+ is "^W^[^[[C".
+
+ We'll get there with key states:
+ ^W - key_prev_state = NULL, key_state = NULL -> ^W
+ ^[ - key_prev_state = NULL, key_state = ^W -> meta
+ ^[ - key_prev_state = ^W, key_state = meta -> meta
+ [ - key_prev_state = ^W-meta, key_state = meta -> meta2
+ C - key_prev_state = ^W-meta, key_state = meta2 -> right
+ key_prev_state = ^W-meta, key_state = right -> ^W-meta-right
+
+ key_state is moved to key_prev_state if there's nothing else in
+ /BINDs matching for key_state-newkey.
+
+ ^X^Y equals to ^X-^Y, ABC equals to A-B-C unless there's ABC
+ named key. ^ can be used with ^^ and - with -- */
+ char *key_state, *key_prev_state;
+
+ /* GUI specific data sent in "key pressed" signal */
+ void *gui_data;
+};
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+ key combo states and sending the gui_data parameter in "key pressed"
+ signal */
+KEYBOARD_REC *keyboard_create(void *data)
+{
+ KEYBOARD_REC *rec;
+
+ rec = g_new0(KEYBOARD_REC, 1);
+ rec->gui_data = data;
+
+ signal_emit("keyboard created", 1, rec);
+ return rec;
+}
+
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard)
+{
+ signal_emit("keyboard destroyed", 1, keyboard);
+
+ g_free_not_null(keyboard->key_state);
+ g_free_not_null(keyboard->key_prev_state);
+ g_free(keyboard);
+}
+
+static void key_destroy(KEY_REC *rec, GHashTable *hash)
+{
+ g_hash_table_remove(hash, rec->key);
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+static void key_default_add(const char *id, const char *key, const char *data)
+{
+ KEYINFO_REC *info;
+ KEY_REC *rec;
+
+ info = key_info_find(id);
+ if (info == NULL)
+ return;
+
+ rec = g_hash_table_lookup(default_keys, key);
+ if (rec != NULL) {
+ /* key already exists, replace */
+ rec->info->default_keys =
+ g_slist_remove(rec->info->default_keys, rec);
+ key_destroy(rec, default_keys);
+ }
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(key);
+ rec->info = info;
+ rec->data = g_strdup(data);
+ info->default_keys = g_slist_append(info->default_keys, rec);
+ g_hash_table_insert(default_keys, rec->key, rec);
+}
+
+static CONFIG_NODE *key_config_find(const char *key)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ /* remove old keyboard settings */
+ node = iconfig_node_traverse("(keyboard", TRUE);
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (strcmp(config_node_get_str(node, "key", ""), key) == 0)
+ return node;
+ }
+
+ return NULL;
+}
+
+static void keyconfig_save(const char *id, const char *key, const char *data)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL);
+
+ node = key_config_find(key);
+ if (node == NULL) {
+ node = iconfig_node_traverse("(keyboard", TRUE);
+ node = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+ }
+
+ iconfig_node_set_str(node, "key", key);
+ iconfig_node_set_str(node, "id", id);
+ iconfig_node_set_str(node, "data", data);
+}
+
+static void keyconfig_clear(const char *key)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(key != NULL);
+
+ /* remove old keyboard settings */
+ node = key_config_find(key);
+ if (node != NULL)
+ iconfig_node_clear(node);
+}
+
+KEYINFO_REC *key_info_find(const char *id)
+{
+ GSList *tmp;
+
+ for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+ KEYINFO_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->id, id) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static KEY_REC *key_combo_find(const char *key)
+{
+ KEYINFO_REC *info;
+ GSList *tmp;
+
+ info = key_info_find("key");
+ if (info == NULL)
+ return NULL;
+
+ for (tmp = info->keys; tmp != NULL; tmp = tmp->next) {
+ KEY_REC *rec = tmp->data;
+
+ if (strcmp(rec->data, key) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void key_states_scan_key(const char *key, KEY_REC *rec, GString *temp)
+{
+ char **keys, **tmp, *p;
+
+ g_string_truncate(temp, 0);
+
+ /* meta-^W^Gfoo -> meta-^W-^G-f-o-o */
+ keys = g_strsplit(key, "-", -1);
+ for (tmp = keys; *tmp != NULL; tmp++) {
+ if (key_combo_find(*tmp)) {
+ /* key combo */
+ g_string_append(temp, *tmp);
+ g_string_append_c(temp, '-');
+ continue;
+ }
+
+ if (**tmp == '\0') {
+ /* '-' */
+ g_string_append(temp, "--");
+ continue;
+ }
+
+ for (p = *tmp; *p != '\0'; p++) {
+ g_string_append_c(temp, *p);
+
+ if (*p == '^') {
+ /* ctrl-code */
+ if (p[1] != '\0')
+ p++;
+ g_string_append_c(temp, *p);
+ }
+
+ g_string_append_c(temp, '-');
+ }
+ }
+ g_strfreev(keys);
+
+ if (temp->len > 0) {
+ g_string_truncate(temp, temp->len-1);
+
+ if (temp->str[1] == '-' || temp->str[1] == '\0')
+ used_keys[(int) (unsigned char) temp->str[0]] = 1;
+ g_tree_insert(key_states, g_strdup(temp->str), rec);
+ }
+}
+
+static int key_state_destroy(char *key)
+{
+ g_free(key);
+ return FALSE;
+}
+
+/* Rescan all the key combos and figure out which characters are supposed
+ to be treated as characters and which as key combos.
+ Yes, this is pretty slow function... */
+static void key_states_rescan(void)
+{
+ GString *temp;
+
+ memset(used_keys, 0, sizeof(used_keys));
+
+ g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy,
+ G_IN_ORDER, NULL);
+ g_tree_destroy(key_states);
+ key_states = g_tree_new((GCompareFunc) strcmp);
+
+ temp = g_string_new(NULL);
+ g_hash_table_foreach(keys, (GHFunc) key_states_scan_key, temp);
+ g_string_free(temp, TRUE);
+}
+
+void key_configure_freeze(void)
+{
+ key_config_frozen++;
+}
+
+void key_configure_thaw(void)
+{
+ g_return_if_fail(key_config_frozen > 0);
+
+ if (--key_config_frozen == 0)
+ key_states_rescan();
+}
+
+static void key_configure_destroy(KEY_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ rec->info->keys = g_slist_remove(rec->info->keys, rec);
+ g_hash_table_remove(keys, rec->key);
+
+ if (!key_config_frozen)
+ key_states_rescan();
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+/* Configure new key */
+static void key_configure_create(const char *id, const char *key,
+ const char *data)
+{
+ KEYINFO_REC *info;
+ KEY_REC *rec;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL && *key != '\0');
+
+ info = key_info_find(id);
+ if (info == NULL)
+ return;
+
+ rec = g_hash_table_lookup(keys, key);
+ if (rec != NULL)
+ key_configure_destroy(rec);
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(key);
+ rec->info = info;
+ rec->data = g_strdup(data);
+ info->keys = g_slist_append(info->keys, rec);
+ g_hash_table_insert(keys, rec->key, rec);
+
+ if (!key_config_frozen)
+ key_states_rescan();
+}
+
+/* Bind a key for function */
+void key_bind(const char *id, const char *description,
+ const char *key_default, const char *data, SIGNAL_FUNC func)
+{
+ KEYINFO_REC *info;
+ char *key;
+
+ g_return_if_fail(id != NULL);
+
+ /* create key info record */
+ info = key_info_find(id);
+ if (info == NULL) {
+ g_return_if_fail(func != NULL);
+
+ if (description == NULL)
+ g_warning("key_bind(%s) should have description!", id);
+ info = g_new0(KEYINFO_REC, 1);
+ info->id = g_strdup(id);
+ info->description = g_strdup(description);
+ keyinfos = g_slist_append(keyinfos, info);
+
+ /* add the signal */
+ key = g_strconcat("key ", id, NULL);
+ signal_add(key, func);
+ g_free(key);
+
+ signal_emit("keyinfo created", 1, info);
+ }
+
+ if (key_default != NULL && *key_default != '\0') {
+ key_default_add(id, key_default, data);
+ key_configure_create(id, key_default, data);
+ }
+}
+
+static void keyinfo_remove(KEYINFO_REC *info)
+{
+ g_return_if_fail(info != NULL);
+
+ keyinfos = g_slist_remove(keyinfos, info);
+ signal_emit("keyinfo destroyed", 1, info);
+
+ /* destroy all keys */
+ g_slist_foreach(info->keys, (GFunc) key_destroy, keys);
+ g_slist_foreach(info->default_keys, (GFunc) key_destroy, default_keys);
+
+ /* destroy key info */
+ g_slist_free(info->keys);
+ g_slist_free(info->default_keys);
+ g_free_not_null(info->description);
+ g_free(info->id);
+ g_free(info);
+}
+
+/* Unbind key */
+void key_unbind(const char *id, SIGNAL_FUNC func)
+{
+ KEYINFO_REC *info;
+ char *key;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(func != NULL);
+
+ /* remove keys */
+ info = key_info_find(id);
+ if (info != NULL)
+ keyinfo_remove(info);
+
+ /* remove signal */
+ key = g_strconcat("key ", id, NULL);
+ signal_remove(key, func);
+ g_free(key);
+}
+
+/* Configure new key */
+void key_configure_add(const char *id, const char *key, const char *data)
+{
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL && *key != '\0');
+
+ key_configure_create(id, key, data);
+ keyconfig_save(id, key, data);
+}
+
+/* Remove key */
+void key_configure_remove(const char *key)
+{
+ KEY_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ rec = g_hash_table_lookup(keys, key);
+ if (rec == NULL) return;
+
+ keyconfig_clear(key);
+ key_configure_destroy(rec);
+}
+
+static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key)
+{
+ int consumed;
+ char *str;
+
+ str = g_strconcat("key ", key->info->id, NULL);
+ consumed = signal_emit(str, 3, key->data, keyboard->gui_data, key->info);
+ g_free(str);
+
+ return consumed;
+}
+
+int key_states_search(const char *combo, const char *search)
+{
+ while (*search != '\0') {
+ if (*combo != *search)
+ return *search - *combo;
+ search++; combo++;
+ }
+
+ return *combo == '\0' || *combo == '-' ? 0 : -1;
+}
+
+/* Returns TRUE if key press was consumed. Control characters should be sent
+ as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */
+int key_pressed(KEYBOARD_REC *keyboard, const char *key)
+{
+ KEY_REC *rec;
+ char *str;
+ int consumed;
+
+ g_return_val_if_fail(keyboard != NULL, FALSE);
+ g_return_val_if_fail(key != NULL && *key != '\0', FALSE);
+
+ if (keyboard->key_state == NULL) {
+ if (key[1] == '\0' &&
+ !used_keys[(int) (unsigned char) key[0]]) {
+ /* fast check - key not used */
+ return FALSE;
+ }
+
+ rec = g_tree_search(key_states,
+ (GSearchFunc) key_states_search,
+ (void *) key);
+ if (rec == NULL ||
+ (g_tree_lookup(key_states, (void *) key) != NULL &&
+ strcmp(rec->info->id, "key") != 0)) {
+ /* a single non-combo key was pressed */
+ rec = g_hash_table_lookup(keys, key);
+ if (rec == NULL)
+ return FALSE;
+ consumed = key_emit_signal(keyboard, rec);
+
+ /* never consume non-control characters */
+ return consumed && key[1] != '\0';
+ }
+ }
+
+ if (keyboard->key_state == NULL) {
+ /* first key in combo */
+ rec = g_tree_lookup(key_states, (void *) key);
+ } else {
+ /* continuing key combination */
+ str = g_strconcat(keyboard->key_state, "-", key, NULL);
+ rec = g_tree_lookup(key_states, str);
+ g_free(str);
+ }
+
+ if (rec != NULL && strcmp(rec->info->id, "key") == 0) {
+ /* combo has a specified name, use it */
+ g_free_not_null(keyboard->key_state);
+ keyboard->key_state = g_strdup(rec->data);
+ } else {
+ /* some unnamed key - move key_state after key_prev_state
+ and replace key_state with this new key */
+ if (keyboard->key_prev_state == NULL)
+ keyboard->key_prev_state = keyboard->key_state;
+ else {
+ str = g_strconcat(keyboard->key_prev_state, "-",
+ keyboard->key_state, NULL);
+ g_free(keyboard->key_prev_state);
+ g_free(keyboard->key_state);
+ keyboard->key_prev_state = str;
+ }
+
+ keyboard->key_state = g_strdup(key);
+ }
+
+ /* what to do with the key combo? */
+ str = keyboard->key_prev_state == NULL ?
+ g_strdup(keyboard->key_state) :
+ g_strconcat(keyboard->key_prev_state, "-",
+ keyboard->key_state, NULL);
+
+ rec = g_tree_lookup(key_states, str);
+ if (rec != NULL) {
+ if (strcmp(rec->info->id, "key") == 0)
+ rec = g_tree_lookup(key_states, rec->data);
+
+ if (rec != NULL) {
+ /* full key combo */
+ key_emit_signal(keyboard, rec);
+ rec = NULL;
+ }
+ } else {
+ /* check that combo is possible */
+ rec = g_tree_search(key_states,
+ (GSearchFunc) key_states_search, str);
+ }
+
+ if (rec == NULL) {
+ /* a) key combo finished, b) unknown key combo, abort */
+ g_free_and_null(keyboard->key_prev_state);
+ g_free_and_null(keyboard->key_state);
+ }
+
+ g_free(str);
+ return TRUE;
+}
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+ int flags, void *data)
+{
+ signal_emit("gui entry redirect", 4, func, entry,
+ GINT_TO_POINTER(flags), data);
+}
+
+static void sig_command(const char *data)
+{
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+ signal_emit("send command", 3, str, active_win->active_server, active_win->active);
+
+ g_free(str);
+}
+
+static void sig_key(const char *data)
+{
+ /* we should never get here */
+}
+
+static void sig_multi(const char *data, void *gui_data)
+{
+ KEYINFO_REC *info;
+ char **list, **tmp, *p, *str;
+
+ list = g_strsplit(data, ";", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ p = strchr(*tmp, ' ');
+ if (p != NULL) *p++ = '\0'; else p = "";
+
+ info = key_info_find(*tmp);
+ if (info != NULL) {
+ str = g_strconcat("key ", info->id, NULL);
+ signal_emit(str, 3, p, gui_data, info);
+ g_free(str);
+ }
+ }
+ g_strfreev(list);
+}
+
+static void cmd_show_keys(const char *searchkey, int full)
+{
+ GSList *info, *key;
+ int len;
+
+ len = searchkey == NULL ? 0 : strlen(searchkey);
+ for (info = keyinfos; info != NULL; info = info->next) {
+ KEYINFO_REC *rec = info->data;
+
+ for (key = rec->keys; key != NULL; key = key->next) {
+ KEY_REC *rec = key->data;
+
+ if ((len == 0 || g_strncasecmp(rec->key, searchkey, len) == 0) &&
+ (!full || rec->key[len] == '\0')) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_KEY,
+ rec->key, rec->info->id, rec->data == NULL ? "" : rec->data);
+ }
+ }
+ }
+}
+
+/* SYNTAX: BIND [-delete] [<key> [<command> [<data>]]] */
+static void cmd_bind(const char *data)
+{
+ GHashTable *optlist;
+ char *key, *id, *keydata;
+ void *free_arg;
+ int command_id;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "bind", &optlist, &key, &id, &keydata))
+ return;
+
+ if (*key != '\0' && g_hash_table_lookup(optlist, "delete")) {
+ /* delete key */
+ key_configure_remove(key);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*id == '\0') {
+ /* show some/all keys */
+ cmd_show_keys(key, FALSE);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ command_id = strchr(settings_get_str("cmdchars"), *id) != NULL;
+ if (command_id) {
+ /* using shortcut to command id */
+ keydata = g_strconcat(id, " ", keydata, NULL);
+ id = "command";
+ }
+
+ if (key_info_find(id) == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_BIND_UNKNOWN_ID, id);
+ else {
+ key_configure_add(id, key, keydata);
+ cmd_show_keys(key, TRUE);
+ }
+
+ if (command_id) g_free(keydata);
+ cmd_params_free(free_arg);
+}
+
+static GList *completion_get_keyinfos(const char *info)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ list = NULL; len = strlen(info);
+ for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+ KEYINFO_REC *rec = tmp->data;
+
+ if (g_strncasecmp(rec->id, info, len) == 0)
+ list = g_list_append(list, g_strdup(rec->id));
+ }
+
+ return list;
+}
+
+static void sig_complete_bind(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0' || strchr(line, ' ') != NULL)
+ return;
+
+ *list = completion_get_keyinfos(word);
+ if (*list != NULL) signal_stop();
+}
+
+static int key_destroy_hash(const char *key, KEY_REC *rec)
+{
+ rec->info->keys = g_slist_remove(rec->info->keys, rec);
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+ return TRUE;
+}
+
+static void key_copy_default(const char *key, KEY_REC *orig)
+{
+ KEY_REC *rec;
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(orig->key);
+ rec->info = orig->info;
+ rec->data = g_strdup(orig->data);
+
+ rec->info->keys = g_slist_append(rec->info->keys, rec);
+ g_hash_table_insert(keys, rec->key, rec);
+}
+
+static void keyboard_reset_defaults(void)
+{
+ g_hash_table_foreach_remove(keys, (GHRFunc) key_destroy_hash, NULL);
+ g_hash_table_foreach(default_keys, (GHFunc) key_copy_default, NULL);
+}
+
+static void key_config_read(CONFIG_NODE *node)
+{
+ char *key, *id, *data;
+
+ g_return_if_fail(node != NULL);
+
+ key = config_node_get_str(node, "key", NULL);
+ id = config_node_get_str(node, "id", NULL);
+ data = config_node_get_str(node, "data", NULL);
+
+ if (key != NULL && id != NULL)
+ key_configure_create(id, key, data);
+}
+
+static void read_keyboard_config(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ key_configure_freeze();
+
+ keyboard_reset_defaults();
+
+ node = iconfig_node_traverse("keyboard", FALSE);
+ if (node == NULL) {
+ key_configure_thaw();
+ return;
+ }
+
+ /* FIXME: backward "compatibility" - remove after irssi .99 */
+ if (node->type != NODE_TYPE_LIST) {
+ iconfig_node_remove(NULL, node);
+ key_configure_thaw();
+ return;
+ }
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next)
+ key_config_read(tmp->data);
+
+ key_configure_thaw();
+}
+
+void keyboard_init(void)
+{
+ keys = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ default_keys = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ keyinfos = NULL;
+ key_states = g_tree_new((GCompareFunc) strcmp);
+ key_combos = NULL;
+ key_config_frozen = 0;
+ memset(used_keys, 0, sizeof(used_keys));
+
+ key_bind("command", "Run any IRC command", NULL, NULL, (SIGNAL_FUNC) sig_command);
+ key_bind("key", "Specify name for key binding", NULL, NULL, (SIGNAL_FUNC) sig_key);
+ key_bind("multi", "Run multiple commands", NULL, NULL, (SIGNAL_FUNC) sig_multi);
+
+ /* read the keyboard config when all key binds are known */
+ signal_add("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+ signal_add("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+ signal_add("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+
+ command_bind("bind", NULL, (SIGNAL_FUNC) cmd_bind);
+ command_set_options("bind", "delete");
+}
+
+void keyboard_deinit(void)
+{
+ while (keyinfos != NULL)
+ keyinfo_remove(keyinfos->data);
+ g_hash_table_destroy(keys);
+ g_hash_table_destroy(default_keys);
+
+ g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy,
+ G_IN_ORDER, NULL);
+ g_tree_destroy(key_states);
+
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+ signal_remove("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+ command_unbind("bind", (SIGNAL_FUNC) cmd_bind);
+}
--- /dev/null
+#ifndef __KEYBOARD_H
+#define __KEYBOARD_H
+
+#include "signals.h"
+
+typedef struct KEYBOARD_REC KEYBOARD_REC;
+
+typedef struct {
+ char *id;
+ char *description;
+
+ GSList *keys, *default_keys;
+} KEYINFO_REC;
+
+typedef struct {
+ KEYINFO_REC *info;
+
+ char *key;
+ char *data;
+} KEY_REC;
+
+extern GSList *keyinfos;
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+ key combo states and sending the gui_data parameter in "key pressed"
+ signal */
+KEYBOARD_REC *keyboard_create(void *gui_data);
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard);
+/* Returns TRUE if key press was consumed. Control characters should be sent
+ as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */
+int key_pressed(KEYBOARD_REC *keyboard, const char *key);
+
+void key_bind(const char *id, const char *description,
+ const char *key_default, const char *data, SIGNAL_FUNC func);
+void key_unbind(const char *id, SIGNAL_FUNC func);
+
+void key_configure_freeze(void);
+void key_configure_thaw(void);
+
+void key_configure_add(const char *id, const char *key, const char *data);
+void key_configure_remove(const char *key);
+
+KEYINFO_REC *key_info_find(const char *id);
+
+#define ENTRY_REDIRECT_FLAG_HOTKEY 0x01
+#define ENTRY_REDIRECT_FLAG_HIDDEN 0x02
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+ int flags, void *data);
+
+void keyboard_init(void);
+void keyboard_deinit(void);
+
+#endif
--- /dev/null
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 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 "formats.h"
+
+FORMAT_REC fecommon_core_formats[] = {
+ { MODULE_NAME, "Core", 0 },
+
+ /* ---- */
+ { NULL, "Windows", 0 },
+
+ { "line_start", "{line_start}", 0 },
+ { "line_start_irssi", "{line_start}{hilight Irssi:} ", 0 },
+ { "timestamp", "{timestamp $Z} ", 6, { 1, 1, 1, 1, 1, 1 } },
+ { "servertag", "[$0] ", 1, { 0 } },
+ { "daychange", "Day changed to $[-2.0]{0} $3 $2", 4, { 1, 1, 1, 0 } },
+ { "talking_with", "You are now talking with {nick $0}", 1, { 0 } },
+ { "refnum_too_low", "Window number must be greater than 1", 0 },
+ { "error_server_sticky", "Window's server is sticky and it cannot be changed without -unsticky option", 0 },
+ { "set_server_sticky", "Window's server set sticky", 1, { 0 } },
+ { "unset_server_sticky", "Window's server isn't sticky anymore", 1, { 0 } },
+ { "window_level", "Window level is now $0", 1, { 0 } },
+ { "windowlist_header", "Ref Name Active item Server Level", 0 },
+ { "windowlist_line", "$[3]0 %|$[20]1 $[15]2 $[15]3 $4", 5, { 1, 0, 0, 0, 0 } },
+ { "windowlist_footer", "", 0 },
+ { "windows_layout_saved", "Layout of windows is now remembered next time you start irssi", 0 },
+ { "windows_layout_reset", "Layout of windows reset to defaults", 0 },
+
+ /* ---- */
+ { NULL, "Server", 0 },
+
+ { "looking_up", "Looking up {server $0}", 1, { 0 } },
+ { "connecting", "Connecting to {server $0} [$1] port {hilight $2}", 3, { 0, 0, 1 } },
+ { "connection_established", "Connection to {server $0} established", 1, { 0 } },
+ { "cant_connect", "Unable to connect server {server $0} port {hilight $1} {reason $2}", 3, { 0, 1, 0 } },
+ { "connection_lost", "Connection lost to {server $0}", 1, { 0 } },
+ { "lag_disconnected", "No PONG reply from server {server $0} in $1 seconds, disconnecting", 2, { 0, 1 } },
+ { "disconnected", "Disconnected from {server $0} {reason $1}", 2, { 0, 0 } },
+ { "server_quit", "Disconnecting from server {server $0}: {reason $1}", 2, { 0, 0 } },
+ { "server_changed", "Changed to {hilight $2} server {server $1}", 3, { 0, 0, 0 } },
+ { "unknown_server_tag", "Unknown server tag {server $0}", 1, { 0 } },
+ { "no_connected_servers", "Not connected to any servers", 0 },
+ { "server_list", "{server $0}: $1:$2 ($3)", 5, { 0, 0, 1, 0, 0 } },
+ { "server_lookup_list", "{server $0}: $1:$2 ($3) (connecting...)", 5, { 0, 0, 1, 0, 0 } },
+ { "server_reconnect_list", "{server $0}: $1:$2 ($3) ($5 left before reconnecting)", 6, { 0, 0, 1, 0, 0, 0 } },
+ { "server_reconnect_removed", "Removed reconnection to server {server $0} port {hilight $1}", 3, { 0, 1, 0 } },
+ { "server_reconnect_not_found", "Reconnection tag {server $0} not found", 1, { 0 } },
+ { "setupserver_added", "Server {server $0} saved", 2, { 0, 1 } },
+ { "setupserver_removed", "Server {server $0} removed", 2, { 0, 1 } },
+ { "setupserver_not_found", "Server {server $0} not found", 2, { 0, 1 } },
+
+ /* ---- */
+ { NULL, "Channels", 0 },
+
+ { "join", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}", 3, { 0, 0, 0 } },
+ { "part", "{channick $0} {chanhost $1} has left {channel $2} {reason $3}", 4, { 0, 0, 0, 0 } },
+ { "kick", "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}", 4, { 0, 0, 0, 0 } },
+ { "quit", "{channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+ { "quit_once", "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+ { "invite", "{nick $0} invites you to {channel $1}", 2, { 0, 0 } },
+ { "new_topic", "{nick $0} changed the topic of {channel $1} to: $2", 3, { 0, 0, 0 } },
+ { "topic_unset", "Topic unset by {nick $0} on {channel $1}", 2, { 0, 0 } },
+ { "your_nick_changed", "You're now known as {nick $1}", 3, { 0, 0, 0 } },
+ { "nick_changed", "{channick $0} is now known as {channick_hilight $1}", 3, { 0, 0, 0 } },
+ { "talking_in", "You are now talking in {channel $0}", 1, { 0 } },
+ { "not_in_channels", "You are not on any channels", 0 },
+ { "current_channel", "Current channel {channel $0}", 1, { 0 } },
+ { "names", "{names_users Users {names_channel $0}} $1", 2, { 0, 0 } },
+ { "names_nick", "{names_nick $0 $1}", 2, { 0, 0 } },
+ { "endofnames", "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} voices, {hilight $4} normal}", 5, { 0, 1, 1, 1, 1 } },
+ { "chanlist_header", "You are on the following channels:", 0 },
+ { "chanlist_line", "{channel $[-10]0} %|+$1 ($2): $3", 4, { 0, 0, 0, 0 } },
+ { "chansetup_not_found", "Channel {channel $0} not found", 2, { 0, 0 } },
+ { "chansetup_added", "Channel {channel $0} saved", 2, { 0, 0 } },
+ { "chansetup_removed", "Channel {channel $0} removed", 2, { 0, 0 } },
+ { "chansetup_header", "Channel IRC net Password Settings", 0 },
+ { "chansetup_line", "{channel $[15]0} %|$[10]1 $[10]2 $3", 4, { 0, 0, 0, 0 } },
+ { "chansetup_footer", "", 0 },
+ { "channel_move_notify", "{channel $0} is already joined in window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } },
+
+ /* ---- */
+ { NULL, "Messages", 0 },
+
+ { "own_msg", "{ownmsgnick $2 {ownnick $0}}$1", 3, { 0, 0, 0 } },
+ { "own_msg_channel", "{ownmsgnick $3 {ownnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "own_msg_private", "{ownprivmsg msg $0}$1", 2, { 0, 0 } },
+ { "own_msg_private_query", "{ownprivmsgnick {ownprivnick $2}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_me", "{pubmsgmenick $2 {menick $0}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_me_channel", "{pubmsgmenick $3 {menick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "pubmsg_hilight", "{pubmsghinick $0 $3 $1}$2", 4, { 0, 0, 0, 0 } },
+ { "pubmsg_hilight_channel", "{pubmsghinick $0 $4 $1{msgchannel $2}}$3", 5, { 0, 0, 0, 0, 0 } },
+ { "pubmsg", "{pubmsgnick $2 {pubnick $0}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_channel", "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "msg_private", "{privmsg $0 $1}$2", 3, { 0, 0, 0 } },
+ { "msg_private_query", "{privmsgnick $0}$2", 3, { 0, 0, 0 } },
+ { "no_msgs_got", "You have not received a message from anyone yet", 0 },
+ { "no_msgs_sent", "You have not sent a message to anyone yet", 0 },
+
+ /* ---- */
+ { NULL, "Queries", 0 },
+
+ { "query_start", "Starting query with {nick $0}", 1, { 0 } },
+ { "no_query", "No query with {nick $0}", 1, { 0 } },
+ { "query_server_changed", "Query with {nick $0} changed to server {server $1}", 2, { 0, 0 } },
+ { "query_move_notify", "Query with {nick $0} is already created to window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } },
+
+ /* ---- */
+ { NULL, "Highlighting", 0 },
+
+ { "hilight_header", "Highlights:", 0 },
+ { "hilight_line", "$[-4]0 $1 $2 $3$4$5", 7, { 1, 0, 0, 0, 0, 0, 0 } },
+ { "hilight_footer", "", 0 },
+ { "hilight_not_found", "Highlight not found: $0", 1, { 0 } },
+ { "hilight_removed", "Highlight removed: $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Aliases", 0 },
+
+ { "alias_added", "Alias $0 added", 1, { 0 } },
+ { "alias_removed", "Alias $0 removed", 1, { 0 } },
+ { "alias_not_found", "No such alias: $0", 1, { 0 } },
+ { "aliaslist_header", "Aliases:", 0 },
+ { "aliaslist_line", "$[10]0 $1", 2, { 0, 0 } },
+ { "aliaslist_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Logging", 0 },
+
+ { "log_opened", "Log file {hilight $0} opened", 1, { 0 } },
+ { "log_closed", "Log file {hilight $0} closed", 1, { 0 } },
+ { "log_create_failed", "Couldn't create log file {hilight $0}: $1", 2, { 0, 0 } },
+ { "log_locked", "Log file {hilight $0} is locked, probably by another running Irssi", 1, { 0 } },
+ { "log_not_open", "Log file {hilight $0} not open", 1, { 0 } },
+ { "log_started", "Started logging to file {hilight $0}", 1, { 0 } },
+ { "log_stopped", "Stopped logging to file {hilight $0}", 1, { 0 } },
+ { "log_list_header", "Logs:", 0 },
+ { "log_list", "$0 $1: $2 $3$4", 5, { 1, 0, 0, 0, 0, 0 } },
+ { "log_list_footer", "", 0 },
+ { "windowlog_file", "Window LOGFILE set to $0", 1, { 0 } },
+ { "windowlog_file_logging", "Can't change window's logfile while log is on", 0 },
+ { "no_away_msgs", "No new messages in awaylog", 1, { 0 } },
+ { "away_msgs", "{hilight $1} new messages in awaylog:", 2, { 0, 1 } },
+
+ /* ---- */
+ { NULL, "Modules", 0 },
+
+ { "module_header", "Loaded modules:", 0, },
+ { "module_line", " $0", 1, { 0 } },
+ { "module_footer", "", 0, },
+ { "module_already_loaded", "Module {hilight $0} already loaded", 1, { 0 } },
+ { "module_load_error", "Error loading module {hilight $0}: $1", 2, { 0, 0 } },
+ { "module_invalid", "{hilight $0} isn't Irssi module", 1, { 0 } },
+ { "module_loaded", "Loaded module {hilight $0}", 1, { 0 } },
+ { "module_unloaded", "Unloaded module {hilight $0}", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Commands", 0 },
+
+ { "command_unknown", "Unknown command: $0", 1, { 0 } },
+ { "command_ambiguous", "Ambiguous command: $0", 1, { 0 } },
+ { "option_unknown", "Unknown option: $0", 1, { 0 } },
+ { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } },
+ { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } },
+ { "not_enough_params", "Not enough parameters given", 0 },
+ { "not_connected", "Not connected to server", 0 },
+ { "not_joined", "Not joined to any channel", 0 },
+ { "chan_not_found", "Not joined to such channel", 0 },
+ { "chan_not_synced", "Channel not fully synchronized yet, try again after a while", 0 },
+ { "not_good_idea", "Doing this is not a good idea. Add -YES if you really mean it", 0 },
+
+ /* ---- */
+ { NULL, "Themes", 0 },
+
+ { "theme_saved", "Theme saved to $0", 1, { 0 } },
+ { "theme_save_failed", "Error saving theme to $0: $1", 2, { 0, 0 } },
+ { "theme_not_found", "Theme {hilight $0} not found", 1, { 0 } },
+ { "theme_changed", "Using now theme {hilight $0} ($1)", 2, { 0, 0 } },
+ { "window_theme_changed", "Using theme {hilight $0} ($1) in this window", 2, { 0, 0 } },
+ { "format_title", "%:[{hilight $0}] - [{hilight $1}]%:", 2, { 0, 0 } },
+ { "format_subtitle", "[{hilight $0}]", 1, { 0 } },
+ { "format_item", "$0 = $1", 2, { 0, 0 } },
+
+ /* ---- */
+ { NULL, "Ignores", 0 },
+
+ { "ignored", "Ignoring {hilight $1} from {nick $0}", 2, { 0, 0 } },
+ { "unignored", "Unignored {nick $0}", 1, { 0 } },
+ { "ignore_not_found", "{nick $0} is not being ignored", 1, { 0 } },
+ { "ignore_no_ignores", "There are no ignores", 0 },
+ { "ignore_header", "Ignorance List:", 0 },
+ { "ignore_line", "$[-4]0 $1: $2 $3 $4", 4, { 1, 0, 0, 0 } },
+ { "ignore_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Misc", 0 },
+
+ { "unknown_chat_protocol", "Unknown chat protocol: $0", 1, { 0 } },
+ { "unknown_chatnet", "Unknown chat network: $0", 1, { 0 } },
+ { "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 },
+ { "perl_error", "Perl error: $0", 1, { 0 } },
+ { "bind_key", "$[10]0 $1 $2", 3, { 0, 0, 0 } },
+ { "bind_unknown_id", "Unknown bind action: $0", 1, { 0 } },
+ { "config_saved", "Saved configuration to file $0", 1, { 0 } },
+ { "config_reloaded", "Reloaded configuration", 1, { 0 } },
+ { "config_modified", "Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?", 1, { 0 } },
+ { "glib_error", "{error GLib $0} $1", 2, { 0, 0 } },
+ { "overwrite_config", "Overwrite config (y/N)?", 0 },
+
+ { NULL, NULL, 0 }
+};
--- /dev/null
+#include "formats.h"
+
+enum {
+ TXT_MODULE_NAME,
+
+ TXT_FILL_1,
+
+ TXT_LINE_START,
+ TXT_LINE_START_IRSSI,
+ TXT_TIMESTAMP,
+ TXT_SERVERTAG,
+ TXT_DAYCHANGE,
+ TXT_TALKING_WITH,
+ TXT_REFNUM_TOO_LOW,
+ TXT_ERROR_SERVER_STICKY,
+ TXT_SET_SERVER_STICKY,
+ TXT_UNSET_SERVER_STICKY,
+ TXT_WINDOW_LEVEL,
+ TXT_WINDOWLIST_HEADER,
+ TXT_WINDOWLIST_LINE,
+ TXT_WINDOWLIST_FOOTER,
+ TXT_WINDOWS_LAYOUT_SAVED,
+ TXT_WINDOWS_LAYOUT_RESET,
+
+ TXT_FILL_2,
+
+ TXT_LOOKING_UP,
+ TXT_CONNECTING,
+ TXT_CONNECTION_ESTABLISHED,
+ TXT_CANT_CONNECT,
+ TXT_CONNECTION_LOST,
+ TXT_LAG_DISCONNECTED,
+ TXT_DISCONNECTED,
+ TXT_SERVER_QUIT,
+ TXT_SERVER_CHANGED,
+ TXT_UNKNOWN_SERVER_TAG,
+ TXT_NO_CONNECTED_SERVERS,
+ TXT_SERVER_LIST,
+ TXT_SERVER_LOOKUP_LIST,
+ TXT_SERVER_RECONNECT_LIST,
+ TXT_RECONNECT_REMOVED,
+ TXT_RECONNECT_NOT_FOUND,
+ TXT_SETUPSERVER_ADDED,
+ TXT_SETUPSERVER_REMOVED,
+ TXT_SETUPSERVER_NOT_FOUND,
+
+ TXT_FILL_3,
+
+ TXT_JOIN,
+ TXT_PART,
+ TXT_KICK,
+ TXT_QUIT,
+ TXT_QUIT_ONCE,
+ TXT_INVITE,
+ TXT_NEW_TOPIC,
+ TXT_TOPIC_UNSET,
+ TXT_YOUR_NICK_CHANGED,
+ TXT_NICK_CHANGED,
+ TXT_TALKING_IN,
+ TXT_NOT_IN_CHANNELS,
+ TXT_CURRENT_CHANNEL,
+ TXT_NAMES,
+ TXT_NAMES_NICK,
+ TXT_ENDOFNAMES,
+ TXT_CHANLIST_HEADER,
+ TXT_CHANLIST_LINE,
+ TXT_CHANSETUP_NOT_FOUND,
+ TXT_CHANSETUP_ADDED,
+ TXT_CHANSETUP_REMOVED,
+ TXT_CHANSETUP_HEADER,
+ TXT_CHANSETUP_LINE,
+ TXT_CHANSETUP_FOOTER,
+ TXT_CHANNEL_MOVE_NOTIFY,
+
+ TXT_FILL_4,
+
+ TXT_OWN_MSG,
+ TXT_OWN_MSG_CHANNEL,
+ TXT_OWN_MSG_PRIVATE,
+ TXT_OWN_MSG_PRIVATE_QUERY,
+ TXT_PUBMSG_ME,
+ TXT_PUBMSG_ME_CHANNEL,
+ TXT_PUBMSG_HILIGHT,
+ TXT_PUBMSG_HILIGHT_CHANNEL,
+ TXT_PUBMSG,
+ TXT_PUBMSG_CHANNEL,
+ TXT_MSG_PRIVATE,
+ TXT_MSG_PRIVATE_QUERY,
+ TXT_NO_MSGS_GOT,
+ TXT_NO_MSGS_SENT,
+
+ TXT_FILL_5,
+
+ TXT_QUERY_STARTED,
+ TXT_NO_QUERY,
+ TXT_QUERY_SERVER_CHANGED,
+ TXT_QUERY_MOVE_NOTIFY,
+
+ TXT_FILL_6,
+
+ TXT_HILIGHT_HEADER,
+ TXT_HILIGHT_LINE,
+ TXT_HILIGHT_FOOTER,
+ TXT_HILIGHT_NOT_FOUND,
+ TXT_HILIGHT_REMOVED,
+
+ TXT_FILL_7,
+
+ TXT_ALIAS_ADDED,
+ TXT_ALIAS_REMOVED,
+ TXT_ALIAS_NOT_FOUND,
+ TXT_ALIASLIST_HEADER,
+ TXT_ALIASLIST_LINE,
+ TXT_ALIASLIST_FOOTER,
+
+ TXT_FILL_8,
+
+ TXT_LOG_OPENED,
+ TXT_LOG_CLOSED,
+ TXT_LOG_CREATE_FAILED,
+ TXT_LOG_LOCKED,
+ TXT_LOG_NOT_OPEN,
+ TXT_LOG_STARTED,
+ TXT_LOG_STOPPED,
+ TXT_LOG_LIST_HEADER,
+ TXT_LOG_LIST,
+ TXT_LOG_LIST_FOOTER,
+ TXT_WINDOWLOG_FILE,
+ TXT_WINDOWLOG_FILE_LOGGING,
+ TXT_LOG_NO_AWAY_MSGS,
+ TXT_LOG_AWAY_MSGS,
+
+ TXT_FILL_9,
+
+ TXT_MODULE_HEADER,
+ TXT_MODULE_LINE,
+ TXT_MODULE_FOOTER,
+ TXT_MODULE_ALREADY_LOADED,
+ TXT_MODULE_LOAD_ERROR,
+ TXT_MODULE_INVALID,
+ TXT_MODULE_LOADED,
+ TXT_MODULE_UNLOADED,
+
+ TXT_FILL_10,
+
+ TXT_COMMAND_UNKNOWN,
+ TXT_COMMAND_AMBIGUOUS,
+ TXT_OPTION_UNKNOWN,
+ TXT_OPTION_AMBIGUOUS,
+ TXT_OPTION_MISSING_ARG,
+ TXT_NOT_ENOUGH_PARAMS,
+ TXT_NOT_CONNECTED,
+ TXT_NOT_JOINED,
+ TXT_CHAN_NOT_FOUND,
+ TXT_CHAN_NOT_SYNCED,
+ TXT_NOT_GOOD_IDEA,
+
+ TXT_FILL_11,
+
+ TXT_THEME_SAVED,
+ TXT_THEME_SAVE_FAILED,
+ TXT_THEME_NOT_FOUND,
+ TXT_THEME_CHANGED,
+ TXT_WINDOW_THEME_CHANGED,
+ TXT_FORMAT_TITLE,
+ TXT_FORMAT_SUBTITLE,
+ TXT_FORMAT_ITEM,
+
+ TXT_FILL_12,
+
+ TXT_IGNORED,
+ TXT_UNIGNORED,
+ TXT_IGNORE_NOT_FOUND,
+ TXT_IGNORE_NO_IGNORES,
+ TXT_IGNORE_HEADER,
+ TXT_IGNORE_LINE,
+ TXT_IGNORE_FOOTER,
+
+ TXT_FILL_13,
+
+ TXT_UNKNOWN_CHAT_PROTOCOL,
+ TXT_UNKNOWN_CHATNET,
+ TXT_NOT_TOGGLE,
+ TXT_PERL_ERROR,
+ TXT_BIND_KEY,
+ TXT_BIND_UNKNOWN_ID,
+ TXT_CONFIG_SAVED,
+ TXT_CONFIG_RELOADED,
+ TXT_CONFIG_MODIFIED,
+ TXT_GLIB_ERROR,
+ TXT_OVERWRITE_CONFIG
+};
+
+extern FORMAT_REC fecommon_core_formats[];
--- /dev/null
+#include "common.h"
+
+#define MODULE_NAME "fe-common/core"
+
+typedef struct {
+ time_t time;
+ char *nick;
+
+ /* channel specific msg to/from me - this is actually a reference
+ count. it begins from `completion_keep_publics' and is decreased
+ every time some nick is added to lastmsgs list.
+
+ this is because of how the nick completion works. the same nick
+ is never in the lastmsgs list twice, but every time it's used
+ it's just moved to the beginning of the list. if this would be
+ just a boolean value the own-status would never be removed
+ from the nick if it didn't keep quiet for long enough.
+
+ so, the own-status is rememberd only for the last
+ `completion_keep_publics' lines */
+ int own;
+} LAST_MSG_REC;
+
+typedef struct {
+ /* /MSG completion: */
+ GSList *lastmsgs; /* list of nicks who sent you msg or
+ to who you send msg */
+} MODULE_SERVER_REC;
+
+typedef struct {
+ /* nick completion: */
+ GSList *lastmsgs; /* list of nicks who sent latest msgs and
+ list of nicks who you sent msgs to */
+} MODULE_CHANNEL_REC;
--- /dev/null
+/*
+ printtext.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "settings.h"
+
+#include "levels.h"
+#include "servers.h"
+
+#include "themes.h"
+#include "fe-windows.h"
+#include "printtext.h"
+
+static int beep_msg_level, beep_when_away, beep_when_window_active;
+
+static int signal_gui_print_text;
+static int signal_print_text_stripped;
+static int signal_print_text;
+static int signal_print_text_finished;
+static int signal_print_format;
+static int signal_print_starting;
+
+static int sending_print_starting;
+
+static void print_line(TEXT_DEST_REC *dest, const char *text);
+
+static void printformat_module_dest(const char *module, TEXT_DEST_REC *dest,
+ int formatnum, va_list va)
+{
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+ THEME_REC *theme;
+ char *str;
+
+ theme = dest->window->theme == NULL ? current_theme :
+ dest->window->theme;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, dest);
+ sending_print_starting = FALSE;
+ }
+
+ signal_emit_id(signal_print_format, 5, theme, module,
+ dest, GINT_TO_POINTER(formatnum), arglist);
+
+ str = format_get_text_theme_charargs(theme, module, dest,
+ formatnum, arglist);
+ if (*str != '\0') print_line(dest, str);
+ g_free(str);
+}
+
+void printformat_module_args(const char *module, void *server,
+ const char *target, int level,
+ int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+
+ format_create_dest(&dest, server, target, level, NULL);
+ printformat_module_dest(module, &dest, formatnum, va);
+}
+
+void printformat_module(const char *module, void *server, const char *target,
+ int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_args(module, server, target, level, formatnum, va);
+ va_end(va);
+}
+
+void printformat_module_window_args(const char *module, WINDOW_REC *window,
+ int level, int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+
+ format_create_dest(&dest, NULL, NULL, level, window);
+ printformat_module_dest(module, &dest, formatnum, va);
+}
+
+void printformat_module_window(const char *module, WINDOW_REC *window,
+ int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_window_args(module, window, level, formatnum, va);
+ va_end(va);
+}
+
+void printformat_module_gui_args(const char *module, int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+ char *str;
+
+ g_return_if_fail(module != NULL);
+
+ memset(&dest, 0, sizeof(dest));
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ str = format_get_text_theme_charargs(current_theme, module, &dest,
+ formatnum, arglist);
+ if (*str != '\0') format_send_to_gui(&dest, str);
+ g_free(str);
+}
+
+void printformat_module_gui(const char *module, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_gui_args(module, formatnum, va);
+ va_end(va);
+}
+
+static void print_line(TEXT_DEST_REC *dest, const char *text)
+{
+ char *str, *tmp;
+
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(text != NULL);
+
+ tmp = format_get_level_tag(current_theme, dest);
+ str = format_add_linestart(text, tmp);
+ g_free_not_null(tmp);
+
+ /* send the plain text version for logging etc.. */
+ tmp = strip_codes(str);
+ signal_emit_id(signal_print_text_stripped, 2, dest, tmp);
+ g_free(tmp);
+
+ signal_emit_id(signal_print_text, 2, dest, str);
+ g_free(str);
+}
+
+/* append string to `out', expand newlines. */
+static void printtext_append_str(TEXT_DEST_REC *dest, GString *out,
+ const char *str)
+{
+ while (*str != '\0') {
+ if (*str != '\n')
+ g_string_append_c(out, *str);
+ else {
+ print_line(dest, out->str);
+ g_string_truncate(out, 0);
+ }
+ str++;
+ }
+}
+
+static char *printtext_get_args(TEXT_DEST_REC *dest, const char *str,
+ va_list va)
+{
+ GString *out;
+ char *ret;
+
+ out = g_string_new(NULL);
+ for (; *str != '\0'; str++) {
+ if (*str != '%') {
+ g_string_append_c(out, *str);
+ continue;
+ }
+
+ if (*++str == '\0')
+ break;
+
+ /* standard parameters */
+ switch (*str) {
+ case 's': {
+ char *s = (char *) va_arg(va, char *);
+ if (s && *s) printtext_append_str(dest, out, s);
+ break;
+ }
+ case 'd': {
+ int d = (int) va_arg(va, int);
+ g_string_sprintfa(out, "%d", d);
+ break;
+ }
+ case 'f': {
+ double f = (double) va_arg(va, double);
+ g_string_sprintfa(out, "%0.2f", f);
+ break;
+ }
+ case 'u': {
+ unsigned int d =
+ (unsigned int) va_arg(va, unsigned int);
+ g_string_sprintfa(out, "%u", d);
+ break;
+ }
+ case 'l': {
+ long d = (long) va_arg(va, long);
+
+ if (*++str != 'd' && *str != 'u') {
+ g_string_sprintfa(out, "%ld", d);
+ str--;
+ } else {
+ if (*str == 'd')
+ g_string_sprintfa(out, "%ld", d);
+ else
+ g_string_sprintfa(out, "%lu", d);
+ }
+ break;
+ }
+ default:
+ if (!format_expand_styles(out, *str)) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *str);
+ }
+ break;
+ }
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+static char *printtext_expand_formats(const char *str)
+{
+ GString *out;
+ char *ret;
+
+ out = g_string_new(NULL);
+ for (; *str != '\0'; str++) {
+ if (*str != '%') {
+ g_string_append_c(out, *str);
+ continue;
+ }
+
+ if (*++str == '\0')
+ break;
+
+ if (!format_expand_styles(out, *str)) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *str);
+ }
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+void printtext_dest(TEXT_DEST_REC *dest, const char *text, va_list va)
+{
+ char *str;
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, dest);
+ sending_print_starting = FALSE;
+ }
+
+ str = printtext_get_args(dest, text, va);
+ print_line(dest, str);
+ g_free(str);
+}
+
+/* Write text to target - convert color codes */
+void printtext(void *server, const char *target, int level, const char *text, ...)
+{
+ TEXT_DEST_REC dest;
+ va_list va;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, server, target, level, NULL);
+
+ va_start(va, text);
+ printtext_dest(&dest, text, va);
+ va_end(va);
+}
+
+/* Like printtext(), but don't handle %s etc. */
+void printtext_string(void *server, const char *target, int level, const char *text)
+{
+ TEXT_DEST_REC dest;
+ char *str;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, server, target, level, NULL);
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, dest);
+ sending_print_starting = FALSE;
+ }
+
+ str = printtext_expand_formats(text);
+ print_line(&dest, str);
+ g_free(str);
+}
+
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...)
+{
+ TEXT_DEST_REC dest;
+ va_list va;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, NULL, NULL, level,
+ window != NULL ? window : active_win);
+
+ va_start(va, text);
+ printtext_dest(&dest, text, va);
+ va_end(va);
+}
+
+void printtext_gui(const char *text)
+{
+ TEXT_DEST_REC dest;
+ char *str;
+
+ g_return_if_fail(text != NULL);
+
+ memset(&dest, 0, sizeof(dest));
+
+ str = printtext_expand_formats(text);
+ format_send_to_gui(&dest, str);
+ g_free(str);
+}
+
+static void msg_beep_check(TEXT_DEST_REC *dest)
+{
+ if (dest->level != 0 && (dest->level & MSGLEVEL_NOHILIGHT) == 0 &&
+ (beep_msg_level & dest->level) &&
+ (beep_when_away || (dest->server != NULL &&
+ !dest->server->usermode_away)) &&
+ (beep_when_window_active || dest->window != active_win)) {
+ signal_emit("beep", 0);
+ }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *text)
+{
+ char *str, *tmp;
+
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(text != NULL);
+
+ msg_beep_check(dest);
+
+ dest->window->last_line = time(NULL);
+
+ /* add timestamp/server tag here - if it's done in print_line()
+ it would be written to log files too */
+ tmp = format_get_line_start(current_theme, dest, time(NULL));
+ str = format_add_linestart(text, tmp);
+ g_free_not_null(tmp);
+
+ format_send_to_gui(dest, str);
+ g_free(str);
+
+ signal_emit_id(signal_print_text_finished, 1, dest->window);
+}
+
+static void sig_print_text_free(TEXT_DEST_REC *dest, const char *text)
+{
+ g_free_and_null(dest->hilight_color);
+}
+
+void printtext_multiline(void *server, const char *target, int level,
+ const char *format, const char *text)
+{
+ char **lines, **tmp;
+
+ g_return_if_fail(format != NULL);
+ g_return_if_fail(text != NULL);
+
+ lines = g_strsplit(text, "\n", -1);
+ for (tmp = lines; *tmp != NULL; tmp++)
+ printtext(NULL, NULL, level, format, *tmp);
+ g_strfreev(lines);
+}
+
+static void sig_gui_dialog(const char *type, const char *text)
+{
+ char *format;
+
+ if (g_strcasecmp(type, "warning") == 0)
+ format = "%_Warning:%_ %s";
+ else if (g_strcasecmp(type, "error") == 0)
+ format = "%_Error:%_ %s";
+ else
+ format = "%s";
+
+ printtext_multiline(NULL, NULL, MSGLEVEL_NEVER, format, text);
+}
+
+static void read_settings(void)
+{
+ beep_msg_level = level2bits(settings_get_str("beep_msg_level"));
+ beep_when_away = settings_get_bool("beep_when_away");
+ beep_when_window_active = settings_get_bool("beep_when_window_active");
+}
+
+void printtext_init(void)
+{
+ sending_print_starting = FALSE;
+ signal_gui_print_text = signal_get_uniq_id("gui print text");
+ signal_print_text_stripped = signal_get_uniq_id("print text stripped");
+ signal_print_text = signal_get_uniq_id("print text");
+ signal_print_text_finished = signal_get_uniq_id("print text finished");
+ signal_print_format = signal_get_uniq_id("print format");
+ signal_print_starting = signal_get_uniq_id("print starting");
+
+ read_settings();
+ signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_add_last("print text", (SIGNAL_FUNC) sig_print_text_free);
+ signal_add("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void printtext_deinit(void)
+{
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text_free);
+ signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __PRINTTEXT_H
+#define __PRINTTEXT_H
+
+#include "fe-windows.h"
+
+void printformat_module(const char *module, void *server, const char *target, int level, int formatnum, ...);
+void printformat_module_window(const char *module, WINDOW_REC *window, int level, int formatnum, ...);
+
+void printformat_module_args(const char *module, void *server, const char *target, int level, int formatnum, va_list va);
+void printformat_module_window_args(const char *module, WINDOW_REC *window, int level, int formatnum, va_list va);
+
+void printtext(void *server, const char *target, int level, const char *text, ...);
+void printtext_string(void *server, const char *target, int level, const char *text);
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...);
+void printtext_multiline(void *server, const char *target, int level, const char *format, const char *text);
+
+/* only GUI should call these - used for printing text to somewhere else
+ than windows */
+void printtext_gui(const char *text);
+void printformat_module_gui(const char *module, int formatnum, ...);
+void printformat_module_gui_args(const char *module, int formatnum, va_list va);
+
+void printtext_init(void);
+void printtext_deinit(void);
+
+/* printformat(...) = printformat_format(MODULE_NAME, ...)
+
+ Could this be any harder? :) With GNU C compiler and C99 compilers,
+ use #define. With others use either inline functions if they are
+ supported or static functions if they are not..
+ */
+#if defined (__GNUC__) && !defined (__STRICT_ANSI__)
+/* GCC */
+# define printformat(server, target, level, formatnum...) \
+ printformat_module(MODULE_NAME, server, target, level, ##formatnum)
+# define printformat_window(window, level, formatnum...) \
+ printformat_module_window(MODULE_NAME, window, level, ##formatnum)
+# define printformat_gui(formatnum...) \
+ printformat_module_gui(MODULE_NAME, ##formatnum)
+#elif defined (_ISOC99_SOURCE)
+/* C99 */
+# define printformat(server, target, level, formatnum, ...) \
+ printformat_module(MODULE_NAME, server, target, level, formatnum, __VA_ARGS__)
+# define printformat_window(window, level, formatnum, ...) \
+ printformat_module_window(MODULE_NAME, window, level, formatnum, __VA_ARGS__)
+# define printformat_gui(formatnum, ...) \
+ printformat_module_gui(MODULE_NAME, formatnum, __VA_ARGS__)
+#else
+/* inline/static */
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat(void *server, const char *target, int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_args(MODULE_NAME, server, target, level, formatnum, va);
+ va_end(va);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat_window(WINDOW_REC *window, int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_window_args(MODULE_NAME, window, level, formatnum, va);
+ va_end(va);
+}
+
+#ifdef G_CAN_INLINE
+G_INLINE_FUNC
+#else
+static
+#endif
+void printformat_gui(int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_gui_args(MODULE_NAME, formatnum, va);
+ va_end(va);
+}
+#endif
+
+#endif
--- /dev/null
+/*
+ themes.c : irssi
+
+ Copyright (C) 1999-2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "special-vars.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "themes.h"
+#include "printtext.h"
+
+#include "default-theme.h"
+
+GSList *themes;
+THEME_REC *current_theme;
+GHashTable *default_formats;
+
+static int init_finished;
+static char *init_errors;
+
+static int theme_read(THEME_REC *theme, const char *path, const char *data);
+
+THEME_REC *theme_create(const char *path, const char *name)
+{
+ THEME_REC *rec;
+
+ g_return_val_if_fail(path != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = g_new0(THEME_REC, 1);
+ rec->path = g_strdup(path);
+ rec->name = g_strdup(name);
+ rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ rec->modules = g_hash_table_new((GHashFunc) g_istr_hash,
+ (GCompareFunc) g_istr_equal);
+ themes = g_slist_append(themes, rec);
+ signal_emit("theme created", 1, rec);
+
+ return rec;
+}
+
+static void theme_abstract_destroy(char *key, char *value)
+{
+ g_free(key);
+ g_free(value);
+}
+
+static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < rec->count; n++) {
+ g_free_not_null(rec->formats[n]);
+ g_free_not_null(rec->expanded_formats[n]);
+ }
+ g_free(rec->formats);
+ g_free(rec->expanded_formats);
+
+ g_free(rec->name);
+ g_free(rec);
+}
+
+void theme_destroy(THEME_REC *rec)
+{
+ themes = g_slist_remove(themes, rec);
+
+ signal_emit("theme destroyed", 1, rec);
+
+ g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL);
+ g_hash_table_destroy(rec->abstracts);
+ g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL);
+ g_hash_table_destroy(rec->modules);
+
+ g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL);
+ g_slist_free(rec->replace_values);
+
+ g_free(rec->path);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+static char *theme_replace_expand(THEME_REC *theme, int index,
+ char default_fg, char default_bg,
+ char *last_fg, char *last_bg,
+ char chr, int flags)
+{
+ GSList *rec;
+ char *ret, *abstract, data[2];
+
+ rec = g_slist_nth(theme->replace_values, index);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ data[0] = chr; data[1] = '\0';
+
+ abstract = rec->data;
+ abstract = theme_format_expand_data(theme, (const char **) &abstract,
+ default_fg, default_bg,
+ last_fg, last_bg, flags);
+ ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+ g_free(abstract);
+ return ret;
+}
+
+static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW";
+static const char *bgcolorformats = "n01234567";
+
+#define IS_FGCOLOR_FORMAT(c) \
+ ((c) != '\0' && strchr(fgcolorformats, c) != NULL)
+#define IS_BGCOLOR_FORMAT(c) \
+ ((c) != '\0' && strchr(bgcolorformats, c) != NULL)
+
+/* append "variable" part in $variable, ie. not the contents of the variable */
+static void theme_format_append_variable(GString *str, const char **format)
+{
+ const char *orig;
+ char *value, *args[1] = { NULL };
+ int free_ret;
+
+ orig = *format;
+ (*format)++;
+
+ value = parse_special((char **) format, NULL, NULL,
+ args, &free_ret, NULL, 0);
+ if (free_ret) g_free(value);
+ (*format)++;
+
+ /* append the variable name */
+ value = g_strndup(orig, (int) (*format-orig));
+ g_string_append(str, value);
+ g_free(value);
+}
+
+/* append next "item", either a character, $variable or %format */
+static void theme_format_append_next(THEME_REC *theme, GString *str,
+ const char **format,
+ char default_fg, char default_bg,
+ char *last_fg, char *last_bg,
+ int flags)
+{
+ int index;
+ unsigned char chr;
+
+ chr = **format;
+ if ((chr == '$' || chr == '%') &&
+ (*format)[1] == '\0') {
+ /* last char, always append */
+ g_string_append_c(str, chr);
+ (*format)++;
+ return;
+ }
+
+ if (chr == '$') {
+ /* $variable .. we'll always need to skip this, since it
+ may contain characters that are in replace chars. */
+ theme_format_append_variable(str, format);
+ return;
+ }
+
+ if (**format == '%') {
+ /* format */
+ (*format)++;
+ if (**format != '{' && **format != '}') {
+ chr = **format;
+ if (**format == 'n') {
+ /* %n = change to default color */
+ g_string_append(str, "%n");
+
+ if (default_bg != 'n') {
+ g_string_append_c(str, '%');
+ g_string_append_c(str, default_bg);
+ }
+ if (default_fg != 'n') {
+ g_string_append_c(str, '%');
+ g_string_append_c(str, default_fg);
+ }
+
+ *last_fg = default_fg;
+ *last_bg = default_bg;
+ } else {
+ if (IS_FGCOLOR_FORMAT(chr))
+ *last_fg = chr;
+ if (IS_BGCOLOR_FORMAT(chr))
+ *last_bg = chr;
+ g_string_append_c(str, '%');
+ g_string_append_c(str, chr);
+ }
+ (*format)++;
+ return;
+ }
+
+ /* %{ or %} gives us { or } char */
+ chr = **format;
+ }
+
+ index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
+ theme->replace_keys[(int) chr];
+ if (index == -1)
+ g_string_append_c(str, chr);
+ else {
+ char *value;
+
+ value = theme_replace_expand(theme, index,
+ default_fg, default_bg,
+ last_fg, last_bg, chr, flags);
+ g_string_append(str, value);
+ g_free(value);
+ }
+
+ (*format)++;
+}
+
+/* expand a single {abstract ...data... } */
+static char *theme_format_expand_abstract(THEME_REC *theme,
+ const char **formatp,
+ char default_fg, char default_bg,
+ int flags)
+{
+ const char *p, *format;
+ char *abstract, *data, *ret;
+ int len;
+
+ format = *formatp;
+
+ /* get abstract name first */
+ p = format;
+ while (*p != '\0' && *p != ' ' &&
+ *p != '{' && *p != '}') p++;
+ if (*p == '\0' || p == format)
+ return NULL; /* error */
+
+ len = (int) (p-format);
+ abstract = g_strndup(format, len);
+
+ /* skip the following space, if there's any more spaces they're
+ treated as arguments */
+ if (*p == ' ') {
+ len++;
+ if ((flags & EXPAND_FLAG_IGNORE_EMPTY)) {
+ /* if the data is empty, ignore the abstract */
+ p = format+len;
+ while (*p == ' ') p++;
+ if (*p == '}') {
+ *formatp = p+1;
+ g_free(abstract);
+ return NULL;
+ }
+ }
+
+ }
+ *formatp = format+len;
+
+ /* get the abstract data */
+ data = g_hash_table_lookup(theme->abstracts, abstract);
+ g_free(abstract);
+ if (data == NULL) {
+ /* unknown abstract, just display the data */
+ data = "$0-";
+ }
+ abstract = g_strdup(data);
+
+ /* we'll need to get the data part. it may contain
+ more abstracts, they are automatically expanded. */
+ data = theme_format_expand_data(theme, formatp, default_fg, default_bg,
+ NULL, NULL, flags);
+ len = strlen(data);
+
+ if (len > 1 && isdigit(data[len-1]) && data[len-2] == '$') {
+ /* ends with $<digit> .. this breaks things if next
+ character is digit or '-' */
+ char digit, *tmp;
+
+ tmp = data;
+ digit = tmp[len-1];
+ tmp[len-1] = '\0';
+
+ data = g_strdup_printf("%s{%c}", tmp, digit);
+ g_free(tmp);
+ }
+
+ ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+ g_free(abstract);
+ g_free(data);
+ abstract = ret;
+
+ /* abstract may itself contain abstracts or replaces */
+ p = abstract;
+ ret = theme_format_expand_data(theme, &p, default_fg, default_bg,
+ &default_fg, &default_bg,
+ flags | EXPAND_FLAG_LASTCOLOR_ARG);
+ g_free(abstract);
+ return ret;
+}
+
+/* expand the data part in {abstract data} */
+char *theme_format_expand_data(THEME_REC *theme, const char **format,
+ char default_fg, char default_bg,
+ char *save_last_fg, char *save_last_bg,
+ int flags)
+{
+ GString *str;
+ char *ret, *abstract;
+ char last_fg, last_bg;
+ int recurse_flags;
+
+ last_fg = default_fg;
+ last_bg = default_bg;
+ recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;
+
+ str = g_string_new(NULL);
+ while (**format != '\0') {
+ if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
+ /* ignore } if we're expanding original string */
+ (*format)++;
+ break;
+ }
+
+ if (**format != '{') {
+ if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
+ **format == '$' && (*format)[1] == '0') {
+ /* save the color before $0 ..
+ this is for the %n replacing */
+ if (save_last_fg != NULL) {
+ *save_last_fg = last_fg;
+ save_last_fg = NULL;
+ }
+ if (save_last_bg != NULL) {
+ *save_last_bg = last_bg;
+ save_last_bg = NULL;
+ }
+ }
+
+ theme_format_append_next(theme, str, format,
+ default_fg, default_bg,
+ &last_fg, &last_bg,
+ recurse_flags);
+ continue;
+ }
+
+ (*format)++;
+ if (**format == '\0' || **format == '}')
+ break; /* error */
+
+ /* get a single {...} */
+ abstract = theme_format_expand_abstract(theme, format,
+ last_fg, last_bg,
+ recurse_flags);
+ if (abstract != NULL) {
+ g_string_append(str, abstract);
+ g_free(abstract);
+ }
+ }
+
+ if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) == 0) {
+ /* save the last color */
+ if (save_last_fg != NULL)
+ *save_last_fg = last_fg;
+ if (save_last_bg != NULL)
+ *save_last_bg = last_bg;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+#define IS_OLD_FORMAT(code, last_fg, last_bg) \
+ (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \
+ ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg))))
+
+static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
+{
+ GString *str;
+ char *ret, last_fg, last_bg;
+
+ str = g_string_new(NULL);
+
+ last_fg = last_bg = 'n';
+ while (*format != '\0') {
+ if (*format == '$') {
+ /* $variable, skrip it entirely */
+ theme_format_append_variable(str, &format);
+ last_fg = last_bg = '\0';
+ } else if (*format != '%') {
+ /* a normal character */
+ g_string_append_c(str, *format);
+ format++;
+ } else {
+ /* %format */
+ format++;
+ if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
+ /* active color set again */
+ } else if (IS_FGCOLOR_FORMAT(*format) &&
+ (*format != 'n' || format[2] == 'n') &&
+ format[1] == '%' &&
+ IS_FGCOLOR_FORMAT(format[2])) {
+ /* two fg colors in a row. bg colors are
+ so rare that we don't bother checking
+ them */
+ } else {
+ /* some format, add it */
+ g_string_append_c(str, '%');
+ g_string_append_c(str, *format);
+
+ if (IS_FGCOLOR_FORMAT(*format))
+ last_fg = *format;
+ if (IS_BGCOLOR_FORMAT(*format))
+ last_bg = *format;
+ }
+ format++;
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+char *theme_format_expand(THEME_REC *theme, const char *format)
+{
+ char *data, *ret;
+
+ g_return_val_if_fail(theme != NULL, NULL);
+ g_return_val_if_fail(format != NULL, NULL);
+
+ data = theme_format_expand_data(theme, &format, 'n', 'n', NULL, NULL,
+ EXPAND_FLAG_ROOT);
+ ret = theme_format_compress_colors(theme, data);
+ g_free(data);
+ return ret;
+}
+
+static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module)
+{
+ MODULE_THEME_REC *rec;
+ FORMAT_REC *formats;
+
+ rec = g_hash_table_lookup(theme->modules, module);
+ if (rec != NULL) return rec;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ g_return_val_if_fail(formats != NULL, NULL);
+
+ rec = g_new0(MODULE_THEME_REC, 1);
+ rec->name = g_strdup(module);
+
+ for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ;
+ rec->formats = g_new0(char *, rec->count);
+ rec->expanded_formats = g_new0(char *, rec->count);
+
+ g_hash_table_insert(theme->modules, rec->name, rec);
+ return rec;
+}
+
+static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+ const char *p;
+ int index;
+
+ /* reset replace keys */
+ for (index = 0; index < 256; index++)
+ theme->replace_keys[index] = -1;
+ index = 0;
+
+ node = config_node_traverse(config, "replaces", FALSE);
+ if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key != NULL && node->value != NULL) {
+ for (p = node->key; *p != '\0'; p++)
+ theme->replace_keys[(int) *p] = index;
+
+ theme->replace_values =
+ g_slist_append(theme->replace_values,
+ g_strdup(node->value));
+ index++;
+ }
+ }
+}
+
+static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+ gpointer oldkey, oldvalue;
+
+ node = config_node_traverse(config, "abstracts", FALSE);
+ if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key == NULL || node->value == NULL)
+ continue;
+
+ if (g_hash_table_lookup_extended(theme->abstracts, node->key,
+ &oldkey, &oldvalue)) {
+ /* new values override old ones */
+ g_hash_table_remove(theme->abstracts, oldkey);
+ g_free(oldkey);
+ g_free(oldvalue);
+ }
+
+ g_hash_table_insert(theme->abstracts, g_strdup(node->key),
+ g_strdup(node->value));
+ }
+}
+
+static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec,
+ const char *module,
+ const char *key, const char *value)
+{
+ int num;
+
+ num = format_find_tag(module, key);
+ if (num != -1) {
+ rec->formats[num] = g_strdup(value);
+ rec->expanded_formats[num] = theme_format_expand(theme, value);
+ }
+}
+
+static void theme_read_formats(THEME_REC *theme, const char *module,
+ CONFIG_REC *config, MODULE_THEME_REC *rec)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ node = config_node_traverse(config, "formats", FALSE);
+ if (node == NULL) return;
+ node = config_node_section(node, module, -1);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key != NULL && node->value != NULL) {
+ theme_set_format(theme, rec, module,
+ node->key, node->value);
+ }
+ }
+}
+
+static void theme_init_module(THEME_REC *theme, const char *module,
+ CONFIG_REC *config)
+{
+ MODULE_THEME_REC *rec;
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ g_return_if_fail(formats != NULL);
+
+ rec = theme_module_create(theme, module);
+
+ if (config != NULL)
+ theme_read_formats(theme, module, config, rec);
+
+ /* expand the remaining formats */
+ for (n = 0; n < rec->count; n++) {
+ if (rec->expanded_formats[n] == NULL) {
+ rec->expanded_formats[n] =
+ theme_format_expand(theme, formats[n].def);
+ }
+ }
+}
+
+static void sig_print_errors(void)
+{
+ init_finished = TRUE;
+
+ if (init_errors != NULL) {
+ signal_emit("gui dialog", 2, "error", init_errors);
+ g_free(init_errors);
+ }
+}
+
+static void theme_read_module(THEME_REC *theme, const char *module)
+{
+ CONFIG_REC *config;
+
+ config = config_open(theme->path, -1);
+ if (config != NULL)
+ config_parse(config);
+
+ theme_init_module(theme, module, config);
+
+ if (config != NULL) config_close(config);
+}
+
+static void themes_read_module(const char *module)
+{
+ g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module);
+}
+
+static void theme_remove_module(THEME_REC *theme, const char *module)
+{
+ MODULE_THEME_REC *rec;
+
+ rec = g_hash_table_lookup(theme->modules, module);
+ if (rec == NULL) return;
+
+ g_hash_table_remove(theme->modules, module);
+ theme_module_destroy(module, rec);
+}
+
+static void themes_remove_module(const char *module)
+{
+ g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module);
+}
+
+void theme_register_module(const char *module, FORMAT_REC *formats)
+{
+ if (g_hash_table_lookup(default_formats, module) != NULL)
+ return;
+
+ g_hash_table_insert(default_formats, g_strdup(module), formats);
+ themes_read_module(module);
+}
+
+void theme_unregister_module(const char *module)
+{
+ gpointer key, value;
+
+ if (default_formats == NULL)
+ return; /* already uninitialized */
+
+ if (!g_hash_table_lookup_extended(default_formats, module, &key, &value))
+ return;
+
+ g_hash_table_remove(default_formats, key);
+ g_free(key);
+
+ themes_remove_module(module);
+}
+
+static THEME_REC *theme_find(const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = themes; tmp != NULL; tmp = tmp->next) {
+ THEME_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void window_themes_update(void)
+{
+ GSList *tmp;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->theme_name != NULL)
+ rec->theme = theme_load(rec->theme_name);
+ }
+}
+
+THEME_REC *theme_load(const char *setname)
+{
+ THEME_REC *theme, *oldtheme;
+ struct stat statbuf;
+ char *fname, *name, *p;
+
+ name = g_strdup(setname);
+ p = strrchr(name, '.');
+ if (p != NULL && strcmp(p, ".theme") == 0) {
+ /* remove the trailing .theme */
+ *p = '\0';
+ }
+
+ theme = theme_find(name);
+
+ /* check home dir */
+ fname = g_strdup_printf("%s/.irssi/%s.theme", g_get_home_dir(), name);
+ if (stat(fname, &statbuf) != 0) {
+ /* check global config dir */
+ g_free(fname);
+ fname = g_strdup_printf(SYSCONFDIR"/irssi/%s.theme", name);
+ if (stat(fname, &statbuf) != 0) {
+ /* theme not found */
+ g_free(fname);
+ g_free(name);
+ return theme; /* use the one in memory if possible */
+ }
+ }
+
+ if (theme != NULL && theme->last_modify == statbuf.st_mtime) {
+ /* theme not modified, use the one already in memory */
+ g_free(fname);
+ g_free(name);
+ return theme;
+ }
+
+ oldtheme = theme;
+ theme = theme_create(fname, name);
+ theme->last_modify = statbuf.st_mtime;
+ if (!theme_read(theme, theme->path, NULL)) {
+ /* error reading .theme file */
+ theme_destroy(theme);
+ theme = NULL;
+ }
+
+ if (oldtheme != NULL && theme != NULL) {
+ theme_destroy(oldtheme);
+ window_themes_update();
+ }
+
+ g_free(fname);
+ g_free(name);
+ return theme;
+}
+
+typedef struct {
+ THEME_REC *theme;
+ CONFIG_REC *config;
+} THEME_READ_REC;
+
+static void theme_read_modules(const char *module, void *value,
+ THEME_READ_REC *rec)
+{
+ theme_init_module(rec->theme, module, rec->config);
+}
+
+static void read_error(const char *str)
+{
+ char *old;
+
+ if (init_finished)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
+ else if (init_errors == NULL)
+ init_errors = g_strdup(str);
+ else {
+ old = init_errors;
+ init_errors = g_strconcat(init_errors, "\n", str, NULL);
+ g_free(old);
+ }
+}
+
+static int theme_read(THEME_REC *theme, const char *path, const char *data)
+{
+ CONFIG_REC *config;
+ THEME_READ_REC rec;
+ char *str;
+
+ config = config_open(data == NULL ? path : NULL, -1) ;
+ if (config == NULL) {
+ /* didn't exist or no access? */
+ str = g_strdup_printf("Error reading theme file %s: %s",
+ path, g_strerror(errno));
+ read_error(str);
+ g_free(str);
+ return FALSE;
+ }
+
+ if (data != NULL)
+ config_parse_data(config, data, "internal");
+ else
+ config_parse(config);
+
+ if (config_last_error(config) != NULL) {
+ str = g_strdup_printf("Ignored errors in theme %s:\n%s",
+ theme->name, config_last_error(config));
+ read_error(str);
+ g_free(str);
+ }
+
+ theme->default_color =
+ config_get_int(config, NULL, "default_color", 0);
+ theme->default_real_color =
+ config_get_int(config, NULL, "default_real_color", 7);
+ theme_read_replaces(config, theme);
+
+ if (data == NULL) {
+ /* get the default abstracts from default theme. */
+ CONFIG_REC *default_config;
+
+ default_config = config_open(NULL, -1);
+ config_parse_data(default_config, default_theme, "internal");
+ theme_read_abstracts(default_config, theme);
+ config_close(default_config);
+ }
+ theme_read_abstracts(config, theme);
+
+ rec.theme = theme;
+ rec.config = config;
+ g_hash_table_foreach(default_formats,
+ (GHFunc) theme_read_modules, &rec);
+ config_close(config);
+
+ return TRUE;
+}
+
+typedef struct {
+ char *name;
+ char *short_name;
+} THEME_SEARCH_REC;
+
+static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2)
+{
+ return g_strcasecmp(r1->short_name, r2->short_name);
+}
+
+static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list)
+{
+ THEME_SEARCH_REC *rec;
+
+ rec = g_new(THEME_SEARCH_REC, 1);
+ rec->name = module;
+ rec->short_name = strrchr(module, '/');
+ if (rec->short_name != NULL)
+ rec->short_name++; else rec->short_name = module;
+ *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal);
+}
+
+static GSList *get_sorted_modules(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list);
+ return list;
+}
+
+static THEME_SEARCH_REC *theme_search(GSList *list, const char *module)
+{
+ THEME_SEARCH_REC *rec;
+
+ while (list != NULL) {
+ rec = list->data;
+
+ if (g_strcasecmp(rec->short_name, module) == 0)
+ return rec;
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset)
+{
+ MODULE_THEME_REC *theme;
+ FORMAT_REC *formats;
+ const char *text, *last_title;
+ int n, first;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+ theme = g_hash_table_lookup(current_theme->modules, rec->name);
+
+ last_title = NULL; first = TRUE;
+ for (n = 1; formats[n].def != NULL; n++) {
+ text = theme != NULL && theme->formats[n] != NULL ?
+ theme->formats[n] : formats[n].def;
+
+ if (formats[n].tag == NULL)
+ last_title = text;
+ else if ((value != NULL && key != NULL && g_strcasecmp(formats[n].tag, key) == 0) ||
+ (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) {
+ if (first) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def);
+ first = FALSE;
+ }
+ if (last_title != NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title);
+ if (reset || value != NULL) {
+ theme = theme_module_create(current_theme, rec->name);
+ g_free_not_null(theme->formats[n]);
+ g_free_not_null(theme->expanded_formats[n]);
+
+ text = reset ? formats[n].def : value;
+ theme->formats[n] = reset ? NULL : g_strdup(value);
+ theme->expanded_formats[n] = theme_format_expand(current_theme, text);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text);
+ last_title = NULL;
+ }
+ }
+}
+
+/* SYNTAX: FORMAT [-delete | -reset] [<module>] [<key> [<value>]] */
+static void cmd_format(const char *data)
+{
+ GHashTable *optlist;
+ GSList *tmp, *modules;
+ char *module, *key, *value;
+ void *free_arg;
+ int reset;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "format", &optlist, &module, &key, &value))
+ return;
+
+ modules = get_sorted_modules();
+ if (*module == '\0')
+ module = NULL;
+ else if (theme_search(modules, module) == NULL) {
+ /* first argument isn't module.. */
+ cmd_params_free(free_arg);
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "format", &optlist, &key, &value))
+ return;
+ module = NULL;
+ }
+
+ reset = FALSE;
+ if (*key == '\0') key = NULL;
+ if (g_hash_table_lookup(optlist, "reset"))
+ reset = TRUE;
+ else if (g_hash_table_lookup(optlist, "delete"))
+ value = "";
+ else if (*value == '\0')
+ value = NULL;
+
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ THEME_SEARCH_REC *rec = tmp->data;
+
+ if (module == NULL || g_strcasecmp(rec->short_name, module) == 0)
+ theme_show(rec, key, value, reset);
+ }
+ g_slist_foreach(modules, (GFunc) g_free, NULL);
+ g_slist_free(modules);
+
+ cmd_params_free(free_arg);
+}
+
+static void module_save(const char *module, MODULE_THEME_REC *rec,
+ CONFIG_REC *config)
+{
+ CONFIG_NODE *fnode, *node;
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+ if (formats == NULL) return;
+
+ fnode = config_node_traverse(config, "formats", TRUE);
+
+ node = config_node_section(fnode, rec->name, NODE_TYPE_BLOCK);
+ for (n = 0; formats[n].def != NULL; n++) {
+ if (rec->formats[n] != NULL) {
+ config_node_set_str(config, node, formats[n].tag,
+ rec->formats[n]);
+ }
+ }
+
+ if (node->value == NULL) {
+ /* not modified, don't keep the empty section */
+ config_node_remove(config, fnode, node);
+ if (fnode->value == NULL)
+ config_node_remove(config, config->mainnode, fnode);
+ }
+}
+
+static void theme_save(THEME_REC *theme)
+{
+ CONFIG_REC *config;
+ char *path;
+ int ok;
+
+ config = config_open(theme->path, -1);
+ if (config != NULL)
+ config_parse(config);
+ else {
+ if (g_strcasecmp(theme->name, "default") == 0) {
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_theme, "internal");
+ config_change_file_name(config, theme->path, 0660);
+ } else {
+ config = config_open(theme->path, 0660);
+ if (config == NULL)
+ return;
+ config_parse(config);
+ }
+ }
+
+ g_hash_table_foreach(theme->modules, (GHFunc) module_save, config);
+
+ /* always save the theme to ~/.irssi/ */
+ path = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(),
+ g_basename(theme->path));
+ ok = config_write(config, path, 0660) == 0;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED,
+ path, config_last_error(config));
+
+ g_free(path);
+ config_close(config);
+}
+
+/* save changed formats */
+static void cmd_save(void)
+{
+ GSList *tmp;
+
+ for (tmp = themes; tmp != NULL; tmp = tmp->next) {
+ THEME_REC *theme = tmp->data;
+
+ theme_save(theme);
+ }
+}
+
+static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
+{
+ FORMAT_REC *formats;
+ int n, len;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+
+ len = strlen(key);
+ for (n = 1; formats[n].def != NULL; n++) {
+ const char *item = formats[n].tag;
+
+ if (item != NULL && g_strncasecmp(item, key, len) == 0)
+ *list = g_list_append(*list, g_strdup(item));
+ }
+}
+
+static GList *completion_get_formats(const char *module, const char *key)
+{
+ GSList *modules, *tmp;
+ GList *list;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ list = NULL;
+
+ modules = get_sorted_modules();
+ if (*module == '\0' || theme_search(modules, module) != NULL) {
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ THEME_SEARCH_REC *rec = tmp->data;
+
+ if (*module == '\0' || g_strcasecmp(rec->short_name, module) == 0)
+ complete_format_list(rec, key, &list);
+ }
+ }
+ g_slist_foreach(modules, (GFunc) g_free, NULL);
+ g_slist_free(modules);
+
+ return list;
+}
+
+static void sig_complete_format(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ const char *ptr;
+ int words;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ ptr = line;
+
+ words = 0;
+ do {
+ words++;
+ ptr = strchr(ptr, ' ');
+ } while (ptr != NULL);
+
+ if (words > 2)
+ return;
+
+ *list = completion_get_formats(line, word);
+ if (*list != NULL) signal_stop();
+}
+
+static void change_theme(const char *name, int verbose)
+{
+ THEME_REC *rec;
+
+ rec = theme_load(name);
+ if (rec != NULL) {
+ current_theme = rec;
+ if (verbose) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_THEME_CHANGED,
+ rec->name, rec->path);
+ }
+ } else if (verbose) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_THEME_NOT_FOUND, name);
+ }
+}
+
+static void read_settings(void)
+{
+ const char *theme;
+
+ theme = settings_get_str("theme");
+ if (strcmp(current_theme->name, theme) != 0)
+ change_theme(theme, TRUE);
+}
+
+static void themes_read(void)
+{
+ char *fname;
+
+ while (themes != NULL)
+ theme_destroy(themes->data);
+
+ /* first there's default theme.. */
+ current_theme = theme_load("default");
+ if (current_theme == NULL) {
+ fname = g_strdup_printf("%s/.irssi/default.theme",
+ g_get_home_dir());
+ current_theme = theme_create(fname, "default");
+ current_theme->default_color = 0;
+ current_theme->default_real_color = 7;
+ theme_read(current_theme, NULL, default_theme);
+ g_free(fname);
+ }
+
+ window_themes_update();
+ change_theme(settings_get_str("theme"), FALSE);
+}
+
+void themes_init(void)
+{
+ settings_add_str("lookandfeel", "theme", "default");
+
+ default_formats = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ init_finished = FALSE;
+ init_errors = NULL;
+
+ themes = NULL;
+ themes_read();
+
+ command_bind("format", NULL, (SIGNAL_FUNC) cmd_format);
+ command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+ signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) themes_read);
+
+ command_set_options("format", "delete reset");
+}
+
+void themes_deinit(void)
+{
+ while (themes != NULL)
+ theme_destroy(themes->data);
+
+ g_hash_table_destroy(default_formats);
+ default_formats = NULL;
+
+ command_unbind("format", (SIGNAL_FUNC) cmd_format);
+ command_unbind("save", (SIGNAL_FUNC) cmd_save);
+ signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) themes_read);
+}
--- /dev/null
+#ifndef __THEMES_H
+#define __THEMES_H
+
+typedef struct {
+ char *name;
+
+ int count;
+ char **formats; /* in same order as in module's default formats */
+ char **expanded_formats; /* this contains the formats after
+ expanding {templates} */
+} MODULE_THEME_REC;
+
+typedef struct {
+ char *path;
+ char *name;
+ time_t last_modify;
+
+ int default_color; /* default color to use with text with default
+ background. default is 0 which means the default
+ color set by terminal */
+ int default_real_color; /* default color to use with background set.
+ this shouldn't be 0, unless black is really
+ wanted. default is 7 (white). */
+ GHashTable *modules;
+
+ int replace_keys[256]; /* index to replace_values for each char */
+ GSList *replace_values;
+ GHashTable *abstracts;
+
+ void *gui_data;
+} THEME_REC;
+
+typedef struct _FORMAT_REC FORMAT_REC;
+
+extern GSList *themes;
+extern THEME_REC *current_theme;
+extern GHashTable *default_formats;
+
+THEME_REC *theme_create(const char *path, const char *name);
+void theme_destroy(THEME_REC *rec);
+
+THEME_REC *theme_load(const char *name);
+
+#define theme_register(formats) theme_register_module(MODULE_NAME, formats)
+#define theme_unregister() theme_unregister_module(MODULE_NAME)
+void theme_register_module(const char *module, FORMAT_REC *formats);
+void theme_unregister_module(const char *module);
+
+#define EXPAND_FLAG_IGNORE_REPLACES 0x01 /* don't use the character replaces when expanding */
+#define EXPAND_FLAG_IGNORE_EMPTY 0x02 /* if abstract's argument is empty, don't try to expand it (ie. {xx }, but not {xx}) */
+#define EXPAND_FLAG_RECURSIVE_MASK 0x0f
+/* private */
+#define EXPAND_FLAG_ROOT 0x10
+#define EXPAND_FLAG_LASTCOLOR_ARG 0x20
+
+char *theme_format_expand(THEME_REC *theme, const char *format);
+char *theme_format_expand_data(THEME_REC *theme, const char **format,
+ char default_fg, char default_bg,
+ char *save_last_fg, char *save_last_bg,
+ int flags);
+
+void themes_init(void);
+void themes_deinit(void);
+
+#endif
--- /dev/null
+/*
+ translation.c : irssi
+
+ Copyright (C) 1999-2000 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 "line-split.h"
+#include "misc.h"
+#include "settings.h"
+
+unsigned char translation_in[256], translation_out[256];
+
+void translation_reset(void)
+{
+ int n;
+
+ for (n = 0; n < 256; n++)
+ translation_in[n] = (unsigned char) n;
+ for (n = 0; n < 256; n++)
+ translation_out[n] = (unsigned char) n;
+}
+
+void translate_output(char *text)
+{
+ while (*text != '\0') {
+ *text = (char) translation_out[(int) (unsigned char) *text];
+ text++;
+ }
+}
+
+#define gethex(a) \
+ (isdigit(a) ? ((a)-'0') : (toupper(a)-'A'+10))
+
+void translation_parse_line(const char *str, int *pos)
+{
+ const char *ptr;
+ int value;
+
+ for (ptr = str; *ptr != '\0'; ptr++) {
+ if (ptr[0] != '0' || ptr[1] != 'x')
+ break;
+ ptr += 2;
+
+ value = (gethex(ptr[0]) << 4) + gethex(ptr[1]);
+ if (*pos < 256)
+ translation_in[*pos] = (unsigned char) value;
+ else
+ translation_out[*pos-256] = (unsigned char) value;
+ (*pos)++;
+
+ ptr += 2;
+ if (*ptr != ',') break;
+ }
+}
+
+int translation_read(const char *file)
+{
+ char tmpbuf[1024], *str, *path;
+ LINEBUF_REC *buffer;
+ int f, pos, ret, recvlen;
+
+ g_return_val_if_fail(file != NULL, FALSE);
+
+ path = convert_home(file);
+ f = open(file, O_RDONLY);
+ g_free(path);
+
+ if (f == -1) return FALSE;
+
+ pos = 0; buffer = NULL;
+ while (pos < 512) {
+ recvlen = read(f, tmpbuf, sizeof(tmpbuf));
+
+ ret = line_split(tmpbuf, recvlen, &str, &buffer);
+ if (ret <= 0) break;
+
+ translation_parse_line(str, &pos);
+ }
+ line_split_free(buffer);
+
+ close(f);
+ if (pos != 512)
+ translation_reset();
+ return pos == 512;
+}
+
+static void read_settings(void)
+{
+ translation_read(settings_get_str("translation"));
+}
+
+void translation_init(void)
+{
+ translation_reset();
+
+ settings_add_str("misc", "translation", "");
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ read_settings();
+}
+
+void translation_deinit(void)
+{
+ read_settings();
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+#ifndef __TRANSLATION_H
+#define __TRANSLATION_H
+
+extern unsigned char translation_in[256], translation_out[256];
+
+int translation_read(const char *file);
+void translate_output(char *text);
+
+void translation_init(void);
+void translation_deinit(void);
+
+#endif
--- /dev/null
+/*
+ window-activity.c : irssi
+
+ Copyright (C) 1999-2000 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 "levels.h"
+#include "servers.h"
+#include "channels.h"
+#include "misc.h"
+#include "settings.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "nicklist.h"
+#include "hilight-text.h"
+#include "formats.h"
+
+static char **hide_targets;
+static int hide_level, msg_level, hilight_level;
+
+static void window_activity(WINDOW_REC *window, int data_level,
+ const char *hilight_color)
+{
+ int old_data_level;
+
+ old_data_level = window->data_level;
+ if (data_level == 0 || window->data_level < data_level) {
+ window->data_level = data_level;
+ g_free_not_null(window->hilight_color);
+ window->hilight_color = g_strdup(hilight_color);
+ signal_emit("window hilight", 1, window);
+ }
+
+ signal_emit("window activity", 2, window,
+ GINT_TO_POINTER(old_data_level));
+}
+
+static void window_item_activity(WI_ITEM_REC *item, int data_level,
+ const char *hilight_color)
+{
+ int old_data_level;
+
+ old_data_level = item->data_level;
+ if (data_level == 0 || item->data_level < data_level) {
+ item->data_level = data_level;
+ g_free_not_null(item->hilight_color);
+ item->hilight_color = g_strdup(hilight_color);
+ signal_emit("window item hilight", 1, item);
+ }
+
+ signal_emit("window item activity", 2, item,
+ GINT_TO_POINTER(old_data_level));
+}
+
+#define hide_target_activity(data_level, target) \
+ ((target) != NULL && hide_targets != NULL && \
+ strarray_find(hide_targets, target) != -1)
+
+static void sig_hilight_text(TEXT_DEST_REC *dest, const char *msg)
+{
+ WI_ITEM_REC *item;
+ int data_level;
+
+ if (dest->window == active_win || (dest->level & hide_level))
+ return;
+
+ if (dest->level & hilight_level) {
+ data_level = DATA_LEVEL_HILIGHT+dest->hilight_priority;
+ } else {
+ data_level = (dest->level & msg_level) ?
+ DATA_LEVEL_MSG : DATA_LEVEL_TEXT;
+ }
+
+ if ((dest->level & MSGLEVEL_HILIGHT) == 0 &&
+ hide_target_activity(data_level, dest->target))
+ return;
+
+ if (dest->target != NULL) {
+ item = window_item_find(dest->server, dest->target);
+ if (item != NULL) {
+ window_item_activity(item, data_level,
+ dest->hilight_color);
+ }
+ }
+ window_activity(dest->window, data_level, dest->hilight_color);
+}
+
+static void sig_dehilight_window(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ g_return_if_fail(window != NULL);
+
+ if (window->data_level != 0) {
+ window_activity(window, 0, NULL);
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next)
+ window_item_activity(tmp->data, 0, NULL);
+ }
+}
+
+static void read_settings(void)
+{
+ const char *targets;
+
+ if (hide_targets != NULL)
+ g_strfreev(hide_targets);
+
+ targets = settings_get_str("activity_hide_targets");
+ hide_targets = *targets == '\0' ? NULL :
+ g_strsplit(targets, " ", -1);
+
+ hide_level = MSGLEVEL_NEVER | MSGLEVEL_NO_ACT |
+ level2bits(settings_get_str("activity_hide_level"));
+ msg_level = level2bits(settings_get_str("activity_msg_level"));
+ hilight_level = MSGLEVEL_HILIGHT |
+ level2bits(settings_get_str("activity_hilight_level"));
+}
+
+void window_activity_init(void)
+{
+ settings_add_str("lookandfeel", "activity_hide_targets", "");
+ settings_add_str("lookandfeel", "activity_hide_level", "");
+ settings_add_str("lookandfeel", "activity_msg_level", "PUBLIC");
+ settings_add_str("lookandfeel", "activity_hilight_level", "MSGS DCCMSGS");
+
+ read_settings();
+ signal_add("print text", (SIGNAL_FUNC) sig_hilight_text);
+ signal_add("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_add("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void window_activity_deinit(void)
+{
+ if (hide_targets != NULL)
+ g_strfreev(hide_targets);
+
+ signal_remove("print text", (SIGNAL_FUNC) sig_hilight_text);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_remove("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
--- /dev/null
+/*
+ window-commands.c : irssi
+
+ Copyright (C) 2000 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 "module-formats.h"
+#include "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "servers.h"
+
+#include "levels.h"
+
+#include "themes.h"
+#include "fe-windows.h"
+#include "window-items.h"
+#include "windows-layout.h"
+#include "printtext.h"
+
+static void cmd_window(const char *data, void *server, WI_ITEM_REC *item)
+{
+ if (is_numeric(data, 0)) {
+ signal_emit("command window refnum", 3, data, server, item);
+ return;
+ }
+
+ command_runsub("window", data, server, item);
+}
+
+/* SYNTAX: WINDOW NEW [hide] */
+static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+ int type;
+
+ g_return_if_fail(data != NULL);
+
+ type = (g_strncasecmp(data, "hid", 3) == 0 || g_strcasecmp(data, "tab") == 0) ? 1 :
+ (g_strcasecmp(data, "split") == 0 ? 2 : 0);
+ signal_emit("gui window create override", 1, GINT_TO_POINTER(type));
+
+ window = window_create(NULL, FALSE);
+ window_change_server(window, server);
+}
+
+/* SYNTAX: WINDOW CLOSE [<first> [<last>] */
+static void cmd_window_close(const char *data)
+{
+ GSList *tmp, *destroys;
+ char *first, *last;
+ int first_num, last_num;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 2, &first, &last))
+ return;
+
+ if ((*first != '\0' && !is_numeric(first, '\0')) ||
+ ((*last != '\0') && !is_numeric(last, '\0'))) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ first_num = *first == '\0' ? active_win->refnum : atoi(first);
+ last_num = *last == '\0' ? active_win->refnum : atoi(last);
+
+ /* get list of windows to destroy */
+ destroys = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum >= first_num && rec->refnum <= last_num)
+ destroys = g_slist_append(destroys, rec);
+ }
+
+ /* really destroy the windows */
+ while (destroys != NULL) {
+ WINDOW_REC *rec = destroys->data;
+
+ if (windows->next != NULL)
+ window_destroy(rec);
+
+ destroys = g_slist_remove(destroys, rec);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW REFNUM <number> */
+static void cmd_window_refnum(const char *data)
+{
+ WINDOW_REC *window;
+
+ if (!is_numeric(data, 0))
+ return;
+
+ window = window_find_refnum(atoi(data));
+ if (window != NULL)
+ window_set_active(window);
+}
+
+/* return the first window number with the highest activity */
+static WINDOW_REC *window_highest_activity(WINDOW_REC *window)
+{
+ WINDOW_REC *rec, *max_win;
+ GSList *tmp;
+ int max_act, through;
+
+ g_return_val_if_fail(window != NULL, NULL);
+
+ max_win = NULL; max_act = 0; through = FALSE;
+
+ tmp = g_slist_find(windows, window);
+ for (;; tmp = tmp->next) {
+ if (tmp == NULL) {
+ tmp = windows;
+ through = TRUE;
+ }
+
+ if (through && tmp->data == window)
+ break;
+
+ rec = tmp->data;
+
+ if (rec->data_level > 0 && max_act < rec->data_level) {
+ max_act = rec->data_level;
+ max_win = rec;
+ }
+ }
+
+ return max_win;
+}
+
+/* SYNTAX: WINDOW GOTO active|<number>|<name> */
+static void cmd_window_goto(const char *data)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(data != NULL);
+
+ if (is_numeric(data, 0)) {
+ cmd_window_refnum(data);
+ return;
+ }
+
+ if (g_strcasecmp(data, "active") == 0)
+ window = window_highest_activity(active_win);
+ else
+ window = window_find_item(active_win->active_server, data);
+
+ if (window != NULL)
+ window_set_active(window);
+}
+
+/* SYNTAX: WINDOW NEXT */
+static void cmd_window_next(void)
+{
+ int num;
+
+ num = window_refnum_next(active_win->refnum, TRUE);
+ if (num < 1) num = windows_refnum_last();
+
+ window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LAST */
+static void cmd_window_last(void)
+{
+ if (windows->next != NULL)
+ window_set_active(windows->next->data);
+}
+
+/* SYNTAX: WINDOW PREVIOUS */
+static void cmd_window_previous(void)
+{
+ int num;
+
+ num = window_refnum_prev(active_win->refnum, TRUE);
+ if (num < 1) num = window_refnum_next(0, TRUE);
+
+ window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LEVEL [<level>] */
+static void cmd_window_level(const char *data)
+{
+ char *level;
+
+ g_return_if_fail(data != NULL);
+
+ window_set_level(active_win, combine_level(active_win->level, data));
+
+ level = active_win->level == 0 ? g_strdup("NONE") :
+ bits2level(active_win->level);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_LEVEL, level);
+ g_free(level);
+}
+
+/* SYNTAX: WINDOW SERVER [-sticky | -unsticky] <tag> */
+static void cmd_window_server(const char *data)
+{
+ GHashTable *optlist;
+ SERVER_REC *server;
+ char *tag;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "window server", &optlist, &tag))
+ return;
+
+ if (*tag == '\0' &&
+ (g_hash_table_lookup(optlist, "sticky") != NULL ||
+ g_hash_table_lookup(optlist, "unsticky") != NULL)) {
+ tag = active_win->active_server->tag;
+ }
+
+ if (*tag == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ server = server_find_tag(tag);
+
+ if (g_hash_table_lookup(optlist, "unsticky") != NULL &&
+ active_win->servertag != NULL) {
+ g_free_and_null(active_win->servertag);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNSET_SERVER_STICKY, server->tag);
+ }
+
+ if (active_win->servertag != NULL &&
+ g_hash_table_lookup(optlist, "sticky") == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_ERROR_SERVER_STICKY);
+ } else if (server == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_SERVER_TAG, tag);
+ } else if (active_win->active == NULL) {
+ window_change_server(active_win, server);
+ if (g_hash_table_lookup(optlist, "sticky") != NULL) {
+ g_free_not_null(active_win->servertag);
+ active_win->servertag = g_strdup(server->tag);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_SET_SERVER_STICKY, server->tag);
+ }
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_SERVER_CHANGED,
+ server->tag, server->connrec->address,
+ server->connrec->chatnet == NULL ? "" :
+ server->connrec->chatnet);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_window_item(const char *data, void *server, WI_ITEM_REC *item)
+{
+ command_runsub("window item", data, server, item);
+}
+
+/* SYNTAX: WINDOW ITEM PREV */
+static void cmd_window_item_prev(void)
+{
+ window_item_prev(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM NEXT */
+static void cmd_window_item_next(void)
+{
+ window_item_next(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM GOTO <name> */
+static void cmd_window_item_goto(const char *data, SERVER_REC *server)
+{
+ WI_ITEM_REC *item;
+
+ item = window_item_find_window(active_win, server, data);
+ if (item != NULL)
+ window_item_set_active(active_win, item);
+}
+
+/* SYNTAX: WINDOW ITEM MOVE <number>|<name> */
+static void cmd_window_item_move(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+
+ if (is_numeric(data, '\0')) {
+ /* move current window item to specified window */
+ window = window_find_refnum(atoi(data));
+ } else {
+ /* move specified window item to current window */
+ item = window_item_find(server, data);
+ window = active_win;
+ }
+ if (window != NULL && item != NULL)
+ window_item_set_active(window, item);
+}
+
+/* SYNTAX: WINDOW NUMBER [-sticky] <number> */
+static void cmd_window_number(const char *data)
+{
+ GHashTable *optlist;
+ char *refnum;
+ void *free_arg;
+ int num;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "window number", &optlist, &refnum))
+ return;
+
+ if (*refnum == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ num = atoi(refnum);
+ if (num < 1) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_REFNUM_TOO_LOW);
+ } else {
+ window_set_refnum(active_win, num);
+ active_win->sticky_refnum =
+ g_hash_table_lookup(optlist, "sticky") != NULL;
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW NAME <name> */
+static void cmd_window_name(const char *data)
+{
+ window_set_name(active_win, data);
+}
+
+/* we're moving the first window to last - move the first contiguous block
+ of refnums to left. Like if there's windows 1..5 and 7..10, move 1 to
+ 11, 2..5 to 1..4 and leave 7..10 alone */
+static void windows_move_left(WINDOW_REC *move_window)
+{
+ WINDOW_REC *window;
+ int refnum;
+
+ window_set_refnum(move_window, windows_refnum_last()+1);
+ for (refnum = 2;; refnum++) {
+ window = window_find_refnum(refnum);
+ if (window == NULL) break;
+
+ window_set_refnum(window, refnum-1);
+ }
+}
+
+/* we're moving the last window to first - make some space so we can use the
+ refnum 1 */
+static void windows_move_right(WINDOW_REC *move_window)
+{
+ WINDOW_REC *window;
+ int refnum;
+
+ /* find the first unused refnum, like if there's windows
+ 1..5 and 7..10, we only need to move 1..5 to 2..6 */
+ refnum = 1;
+ while (window_find_refnum(refnum) != NULL) refnum++;
+
+ refnum--;
+ while (refnum > 0) {
+ window = window_find_refnum(refnum);
+ g_return_if_fail(window != NULL);
+ window_set_refnum(window, window == move_window ? 1 : refnum+1);
+
+ refnum--;
+ }
+}
+
+static void cmd_window_move_left(void)
+{
+ int refnum;
+
+ refnum = window_refnum_prev(active_win->refnum, TRUE);
+ if (refnum != -1) {
+ window_set_refnum(active_win, refnum);
+ return;
+ }
+
+ windows_move_left(active_win);
+}
+
+static void cmd_window_move_right(void)
+{
+ int refnum;
+
+ refnum = window_refnum_next(active_win->refnum, TRUE);
+ if (refnum != -1) {
+ window_set_refnum(active_win, refnum);
+ return;
+ }
+
+ windows_move_right(active_win);
+}
+
+/* SYNTAX: WINDOW MOVE <number>|left|right */
+static void cmd_window_move(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ int new_refnum, refnum;
+
+ if (!is_numeric(data, 0)) {
+ command_runsub("window move", data, server, item);
+ return;
+ }
+
+ new_refnum = atoi(data);
+ if (new_refnum > active_win->refnum) {
+ for (;;) {
+ refnum = window_refnum_next(active_win->refnum, FALSE);
+ if (refnum == -1 || refnum > new_refnum)
+ break;
+
+ window_set_refnum(active_win, refnum);
+ }
+ } else {
+ for (;;) {
+ refnum = window_refnum_prev(active_win->refnum, FALSE);
+ if (refnum == -1 || refnum < new_refnum)
+ break;
+
+ window_set_refnum(active_win, refnum);
+ }
+ }
+}
+
+/* SYNTAX: WINDOW LIST */
+static void cmd_window_list(void)
+{
+ GSList *tmp, *sorted;
+ char *levelstr;
+
+ sorted = windows_get_sorted();
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_HEADER);
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ levelstr = bits2level(rec->level);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_LINE,
+ rec->refnum, rec->name == NULL ? "" : rec->name,
+ rec->active == NULL ? "" : rec->active->name,
+ rec->active_server == NULL ? "" : ((SERVER_REC *) rec->active_server)->tag,
+ levelstr);
+ g_free(levelstr);
+ }
+ g_slist_free(sorted);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_FOOTER);
+}
+
+/* SYNTAX: WINDOW THEME <name> */
+static void cmd_window_theme(const char *data)
+{
+ THEME_REC *theme;
+
+ g_free_not_null(active_win->theme_name);
+ active_win->theme_name = g_strdup(data);
+
+ active_win->theme = theme = theme_load(data);
+ if (theme != NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_THEME_CHANGED,
+ theme->name, theme->path);
+ } else {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_THEME_NOT_FOUND, data);
+ }
+}
+
+static void cmd_layout(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ command_runsub("layout", data, server, item);
+}
+
+/* SYNTAX: FOREACH WINDOW <command> */
+static void cmd_foreach_window(const char *data)
+{
+ WINDOW_REC *old;
+ GSList *tmp;
+
+ old = active_win;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ active_win = rec;
+ signal_emit("send command", 3, data, rec->active_server,
+ rec->active);
+ }
+ active_win = old;
+}
+
+void window_commands_init(void)
+{
+ command_bind("window", NULL, (SIGNAL_FUNC) cmd_window);
+ command_bind("window new", NULL, (SIGNAL_FUNC) cmd_window_new);
+ command_bind("window close", NULL, (SIGNAL_FUNC) cmd_window_close);
+ command_bind("window kill", NULL, (SIGNAL_FUNC) cmd_window_close);
+ command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+ command_bind("window refnum", NULL, (SIGNAL_FUNC) cmd_window_refnum);
+ command_bind("window goto", NULL, (SIGNAL_FUNC) cmd_window_goto);
+ command_bind("window previous", NULL, (SIGNAL_FUNC) cmd_window_previous);
+ command_bind("window next", NULL, (SIGNAL_FUNC) cmd_window_next);
+ command_bind("window last", NULL, (SIGNAL_FUNC) cmd_window_last);
+ command_bind("window level", NULL, (SIGNAL_FUNC) cmd_window_level);
+ command_bind("window item", NULL, (SIGNAL_FUNC) cmd_window_item);
+ command_bind("window item prev", NULL, (SIGNAL_FUNC) cmd_window_item_prev);
+ command_bind("window item next", NULL, (SIGNAL_FUNC) cmd_window_item_next);
+ command_bind("window item goto", NULL, (SIGNAL_FUNC) cmd_window_item_goto);
+ command_bind("window item move", NULL, (SIGNAL_FUNC) cmd_window_item_move);
+ command_bind("window number", NULL, (SIGNAL_FUNC) cmd_window_number);
+ command_bind("window name", NULL, (SIGNAL_FUNC) cmd_window_name);
+ command_bind("window move", NULL, (SIGNAL_FUNC) cmd_window_move);
+ command_bind("window move left", NULL, (SIGNAL_FUNC) cmd_window_move_left);
+ command_bind("window move right", NULL, (SIGNAL_FUNC) cmd_window_move_right);
+ command_bind("window list", NULL, (SIGNAL_FUNC) cmd_window_list);
+ command_bind("window theme", NULL, (SIGNAL_FUNC) cmd_window_theme);
+ command_bind("layout", NULL, (SIGNAL_FUNC) cmd_layout);
+ /* SYNTAX: LAYOUT SAVE */
+ command_bind("layout save", NULL, (SIGNAL_FUNC) windows_layout_save);
+ /* SYNTAX: LAYOUT RESET */
+ command_bind("layout reset", NULL, (SIGNAL_FUNC) windows_layout_reset);
+ command_bind("foreach window", NULL, (SIGNAL_FUNC) cmd_foreach_window);
+
+ command_set_options("window number", "sticky");
+ command_set_options("window server", "sticky unsticky");
+}
+
+void window_commands_deinit(void)
+{
+ command_unbind("window", (SIGNAL_FUNC) cmd_window);
+ command_unbind("window new", (SIGNAL_FUNC) cmd_window_new);
+ command_unbind("window close", (SIGNAL_FUNC) cmd_window_close);
+ command_unbind("window kill", (SIGNAL_FUNC) cmd_window_close);
+ command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+ command_unbind("window refnum", (SIGNAL_FUNC) cmd_window_refnum);
+ command_unbind("window goto", (SIGNAL_FUNC) cmd_window_goto);
+ command_unbind("window previous", (SIGNAL_FUNC) cmd_window_previous);
+ command_unbind("window next", (SIGNAL_FUNC) cmd_window_next);
+ command_unbind("window last", (SIGNAL_FUNC) cmd_window_last);
+ command_unbind("window level", (SIGNAL_FUNC) cmd_window_level);
+ command_unbind("window item", (SIGNAL_FUNC) cmd_window_item);
+ command_unbind("window item prev", (SIGNAL_FUNC) cmd_window_item_prev);
+ command_unbind("window item next", (SIGNAL_FUNC) cmd_window_item_next);
+ command_unbind("window item goto", (SIGNAL_FUNC) cmd_window_item_goto);
+ command_unbind("window item move", (SIGNAL_FUNC) cmd_window_item_move);
+ command_unbind("window number", (SIGNAL_FUNC) cmd_window_number);
+ command_unbind("window name", (SIGNAL_FUNC) cmd_window_name);
+ command_unbind("window move", (SIGNAL_FUNC) cmd_window_move);
+ command_unbind("window move left", (SIGNAL_FUNC) cmd_window_move_left);
+ command_unbind("window move right", (SIGNAL_FUNC) cmd_window_move_right);
+ command_unbind("window list", (SIGNAL_FUNC) cmd_window_list);
+ command_unbind("window theme", (SIGNAL_FUNC) cmd_window_theme);
+ command_unbind("layout", (SIGNAL_FUNC) cmd_layout);
+ command_unbind("layout save", (SIGNAL_FUNC) windows_layout_save);
+ command_unbind("layout reset", (SIGNAL_FUNC) windows_layout_reset);
+ command_unbind("foreach window", (SIGNAL_FUNC) cmd_foreach_window);
+}
--- /dev/null
+/*
+ window-items.c : irssi
+
+ Copyright (C) 2000 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 "module-formats.h"
+#include "modules.h"
+#include "signals.h"
+#include "servers.h"
+#include "settings.h"
+
+#include "levels.h"
+
+#include "fe-windows.h"
+#include "window-items.h"
+#include "printtext.h"
+
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic)
+{
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(item != NULL);
+
+ item->window = window;
+
+ if (window->items == NULL) {
+ window->active = item;
+ window->active_server = item->server;
+ }
+
+ if (!automatic || settings_get_bool("window_auto_change")) {
+ if (automatic)
+ signal_emit("window changed automatic", 1, window);
+ window_set_active(window);
+ }
+
+ window->items = g_slist_append(window->items, item);
+ signal_emit("window item new", 2, window, item);
+
+ if (!automatic || g_slist_length(window->items) == 1) {
+ window->active = NULL;
+ window_item_set_active(window, item);
+ }
+}
+
+void window_item_remove(WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(item != NULL);
+
+ window = window_item_window(item);
+
+ if (g_slist_find(window->items, item) == NULL)
+ return;
+
+ item->window = NULL;
+ window->items = g_slist_remove(window->items, item);
+
+ if (window->active == item) {
+ window_item_set_active(window, window->items == NULL ? NULL :
+ window->items->data);
+ }
+
+ signal_emit("window item remove", 2, window, item);
+}
+
+void window_item_destroy(WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+
+ window = window_item_window(item);
+ window_item_remove(item);
+
+ signal_emit("window item destroy", 2, window, item);
+}
+
+void window_item_change_server(WI_ITEM_REC *item, void *server)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(item != NULL);
+
+ window = window_item_window(item);
+ item->server = server;
+
+ signal_emit("window item server changed", 2, window, item);
+ if (window->active == item) window_change_server(window, item->server);
+}
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ g_return_if_fail(window != NULL);
+
+ if (item != NULL && window_item_window(item) != window) {
+ /* move item to different window */
+ window_item_remove(item);
+ window_item_add(window, item, FALSE);
+ }
+
+ if (window->active != item) {
+ window->active = item;
+ if (item != NULL && window->active_server != item->server)
+ window_change_server(window, item->server);
+ signal_emit("window item changed", 2, window, item);
+ }
+}
+
+/* Return TRUE if `item' is the active window item in the window.
+ `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+
+ if (item == NULL)
+ return FALSE;
+
+ window = window_item_window(item);
+ if (window == NULL)
+ return FALSE;
+
+ return window->active == item;
+}
+
+void window_item_prev(WINDOW_REC *window)
+{
+ WI_ITEM_REC *last;
+ GSList *tmp;
+
+ g_return_if_fail(window != NULL);
+
+ last = NULL;
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if (rec != window->active)
+ last = rec;
+ else {
+ /* current channel. did we find anything?
+ if not, go to the last channel */
+ if (last != NULL) break;
+ }
+ }
+
+ if (last != NULL)
+ window_item_set_active(window, last);
+}
+
+void window_item_next(WINDOW_REC *window)
+{
+ WI_ITEM_REC *next;
+ GSList *tmp;
+ int gone;
+
+ g_return_if_fail(window != NULL);
+
+ next = NULL; gone = FALSE;
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if (rec == window->active)
+ gone = TRUE;
+ else {
+ if (gone) {
+ /* found the next channel */
+ next = rec;
+ break;
+ }
+
+ if (next == NULL)
+ next = rec; /* fallback to first channel */
+ }
+ }
+
+ if (next != NULL)
+ window_item_set_active(window, next);
+}
+
+WI_ITEM_REC *window_item_find_window(WINDOW_REC *window,
+ void *server, const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if ((server == NULL || rec->server == server) &&
+ g_strcasecmp(name, rec->name) == 0) return rec;
+ }
+
+ return NULL;
+}
+
+/* Find wanted window item by name. `server' can be NULL. */
+WI_ITEM_REC *window_item_find(void *server, const char *name)
+{
+ WI_ITEM_REC *item;
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ item = window_item_find_window(rec, server, name);
+ if (item != NULL) return item;
+ }
+
+ return NULL;
+}
+
+static int window_bind_has_sticky(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ if (rec->sticky)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void window_item_create(WI_ITEM_REC *item, int automatic)
+{
+ WINDOW_REC *window;
+ GSList *tmp, *sorted;
+ int clear_waiting, reuse_unused_windows;
+
+ g_return_if_fail(item != NULL);
+
+ reuse_unused_windows =
+ !settings_get_bool("autoclose_windows") ||
+ settings_get_bool("reuse_unused_windows");
+
+ clear_waiting = TRUE;
+ window = NULL;
+ sorted = windows_get_sorted();
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ /* is item bound to this window? */
+ if (item->server != NULL &&
+ window_bind_find(rec, item->server->tag, item->name)) {
+ window = rec;
+ clear_waiting = FALSE;
+ break;
+ }
+
+ /* use this window IF:
+ - reuse_unused_windows is ON
+ - window has no existing items
+ - window has no name
+ - window has no sticky binds (/LAYOUT SAVEd)
+ - we already haven't found "good enough" window,
+ except if
+ - this is the active window
+ - old window had some temporary bounds and this
+ one doesn't
+ */
+ if (reuse_unused_windows && rec->items == NULL &&
+ rec->name == NULL && !window_bind_has_sticky(rec) &&
+ (window == NULL || rec == active_win ||
+ window->bound_items != NULL))
+ window = rec;
+ }
+ g_slist_free(sorted);
+
+ if (window == NULL && !settings_get_bool("autocreate_windows")) {
+ /* never create new windows automatically */
+ window = active_win;
+ }
+
+ if (window == NULL) {
+ /* create new window to use */
+ window = window_create(item, automatic);
+ } else {
+ /* use existing window */
+ window_item_add(window, item, automatic);
+ }
+
+ if (clear_waiting)
+ window_bind_remove_unsticky(window);
+}
+
+static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ g_return_if_fail(window != NULL);
+
+ if (g_slist_length(window->items) > 1) {
+ /* default to printing "talking with ...",
+ you can override it it you wish */
+ printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE,
+ TXT_TALKING_WITH, item->name);
+ }
+}
+
+void window_items_init(void)
+{
+ settings_add_bool("lookandfeel", "reuse_unused_windows", FALSE);
+ settings_add_bool("lookandfeel", "autocreate_windows", TRUE);
+
+ signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+}
+
+void window_items_deinit(void)
+{
+ signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+}
--- /dev/null
+#ifndef __WINDOW_ITEMS_H
+#define __WINDOW_ITEMS_H
+
+#include "fe-windows.h"
+
+/* Add/remove/destroy window item from `window' */
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic);
+void window_item_remove(WI_ITEM_REC *item);
+void window_item_destroy(WI_ITEM_REC *item);
+
+/* Find a window for `item' and call window_item_add(). */
+void window_item_create(WI_ITEM_REC *item, int automatic);
+
+#define window_item_window(item) \
+ ((WINDOW_REC *) ((WI_ITEM_REC *) (item))->window)
+void window_item_change_server(WI_ITEM_REC *item, void *server);
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item);
+/* Return TRUE if `item' is the active window item in the window.
+ `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item);
+
+void window_item_prev(WINDOW_REC *window);
+void window_item_next(WINDOW_REC *window);
+
+/* Find wanted window item by name. `server' can be NULL. */
+WI_ITEM_REC *window_item_find(void *server, const char *name);
+WI_ITEM_REC *window_item_find_window(WINDOW_REC *window,
+ void *server, const char *name);
+
+void window_items_init(void);
+void window_items_deinit(void);
+
+#endif
--- /dev/null
+/*
+ windows-layout.c : irssi
+
+ Copyright (C) 2000-2001 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 "misc.h"
+#include "levels.h"
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#include "chat-protocols.h"
+#include "servers.h"
+#include "queries.h"
+
+#include "module-formats.h"
+#include "printtext.h"
+#include "themes.h"
+#include "fe-windows.h"
+#include "window-items.h"
+
+static void sig_window_restore_item(WINDOW_REC *window, const char *type,
+ CONFIG_NODE *node)
+{
+ char *name, *tag, *chat_type;
+
+ chat_type = config_node_get_str(node, "chat_type", NULL);
+ name = config_node_get_str(node, "name", NULL);
+ tag = config_node_get_str(node, "tag", NULL);
+
+ if (name == NULL || tag == NULL)
+ return;
+
+ if (g_strcasecmp(type, "CHANNEL") == 0) {
+ /* bind channel to window */
+ WINDOW_BIND_REC *rec = window_bind_add(window, tag, name);
+ rec->sticky = TRUE;
+ } else if (g_strcasecmp(type, "QUERY") == 0 && chat_type != NULL) {
+ /* create query immediately */
+ chat_protocol_find(chat_type)->query_create(tag, name, TRUE);
+ }
+}
+
+static void window_add_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ GSList *tmp;
+ char *type;
+
+ if (node == NULL)
+ return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ type = config_node_get_str(node, "type", NULL);
+ if (type != NULL) {
+ signal_emit("window restore item", 3,
+ window, type, node);
+ }
+ }
+}
+
+void windows_layout_restore(void)
+{
+ WINDOW_REC *window;
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ node = iconfig_node_traverse("windows", FALSE);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ window = window_create(NULL, TRUE);
+ window_set_refnum(window, atoi(node->key));
+ window->sticky_refnum = config_node_get_bool(node, "sticky_refnum", FALSE);
+ window_set_name(window, config_node_get_str(node, "name", NULL));
+ window_set_level(window, level2bits(config_node_get_str(node, "level", "")));
+
+ window->servertag = g_strdup(config_node_get_str(node, "servertag", NULL));
+ window->theme_name = g_strdup(config_node_get_str(node, "theme", NULL));
+ if (window->theme_name != NULL)
+ window->theme = theme_load(window->theme_name);
+
+ window_add_items(window, config_node_section(node, "items", -1));
+ signal_emit("window restore", 2, window, node);
+ }
+
+ signal_emit("windows restored", 0);
+}
+
+static void window_save_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ CONFIG_NODE *subnode;
+ GSList *tmp;
+ const char *type;
+
+ node = config_node_section(node, "items", NODE_TYPE_LIST);
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+ SERVER_REC *server = rec->server;
+
+ type = module_find_id_str("WINDOW ITEM TYPE", rec->type);
+ if (type == NULL) continue;
+
+ subnode = config_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_str(subnode, "type", type);
+ type = chat_protocol_find_id(rec->chat_type)->name;
+ iconfig_node_set_str(subnode, "chat_type", type);
+ iconfig_node_set_str(subnode, "name", rec->name);
+
+ if (server != NULL)
+ iconfig_node_set_str(subnode, "tag", server->tag);
+ else if (IS_QUERY(rec)) {
+ iconfig_node_set_str(subnode, "tag",
+ QUERY(rec)->server_tag);
+ }
+ }
+}
+
+static void window_save(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ char refnum[MAX_INT_STRLEN];
+
+ ltoa(refnum, window->refnum);
+ node = config_node_section(node, refnum, NODE_TYPE_BLOCK);
+
+ if (window->sticky_refnum)
+ iconfig_node_set_bool(node, "sticky_refnum", TRUE);
+
+ if (window->name != NULL)
+ iconfig_node_set_str(node, "name", window->name);
+ if (window->servertag != NULL)
+ iconfig_node_set_str(node, "servertag", window->servertag);
+ if (window->level != 0) {
+ char *level = bits2level(window->level);
+ iconfig_node_set_str(node, "level", level);
+ g_free(level);
+ }
+ if (window->theme_name != NULL)
+ iconfig_node_set_str(node, "theme", window->theme_name);
+
+ if (window->items != NULL)
+ window_save_items(window, node);
+
+ signal_emit("window save", 2, window, node);
+}
+
+void windows_layout_save(void)
+{
+ CONFIG_NODE *node;
+
+ iconfig_set_str(NULL, "windows", NULL);
+ node = iconfig_node_traverse("windows", TRUE);
+
+ g_slist_foreach(windows, (GFunc) window_save, node);
+ signal_emit("windows saved", 0);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOWS_LAYOUT_SAVED);
+}
+
+void windows_layout_reset(void)
+{
+ iconfig_set_str(NULL, "windows", NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOWS_LAYOUT_RESET);
+}
+
+void windows_layout_init(void)
+{
+ signal_add("window restore item", (SIGNAL_FUNC) sig_window_restore_item);
+}
+
+void windows_layout_deinit(void)
+{
+ signal_remove("window restore item", (SIGNAL_FUNC) sig_window_restore_item);
+}
--- /dev/null
+#ifndef __WINDOWS_LAYOUT_H
+#define __WINDOWS_LAYOUT_H
+
+void windows_layout_restore(void);
+void windows_layout_save(void);
+void windows_layout_reset(void);
+
+void windows_layout_init(void);
+void windows_layout_deinit(void);
+
+#endif
+moduledir = $(libdir)/irssi/modules
+
INCLUDES = $(GLIB_CFLAGS) -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src
-SILC_INCLUDE=../../../..
-IRSSI_INCLUDE=../../..
+module_LTLIBRARIES = libsilc_core.la
+
+libsilc_core_la_LDFLAGS = -avoid-version
INCLUDES = \
- $(GLIB_CFLAGS) \
- -DSYSCONFDIR=\""$(sysconfdir)"\" \
- -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \
- -I$(IRSSI_INCLUDE)/src/core \
- -I$(SILC_INCLUDE)/includes \
- -I$(SILC_INCLUDE)/lib/silccore \
- -I$(SILC_INCLUDE)/lib/silccrypt \
- -I$(SILC_INCLUDE)/lib/silcmath \
- -I$(SILC_INCLUDE)/lib/silcske \
- -I$(SILC_INCLUDE)/lib/silcsim \
- -I$(SILC_INCLUDE)/lib/silcutil \
- -I$(SILC_INCLUDE)/lib/silcclient \
- -I$(SILC_INCLUDE)/lib/silcmath/gmp \
- -I$(SILC_INCLUDE)/lib/trq
-
-noinst_LIBRARIES=libsilc_core.a
-
-libsilc_core_a_SOURCES = \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \
+ -I$(IRSSI_INCLUDE)/src/core \
+ -I$(SILC_INCLUDE)/includes \
+ -I$(SILC_INCLUDE)/lib/silccore \
+ -I$(SILC_INCLUDE)/lib/silccrypt \
+ -I$(SILC_INCLUDE)/lib/silcmath \
+ -I$(SILC_INCLUDE)/lib/silcske \
+ -I$(SILC_INCLUDE)/lib/silcsim \
+ -I$(SILC_INCLUDE)/lib/silcutil \
+ -I$(SILC_INCLUDE)/lib/silcclient \
+ -I$(SILC_INCLUDE)/lib/silcmath/gmp \
+ -I$(SILC_INCLUDE)/lib/trq
+
+libsilc_core_la_DEPENDENCIES = .libs/libsilcclient.a .libs/libsilcorig.a
+
+libsilc_core_la_SOURCES = \
silc-channels.c \
silc-core.c \
silc-nicklist.c \
silc-nicklist.h \
silc-queries.h \
silc-servers.h
+
+SILC_LIBS = \
+ libsilcclient.la \
+ libsilcorig.la
+
+libsilc_core_la_LIBADD = \
+ $(SILC_LIBS)
+
+EXTRA_DIST = \
+ $(SILC_LIBS)
+
+.libs/libsilcclient.a:
+ if [ ! -d .libs ]; then mkdir .libs; fi
+ cd .libs && ln -sf ../../../libsilcclient.a . && cd ..
+
+.libs/libsilcorig.a: .libs/libsilcclient.a
+ cd .libs && ln -sf ../../../libsilcorig.a . && cd ..
--- /dev/null
+/*
+ clientutil.c : irssi
+
+ Copyright (C) 2000 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 "silc-servers.h"
+
+/* Verifies received public key. If user decides to trust the key it is
+ saved as trusted server key for later use. If user does not trust the
+ key this returns FALSE. */
+
+int silc_client_verify_server_key(SILC_SERVER_REC *server,
+ unsigned char *pk, unsigned int pk_len,
+ SilcSKEPKType pk_type)
+{
+ char filename[256];
+ char file[256];
+ char *hostname, *fingerprint;
+ struct stat st;
+
+ hostname = server->connrec->address;
+
+ if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+ //silc_say(client, "We don't support server %s key type", hostname);
+ return FALSE;
+ }
+
+ memset(filename, 0, sizeof(filename));
+ memset(file, 0, sizeof(file));
+ snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
+ server->connrec->port);
+ snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s",
+ g_get_home_dir(), file);
+
+ /* Check wheter this key already exists */
+ if (stat(filename, &st) < 0) {
+
+ fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+ /*silc_say(client, "Received server %s public key", hostname);
+ silc_say(client, "Fingerprint for the server %s key is", hostname);
+ silc_say(client, "%s", fingerprint);*/
+ silc_free(fingerprint);
+
+ /* Ask user to verify the key and save it */
+ /*if (silc_client_ask_yes_no(client,
+ "Would you like to accept the key (y/n)? "))*/
+ {
+ /* Save the key for future checking */
+ silc_pkcs_save_public_key_data(filename, pk, pk_len,
+ SILC_PKCS_FILE_PEM);
+ return TRUE;
+ }
+ } else {
+ /* The key already exists, verify it. */
+ SilcPublicKey public_key;
+ unsigned char *encpk;
+ unsigned int encpk_len;
+
+ /* Load the key file */
+ if (!silc_pkcs_load_public_key(filename, &public_key,
+ SILC_PKCS_FILE_PEM))
+ if (!silc_pkcs_load_public_key(filename, &public_key,
+ SILC_PKCS_FILE_BIN)) {
+ fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+ /*silc_say(client, "Received server %s public key", hostname);
+ silc_say(client, "Fingerprint for the server %s key is", hostname);
+ silc_say(client, "%s", fingerprint);*/
+ silc_free(fingerprint);
+ /*silc_say(client, "Could not load your local copy of the server %s key",
+ hostname);
+ if (silc_client_ask_yes_no(client,
+ "Would you like to accept the key anyway (y/n)? "))*/
+ {
+ /* Save the key for future checking */
+ unlink(filename);
+ silc_pkcs_save_public_key_data(filename, pk, pk_len,
+ SILC_PKCS_FILE_PEM);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /* Encode the key data */
+ encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+ if (!encpk) {
+ fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+ /*silc_say(client, "Received server %s public key", hostname);
+ silc_say(client, "Fingerprint for the server %s key is", hostname);
+ silc_say(client, "%s", fingerprint);*/
+ silc_free(fingerprint);
+ /*silc_say(client, "Your local copy of the server %s key is malformed",
+ hostname);
+ if (silc_client_ask_yes_no(client,
+ "Would you like to accept the key anyway (y/n)? "))*/
+ {
+ /* Save the key for future checking */
+ unlink(filename);
+ silc_pkcs_save_public_key_data(filename, pk, pk_len,
+ SILC_PKCS_FILE_PEM);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ if (memcmp(encpk, pk, encpk_len)) {
+ fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+ /*silc_say(client, "Received server %s public key", hostname);
+ silc_say(client, "Fingerprint for the server %s key is", hostname);
+ silc_say(client, "%s", fingerprint);*/
+ silc_free(fingerprint);
+ /*silc_say(client, "Server %s key does not match with your local copy",
+ hostname);
+ silc_say(client, "It is possible that the key has expired or changed");
+ silc_say(client, "It is also possible that some one is performing "
+ "man-in-the-middle attack");*/
+
+ /* Ask user to verify the key and save it */
+ /*if (silc_client_ask_yes_no(client,
+ "Would you like to accept the key anyway (y/n)? "))*/
+ {
+ /* Save the key for future checking */
+ unlink(filename);
+ silc_pkcs_save_public_key_data(filename, pk, pk_len,
+ SILC_PKCS_FILE_PEM);
+ return TRUE;
+ }
+
+ //silc_say(client, "Will not accept server %s key", hostname);
+ return FALSE;
+ }
+
+ /* Local copy matched */
+ return TRUE;
+ }
+
+ //silc_say(client, "Will not accept server %s key", hostname);
+ return FALSE;
+}
--- /dev/null
+#ifndef __CLIENTUTIL_H
+#define __CLIENTUTIL_H
+
+int silc_client_verify_server_key(SILC_SERVER_REC *server,
+ unsigned char *pk, unsigned int pk_len,
+ SilcSKEPKType pk_type);
+
+#endif
--- /dev/null
+#include "common.h"
+
+#define MODULE_NAME "silc"
+
+#undef PACKAGE
+#undef VERSION
+#include "silcincludes.h"
+#include "clientlibincludes.h"
+#include "silc-core.h"
+
+#define SILC_PROTOCOL (chat_protocol_lookup("SILC"))
static void event_cmode(SILC_SERVER_REC *server, va_list va)
{
- SILC_CHANNEL_REC *chanrec;
- SilcClientEntry client;
- SilcChannelEntry channel;
- char *mode;
- uint32 modei;
-
- client = va_arg(va, SilcClientEntry);
- modei = va_arg(va, uint32);
- channel = va_arg(va, SilcChannelEntry);
- mode = silc_client_chmode(modei, channel);
-
- chanrec = silc_channel_find_entry(server, channel);
- if (chanrec != NULL) {
- g_free_not_null(chanrec->mode);
- chanrec->mode = g_strdup(mode == NULL ? "" : mode);
- signal_emit("channel mode changed", 1, chanrec);
- }
-
- /*signal_emit("message mode", 5, server, chanrec->name,
- client->nickname, client->username, mode);*/
- printtext(server, channel->channel_name, MSGLEVEL_MODES,
- "mode/%s [%s] by %s", channel->channel_name, mode,
- client->nickname);
-
- g_free(mode);
+ SILC_CHANNEL_REC *chanrec;
+ SilcClientEntry client;
+ SilcChannelEntry channel;
+ char *mode;
+
+ client = va_arg(va, SilcClientEntry);
+ mode = silc_client_chmode(va_arg(va, unsigned int));
+ channel = va_arg(va, SilcChannelEntry);
+
+ chanrec = silc_channel_find_entry(server, channel);
+ if (chanrec != NULL) {
+ g_free_not_null(chanrec->mode);
+ chanrec->mode = g_strdup(mode == NULL ? "" : mode);
+ signal_emit("channel mode changed", 1, chanrec);
+ }
+
+ /*signal_emit("message mode", 5, server, chanrec->name,
+ client->nickname, client->username, mode);*/
+ printtext(server, channel->channel_name, MSGLEVEL_MODES,
+ "mode/%s [%s] by %s", channel->channel_name, mode,
+ client->nickname);
+
+ g_free(mode);
}
static void event_cumode(SILC_SERVER_REC *server, va_list va)
{
- SILC_CHANNEL_REC *chanrec;
- SilcClientEntry client, destclient;
- SilcChannelEntry channel;
- int mode;
- char *modestr;
-
- client = va_arg(va, SilcClientEntry);
- mode = va_arg(va, uint32);
- destclient = va_arg(va, SilcClientEntry);
- channel = va_arg(va, SilcChannelEntry);
-
- modestr = silc_client_chumode(mode);
- chanrec = silc_channel_find_entry(server, channel);
- if (chanrec != NULL) {
- SILC_NICK_REC *nick;
-
- if (destclient == server->conn->local_entry) {
- chanrec->chanop =
- (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
- }
-
- nick = silc_nicklist_find(chanrec, client);
- if (nick != NULL) {
- nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
- signal_emit("nick mode changed", 2, chanrec, nick);
- }
- }
-
- /*signal_emit("message mode", 5, server, chanrec->name,
- client->nickname, client->username, modestr);*/
- printtext(server, channel->channel_name, MSGLEVEL_MODES,
- "mode/%s [%s] by %s", channel->channel_name, modestr,
- client->nickname);
-
- g_free(modestr);
+ SILC_CHANNEL_REC *chanrec;
+ SilcClientEntry client, destclient;
+ SilcChannelEntry channel;
+ int mode;
+ char *modestr;
+
+ client = va_arg(va, SilcClientEntry);
+ mode = va_arg(va, unsigned int);
+ destclient = va_arg(va, SilcClientEntry);
+ channel = va_arg(va, SilcChannelEntry);
+
+ modestr = silc_client_chumode(mode);
+ chanrec = silc_channel_find_entry(server, channel);
+ if (chanrec != NULL) {
+ SILC_NICK_REC *nick;
+
+ if (destclient == server->conn->local_entry) {
+ chanrec->chanop =
+ (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+ }
+
+ nick = silc_nicklist_find(chanrec, client);
+ if (nick != NULL) {
+ nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+ signal_emit("nick mode changed", 2, chanrec, nick);
+ }
+ }
+
+ /*signal_emit("message mode", 5, server, chanrec->name,
+ client->nickname, client->username, modestr);*/
+ printtext(server, channel->channel_name, MSGLEVEL_MODES,
+ "mode/%s [%s] by %s", channel->channel_name, modestr,
+ client->nickname);
+
+ g_free(modestr);
}
static void command_part(const char *data, SILC_SERVER_REC *server,
--- /dev/null
+#ifndef __SILC_CHANNELS_H
+#define __SILC_CHANNELS_H
+
+#include "chat-protocols.h"
+#include "channels.h"
+#include "silc-servers.h"
+
+/* Returns SILC_CHANNEL_REC if it's SILC channel, NULL if it isn't. */
+#define SILC_CHANNEL(channel) \
+ PROTO_CHECK_CAST(CHANNEL(channel), SILC_CHANNEL_REC, chat_type, "SILC")
+
+#define IS_SILC_CHANNEL(channel) \
+ (SILC_CHANNEL(channel) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SILC_SERVER_REC
+typedef struct {
+#include "channel-rec.h"
+
+ GSList *banlist; /* list of bans */
+ GSList *ebanlist; /* list of ban exceptions */
+ GSList *invitelist; /* invite list */
+
+ SilcChannelEntry entry;
+} SILC_CHANNEL_REC;
+
+void silc_channels_init(void);
+void silc_channels_deinit(void);
+
+/* Create new SILC channel record */
+SILC_CHANNEL_REC *silc_channel_create(SILC_SERVER_REC *server,
+ const char *name, int automatic);
+
+#define silc_channel_find(server, name) \
+ SILC_CHANNEL(channel_find(SERVER(server), name))
+
+SILC_CHANNEL_REC *silc_channel_find_entry(SILC_SERVER_REC *server,
+ SilcChannelEntry entry);
+
+#endif
-/*
-
- silc-core.c
-
- Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
-
- Copyright (C) 2001 Pekka Riikonen
-
- 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.
-
-*/
-
#include "module.h"
#include "chat-protocols.h"
static void silc_say(SilcClient client, SilcClientConnection conn,
char *msg, ...)
{
- SILC_SERVER_REC *server;
- va_list va;
- char *str;
-
- server = conn == NULL ? NULL : conn->context;
-
- va_start(va, msg);
- str = g_strdup_vprintf(msg, va);
- printtext(server, "#silc", MSGLEVEL_CRAP, "%s", str);
- g_free(str);
- va_end(va);
-}
+ SILC_SERVER_REC *server;
+ va_list va;
+ char *str;
-/* Message for a channel. The `sender' is the nickname of the sender
- received in the packet. The `channel_name' is the name of the channel. */
+ server = conn == NULL ? NULL : conn->context;
-static void
-silc_channel_message(SilcClient client, SilcClientConnection conn,
- SilcClientEntry sender, SilcChannelEntry channel,
- SilcMessageFlags flags, char *msg)
-{
- SILC_SERVER_REC *server;
- SILC_NICK_REC *nick;
- SILC_CHANNEL_REC *chanrec;
-
- server = conn == NULL ? NULL : conn->context;
- chanrec = silc_channel_find_entry(server, channel);
-
- nick = silc_nicklist_find(chanrec, sender);
- signal_emit("message public", 6, server, msg,
- nick == NULL ? "(unknown)" : nick->nick,
- nick == NULL ? NULL : nick->host,
- chanrec->name, nick);
+ va_start(va, msg);
+ str = g_strdup_vprintf(msg, va);
+ printtext(server, "#silc", MSGLEVEL_CRAP, "%s", str);
+ g_free(str);
+ va_end(va);
}
-/* Private message to the client. The `sender' is the nickname of the
- sender received in the packet. */
-
-static void
-silc_private_message(SilcClient client, SilcClientConnection conn,
- SilcClientEntry sender, SilcMessageFlags flags,
- char *msg)
+static void silc_channel_message(SilcClient c, SilcClientConnection conn,
+ SilcClientEntry client,
+ SilcChannelEntry channel, char *msg)
{
- SILC_SERVER_REC *server;
-
- server = conn == NULL ? NULL : conn->context;
- signal_emit("message private", 4, server, msg,
- sender->nickname ? sender->nickname : "(unknown)",
- sender->username ? sender->username : NULL);
+ SILC_SERVER_REC *server;
+ SILC_NICK_REC *nick;
+ SILC_CHANNEL_REC *chanrec;
+
+ server = conn == NULL ? NULL : conn->context;
+ chanrec = silc_channel_find_entry(server, channel);
+
+ nick = client == NULL ? NULL : silc_nicklist_find(chanrec, client);
+ signal_emit("message public", 6, server, msg,
+ nick == NULL ? "(unknown)" : nick->nick,
+ nick == NULL ? NULL : nick->host,
+ chanrec->name, nick);
}
-/* Notify message to the client. The notify arguments are sent in the
- same order as servers sends them. The arguments are same as received
- from the server except for ID's. If ID is received application receives
- the corresponding entry to the ID. For example, if Client ID is received
- application receives SilcClientEntry. Also, if the notify type is
- for channel the channel entry is sent to application (even if server
- does not send it). */
+static void silc_private_message(SilcClient c, SilcClientConnection conn,
+ SilcClientEntry client, char *msg)
+{
+ SILC_SERVER_REC *server;
+
+ server = conn == NULL ? NULL : conn->context;
+ signal_emit("message private", 4, server, msg,
+ client == NULL ? "(unknown)" : client->nickname,
+ client == NULL ? NULL : client->username);
+}
typedef struct {
- int type;
- const char *name;
+ int type;
+ const char *name;
} NOTIFY_REC;
#define MAX_NOTIFY (sizeof(notifies)/sizeof(notifies[0]))
static NOTIFY_REC notifies[] = {
- { SILC_NOTIFY_TYPE_NONE, NULL },
- { SILC_NOTIFY_TYPE_INVITE, "invite" },
- { SILC_NOTIFY_TYPE_JOIN, "join" },
- { SILC_NOTIFY_TYPE_LEAVE, "leave" },
- { SILC_NOTIFY_TYPE_SIGNOFF, "signoff" },
- { SILC_NOTIFY_TYPE_TOPIC_SET, "topic" },
- { SILC_NOTIFY_TYPE_NICK_CHANGE, "nick" },
- { SILC_NOTIFY_TYPE_CMODE_CHANGE, "cmode" },
- { SILC_NOTIFY_TYPE_CUMODE_CHANGE, "cumode" },
- { SILC_NOTIFY_TYPE_MOTD, "motd" }
+ { SILC_NOTIFY_TYPE_NONE, NULL },
+ { SILC_NOTIFY_TYPE_INVITE, "invite" },
+ { SILC_NOTIFY_TYPE_JOIN, "join" },
+ { SILC_NOTIFY_TYPE_LEAVE, "leave" },
+ { SILC_NOTIFY_TYPE_SIGNOFF, "signoff" },
+ { SILC_NOTIFY_TYPE_TOPIC_SET, "topic" },
+ { SILC_NOTIFY_TYPE_NICK_CHANGE, "nick" },
+ { SILC_NOTIFY_TYPE_CMODE_CHANGE, "cmode" },
+ { SILC_NOTIFY_TYPE_CUMODE_CHANGE, "cumode" },
+ { SILC_NOTIFY_TYPE_MOTD, "motd" }
};
static void silc_notify(SilcClient client, SilcClientConnection conn,
- SilcNotifyType type, ...)
+ SilcNotifyType type, ...)
{
- SILC_SERVER_REC *server;
- va_list va;
-
- server = conn == NULL ? NULL : conn->context;
- va_start(va, type);
-
- if (type == SILC_NOTIFY_TYPE_NONE) {
- /* some generic notice from server */
- printtext(server, NULL, MSGLEVEL_CRAP, "%s",
- (char *) va_arg(va, char *));
- } else if (type < MAX_NOTIFY) {
- /* send signal about the notify event */
- char signal[50];
-
- g_snprintf(signal, sizeof(signal), "silc event %s",
- notifies[type].name);
- signal_emit(signal, 2, server, va);
- } else {
- /* unknown notify */
- printtext(server, NULL, MSGLEVEL_CRAP,
- "Unknown notify %d", type);
- }
- va_end(va);
+ SILC_SERVER_REC *server;
+ va_list va;
+
+ server = conn == NULL ? NULL : conn->context;
+ va_start(va, type);
+
+ if (type == SILC_NOTIFY_TYPE_NONE) {
+ /* some generic notice from server */
+ printtext(server, NULL, MSGLEVEL_CRAP, "%s",
+ (char *) va_arg(va, char *));
+ } else if (type < MAX_NOTIFY) {
+ /* send signal about the notify event */
+ char signal[50];
+
+ g_snprintf(signal, sizeof(signal), "silc event %s",
+ notifies[type].name);
+ signal_emit(signal, 2, server, va);
+ } else {
+ /* unknown notify */
+ printtext(server, NULL, MSGLEVEL_CRAP,
+ "Unknown notify %d", type);
+ }
+ va_end(va);
}
-/* Called to indicate that connection was either successfully established
- or connecting failed. This is also the first time application receives
- the SilcClientConnection objecet which it should save somewhere. */
-
-static void
-silc_connect(SilcClient client, SilcClientConnection conn, int success)
+static void silc_connect(SilcClient client, SilcClientConnection conn, int success)
{
- SILC_SERVER_REC *server = conn->context;
-
- if (success) {
- server->connected = TRUE;
- signal_emit("event connected", 1, server);
- } else {
- server->connection_lost = TRUE;
- server->conn->context = NULL;
- server_disconnect(SERVER(server));
- }
-}
+ SILC_SERVER_REC *server = conn->context;
-/* Called to indicate that connection was disconnected to the server. */
+ if (success) {
+ server->connected = TRUE;
+ signal_emit("event connected", 1, server);
+ } else {
+ server->connection_lost = TRUE;
+ server->conn->context = NULL;
+ server_disconnect(SERVER(server));
+ }
+}
-static void
-silc_disconnect(SilcClient client, SilcClientConnection conn)
+static void silc_disconnect(SilcClient client, SilcClientConnection conn)
{
SILC_SERVER_REC *server = conn->context;
server_disconnect(SERVER(server));
}
-/* Command handler. This function is called always in the command function.
- If error occurs it will be called as well. `conn' is the associated
- client connection. `cmd_context' is the command context that was
- originally sent to the command. `success' is FALSE if error occured
- during command. `command' is the command being processed. It must be
- noted that this is not reply from server. This is merely called just
- after application has called the command. Just to tell application
- that the command really was processed. */
-
-static void
-silc_command(SilcClient client, SilcClientConnection conn,
- SilcClientCommandContext cmd_context, int success,
- SilcCommand command)
+static void silc_command(SilcClient client, SilcClientConnection conn,
+ SilcClientCommandContext cmd_context, int success,
+ SilcCommand command)
{
}
-/* Command reply handler. This function is called always in the command reply
- function. If error occurs it will be called as well. Normal scenario
- is that it will be called after the received command data has been parsed
- and processed. The function is used to pass the received command data to
- the application.
-
- `conn' is the associated client connection. `cmd_payload' is the command
- payload data received from server and it can be ignored. It is provided
- if the application would like to re-parse the received command data,
- however, it must be noted that the data is parsed already by the library
- thus the payload can be ignored. `success' is FALSE if error occured.
- In this case arguments are not sent to the application. `command' is the
- command reply being processed. The function has variable argument list
- and each command defines the number and type of arguments it passes to the
- application (on error they are not sent). */
-
-static void
-silc_command_reply(SilcClient client, SilcClientConnection conn,
- SilcCommandPayload cmd_payload, int success,
- SilcCommand command, SilcCommandStatus status, ...)
-
+static void silc_command_reply(SilcClient client, SilcClientConnection conn,
+ SilcCommandPayload cmd_payload, int success,
+ SilcCommand command,
+ SilcCommandStatus status, ...)
{
- SILC_SERVER_REC *server = conn->context;
- SILC_CHANNEL_REC *chanrec;
- va_list va;
-
- va_start(va, status);
-
- /*g_snprintf(signal, sizeof(signal), "silc command reply %s",
- silc_commands[type]);
- signal_emit(signal, 2, server, va);*/
-
- switch(command) {
- case SILC_COMMAND_JOIN:
- {
- char *channel, *mode;
- uint32 modei;
- SilcChannelEntry channel_entry;
-
- channel = va_arg(va, char *);
- channel_entry = va_arg(va, SilcChannelEntry);
- modei = va_arg(va, uint32);
- mode = silc_client_chmode(modei, channel_entry);
-
- chanrec = silc_channel_find(server, channel);
- if (chanrec != NULL && !success)
- channel_destroy(CHANNEL(chanrec));
- else if (chanrec == NULL && success)
- chanrec = silc_channel_create(server, channel, TRUE);
-
- g_free_not_null(chanrec->mode);
- chanrec->mode = g_strdup(mode == NULL ? "" : mode);
- signal_emit("channel mode changed", 1, chanrec);
- break;
- }
- case SILC_COMMAND_NICK:
- {
- SilcClientEntry client = va_arg(va, SilcClientEntry);
- char *old;
-
- old = g_strdup(server->nick);
- server_change_nick(SERVER(server), client->nickname);
- nicklist_rename_unique(SERVER(server),
- server->conn->local_entry, server->nick,
- client, client->nickname);
-
- signal_emit("message own_nick", 4,
- server, server->nick, old, "");
- g_free(old);
- break;
- }
- case SILC_COMMAND_USERS:
- {
- SilcChannelEntry channel;
- SilcChannelUser user;
- NICK_REC *ownnick;
-
- channel = va_arg(va, SilcChannelEntry);
- chanrec = silc_channel_find_entry(server, channel);
- if (chanrec == NULL)
- break;
-
- silc_list_start(channel->clients);
- while ((user = silc_list_get(channel->clients)) != NULL)
- silc_nicklist_insert(chanrec, user, FALSE);
-
- ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
- nicklist_set_own(CHANNEL(chanrec), ownnick);
- signal_emit("channel joined", 1, chanrec);
- fe_channels_nicklist(CHANNEL(chanrec),
- CHANNEL_NICKLIST_FLAG_ALL);
- break;
- }
- }
-
- va_end(va);
-}
+ SILC_SERVER_REC *server = conn->context;
+ SILC_CHANNEL_REC *chanrec;
+ va_list va;
+
+ va_start(va, status);
+
+ /*g_snprintf(signal, sizeof(signal), "silc command reply %s",
+ silc_commands[type]);
+ signal_emit(signal, 2, server, va);*/
+
+ switch(command) {
+ case SILC_COMMAND_JOIN: {
+ char *channel, *mode;
+
+ channel = va_arg(va, char *);
+ (void)va_arg(va, SilcChannelEntry);
+ mode = silc_client_chmode(va_arg(va, unsigned int));
+
+ chanrec = silc_channel_find(server, channel);
+ if (chanrec != NULL && !success)
+ channel_destroy(CHANNEL(chanrec));
+ else if (chanrec == NULL && success)
+ chanrec = silc_channel_create(server, channel, TRUE);
+
+ g_free_not_null(chanrec->mode);
+ chanrec->mode = g_strdup(mode == NULL ? "" : mode);
+ signal_emit("channel mode changed", 1, chanrec);
+ break;
+ }
+ case SILC_COMMAND_NICK: {
+ SilcClientEntry client = va_arg(va, SilcClientEntry);
+ char *old;
+
+ old = g_strdup(server->nick);
+ server_change_nick(SERVER(server), client->nickname);
+ nicklist_rename_unique(SERVER(server),
+ server->conn->local_entry, server->nick,
+ client, client->nickname);
+
+ signal_emit("message own_nick", 4,
+ server, server->nick, old, "");
+ g_free(old);
+ break;
+ }
+ case SILC_COMMAND_USERS: {
+ SilcChannelEntry channel;
+ SilcChannelUser user;
+ NICK_REC *ownnick;
+
+ channel = va_arg(va, SilcChannelEntry);
+ chanrec = silc_channel_find_entry(server, channel);
+ if (chanrec == NULL)
+ break;
+
+ silc_list_start(channel->clients);
+ while ((user = silc_list_get(channel->clients)) != NULL)
+ silc_nicklist_insert(chanrec, user, FALSE);
+
+ ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
+ nicklist_set_own(CHANNEL(chanrec), ownnick);
+ signal_emit("channel joined", 1, chanrec);
+ fe_channels_nicklist(CHANNEL(chanrec),
+ CHANNEL_NICKLIST_FLAG_ALL);
+ break;
+ }
+ }
-/* Verifies received public key. If user decides to trust the key it is
- saved as public server key for later use. If user does not trust the
- key this returns FALSE. */
+ va_end(va);
+}
-static int silc_verify_public_key(SilcClient client,
- SilcClientConnection conn,
- SilcSocketType conn_type,
- unsigned char *pk, uint32 pk_len,
+static int silc_verify_server_key(SilcClient client, SilcClientConnection conn,
+ unsigned char *pk, unsigned int pk_len,
SilcSKEPKType pk_type)
{
- return TRUE;
+ return TRUE;
}
-/* Asks passphrase from user on the input line. */
-
static unsigned char *silc_ask_passphrase(SilcClient client,
SilcClientConnection conn)
{
return NULL;
}
-/* Find authentication method and authentication data by hostname and
- port. The hostname may be IP address as well. The found authentication
- method and authentication data is returned to `auth_meth', `auth_data'
- and `auth_data_len'. The function returns TRUE if authentication method
- is found and FALSE if not. `conn' may be NULL. */
-
-static int
-silc_get_auth_method(SilcClient client, SilcClientConnection conn,
- char *hostname, uint16 port,
- SilcProtocolAuthMeth *auth_meth,
- unsigned char **auth_data,
- uint32 *auth_data_len)
+static int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+ char *hostname, unsigned short port,
+ SilcProtocolAuthMeth *auth_meth,
+ unsigned char **auth_data,
+ unsigned int *auth_data_len)
{
- return FALSE;
+ return FALSE;
}
-/* Notifies application that failure packet was received. This is called
- if there is some protocol active in the client. The `protocol' is the
- protocol context. The `failure' is opaque pointer to the failure
- indication. Note, that the `failure' is protocol dependant and application
- must explicitly cast it to correct type. Usually `failure' is 32 bit
- failure type (see protocol specs for all protocol failure types). */
-
-static void
-silc_failure(SilcClient client, SilcClientConnection conn,
- SilcProtocol protocol, void *failure)
+static void silc_failure(SilcClient client, SilcClientConnection conn,
+ SilcProtocol protocol, void *failure)
{
- if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
- SilcSKEStatus status = (SilcSKEStatus)failure;
-
- if (status == SILC_SKE_STATUS_BAD_VERSION)
- silc_say(client, conn,
- "You are running incompatible client version (it may be "
- "too old or too new)");
- if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
- silc_say(client, conn, "Server does not support your public key type");
- if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
- silc_say(client, conn,
- "Server does not support one of your proposed KE group");
- if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
- silc_say(client, conn,
- "Server does not support one of your proposed cipher");
- if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
- silc_say(client, conn,
- "Server does not support one of your proposed PKCS");
- if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
- silc_say(client, conn,
- "Server does not support one of your proposed hash function");
- if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
- silc_say(client, conn,
- "Server does not support one of your proposed HMAC");
- if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
- silc_say(client, conn, "Incorrect signature");
- }
-
- if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
- uint32 err = (uint32)failure;
-
- if (err == SILC_AUTH_FAILED)
- silc_say(client, conn, "Authentication failed");
- }
}
-/* Asks whether the user would like to perform the key agreement protocol.
- This is called after we have received an key agreement packet or an
- reply to our key agreement packet. This returns TRUE if the user wants
- the library to perform the key agreement protocol and FALSE if it is not
- desired (application may start it later by calling the function
- silc_client_perform_key_agreement). */
-
-static int
-silc_key_agreement(SilcClient client, SilcClientConnection conn,
- SilcClientEntry client_entry, char *hostname,
- int port,
- SilcKeyAgreementCallback *completion,
- void **context)
+static int key_agreement(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry client_entry, char *hostname,
+ int port)
{
- char host[256];
-
- /* We will just display the info on the screen and return FALSE and user
- will have to start the key agreement with a command. */
-
- if (hostname) {
- memset(host, 0, sizeof(host));
- snprintf(host, sizeof(host) - 1, "(%s on port %d)", hostname, port);
- }
-
- silc_say(client, conn, "%s wants to perform key agreement %s",
- client_entry->nickname, hostname ? host : "");
-
- *completion = NULL;
- *context = NULL;
-
- return FALSE;
+ return FALSE;
}
-/* SILC client operations */
SilcClientOperations ops = {
- silc_say,
- silc_channel_message,
- silc_private_message,
- silc_notify,
- silc_command,
- silc_command_reply,
- silc_connect,
- silc_disconnect,
- silc_get_auth_method,
- silc_verify_public_key,
- silc_ask_passphrase,
- silc_failure,
- silc_key_agreement,
+ silc_say,
+ silc_channel_message,
+ silc_private_message,
+ silc_notify,
+ silc_command,
+ silc_command_reply,
+ silc_connect,
+ silc_disconnect,
+ silc_get_auth_method,
+ silc_verify_server_key,
+ silc_ask_passphrase,
+ silc_failure,
+ key_agreement
};
/* Loads public and private key from files. */
SilcPKCS pkcs;
SilcRng rng;
unsigned char *key;
- uint32 key_len;
+ unsigned int key_len;
rng = silc_rng_alloc();
silc_rng_init(rng);
--- /dev/null
+#ifndef __SILC_CORE_H
+#define __SILC_CORE_H
+
+extern SilcClient silc_client;
+
+#define IS_SILC_ITEM(rec) (IS_SILC_CHANNEL(rec) || IS_SILC_QUERY(rec))
+
+#endif
--- /dev/null
+/*
+ silc-nicklist.c : irssi
+
+ Copyright (C) 2000 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 "misc.h"
+#include "servers.h"
+
+#include "silc-channels.h"
+#include "silc-nicklist.h"
+
+SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel,
+ SilcChannelUser user, int send_massjoin)
+{
+ SILC_NICK_REC *rec;
+
+ g_return_val_if_fail(IS_SILC_CHANNEL(channel), NULL);
+ g_return_val_if_fail(user != NULL, NULL);
+
+ rec = g_new0(SILC_NICK_REC, 1);
+ rec->nick = g_strdup(user->client->nickname);
+ rec->host = g_strdup(user->client->username);
+ rec->silc_user = user;
+ rec->unique_id = user->client;
+
+ if (user->mode & SILC_CHANNEL_UMODE_CHANOP) rec->op = TRUE;
+ if (user->mode & SILC_CHANNEL_UMODE_CHANFO) rec->founder = TRUE;
+ rec->send_massjoin = send_massjoin;
+
+ nicklist_insert(CHANNEL(channel), (NICK_REC *) rec);
+ return rec;
+}
+
+SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel,
+ SilcClientEntry client)
+{
+ return (SILC_NICK_REC *)
+ nicklist_find_unique(CHANNEL(channel),
+ client->nickname, client);
+}
+
+#define isnickchar(a) \
+ (isalnum((int) (a)) || (a) == '`' || (a) == '-' || (a) == '_' || \
+ (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \
+ (a) == '|' || (a) == '\\' || (a) == '^')
+
+/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */
+char *silc_nick_strip(const char *nick)
+{
+ char *stripped, *spos;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ spos = stripped = g_strdup(nick);
+ while (isnickchar(*nick)) {
+ if (isalnum((int) *nick))
+ *spos++ = *nick;
+ nick++;
+ }
+ if ((unsigned char) *nick >= 128)
+ *spos++ = *nick; /* just add it so that nicks won't match.. */
+ *spos = '\0';
+ return stripped;
+}
+
+/* Check is `msg' is meant for `nick'. */
+int silc_nick_match(const char *nick, const char *msg)
+{
+ char *stripnick, *stripmsg;
+ int ret, len;
+
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ len = strlen(nick);
+ if (g_strncasecmp(msg, nick, len) == 0 && !isalnum((int) msg[len]))
+ return TRUE;
+
+ stripnick = silc_nick_strip(nick);
+ stripmsg = silc_nick_strip(msg);
+
+ len = strlen(stripnick);
+ ret = len > 0 && g_strncasecmp(stripmsg, stripnick, len) == 0 &&
+ !isalnum((int) stripmsg[len]) &&
+ (unsigned char) stripmsg[len] < 128;
+
+ g_free(stripnick);
+ g_free(stripmsg);
+ return ret;
+}
+
+static const char *get_nick_flags(void)
+{
+ static char flags[3] = { '@', '+', '\0' };
+ return flags;
+}
+
+static void sig_connected(SILC_SERVER_REC *server)
+{
+ if (IS_SILC_SERVER(server))
+ server->get_nick_flags = (void *) get_nick_flags;
+}
+
+void silc_nicklist_init(void)
+{
+ signal_add("server connected", (SIGNAL_FUNC) sig_connected);
+}
+
+void silc_nicklist_deinit(void)
+{
+ signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+}
--- /dev/null
+#ifndef __SILC_NICKLIST_H
+#define __SILC_NICKLIST_H
+
+#include "nicklist.h"
+
+typedef struct {
+#include "nick-rec.h"
+ SilcChannelUser silc_user;
+
+ unsigned int founder:1;
+} SILC_NICK_REC;
+
+SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel,
+ SilcChannelUser user, int send_massjoin);
+
+SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel,
+ SilcClientEntry client);
+
+/* Check if `msg' is meant for `nick'. */
+int silc_nick_match(const char *nick, const char *msg);
+
+void silc_nicklist_init(void);
+void silc_nicklist_deinit(void);
+
+#endif
--- /dev/null
+/*
+ silc-queries.c : irssi
+
+ Copyright (C) 2000 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 "misc.h"
+
+#include "silc-queries.h"
+
+QUERY_REC *silc_query_create(SILC_SERVER_REC *server,
+ const char *nick, int automatic)
+{
+ QUERY_REC *rec;
+
+ g_return_val_if_fail(server == NULL || IS_SILC_SERVER(server), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new0(QUERY_REC, 1);
+ rec->chat_type = SILC_PROTOCOL;
+ rec->name = g_strdup(nick);
+ rec->server = (SERVER_REC *) server;
+ query_init(rec, automatic);
+ return rec;
+}
+
+void silc_queries_init(void)
+{
+}
+
+void silc_queries_deinit(void)
+{
+}
--- /dev/null
+#ifndef __SILC_QUERIES_H
+#define __SILC_QUERIES_H
+
+#include "chat-protocols.h"
+#include "queries.h"
+#include "silc-servers.h"
+
+/* Returns SILC_QUERY_REC if it's SILC query, NULL if it isn't. */
+#define SILC_QUERY(query) \
+ PROTO_CHECK_CAST(QUERY(query), QUERY_REC, chat_type, "SILC")
+
+#define IS_SILC_QUERY(query) \
+ (SILC_QUERY(query) ? TRUE : FALSE)
+
+void silc_queries_init(void);
+void silc_queries_deinit(void);
+
+#define silc_query_find(server, name) \
+ query_find(SERVER(server), name)
+
+QUERY_REC *silc_query_create(SILC_SERVER_REC *server,
+ const char *nick, int automatic);
+
+#endif
--- /dev/null
+/*
+ silc-servers-reconnect.c : irssi
+
+ Copyright (C) 2000 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 "silc-servers.h"
+
+static void sig_server_reconnect_save_status(SILC_SERVER_CONNECT_REC *conn,
+ SILC_SERVER_REC *server)
+{
+ if (!IS_SILC_SERVER_CONNECT(conn) || !IS_SILC_SERVER(server))
+ return;
+
+ g_free_not_null(conn->channels);
+ conn->channels = silc_server_get_channels(server);
+}
+
+static void sig_server_connect_copy(SERVER_CONNECT_REC **dest,
+ SILC_SERVER_CONNECT_REC *src)
+{
+ SILC_SERVER_CONNECT_REC *rec;
+
+ g_return_if_fail(dest != NULL);
+ if (!IS_SILC_SERVER_CONNECT(src))
+ return;
+
+ rec = g_new0(SILC_SERVER_CONNECT_REC, 1);
+ rec->chat_type = SILC_PROTOCOL;
+ *dest = (SERVER_CONNECT_REC *) rec;
+}
+
+void silc_servers_reconnect_init(void)
+{
+ signal_add("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status);
+ signal_add("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy);
+}
+
+void silc_servers_reconnect_deinit(void)
+{
+ signal_remove("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status);
+ signal_remove("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy);
+}
static void silc_send_channel(SILC_SERVER_REC *server,
char *channel, char *msg)
{
- SILC_CHANNEL_REC *rec;
-
- rec = silc_channel_find(server, channel);
- if (rec == NULL)
- return;
-
- silc_client_send_channel_message(silc_client, server->conn, rec->entry,
- NULL, 0, msg, strlen(msg), TRUE);
+ SILC_CHANNEL_REC *rec;
+
+ rec = silc_channel_find(server, channel);
+ if (rec == NULL)
+ return;
+
+ silc_client_send_channel_message(silc_client, server->conn,
+ rec->entry, msg, strlen(msg), TRUE);
}
typedef struct {
static void silc_send_msg_clients(SilcClient client,
SilcClientConnection conn,
SilcClientEntry *clients,
- uint32 clients_count,
+ unsigned int clients_count,
void *context)
{
- PRIVMSG_REC *rec = context;
- SilcClientEntry target;
-
- if (clients_count == 0) {
- printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
- "Unknown nick: %s", rec->nick);
- } else {
- target = clients[0]; /* FIXME: not a good idea :) */
-
- silc_client_send_private_message(client, conn, target, 0,
- rec->msg, strlen(rec->msg),
- TRUE);
- }
-
- g_free(rec->nick);
- g_free(rec->msg);
- g_free(rec);
+ PRIVMSG_REC *rec = context;
+ SilcClientEntry target;
+
+ if (clients_count == 0) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown nick: %s", rec->nick);
+ } else {
+ target = clients[0]; /* FIXME: not a good idea :) */
+
+ silc_client_send_private_message(client, conn, target,
+ rec->msg, strlen(rec->msg),
+ TRUE);
+ }
+
+ g_free(rec->nick);
+ g_free(rec->msg);
+ g_free(rec);
}
static void silc_send_msg(SILC_SERVER_REC *server, char *nick, char *msg)
{
- PRIVMSG_REC *rec;
-
- rec = g_new0(PRIVMSG_REC, 1);
- rec->nick = g_strdup(nick);
- rec->msg = g_strdup(msg);
-
- silc_client_get_clients(silc_client, server->conn,
- nick, "", silc_send_msg_clients, rec);
+ PRIVMSG_REC *rec;
+
+ rec = g_new0(PRIVMSG_REC, 1);
+ rec->nick = g_strdup(nick);
+ rec->msg = g_strdup(msg);
+
+ silc_client_get_clients(silc_client, server->conn,
+ nick, "", silc_send_msg_clients, rec);
}
static int isnickflag_func(char flag)
static void sig_disconnected(SILC_SERVER_REC *server)
{
- if (!IS_SILC_SERVER(server) || server->conn == NULL)
- return;
-
- if (server->conn->sock != NULL) {
- silc_client_close_connection(silc_client, NULL, server->conn);
-
- /* SILC closes the handle */
- g_io_channel_unref(net_sendbuffer_handle(server->handle));
- net_sendbuffer_destroy(server->handle, FALSE);
- server->handle = NULL;
- }
+ if (!IS_SILC_SERVER(server) || server->conn == NULL)
+ return;
+
+ if (server->conn->sock != NULL) {
+ silc_client_close_connection(silc_client, server->conn);
+
+ /* SILC closes the handle */
+ g_io_channel_unref(net_sendbuffer_handle(server->handle));
+ net_sendbuffer_destroy(server->handle, FALSE);
+ server->handle = NULL;
+ }
}
SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn)
void silc_command_exec(SILC_SERVER_REC *server,
const char *command, const char *args)
{
- uint32 argc = 0;
+ unsigned int argc = 0;
unsigned char **argv;
- uint32 *argv_lens, *argv_types;
+ unsigned int *argv_lens, *argv_types;
char *data, *tmpcmd;
SilcClientCommand *cmd;
SilcClientCommandContext ctx;
--- /dev/null
+#ifndef __SILC_SERVER_H
+#define __SILC_SERVER_H
+
+#include "chat-protocols.h"
+#include "servers.h"
+
+/* returns SILC_SERVER_REC if it's SILC server, NULL if it isn't */
+#define SILC_SERVER(server) \
+ PROTO_CHECK_CAST(SERVER(server), SILC_SERVER_REC, chat_type, "SILC")
+
+#define SILC_SERVER_CONNECT(conn) \
+ PROTO_CHECK_CAST(SERVER_CONNECT(conn), SILC_SERVER_CONNECT_REC, \
+ chat_type, "SILC")
+
+#define IS_SILC_SERVER(server) \
+ (SILC_SERVER(server) ? TRUE : FALSE)
+
+#define IS_SILC_SERVER_CONNECT(conn) \
+ (SILC_SERVER_CONNECT(conn) ? TRUE : FALSE)
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+typedef struct {
+#include "server-connect-rec.h"
+} SILC_SERVER_CONNECT_REC;
+
+#define STRUCT_SERVER_CONNECT_REC SILC_SERVER_CONNECT_REC
+typedef struct {
+#include "server-rec.h"
+ /* Command sending queue */
+ int cmdcount; /* number of commands in `cmdqueue'. Can be more than
+ there actually is, to make flood control remember
+ how many messages can be sent before starting the
+ flood control */
+ int cmd_last_split; /* Last command wasn't sent entirely to server.
+ First item in `cmdqueue' should be re-sent. */
+ GSList *cmdqueue;
+ GTimeVal last_cmd; /* last time command was sent to server */
+
+ GSList *idles; /* Idle queue - send these commands to server
+ if there's nothing else to do */
+
+ gpointer chanqueries;
+ SilcClientConnection conn;
+} SILC_SERVER_REC;
+
+SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn);
+
+/* Return a string of all channels in server in
+ server->channels_join() format */
+char *silc_server_get_channels(SILC_SERVER_REC *server);
+
+void silc_command_exec(SILC_SERVER_REC *server,
+ const char *command, const char *args);
+
+void silc_server_init(void);
+void silc_server_deinit(void);
+
+#endif