From: Pekka Riikonen Date: Thu, 24 May 2001 12:27:08 +0000 (+0000) Subject: imported. X-Git-Tag: robodoc-323~301 X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=commitdiff_plain;h=6bdd7d2dd5279434f63b673e327b43d2513ec3a5 imported. --- diff --git a/apps/irssi/src/core/Makefile.am b/apps/irssi/src/core/Makefile.am new file mode 100644 index 00000000..1dc2f5f9 --- /dev/null +++ b/apps/irssi/src/core/Makefile.am @@ -0,0 +1,102 @@ +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 diff --git a/apps/irssi/src/core/args.c b/apps/irssi/src/core/args.c new file mode 100644 index 00000000..093a8d56 --- /dev/null +++ b/apps/irssi/src/core/args.c @@ -0,0 +1,64 @@ +/* + 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; +} diff --git a/apps/irssi/src/core/args.h b/apps/irssi/src/core/args.h new file mode 100644 index 00000000..4d1b82fb --- /dev/null +++ b/apps/irssi/src/core/args.h @@ -0,0 +1,15 @@ +#ifndef __ARGS_H +#define __ARGS_H + +#ifdef HAVE_POPT_H +# include +#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 diff --git a/apps/irssi/src/core/channel-rec.h b/apps/irssi/src/core/channel-rec.h new file mode 100644 index 00000000..2ff6a536 --- /dev/null +++ b/apps/irssi/src/core/channel-rec.h @@ -0,0 +1,30 @@ +/* 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); diff --git a/apps/irssi/src/core/channel-setup-rec.h b/apps/irssi/src/core/channel-setup-rec.h new file mode 100644 index 00000000..a0b28978 --- /dev/null +++ b/apps/irssi/src/core/channel-setup-rec.h @@ -0,0 +1,12 @@ +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; diff --git a/apps/irssi/src/core/channels-setup.c b/apps/irssi/src/core/channels-setup.c new file mode 100644 index 00000000..a53bd3f3 --- /dev/null +++ b/apps/irssi/src/core/channels-setup.c @@ -0,0 +1,182 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/channels-setup.h b/apps/irssi/src/core/channels-setup.h new file mode 100644 index 00000000..423bccb2 --- /dev/null +++ b/apps/irssi/src/core/channels-setup.h @@ -0,0 +1,31 @@ +#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 diff --git a/apps/irssi/src/core/channels.c b/apps/irssi/src/core/channels.c new file mode 100644 index 00000000..f3d3a7a4 --- /dev/null +++ b/apps/irssi/src/core/channels.c @@ -0,0 +1,209 @@ +/* + 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"); +} diff --git a/apps/irssi/src/core/channels.h b/apps/irssi/src/core/channels.h new file mode 100644 index 00000000..98b75ee0 --- /dev/null +++ b/apps/irssi/src/core/channels.h @@ -0,0 +1,34 @@ +#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 diff --git a/apps/irssi/src/core/chat-commands.c b/apps/irssi/src/core/chat-commands.c new file mode 100644 index 00000000..78a05a70 --- /dev/null +++ b/apps/irssi/src/core/chat-commands.c @@ -0,0 +1,384 @@ +/* + 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 - 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 ] [-host ] +
| [ [ []]] */ +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 ] [-host ] + [+]
| [ [ []]] */ +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 *| [] */ +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 [] */ +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] [-] [] */ +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 = 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 [-] */ +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 */ +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 */ +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 */ +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); +} diff --git a/apps/irssi/src/core/chat-protocols.c b/apps/irssi/src/core/chat-protocols.c new file mode 100644 index 00000000..ab7c09d4 --- /dev/null +++ b/apps/irssi/src/core/chat-protocols.c @@ -0,0 +1,228 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/chat-protocols.h b/apps/irssi/src/core/chat-protocols.h new file mode 100644 index 00000000..cc7eaadc --- /dev/null +++ b/apps/irssi/src/core/chat-protocols.h @@ -0,0 +1,57 @@ +#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 diff --git a/apps/irssi/src/core/chatnet-rec.h b/apps/irssi/src/core/chatnet-rec.h new file mode 100644 index 00000000..e3ed8aa0 --- /dev/null +++ b/apps/irssi/src/core/chatnet-rec.h @@ -0,0 +1,12 @@ +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 */ diff --git a/apps/irssi/src/core/chatnets.c b/apps/irssi/src/core/chatnets.c new file mode 100644 index 00000000..a2cff529 --- /dev/null +++ b/apps/irssi/src/core/chatnets.c @@ -0,0 +1,203 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/chatnets.h b/apps/irssi/src/core/chatnets.h new file mode 100644 index 00000000..2b78f64a --- /dev/null +++ b/apps/irssi/src/core/chatnets.h @@ -0,0 +1,32 @@ +#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 diff --git a/apps/irssi/src/core/commands.c b/apps/irssi/src/core/commands.c new file mode 100644 index 00000000..b6fb8a74 --- /dev/null +++ b/apps/irssi/src/core/commands.c @@ -0,0 +1,909 @@ +/* + 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 */ +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 */ +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); +} diff --git a/apps/irssi/src/core/commands.h b/apps/irssi/src/core/commands.h new file mode 100644 index 00000000..9dee2e80 --- /dev/null +++ b/apps/irssi/src/core/commands.h @@ -0,0 +1,147 @@ +#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 [] -nick + + 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 diff --git a/apps/irssi/src/core/core.c b/apps/irssi/src/core/core.c new file mode 100644 index 00000000..fd7459db --- /dev/null +++ b/apps/irssi/src/core/core.c @@ -0,0 +1,109 @@ +/* + 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(); +} diff --git a/apps/irssi/src/core/core.h b/apps/irssi/src/core/core.h new file mode 100644 index 00000000..61d6ef70 --- /dev/null +++ b/apps/irssi/src/core/core.h @@ -0,0 +1,17 @@ +#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 diff --git a/apps/irssi/src/core/expandos.c b/apps/irssi/src/core/expandos.c new file mode 100644 index 00000000..e6572316 --- /dev/null +++ b/apps/irssi/src/core/expandos.c @@ -0,0 +1,585 @@ +/* + 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 +#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); +} diff --git a/apps/irssi/src/core/expandos.h b/apps/irssi/src/core/expandos.h new file mode 100644 index 00000000..3dcb527e --- /dev/null +++ b/apps/irssi/src/core/expandos.h @@ -0,0 +1,38 @@ +#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 diff --git a/apps/irssi/src/core/ignore.c b/apps/irssi/src/core/ignore.c new file mode 100644 index 00000000..69460c5b --- /dev/null +++ b/apps/irssi/src/core/ignore.c @@ -0,0 +1,484 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/ignore.h b/apps/irssi/src/core/ignore.h new file mode 100644 index 00000000..4056062e --- /dev/null +++ b/apps/irssi/src/core/ignore.h @@ -0,0 +1,40 @@ +#ifndef __IGNORE_H +#define __IGNORE_H + +#ifdef HAVE_REGEX_H +# include +#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 diff --git a/apps/irssi/src/core/levels.c b/apps/irssi/src/core/levels.c new file mode 100644 index 00000000..aae506a8 --- /dev/null +++ b/apps/irssi/src/core/levels.c @@ -0,0 +1,174 @@ +/* + 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; +} diff --git a/apps/irssi/src/core/levels.h b/apps/irssi/src/core/levels.h new file mode 100644 index 00000000..2d7288f7 --- /dev/null +++ b/apps/irssi/src/core/levels.h @@ -0,0 +1,45 @@ +#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 diff --git a/apps/irssi/src/core/line-split.c b/apps/irssi/src/core/line-split.c new file mode 100644 index 00000000..b7daf275 --- /dev/null +++ b/apps/irssi/src/core/line-split.c @@ -0,0 +1,141 @@ +/* + 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; +} diff --git a/apps/irssi/src/core/line-split.h b/apps/irssi/src/core/line-split.h new file mode 100644 index 00000000..7c101ddd --- /dev/null +++ b/apps/irssi/src/core/line-split.h @@ -0,0 +1,11 @@ +#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 diff --git a/apps/irssi/src/core/log.c b/apps/irssi/src/core/log.c new file mode 100644 index 00000000..368de25f --- /dev/null +++ b/apps/irssi/src/core/log.c @@ -0,0 +1,565 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/log.h b/apps/irssi/src/core/log.h new file mode 100644 index 00000000..7361b6a0 --- /dev/null +++ b/apps/irssi/src/core/log.h @@ -0,0 +1,56 @@ +#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 diff --git a/apps/irssi/src/core/masks.c b/apps/irssi/src/core/masks.c new file mode 100644 index 00000000..7014e431 --- /dev/null +++ b/apps/irssi/src/core/masks.c @@ -0,0 +1,133 @@ +/* + 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; +} diff --git a/apps/irssi/src/core/masks.h b/apps/irssi/src/core/masks.h new file mode 100644 index 00000000..b6ce20d8 --- /dev/null +++ b/apps/irssi/src/core/masks.h @@ -0,0 +1,11 @@ +#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 diff --git a/apps/irssi/src/core/memdebug.c b/apps/irssi/src/core/memdebug.c new file mode 100644 index 00000000..213d4542 --- /dev/null +++ b/apps/irssi/src/core/memdebug.c @@ -0,0 +1,379 @@ +/* + 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 +#include +#include + +#include +#include + +/*#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; +} diff --git a/apps/irssi/src/core/memdebug.h b/apps/irssi/src/core/memdebug.h new file mode 100644 index 00000000..3f328805 --- /dev/null +++ b/apps/irssi/src/core/memdebug.h @@ -0,0 +1,38 @@ +#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 diff --git a/apps/irssi/src/core/misc.c b/apps/irssi/src/core/misc.c new file mode 100644 index 00000000..7a2c54d2 --- /dev/null +++ b/apps/irssi/src/core/misc.c @@ -0,0 +1,727 @@ +/* + 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 +#ifdef HAVE_REGEX_H +# include +#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 ^ 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; +} diff --git a/apps/irssi/src/core/misc.h b/apps/irssi/src/core/misc.h new file mode 100644 index 00000000..45c8ce11 --- /dev/null +++ b/apps/irssi/src/core/misc.h @@ -0,0 +1,104 @@ +#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 ^ 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 diff --git a/apps/irssi/src/core/module.h b/apps/irssi/src/core/module.h new file mode 100644 index 00000000..89784389 --- /dev/null +++ b/apps/irssi/src/core/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "core" diff --git a/apps/irssi/src/core/modules.c b/apps/irssi/src/core/modules.c new file mode 100644 index 00000000..0dac04ae --- /dev/null +++ b/apps/irssi/src/core/modules.c @@ -0,0 +1,444 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/modules.h b/apps/irssi/src/core/modules.h new file mode 100644 index 00000000..7a83819f --- /dev/null +++ b/apps/irssi/src/core/modules.h @@ -0,0 +1,64 @@ +#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 diff --git a/apps/irssi/src/core/net-disconnect.c b/apps/irssi/src/core/net-disconnect.c new file mode 100644 index 00000000..b0e9535b --- /dev/null +++ b/apps/irssi/src/core/net-disconnect.c @@ -0,0 +1,158 @@ +/* + 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 +} diff --git a/apps/irssi/src/core/net-disconnect.h b/apps/irssi/src/core/net-disconnect.h new file mode 100644 index 00000000..a1ca0643 --- /dev/null +++ b/apps/irssi/src/core/net-disconnect.h @@ -0,0 +1,7 @@ +#ifndef __NET_DISCONNECT_H +#define __NET_DISCONNECT_H + +void net_disconnect_init(void); +void net_disconnect_deinit(void); + +#endif diff --git a/apps/irssi/src/core/net-nonblock.c b/apps/irssi/src/core/net-nonblock.c new file mode 100644 index 00000000..7392f429 --- /dev/null +++ b/apps/irssi/src/core/net-nonblock.c @@ -0,0 +1,252 @@ +/* + 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 + +#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; +} diff --git a/apps/irssi/src/core/net-nonblock.h b/apps/irssi/src/core/net-nonblock.h new file mode 100644 index 00000000..a0e5cddf --- /dev/null +++ b/apps/irssi/src/core/net-nonblock.h @@ -0,0 +1,39 @@ +#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 diff --git a/apps/irssi/src/core/net-sendbuffer.c b/apps/irssi/src/core/net-sendbuffer.c new file mode 100644 index 00000000..04eab80a --- /dev/null +++ b/apps/irssi/src/core/net-sendbuffer.c @@ -0,0 +1,158 @@ +/* + 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) +{ +} diff --git a/apps/irssi/src/core/net-sendbuffer.h b/apps/irssi/src/core/net-sendbuffer.h new file mode 100644 index 00000000..bb6d8e07 --- /dev/null +++ b/apps/irssi/src/core/net-sendbuffer.h @@ -0,0 +1,23 @@ +#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 diff --git a/apps/irssi/src/core/network.c b/apps/irssi/src/core/network.c new file mode 100644 index 00000000..4fc06c05 --- /dev/null +++ b/apps/irssi/src/core/network.c @@ -0,0 +1,597 @@ +/* + 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; +} diff --git a/apps/irssi/src/core/network.h b/apps/irssi/src/core/network.h new file mode 100644 index 00000000..4c25740f --- /dev/null +++ b/apps/irssi/src/core/network.h @@ -0,0 +1,95 @@ +#ifndef __NETWORK_H +#define __NETWORK_H + +#ifdef HAVE_SOCKS_H +#include +#endif + +#include +#ifndef WIN32 +# include +# include +# include +# include +#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 diff --git a/apps/irssi/src/core/nick-rec.h b/apps/irssi/src/core/nick-rec.h new file mode 100644 index 00000000..7dff6f32 --- /dev/null +++ b/apps/irssi/src/core/nick-rec.h @@ -0,0 +1,27 @@ +/* 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 */ diff --git a/apps/irssi/src/core/nicklist.c b/apps/irssi/src/core/nicklist.c new file mode 100644 index 00000000..f9539ff3 --- /dev/null +++ b/apps/irssi/src/core/nicklist.c @@ -0,0 +1,577 @@ +/* + 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"); +} diff --git a/apps/irssi/src/core/nicklist.h b/apps/irssi/src/core/nicklist.h new file mode 100644 index 00000000..a9683500 --- /dev/null +++ b/apps/irssi/src/core/nicklist.h @@ -0,0 +1,60 @@ +#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 diff --git a/apps/irssi/src/core/nickmatch-cache.c b/apps/irssi/src/core/nickmatch-cache.c new file mode 100644 index 00000000..6605a2f4 --- /dev/null +++ b/apps/irssi/src/core/nickmatch-cache.c @@ -0,0 +1,120 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/nickmatch-cache.h b/apps/irssi/src/core/nickmatch-cache.h new file mode 100644 index 00000000..c4140a49 --- /dev/null +++ b/apps/irssi/src/core/nickmatch-cache.h @@ -0,0 +1,26 @@ +#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 diff --git a/apps/irssi/src/core/pidwait.c b/apps/irssi/src/core/pidwait.c new file mode 100644 index 00000000..f54aa34d --- /dev/null +++ b/apps/irssi/src/core/pidwait.c @@ -0,0 +1,77 @@ +/* + 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 + +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); +} diff --git a/apps/irssi/src/core/pidwait.h b/apps/irssi/src/core/pidwait.h new file mode 100644 index 00000000..3f6b84cd --- /dev/null +++ b/apps/irssi/src/core/pidwait.h @@ -0,0 +1,12 @@ +#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 diff --git a/apps/irssi/src/core/queries.c b/apps/irssi/src/core/queries.c new file mode 100644 index 00000000..d1c51352 --- /dev/null +++ b/apps/irssi/src/core/queries.c @@ -0,0 +1,156 @@ +/* + 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"); +} diff --git a/apps/irssi/src/core/queries.h b/apps/irssi/src/core/queries.h new file mode 100644 index 00000000..77ef9c37 --- /dev/null +++ b/apps/irssi/src/core/queries.h @@ -0,0 +1,34 @@ +#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 diff --git a/apps/irssi/src/core/query-rec.h b/apps/irssi/src/core/query-rec.h new file mode 100644 index 00000000..12ef491c --- /dev/null +++ b/apps/irssi/src/core/query-rec.h @@ -0,0 +1,9 @@ +/* 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; diff --git a/apps/irssi/src/core/rawlog.c b/apps/irssi/src/core/rawlog.c new file mode 100644 index 00000000..4e47040c --- /dev/null +++ b/apps/irssi/src/core/rawlog.c @@ -0,0 +1,174 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/rawlog.h b/apps/irssi/src/core/rawlog.h new file mode 100644 index 00000000..9e460a83 --- /dev/null +++ b/apps/irssi/src/core/rawlog.h @@ -0,0 +1,28 @@ +#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 diff --git a/apps/irssi/src/core/server-connect-rec.h b/apps/irssi/src/core/server-connect-rec.h new file mode 100644 index 00000000..f1b3d075 --- /dev/null +++ b/apps/irssi/src/core/server-connect-rec.h @@ -0,0 +1,26 @@ +/* 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; diff --git a/apps/irssi/src/core/server-rec.h b/apps/irssi/src/core/server-rec.h new file mode 100644 index 00000000..7cdc66e5 --- /dev/null +++ b/apps/irssi/src/core/server-rec.h @@ -0,0 +1,72 @@ +/* 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 diff --git a/apps/irssi/src/core/server-setup-rec.h b/apps/irssi/src/core/server-setup-rec.h new file mode 100644 index 00000000..4352bef7 --- /dev/null +++ b/apps/irssi/src/core/server-setup-rec.h @@ -0,0 +1,21 @@ +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; diff --git a/apps/irssi/src/core/servers-reconnect.c b/apps/irssi/src/core/servers-reconnect.c new file mode 100644 index 00000000..a9491f92 --- /dev/null +++ b/apps/irssi/src/core/servers-reconnect.c @@ -0,0 +1,455 @@ +/* + 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 */ +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); +} diff --git a/apps/irssi/src/core/servers-reconnect.h b/apps/irssi/src/core/servers-reconnect.h new file mode 100644 index 00000000..b51486f6 --- /dev/null +++ b/apps/irssi/src/core/servers-reconnect.h @@ -0,0 +1,23 @@ +#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 diff --git a/apps/irssi/src/core/servers-redirect.c b/apps/irssi/src/core/servers-redirect.c new file mode 100644 index 00000000..ca340fc6 --- /dev/null +++ b/apps/irssi/src/core/servers-redirect.c @@ -0,0 +1,359 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/servers-redirect.h b/apps/irssi/src/core/servers-redirect.h new file mode 100644 index 00000000..c9e45ce3 --- /dev/null +++ b/apps/irssi/src/core/servers-redirect.h @@ -0,0 +1,36 @@ +#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 diff --git a/apps/irssi/src/core/servers-setup.c b/apps/irssi/src/core/servers-setup.c new file mode 100644 index 00000000..deaf3cc9 --- /dev/null +++ b/apps/irssi/src/core/servers-setup.c @@ -0,0 +1,532 @@ +/* + 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"); +} diff --git a/apps/irssi/src/core/servers-setup.h b/apps/irssi/src/core/servers-setup.h new file mode 100644 index 00000000..d0807d11 --- /dev/null +++ b/apps/irssi/src/core/servers-setup.h @@ -0,0 +1,46 @@ +#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 diff --git a/apps/irssi/src/core/servers.c b/apps/irssi/src/core/servers.c new file mode 100644 index 00000000..f74e7f58 --- /dev/null +++ b/apps/irssi/src/core/servers.c @@ -0,0 +1,544 @@ +/* + 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"); +} diff --git a/apps/irssi/src/core/servers.h b/apps/irssi/src/core/servers.h new file mode 100644 index 00000000..75e4cbf0 --- /dev/null +++ b/apps/irssi/src/core/servers.h @@ -0,0 +1,66 @@ +#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 diff --git a/apps/irssi/src/core/settings.c b/apps/irssi/src/core/settings.c new file mode 100644 index 00000000..0169b268 --- /dev/null +++ b/apps/irssi/src/core/settings.c @@ -0,0 +1,680 @@ +/* + 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 + +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); +} diff --git a/apps/irssi/src/core/settings.h b/apps/irssi/src/core/settings.h new file mode 100644 index 00000000..cea8c4d0 --- /dev/null +++ b/apps/irssi/src/core/settings.h @@ -0,0 +1,89 @@ +#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 diff --git a/apps/irssi/src/core/signals.c b/apps/irssi/src/core/signals.c new file mode 100644 index 00000000..cb964eb0 --- /dev/null +++ b/apps/irssi/src/core/signals.c @@ -0,0 +1,388 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/signals.h b/apps/irssi/src/core/signals.h new file mode 100644 index 00000000..795f7327 --- /dev/null +++ b/apps/irssi/src/core/signals.h @@ -0,0 +1,51 @@ +#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 diff --git a/apps/irssi/src/core/special-vars.c b/apps/irssi/src/core/special-vars.c new file mode 100644 index 00000000..40372f93 --- /dev/null +++ b/apps/irssi/src/core/special-vars.c @@ -0,0 +1,611 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/special-vars.h b/apps/irssi/src/core/special-vars.h new file mode 100644 index 00000000..af02e121 --- /dev/null +++ b/apps/irssi/src/core/special-vars.h @@ -0,0 +1,33 @@ +#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 diff --git a/apps/irssi/src/core/window-item-def.h b/apps/irssi/src/core/window-item-def.h new file mode 100644 index 00000000..4364c66e --- /dev/null +++ b/apps/irssi/src/core/window-item-def.h @@ -0,0 +1,9 @@ +#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 diff --git a/apps/irssi/src/core/window-item-rec.h b/apps/irssi/src/core/window-item-rec.h new file mode 100644 index 00000000..5c09a5b0 --- /dev/null +++ b/apps/irssi/src/core/window-item-rec.h @@ -0,0 +1,15 @@ +/* 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 diff --git a/apps/irssi/src/core/write-buffer.c b/apps/irssi/src/core/write-buffer.c new file mode 100644 index 00000000..762fc24e --- /dev/null +++ b/apps/irssi/src/core/write-buffer.c @@ -0,0 +1,184 @@ +/* + 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); +} diff --git a/apps/irssi/src/core/write-buffer.h b/apps/irssi/src/core/write-buffer.h new file mode 100644 index 00000000..ef527440 --- /dev/null +++ b/apps/irssi/src/core/write-buffer.h @@ -0,0 +1,10 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/Makefile.am b/apps/irssi/src/fe-common/core/Makefile.am new file mode 100644 index 00000000..9913cfe8 --- /dev/null +++ b/apps/irssi/src/fe-common/core/Makefile.am @@ -0,0 +1,60 @@ +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 diff --git a/apps/irssi/src/fe-common/core/autorun.c b/apps/irssi/src/fe-common/core/autorun.c new file mode 100644 index 00000000..b305b82e --- /dev/null +++ b/apps/irssi/src/fe-common/core/autorun.c @@ -0,0 +1,62 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/chat-completion.c b/apps/irssi/src/fe-common/core/chat-completion.c new file mode 100644 index 00000000..3cbcbf02 --- /dev/null +++ b/apps/irssi/src/fe-common/core/chat-completion.c @@ -0,0 +1,862 @@ +/* + 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" 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); +} diff --git a/apps/irssi/src/fe-common/core/chat-completion.h b/apps/irssi/src/fe-common/core/chat-completion.h new file mode 100644 index 00000000..3cb70ca5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/chat-completion.h @@ -0,0 +1,8 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/command-history.c b/apps/irssi/src/fe-common/core/command-history.c new file mode 100644 index 00000000..cdf4b5a5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/command-history.c @@ -0,0 +1,192 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/command-history.h b/apps/irssi/src/fe-common/core/command-history.h new file mode 100644 index 00000000..9f37f069 --- /dev/null +++ b/apps/irssi/src/fe-common/core/command-history.h @@ -0,0 +1,16 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/completion.c b/apps/irssi/src/fe-common/core/completion.c new file mode 100644 index 00000000..16427610 --- /dev/null +++ b/apps/irssi/src/fe-common/core/completion.c @@ -0,0 +1,681 @@ +/* + 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 " we have to cycle to nick2, etc. + BUT if we start completion with "/msg ", 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); +} + + diff --git a/apps/irssi/src/fe-common/core/completion.h b/apps/irssi/src/fe-common/core/completion.h new file mode 100644 index 00000000..ef0fe06f --- /dev/null +++ b/apps/irssi/src/fe-common/core/completion.h @@ -0,0 +1,16 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/fe-channels.c b/apps/irssi/src/fe-common/core/fe-channels.c new file mode 100644 index 00000000..67557b91 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-channels.c @@ -0,0 +1,611 @@ +/* + 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 ] [-botcmd ] + [] */ +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 */ +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] [ | **] */ +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 [] [] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-channels.h b/apps/irssi/src/fe-common/core/fe-channels.h new file mode 100644 index 00000000..a8aa697e --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-channels.h @@ -0,0 +1,15 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/fe-common-core.c b/apps/irssi/src/fe-common/core/fe-common-core.c new file mode 100644 index 00000000..8cfb3a85 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-common-core.c @@ -0,0 +1,357 @@ +/* + 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 + +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(); +} diff --git a/apps/irssi/src/fe-common/core/fe-common-core.h b/apps/irssi/src/fe-common/core/fe-common-core.h new file mode 100644 index 00000000..1c12047b --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-common-core.h @@ -0,0 +1,8 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/fe-core-commands.c b/apps/irssi/src/fe-common/core/fe-core-commands.c new file mode 100644 index 00000000..7b8f7f9b --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-core-commands.c @@ -0,0 +1,303 @@ +/* + 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 ] [-level ] */ +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 */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-exec.c b/apps/irssi/src/fe-common/core/fe-exec.c new file mode 100644 index 00000000..bc8f4691 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-exec.c @@ -0,0 +1,641 @@ +/* + 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 +#include + +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 */ + 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 | -notice ] + [-name ] + EXEC -out | -window | -msg | -notice | + -close | - + EXEC -in */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-exec.h b/apps/irssi/src/fe-common/core/fe-exec.h new file mode 100644 index 00000000..7f569ece --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-exec.h @@ -0,0 +1,45 @@ +#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 ... */ + 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 diff --git a/apps/irssi/src/fe-common/core/fe-expandos.c b/apps/irssi/src/fe-common/core/fe-expandos.c new file mode 100644 index 00000000..7eb145db --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-expandos.c @@ -0,0 +1,51 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/fe-help.c b/apps/irssi/src/fe-common/core/fe-help.c new file mode 100644 index 00000000..fa90473d --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-help.c @@ -0,0 +1,256 @@ +/* + 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 [] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-ignore-messages.c b/apps/irssi/src/fe-common/core/fe-ignore-messages.c new file mode 100644 index 00000000..770f4a4e --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-ignore-messages.c @@ -0,0 +1,133 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/fe-ignore.c b/apps/irssi/src/fe-common/core/fe-ignore.c new file mode 100644 index 00000000..3523d527 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-ignore.c @@ -0,0 +1,235 @@ +/* + 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 ] [-except] [-replies] + [-channels ] [-time ] [] + IGNORE [-regexp | -full] [-pattern ] [-except] [-replies] + [-time ] [] */ +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 | */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-log.c b/apps/irssi/src/fe-common/core/fe-log.c new file mode 100644 index 00000000..60d96327 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-log.c @@ -0,0 +1,669 @@ +/* + 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] [-] + [-targets ] [] */ +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 = 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 | */ +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 | */ +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 | */ +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 [] */ +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. or irc.log.Window */ + 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 */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-messages.c b/apps/irssi/src/fe-common/core/fe-messages.c new file mode 100644 index 00000000..fa5e88af --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-messages.c @@ -0,0 +1,684 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/fe-messages.h b/apps/irssi/src/fe-common/core/fe-messages.h new file mode 100644 index 00000000..afe7644d --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-messages.h @@ -0,0 +1,10 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/fe-modules.c b/apps/irssi/src/fe-common/core/fe-modules.c new file mode 100644 index 00000000..e351dc71 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-modules.c @@ -0,0 +1,161 @@ +/* + 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 */ +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 */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-queries.c b/apps/irssi/src/fe-common/core/fe-queries.c new file mode 100644 index 00000000..1924464a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-queries.c @@ -0,0 +1,380 @@ +/* + 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 [] */ +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] [-] [] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-queries.h b/apps/irssi/src/fe-common/core/fe-queries.h new file mode 100644 index 00000000..6db9cb44 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-queries.h @@ -0,0 +1,13 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/fe-server.c b/apps/irssi/src/fe-common/core/fe-server.c new file mode 100644 index 00000000..f2327c59 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-server.c @@ -0,0 +1,363 @@ +/* + 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
[] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-settings.c b/apps/irssi/src/fe-common/core/fe-settings.c new file mode 100644 index 00000000..90262ee0 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-settings.c @@ -0,0 +1,330 @@ +/* + 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] [ []] */ +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 [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 [[-] []] */ +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 */ +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 [] */ +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 [] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/fe-windows.c b/apps/irssi/src/fe-common/core/fe-windows.c new file mode 100644 index 00000000..3c48261a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-windows.c @@ -0,0 +1,583 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/fe-windows.h b/apps/irssi/src/fe-common/core/fe-windows.h new file mode 100644 index 00000000..c9ffe69a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-windows.h @@ -0,0 +1,96 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/formats.c b/apps/irssi/src/fe-common/core/formats.c new file mode 100644 index 00000000..b933c068 --- /dev/null +++ b/apps/irssi/src/fe-common/core/formats.c @@ -0,0 +1,941 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/formats.h b/apps/irssi/src/fe-common/core/formats.h new file mode 100644 index 00000000..86c53e69 --- /dev/null +++ b/apps/irssi/src/fe-common/core/formats.h @@ -0,0 +1,115 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/hilight-text.c b/apps/irssi/src/fe-common/core/hilight-text.c new file mode 100644 index 00000000..197957f6 --- /dev/null +++ b/apps/irssi/src/fe-common/core/hilight-text.c @@ -0,0 +1,697 @@ +/* + 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 ] [-actcolor ] [-level ] + [-channels ] */ +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 | */ +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); +} diff --git a/apps/irssi/src/fe-common/core/hilight-text.h b/apps/irssi/src/fe-common/core/hilight-text.h new file mode 100644 index 00000000..92093bb1 --- /dev/null +++ b/apps/irssi/src/fe-common/core/hilight-text.h @@ -0,0 +1,44 @@ +#ifndef __HILIGHT_TEXT_H +#define __HILIGHT_TEXT_H + +#ifdef HAVE_REGEX_H +# include +#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 diff --git a/apps/irssi/src/fe-common/core/keyboard.c b/apps/irssi/src/fe-common/core/keyboard.c new file mode 100644 index 00000000..0b7ac714 --- /dev/null +++ b/apps/irssi/src/fe-common/core/keyboard.c @@ -0,0 +1,820 @@ +/* + 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] [ [ []]] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/keyboard.h b/apps/irssi/src/fe-common/core/keyboard.h new file mode 100644 index 00000000..9f2652e0 --- /dev/null +++ b/apps/irssi/src/fe-common/core/keyboard.h @@ -0,0 +1,55 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/module-formats.c b/apps/irssi/src/fe-common/core/module-formats.c new file mode 100644 index 00000000..9ccdd10a --- /dev/null +++ b/apps/irssi/src/fe-common/core/module-formats.c @@ -0,0 +1,228 @@ +/* + 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 } +}; diff --git a/apps/irssi/src/fe-common/core/module-formats.h b/apps/irssi/src/fe-common/core/module-formats.h new file mode 100644 index 00000000..e0f31f9a --- /dev/null +++ b/apps/irssi/src/fe-common/core/module-formats.h @@ -0,0 +1,194 @@ +#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[]; diff --git a/apps/irssi/src/fe-common/core/module.h b/apps/irssi/src/fe-common/core/module.h new file mode 100644 index 00000000..203e3a30 --- /dev/null +++ b/apps/irssi/src/fe-common/core/module.h @@ -0,0 +1,34 @@ +#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; diff --git a/apps/irssi/src/fe-common/core/printtext.c b/apps/irssi/src/fe-common/core/printtext.c new file mode 100644 index 00000000..38de158d --- /dev/null +++ b/apps/irssi/src/fe-common/core/printtext.c @@ -0,0 +1,456 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/printtext.h b/apps/irssi/src/fe-common/core/printtext.h new file mode 100644 index 00000000..14f46564 --- /dev/null +++ b/apps/irssi/src/fe-common/core/printtext.h @@ -0,0 +1,93 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/themes.c b/apps/irssi/src/fe-common/core/themes.c new file mode 100644 index 00000000..de9b6dee --- /dev/null +++ b/apps/irssi/src/fe-common/core/themes.c @@ -0,0 +1,1184 @@ +/* + 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 $ .. 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] [] [ []] */ +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); +} diff --git a/apps/irssi/src/fe-common/core/themes.h b/apps/irssi/src/fe-common/core/themes.h new file mode 100644 index 00000000..96f23400 --- /dev/null +++ b/apps/irssi/src/fe-common/core/themes.h @@ -0,0 +1,65 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/translation.c b/apps/irssi/src/fe-common/core/translation.c new file mode 100644 index 00000000..2713cc73 --- /dev/null +++ b/apps/irssi/src/fe-common/core/translation.c @@ -0,0 +1,122 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/translation.h b/apps/irssi/src/fe-common/core/translation.h new file mode 100644 index 00000000..48b2c3dc --- /dev/null +++ b/apps/irssi/src/fe-common/core/translation.h @@ -0,0 +1,12 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/window-activity.c b/apps/irssi/src/fe-common/core/window-activity.c new file mode 100644 index 00000000..3e4cd80c --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-activity.c @@ -0,0 +1,159 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/window-commands.c b/apps/irssi/src/fe-common/core/window-commands.c new file mode 100644 index 00000000..17cf65e5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-commands.c @@ -0,0 +1,572 @@ +/* + 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 [ [] */ +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 */ +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|| */ +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 [] */ +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] */ +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 */ +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 | */ +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] */ +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 */ +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 |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 */ +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 */ +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); +} diff --git a/apps/irssi/src/fe-common/core/window-items.c b/apps/irssi/src/fe-common/core/window-items.c new file mode 100644 index 00000000..d7726521 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-items.c @@ -0,0 +1,326 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/window-items.h b/apps/irssi/src/fe-common/core/window-items.h new file mode 100644 index 00000000..f8db3c39 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-items.h @@ -0,0 +1,34 @@ +#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 diff --git a/apps/irssi/src/fe-common/core/windows-layout.c b/apps/irssi/src/fe-common/core/windows-layout.c new file mode 100644 index 00000000..814127fb --- /dev/null +++ b/apps/irssi/src/fe-common/core/windows-layout.c @@ -0,0 +1,196 @@ +/* + 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); +} diff --git a/apps/irssi/src/fe-common/core/windows-layout.h b/apps/irssi/src/fe-common/core/windows-layout.h new file mode 100644 index 00000000..d33fda52 --- /dev/null +++ b/apps/irssi/src/fe-common/core/windows-layout.h @@ -0,0 +1,11 @@ +#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 diff --git a/apps/irssi/src/silc/core/Makefile.am b/apps/irssi/src/silc/core/Makefile.am index f794ae8f..b25d7430 100644 --- a/apps/irssi/src/silc/core/Makefile.am +++ b/apps/irssi/src/silc/core/Makefile.am @@ -1,27 +1,30 @@ +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 \ @@ -36,3 +39,20 @@ noinst_HEADERS = \ 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 .. diff --git a/apps/irssi/src/silc/core/clientutil.c b/apps/irssi/src/silc/core/clientutil.c new file mode 100644 index 00000000..a9b0434e --- /dev/null +++ b/apps/irssi/src/silc/core/clientutil.c @@ -0,0 +1,157 @@ +/* + 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; +} diff --git a/apps/irssi/src/silc/core/clientutil.h b/apps/irssi/src/silc/core/clientutil.h new file mode 100644 index 00000000..4af7425d --- /dev/null +++ b/apps/irssi/src/silc/core/clientutil.h @@ -0,0 +1,8 @@ +#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 diff --git a/apps/irssi/src/silc/core/module.h b/apps/irssi/src/silc/core/module.h new file mode 100644 index 00000000..aadd8161 --- /dev/null +++ b/apps/irssi/src/silc/core/module.h @@ -0,0 +1,11 @@ +#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")) diff --git a/apps/irssi/src/silc/core/silc-channels.c b/apps/irssi/src/silc/core/silc-channels.c index 014ed1d0..b1f82f86 100644 --- a/apps/irssi/src/silc/core/silc-channels.c +++ b/apps/irssi/src/silc/core/silc-channels.c @@ -225,70 +225,68 @@ static void event_nick(SILC_SERVER_REC *server, va_list va) 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, diff --git a/apps/irssi/src/silc/core/silc-channels.h b/apps/irssi/src/silc/core/silc-channels.h new file mode 100644 index 00000000..de6db8ba --- /dev/null +++ b/apps/irssi/src/silc/core/silc-channels.h @@ -0,0 +1,39 @@ +#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 diff --git a/apps/irssi/src/silc/core/silc-core.c b/apps/irssi/src/silc/core/silc-core.c index 43198042..7afa379b 100644 --- a/apps/irssi/src/silc/core/silc-core.c +++ b/apps/irssi/src/silc/core/silc-core.c @@ -1,23 +1,3 @@ -/* - - silc-core.c - - Author: Pekka Riikonen - - 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" @@ -52,135 +32,110 @@ extern SilcClientOperations ops; 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; @@ -190,252 +145,135 @@ silc_disconnect(SilcClient client, SilcClientConnection conn) 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. */ @@ -448,7 +286,7 @@ static void silc_client_create_key_pair(char *pkcs_name, int bits, SilcPKCS pkcs; SilcRng rng; unsigned char *key; - uint32 key_len; + unsigned int key_len; rng = silc_rng_alloc(); silc_rng_init(rng); diff --git a/apps/irssi/src/silc/core/silc-core.h b/apps/irssi/src/silc/core/silc-core.h new file mode 100644 index 00000000..968e15c1 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-core.h @@ -0,0 +1,8 @@ +#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 diff --git a/apps/irssi/src/silc/core/silc-nicklist.c b/apps/irssi/src/silc/core/silc-nicklist.c new file mode 100644 index 00000000..bef2521c --- /dev/null +++ b/apps/irssi/src/silc/core/silc-nicklist.c @@ -0,0 +1,129 @@ +/* + 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); +} diff --git a/apps/irssi/src/silc/core/silc-nicklist.h b/apps/irssi/src/silc/core/silc-nicklist.h new file mode 100644 index 00000000..8d690a2f --- /dev/null +++ b/apps/irssi/src/silc/core/silc-nicklist.h @@ -0,0 +1,25 @@ +#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 diff --git a/apps/irssi/src/silc/core/silc-queries.c b/apps/irssi/src/silc/core/silc-queries.c new file mode 100644 index 00000000..0d3bbe07 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-queries.c @@ -0,0 +1,49 @@ +/* + 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) +{ +} diff --git a/apps/irssi/src/silc/core/silc-queries.h b/apps/irssi/src/silc/core/silc-queries.h new file mode 100644 index 00000000..bd024f6e --- /dev/null +++ b/apps/irssi/src/silc/core/silc-queries.h @@ -0,0 +1,24 @@ +#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 diff --git a/apps/irssi/src/silc/core/silc-servers-reconnect.c b/apps/irssi/src/silc/core/silc-servers-reconnect.c new file mode 100644 index 00000000..79a7b699 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-servers-reconnect.c @@ -0,0 +1,60 @@ +/* + 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); +} diff --git a/apps/irssi/src/silc/core/silc-servers.c b/apps/irssi/src/silc/core/silc-servers.c index b2abf157..21d56214 100644 --- a/apps/irssi/src/silc/core/silc-servers.c +++ b/apps/irssi/src/silc/core/silc-servers.c @@ -46,14 +46,14 @@ void silc_servers_reconnect_deinit(void); 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 { @@ -64,38 +64,38 @@ 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) @@ -155,17 +155,17 @@ static void sig_connected(SILC_SERVER_REC *server) 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) @@ -219,9 +219,9 @@ char *silc_server_get_channels(SILC_SERVER_REC *server) 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; diff --git a/apps/irssi/src/silc/core/silc-servers.h b/apps/irssi/src/silc/core/silc-servers.h new file mode 100644 index 00000000..8ce42c3f --- /dev/null +++ b/apps/irssi/src/silc/core/silc-servers.h @@ -0,0 +1,59 @@ +#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