/* X-Chat
* Copyright (C) 2002 Peter Zelezny.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include "hexchat.h"
#include "fe.h"
#include "util.h"
#include "outbound.h"
#include "cfgfiles.h"
#include "ignore.h"
#include "server.h"
#include "servlist.h"
#include "modes.h"
#include "notify.h"
#include "text.h"
#define PLUGIN_C
typedef struct session hexchat_context;
#include "hexchat-plugin.h"
#include "plugin.h"
#include "typedef.h"
#include "hexchatc.h"
/* the USE_PLUGIN define only removes libdl stuff */
#ifdef USE_PLUGIN
#include <gmodule.h>
#endif
#define DEBUG(x) {x;}
/* crafted to be an even 32 bytes */
struct _hexchat_hook
{
hexchat_plugin *pl; /* the plugin to which it belongs */
char *name; /* "xdcc" */
void *callback; /* pointer to xdcc_callback */
char *help_text; /* help_text for commands only */
void *userdata; /* passed to the callback */
int tag; /* for timers & FDs only */
int type; /* HOOK_* */
int pri; /* fd */ /* priority / fd for HOOK_FD only */
};
struct _hexchat_list
{
int type; /* LIST_* */
GSList *pos; /* current pos */
GSList *next; /* next pos */
GSList *head; /* for LIST_USERS only */
struct notify_per_server *notifyps; /* notify_per_server * */
};
typedef int (hexchat_cmd_cb) (char *word[], char *word_eol[], void *user_data);
typedef int (hexchat_serv_cb) (char *word[], char *word_eol[], void *user_data);
typedef int (hexchat_print_cb) (char *word[], void *user_data);
typedef int (hexchat_serv_attrs_cb) (char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *user_data);
typedef int (hexchat_print_attrs_cb) (char *word[], hexchat_event_attrs *attrs, void *user_data);
typedef int (hexchat_fd_cb) (int fd, int flags, void *user_data);
typedef int (hexchat_timer_cb) (void *user_data);
typedef int (hexchat_init_func) (hexchat_plugin *, char **, char **, char **, char *);
typedef int (hexchat_deinit_func) (hexchat_plugin *);
enum
{
LIST_CHANNELS,
LIST_DCC,
LIST_IGNORE,
LIST_NOTIFY,
LIST_USERS
};
/* We use binary flags here because it makes it possible for plugin_hook_find()
* to match several types of hooks. This is used so that plugin_hook_run()
* match both HOOK_SERVER and HOOK_SERVER_ATTRS hooks when plugin_emit_server()
* is called.
*/
enum
{
HOOK_COMMAND = 1 << 0, /* /command */
HOOK_SERVER = 1 << 1, /* PRIVMSG, NOTICE, numerics */
HOOK_SERVER_ATTRS = 1 << 2, /* same as above, with attributes */
HOOK_PRINT = 1 << 3, /* All print events */
HOOK_PRINT_ATTRS = 1 << 4, /* same as above, with attributes */
HOOK_TIMER = 1 << 5, /* timeouts */
HOOK_FD = 1 << 6, /* sockets & fds */
HOOK_DELETED = 1 << 7 /* marked for deletion */
};
enum
{
CHANNEL_FLAG_CONNECTED = 1 << 0,
CHANNEL_FLAG_CONNECING = 1 << 1,
CHANNEL_FLAG_AWAY = 1 << 2,
CHANNEL_FLAG_END_OF_MOTD = 1 << 3,
CHANNEL_FLAG_HAS_WHOX = 1 << 4,
CHANNEL_FLAG_HAS_IDMSG = 1 << 5,
CHANNEL_FLAG_HIDE_JOIN_PARTS = 1 << 6,
CHANNEL_FLAG_HIDE_JOIN_PARTS_UNSET = 1 << 7,
CHANNEL_FLAG_BEEP = 1 << 8,
CHANNEL_FLAG_BEEP_UNSET = 1 << 9,
CHANNEL_FLAG_UNUSED = 1 << 10,
CHANNEL_FLAG_LOGGING = 1 << 11,
CHANNEL_FLAG_LOGGING_UNSET = 1 << 12,
CHANNEL_FLAG_SCROLLBACK = 1 << 13,
CHANNEL_FLAG_SCROLLBACK_UNSET = 1 << 14,
CHANNEL_FLAG_STRIP_COLORS = 1 << 15,
CHANNEL_FLAG_STRIP_COLORS_UNSET = 1 << 16,
CHANNEL_FLAG_TRAY = 1 << 17,
CHANNEL_FLAG_TRAY_UNSET = 1 << 18,
CHANNEL_FLAG_TASKBAR = 1 << 19,
CHANNEL_FLAG_TASKBAR_UNSET = 1 << 20,
CHANNEL_FLAG_BALLOON = 1 << 21,
CHANNEL_FLAG_BALLOON_UNSET = 1 << 22,
CHANNEL_FLAG_COUNT = 23
};
GSList *plugin_list = NULL; /* export for plugingui.c */
static GSList *hook_list = NULL;
extern const struct prefs vars[]; /* cfgfiles.c */
/* unload a plugin and remove it from our linked list */
static int
plugin_free (hexchat_plugin *pl, int do_deinit, int allow_refuse)
{
GSList *list, *next;
hexchat_hook *hook;
hexchat_deinit_func *deinit_func;
/* fake plugin added by hexchat_plugingui_add() */
if (pl->fake)
goto xit;
/* run the plugin's deinit routine, if any */
if (do_deinit && pl->deinit_callback != NULL)
{
deinit_func = pl->deinit_callback;
if (!deinit_func (pl) && allow_refuse)
return FALSE;
}
/* remove all of this plugin's hooks */
list = hook_list;
while (list)
{
hook = list->data;
next = list->next;
if (hook->pl == pl)
hexchat_unhook (NULL, hook);
list = next;
}
#ifdef USE_PLUGIN
if (pl->handle)
g_module_close (pl->handle);
#endif
xit:
if (pl->free_strings)
{
g_free (pl->name);
g_free (pl->desc);
g_free (pl->version);
}
g_free ((char *)pl->filename);
g_free (pl);
plugin_list = g_slist_remove (plugin_list, pl);
#ifdef USE_PLUGIN
fe_pluginlist_update ();
#endif
return TRUE;
}
static hexchat_plugin *
plugin_list_add (hexchat_context *ctx, char *filename, const char *name,
const char *desc, const char *version, void *handle,
void *deinit_func, int fake, int free_strings)
{
hexchat_plugin *pl;
pl = g_new (hexchat_plugin, 1);
pl->handle = handle;
pl->filename = filename;
pl->context = ctx;
pl->name = (char *)name;
pl->desc = (char *)desc;
pl->version = (char *)version;
pl->deinit_callback = deinit_func;
pl->fake = fake;
pl->free_strings = free_strings; /* free() name,desc,version? */
plugin_list = g_slist_prepend (plugin_list, pl);
return pl;
}
#ifndef WIN32
static void *
hexchat_dummy (hexchat_plugin *ph)
{
return NULL;
}
#else
static int
hexchat_read_fd (hexchat_plugin *ph, GIOChannel *source, char *buf, int *len)
{
GError *error = NULL;
g_io_channel_set_buffered (source, FALSE);
g_io_channel_set_encoding (source, NULL, &error);
if (g_io_channel_read_chars (source, buf, *len, (gsize*)len, &error) == G_IO_STATUS_NORMAL)
{
return 0;
}
else
{
return -1;
}
}
#endif
/* Load a static plugin */
void
plugin_add (session *sess, char *filename, void *handle, void *init_func,
void *deinit_func, char *arg, int fake)
{
hexchat_plugin *pl;
char *file;
file = g_strdup (filename);
pl = plugin_list_add (sess, file, file, NULL, NULL, handle, deinit_func,
fake, FALSE);
if (!fake)
{
/* win32 uses these because it doesn't have --export-dynamic! */
pl->hexchat_hook_command = hexchat_hook_command;
pl->hexchat_hook_server = hexchat_hook_server;
pl->hexchat_hook_print = hexchat_hook_print;
pl->hexchat_hook_timer = hexchat_hook_timer;
pl->hexchat_hook_fd = hexchat_hook_fd;
pl->hexchat_unhook = hexchat_unhook;
pl->hexchat_print = hexchat_print;
pl->hexchat_printf = hexchat_printf;
pl->hexchat_command = hexchat_command;
pl->hexchat_commandf = hexchat_commandf;
pl->hexchat_nickcmp = hexchat_nickcmp;
pl->hexchat_set_context = hexchat_set_context;
pl->hexchat_find_context = hexchat_find_context;
pl->hexchat_get_context = hexchat_get_context;
pl->hexchat_get_info = hexchat_get_info;
pl->hexchat_get_prefs = hexchat_get_prefs;
pl->hexchat_list_get = hexchat_list_get;
pl->hexchat_list_free = hexchat_list_free;
pl->hexchat_list_fields = hexchat_list_fields;
pl->hexchat_list_str = hexchat_list_str;
pl->hexchat_list_next = hexchat_list_next;
pl->hexchat_list_int = hexchat_list_int;
pl->hexchat_plugingui_add = hexchat_plugingui_add;
pl->hexchat_plugingui_remove = hexchat_plugingui_remove;
pl->hexchat_emit_print = hexchat_emit_print;
#ifdef WIN32
pl->hexchat_read_fd = (void *) hexchat_read_fd;
#else
pl->hexchat_read_fd = hexchat_dummy;
#endif
pl->hexchat_list_time = hexchat_list_time;
pl->hexchat_gettext = hexchat_gettext;
pl->hexchat_send_modes = hexchat_send_modes;
pl->hexchat_strip = hexchat_strip;
pl->hexchat_free = hexchat_free;
pl->hexchat_pluginpref_set_str = hexchat_pluginpref_set_str;
pl->hexchat_pluginpref_get_str = hexchat_pluginpref_get_str;
pl->hexchat_pluginpref_set_int = hexchat_pluginpref_set_int;
pl->hexchat_pluginpref_get_int = hexchat_pluginpref_get_int;
pl->hexchat_pluginpref_delete = hexchat_pluginpref_delete;
pl->hexchat_pluginpref_list = hexchat_pluginpref_list;
pl->hexchat_hook_server_attrs = hexchat_hook_server_attrs;
pl->hexchat_hook_print_attrs = hexchat_hook_print_attrs;
pl->hexchat_emit_print_attrs = hexchat_emit_print_attrs;
pl->hexchat_event_attrs_create = hexchat_event_attrs_create;
pl->hexchat_event_attrs_free = hexchat_event_attrs_free;
/* run hexchat_plugin_init, if it returns 0, close the plugin */
if (((hexchat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
{
plugin_free (pl, FALSE, FALSE);
return;
}
}
#ifdef USE_PLUGIN
fe_pluginlist_update ();
#endif
}
/* kill any plugin by the given (file) name (used by /unload) */
int
plugin_kill (char *name, int by_filename)
{
GSList *list;
hexchat_plugin *pl;
list = plugin_list;
while (list)
{
pl = list->data;
/* static-plugins (plugin-timer.c) have a NULL filename */
if ((by_filename && pl->filename && g_ascii_strcasecmp (name, pl->filename) == 0) ||
(by_filename && pl->filename && g_ascii_strcasecmp (name, file_part (pl->filename)) == 0) ||
(!by_filename && g_ascii_strcasecmp (name, pl->name) == 0))
{
/* statically linked plugins have a NULL filename */
if (pl->filename != NULL && !pl->fake)
{
if (plugin_free (pl, TRUE, TRUE))
return 1;
return 2;
}
}
list = list->next;
}
return 0;
}
/* kill all running plugins (at shutdown) */
void
plugin_kill_all (void)
{
GSList *list, *next;
hexchat_plugin *pl;
list = plugin_list;
while (list)
{
pl = list->data;
next = list->next;
if (!pl->fake)
plugin_free (list->data, TRUE, FALSE);
list = next;
}
}
#if defined(USE_PLUGIN) || defined(WIN32)
/* used for loading plugins, and in fe-gtk/notifications/notification-windows.c */
GModule *
module_load (char *filename)
{
void *handle;
char *filepart;
char *pluginpath;
/* get the filename without path */
filepart = file_part (filename);
/* load the plugin */
if (!g_ascii_strcasecmp (filepart, filename))
{
/* no path specified, it's just the filename, try to load from config dir */
pluginpath = g_build_filename (get_xdir (), "addons", filename, NULL);
handle = g_module_open (pluginpath, 0);
g_free (pluginpath);
}
else
{
/* try to load with absolute path */
handle = g_module_open (filename, 0);
}
return handle;
}
#endif
#ifdef USE_PLUGIN
/* load a plugin from a filename. Returns: NULL-success or an error string */
char *
plugin_load (session *sess, char *filename, char *arg)
{
GModule *handle = module_load (filename);
hexchat_init_func *init_func;
hexchat_deinit_func *deinit_func;
if (handle == NULL)
return (char *)g_module_error ();
/* find the init routine hexchat_plugin_init */
if (!g_module_symbol (handle, "hexchat_plugin_init", (gpointer *)&init_func))
{
g_module_close (handle);
return _("No hexchat_plugin_init symbol; is this really a HexChat plugin?");
}
/* find the plugin's deinit routine, if any */
if (!g_module_symbol (handle, "hexchat_plugin_deinit", (gpointer *)&deinit_func))
deinit_func = NULL;
/* add it to our linked list */
plugin_add (sess, filename, handle, init_func, deinit_func, arg, FALSE);
return NULL;
}
static session *ps;
static void
plugin_auto_load_cb (char *filename)
{
char *pMsg;
pMsg = plugin_load (ps, filename, NULL);
if (pMsg)
{
PrintTextf (ps, "AutoLoad failed for: %s\n", filename);
PrintText (ps, pMsg);
}
}
static const char *
plugin_get_libdir (void)
{
const char *libdir;
libdir = g_getenv ("HEXCHAT_LIBDIR");
if (libdir && *libdir)
return libdir;
else
return HEXCHATLIBDIR;
}
void
plugin_auto_load (session *sess)
{
const char *lib_dir;
char *sub_dir;
ps = sess;
lib_dir = plugin_get_libdir ();
sub_dir = g_build_filename (get_xdir (), "addons", NULL);
#ifdef WIN32
/* a long list of bundled plugins that should be loaded automatically,
* user plugins should go to <config>, leave Program Files alone! */
for_files (lib_dir, "hcchecksum.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcexec.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb);
for_files(lib_dir, "hclua.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcupd.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcwinamp.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcsysinfo.dll", plugin_auto_load_cb);
#else
for_files (lib_dir, "*."PLUGIN_SUFFIX, plugin_auto_load_cb);
#endif
for_files (sub_dir, "*."PLUGIN_SUFFIX, plugin_auto_load_cb);
g_free (sub_dir);
}
int
plugin_reload (session *sess, char *name, int by_filename)
{
GSList *list;
char *filename;
char *ret;
hexchat_plugin *pl;
list = plugin_list;
while (list)
{
pl = list->data;
/* static-plugins (plugin-timer.c) have a NULL filename */
if ((by_filename && pl->filename && g_ascii_strcasecmp (name, pl->filename) == 0) ||
(by_filename && pl->filename && g_ascii_strcasecmp (name, file_part (pl->filename)) == 0) ||
(!by_filename && g_ascii_strcasecmp (name, pl->name) == 0))
{
/* statically linked plugins have a NULL filename */
if (pl->filename != NULL && !pl->fake)
{
filename = g_strdup (pl->filename);
plugin_free (pl, TRUE, FALSE);
ret = plugin_load (sess, filename, NULL);
g_free (filename);
if (ret == NULL)
return 1;
else
return 0;
}
else
return 2;
}
list = list->next;
}
return 0;
}
#endif
static GSList *
plugin_hook_find (GSList *list, int type, char *name)
{
hexchat_hook *hook;
while (list)
{
hook = list->data;
if (hook && (hook->type & type))
{
if (g_ascii_strcasecmp (hook->name, name) == 0)
return list;
if ((type & HOOK_SERVER)
&& g_ascii_strcasecmp (hook->name, "RAW LINE") == 0)
return list;
}
list = list->next;
}
return NULL;
}
/* check for plugin hooks and run them */
static int
plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
hexchat_event_attrs *attrs, int type, int mask)
{
/* fix segfault https://github.com/hexchat/hexchat/issues/2265 */
static int depth = 0;
GSList *list, *next;
hexchat_hook *hook;
int ret, eat = 0;
depth++;
list = hook_list;
while (1)
{
list = plugin_hook_find (list, type, name);
if (!list)
goto xit;
hook = list->data;
next = list->next;
hook->pl->context = sess;
/* run the plugin's callback function */
switch (hook->type)
{
case HOOK_COMMAND:
ret = ((hexchat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata);
break;
case HOOK_PRINT_ATTRS:
ret = ((hexchat_print_attrs_cb *)hook->callback) (word, attrs, hook->userdata);
break;
case HOOK_SERVER:
ret = ((hexchat_serv_cb *)hook->callback) (word, word_eol, hook->userdata);
break;
case HOOK_SERVER_ATTRS:
ret = ((hexchat_serv_attrs_cb *)hook->callback) (word, word_eol, attrs, hook->userdata);
break;
default: /*case HOOK_PRINT:*/
ret = ((hexchat_print_cb *)hook->callback) (word, hook->userdata);
break;
}
if ((ret & mask) != ret) {
g_critical("plugin tried to eat cleanup hooks");
}
ret &= mask;
if ((ret & HEXCHAT_EAT_HEXCHAT) && (ret & HEXCHAT_EAT_PLUGIN))
{
eat = 1;
goto xit;
}
if (ret & HEXCHAT_EAT_PLUGIN)
goto xit; /* stop running plugins */
if (ret & HEXCHAT_EAT_HEXCHAT)
eat = 1; /* eventually we'll return 1, but continue running plugins */
list = next;
}
xit:
depth--;
if (!depth)
{
/* really remove deleted hooks now */
list = hook_list;
while (list)
{
hook = list->data;
next = list->next;
if (!hook || hook->type == HOOK_DELETED)
{
hook_list = g_slist_remove (hook_list, hook);
g_free (hook);
}
list = next;
}
}
return eat;
}
/* execute a plugged in command. Called from outbound.c */
int
plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[])
{
return plugin_hook_run (sess, name, word, word_eol, NULL, HOOK_COMMAND, -1);
}
hexchat_event_attrs *
hexchat_event_attrs_create (hexchat_plugin *ph)
{
return g_new0 (hexchat_event_attrs, 1);
}
void
hexchat_event_attrs_free (hexchat_plugin *ph, hexchat_event_attrs *attrs)
{
g_free (attrs);
}
/* got a server PRIVMSG, NOTICE, numeric etc... */
int
plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
time_t server_time)
{
hexchat_event_attrs attrs;
attrs.server_time_utc = server_time;
return plugin_hook_run (sess, name, word, word_eol, &attrs,
HOOK_SERVER | HOOK_SERVER_ATTRS, -1);
}
/* see if any plugins are interested in this print event */
int
plugin_emit_print (session *sess, char *word[], time_t server_time)
{
hexchat_event_attrs attrs;
attrs.server_time_utc = server_time;
return plugin_hook_run (sess, word[0], word, NULL, &attrs,
HOOK_PRINT | HOOK_PRINT_ATTRS, -1);
}
/* used by plugin_emit_dummy_print to fix some UB */
static void
check_and_invalidate(void *plug_, void *killsess_)
{
hexchat_plugin *plug = plug_;
session *killsess = killsess_;
if (plug->context == killsess)
{
plug->context = NULL;
}
}
int
plugin_emit_dummy_print (session *sess, char *name, int mask)
{
char *word[PDIWORDS];
int i;
word[0] = name;
for (i = 1; i < PDIWORDS; i++)
word[i] = "\000";
i = plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT, mask);
/* shoehorned fix for Undefined Behaviour */
/* see https://stackoverflow.com/q/52628773/3691554 */
/* this needs to be done on the hexchat side */
if (strcmp(name, "Close Context") == 0) {
g_slist_foreach(plugin_list, &check_and_invalidate, sess);
}
return i;
}
int
plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key)
{
char *word[PDIWORDS];
char keyval_str[16];
char state_str[16];
char len_str[16];
char key_str[7];
int i, len;
if (!hook_list)
return 0;
sprintf (keyval_str, "%u", keyval);
sprintf (state_str, "%u", state);
if (!key)
len = 0;
else
len = g_unichar_to_utf8 (key, key_str);
key_str[len] = '\0';
sprintf (len_str, "%d", len);
word[0] = "Key Press";
word[1] = keyval_str;
word[2] = state_str;
word[3] = key_str;
word[4] = len_str;
for (i = 5; i < PDIWORDS; i++)
word[i] = "\000";
return plugin_hook_run (sess, word[0], word, NULL, NULL, HOOK_PRINT, -1);
}
static int
plugin_timeout_cb (hexchat_hook *hook)
{
int ret;
/* timer_cb's context starts as front-most-tab */
hook->pl->context = current_sess;
/* call the plugin's timeout function */
ret = ((hexchat_timer_cb *)hook->callback) (hook->userdata);
/* the callback might have already unhooked it! */
if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
return 0;
if (ret == 0)
{
hook->tag = 0; /* avoid fe_timeout_remove, returning 0 is enough! */
hexchat_unhook (hook->pl, hook);
}
return ret;
}
/* insert a hook into hook_list according to its priority */
static void
plugin_insert_hook (hexchat_hook *new_hook)
{
GSList *list;
hexchat_hook *hook;
int new_hook_type;
switch (new_hook->type)
{
case HOOK_PRINT:
case HOOK_PRINT_ATTRS:
new_hook_type = HOOK_PRINT | HOOK_PRINT_ATTRS;
break;
case HOOK_SERVER:
case HOOK_SERVER_ATTRS:
new_hook_type = HOOK_SERVER | HOOK_PRINT_ATTRS;
break;
default:
new_hook_type = new_hook->type;
}
list = hook_list;
while (list)
{
hook = list->data;
if (hook && (hook->type & new_hook_type) && hook->pri <= new_hook->pri)
{
hook_list = g_slist_insert_before (hook_list, list, new_hook);
return;
}
list = list->next;
}
hook_list = g_slist_append (hook_list, new_hook);
}
static gboolean
plugin_fd_cb (GIOChannel *source, GIOCondition condition, hexchat_hook *hook)
{
int flags = 0, ret;
typedef int (hexchat_fd_cb2) (int fd, int flags, void *user_data, GIOChannel *);
if (condition & G_IO_IN)
flags |= HEXCHAT_FD_READ;
if (condition & G_IO_OUT)
flags |= HEXCHAT_FD_WRITE;
if (condition & G_IO_PRI)
flags |= HEXCHAT_FD_EXCEPTION;
ret = ((hexchat_fd_cb2 *)hook->callback) (hook->pri, flags, hook->userdata, source);
/* the callback might have already unhooked it! */
if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
return 0;
if (ret == 0)
{
hook->tag = 0; /* avoid fe_input_remove, returning 0 is enough! */
hexchat_unhook (hook->pl, hook);
}
return ret;
}
/* allocate and add a hook to our list. Used for all 4 types */
static hexchat_hook *
plugin_add_hook (hexchat_plugin *pl, int type, int pri, const char *name,
const char *help_text, void *callb, int timeout, void *userdata)
{
hexchat_hook *hook;
hook = g_new0 (hexchat_hook, 1);
hook->type = type;
hook->pri = pri;
hook->name = g_strdup (name);
hook->help_text = g_strdup (help_text);
hook->callback = callb;
hook->pl = pl;
hook->userdata = userdata;
/* insert it into the linked list */
plugin_insert_hook (hook);
if (type == HOOK_TIMER)
hook->tag = fe_timeout_add (timeout, plugin_timeout_cb, hook);
return hook;
}
GList *
plugin_command_list(GList *tmp_list)
{
hexchat_hook *hook;
GSList *list = hook_list;
while (list)
{
hook = list->data;
if (hook && hook->type == HOOK_COMMAND)
tmp_list = g_list_prepend(tmp_list, hook->name);
list = list->next;
}
return tmp_list;
}
void
plugin_command_foreach (session *sess, void *userdata,
void (*cb) (session *sess, void *userdata, char *name, char *help))
{
GSList *list;
hexchat_hook *hook;
list = hook_list;
while (list)
{
hook = list->data;
if (hook && hook->type == HOOK_COMMAND && hook->name[0])
{
cb (sess, userdata, hook->name, hook->help_text);
}
list = list->next;
}
}
int
plugin_show_help (session *sess, char *cmd)
{
GSList *list;
hexchat_hook *hook;
list = plugin_hook_find (hook_list, HOOK_COMMAND, cmd);
if (list)
{
hook = list->data;
if (hook->help_text)
{
PrintText (sess, hook->help_text);
return 1;
}
}
return 0;
}
session *
plugin_find_context (const char *servname, const char *channel, server *current_server)
{
GSList *slist, *clist, *sessions = NULL;
server *serv;
session *sess;
char *netname;
if (servname == NULL && channel == NULL)
return current_sess;
slist = serv_list;
while (slist)
{
serv = slist->data;
netname = server_get_network (serv, TRUE);
if (servname == NULL ||
rfc_casecmp (servname, serv->servername) == 0 ||
g_ascii_strcasecmp (servname, serv->hostname) == 0 ||
g_ascii_strcasecmp (servname, netname) == 0)
{
if (channel == NULL)
return serv->front_session;
clist = sess_list;
while (clist)
{
sess = clist->data;
if (sess->server == serv)
{
if (rfc_casecmp (channel, sess->channel) == 0)
{
if (sess->server == current_server)
{
g_slist_free (sessions);
return sess;
} else
{
sessions = g_slist_prepend (sessions, sess);
}
}
}
clist = clist->next;
}
}
slist = slist->next;
}
if (sessions)
{
sessions = g_slist_reverse (sessions);
sess = sessions->data;
g_slist_free (sessions);
return sess;
}
return NULL;
}
/* ========================================================= */
/* ===== these are the functions plugins actually call ===== */
/* ========================================================= */
void *
hexchat_unhook (hexchat_plugin *ph, hexchat_hook *hook)
{
/* perl.c trips this */
if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
return NULL;
if (hook->type == HOOK_TIMER && hook->tag != 0)
fe_timeout_remove (hook->tag);
if (hook->type == HOOK_FD && hook->tag != 0)
fe_input_remove (hook->tag);
hook->type = HOOK_DELETED; /* expunge later */
g_free (hook->name); /* NULL for timers & fds */
g_free (hook->help_text); /* NULL for non-commands */
return hook->userdata;
}
hexchat_hook *
hexchat_hook_command (hexchat_plugin *ph, const char *name, int pri,
hexchat_cmd_cb *callb, const char *help_text, void *userdata)
{
return plugin_add_hook (ph, HOOK_COMMAND, pri, name, help_text, callb, 0,
userdata);
}
hexchat_hook *
hexchat_hook_server (hexchat_plugin *ph, const char *name, int pri,
hexchat_serv_cb *callb, void *userdata)
{
return plugin_add_hook (ph, HOOK_SERVER, pri, name, 0, callb, 0, userdata);
}
hexchat_hook *
hexchat_hook_server_attrs (hexchat_plugin *ph, const char *name, int pri,
hexchat_serv_attrs_cb *callb, void *userdata)
{
return plugin_add_hook (ph, HOOK_SERVER_ATTRS, pri, name, 0, callb, 0,
userdata);
}
hexchat_hook *
hexchat_hook_print (hexchat_plugin *ph, const char *name, int pri,
hexchat_print_cb *callb, void *userdata)
{
return plugin_add_hook (ph, HOOK_PRINT, pri, name, 0, callb, 0, userdata);
}
hexchat_hook *
hexchat_hook_print_attrs (hexchat_plugin *ph, const char *name, int pri,
hexchat_print_attrs_cb *callb, void *userdata)
{
return plugin_add_hook (ph, HOOK_PRINT_ATTRS, pri, name, 0, callb, 0,
userdata);
}
hexchat_hook *
hexchat_hook_timer (hexchat_plugin *ph, int timeout, hexchat_timer_cb *callb,
void *userdata)
{
return plugin_add_hook (ph, HOOK_TIMER, 0, 0, 0, callb, timeout, userdata);
}
hexchat_hook *
hexchat_hook_fd (hexchat_plugin *ph, int fd, int flags,
hexchat_fd_cb *callb, void *userdata)
{
hexchat_hook *hook;
hook = plugin_add_hook (ph, HOOK_FD, 0, 0, 0, callb, 0, userdata);
hook->pri = fd;
/* plugin hook_fd flags correspond exactly to FIA_* flags (fe.h) */
hook->tag = fe_input_add (fd, flags, plugin_fd_cb, hook);
return hook;
}
void
hexchat_print (hexchat_plugin *ph, const char *text)
{
if (!is_session (ph->context))
{
DEBUG(PrintTextf(0, "%s\thexchat_print called without a valid context.\n", ph->name));
return;
}
PrintText (ph->context, (char *)text);
}
void
hexchat_printf (hexchat_plugin *ph, const char *format, ...)
{
va_list args;
char *buf;
va_start (args, format);
buf = g_strdup_vprintf (format, args);
va_end (args);
hexchat_print (ph, buf);
g_free (buf);
}
void
hexchat_command (hexchat_plugin *ph, const char *command)
{
char *command_utf8;
if (!is_session (ph->context))
{
DEBUG(PrintTextf(0, "%s\thexchat_command called without a valid context.\n", ph->name));
return;
}
/* scripts/plugins continue to send non-UTF8... *sigh* */
command_utf8 = text_fixup_invalid_utf8 (command, -1, NULL);
handle_command (ph->context, command_utf8, FALSE);
g_free (command_utf8);
}
void
hexchat_commandf (hexchat_plugin *ph, const char *format, ...)
{
va_list args;
char *buf;
va_start (args, format);
buf = g_strdup_vprintf (format, args);
va_end (args);
hexchat_command (ph, buf);
g_free (buf);
}
int
hexchat_nickcmp (hexchat_plugin *ph, const char *s1, const char *s2)
{
return ((session *)ph->context)->server->p_cmp (s1, s2);
}
hexchat_context *
hexchat_get_context (hexchat_plugin *ph)
{
return ph->context;
}
int
hexchat_set_context (hexchat_plugin *ph, hexchat_context *context)
{
if (context == NULL) {
return 0;
}
if (!is_session (context))
{
g_critical("plugin tried to set an invalid context");
return 0;
}
ph->context = context;
return 1;
}
hexchat_context *
hexchat_find_context (hexchat_plugin *ph, const char *servname, const char *channel)
{
return plugin_find_context (servname, channel, ph->context->server);
}
const char *
hexchat_get_info (hexchat_plugin *ph, const char *id)
{
session *sess;
guint32 hash;
/* 1234567890 */
if (!strncmp (id, "event_text", 10))
{
char *e = (char *)id + 10;
if (*e == ' ') e++; /* 2.8.0 only worked without a space */
return text_find_format_string (e);
}
hash = str_hash (id);
/* do the session independant ones first */
switch (hash)
{
case 0x325acab5: /* libdirfs */
#ifdef USE_PLUGIN
return plugin_get_libdir ();
#else
return NULL;
#endif
case 0x14f51cd8: /* version */
return PACKAGE_VERSION;
case 0xdd9b1abd: /* xchatdir */
case 0xe33f6c4a: /* xchatdirfs */
case 0xd00d220b: /* configdir */
return get_xdir ();
}
sess = ph->context;
if (!is_session (sess))
{
DEBUG(PrintTextf(0, "%s\thexchat_get_info called without a valid context.\n", ph->name));
return NULL;
}
switch (hash)
{
case 0x2de2ee: /* away */
if (sess->server->is_away)
return sess->server->last_away_reason;
return NULL;
case 0x2c0b7d03: /* channel */
return sess->channel;
case 0x2c0d614c: /* charset */
{
const char *locale;
if (sess->server->encoding)
return sess->server->encoding;
locale = NULL;
g_get_charset (&locale);
return locale;
}
case 0x30f5a8: /* host */
return sess->server->hostname;
case 0x1c0e99c1: /* inputbox */
return fe_get_inputbox_contents (sess);
case 0x633fb30: /* modes */
return sess->current_modes;
case 0x6de15a2e: /* network */
return server_get_network (sess->server, FALSE);
case 0x339763: /* nick */
return sess->server->nick;
case 0x4889ba9b: /* password */
case 0x438fdf9: /* nickserv */
if (sess->server->network)
return ((ircnet *)sess->server->network)->pass;
return NULL;
case 0xca022f43: /* server */
if (!sess->server->connected)
return NULL;
return sess->server->servername;
case 0x696cd2f: /* topic */
return sess->topic;
case 0x3419f12d: /* gtkwin_ptr */
return fe_gui_info_ptr (sess, 1);
case 0x506d600b: /* native win_ptr */
return fe_gui_info_ptr (sess, 0);
case 0x6d3431b5: /* win_status */
switch (fe_gui_info (sess, 0)) /* check window status */
{
case 0: return "normal";
case 1: return "active";
case 2: return "hidden";
}
return NULL;
}
return NULL;
}
int
hexchat_get_prefs (hexchat_plugin *ph, const char *name, const char **string, int *integer)
{
int i = 0;
/* some special run-time info (not really prefs, but may aswell throw it in here) */
switch (str_hash (name))
{
case 0xf82136c4: /* state_cursor */
*integer = fe_get_inputbox_cursor (ph->context);
return 2;
case 0xd1b: /* id */
*integer = ph->context->server->id;
return 2;
}
do
{
if (!g_ascii_strcasecmp (name, vars[i].name))
{
switch (vars[i].type)
{
case TYPE_STR:
*string = ((char *) &prefs + vars[i].offset);
return 1;
case TYPE_INT:
*integer = *((int *) &prefs + vars[i].offset);
return 2;
default:
/*case TYPE_BOOL:*/
if (*((int *) &prefs + vars[i].offset))
*integer = 1;
else
*integer = 0;
return 3;
}
}
i++;
}
while (vars[i].name);
return 0;
}
hexchat_list *
hexchat_list_get (hexchat_plugin *ph, const char *name)
{
hexchat_list *list;
list = g_new0 (hexchat_list, 1);
switch (str_hash (name))
{
case 0x556423d0: /* channels */
list->type = LIST_CHANNELS;
list->next = sess_list;
break;
case 0x183c4: /* dcc */
list->type = LIST_DCC;
list->next = dcc_list;
break;
case 0xb90bfdd2: /* ignore */
list->type = LIST_IGNORE;
list->next = ignore_list;
break;
case 0xc2079749: /* notify */
list->type = LIST_NOTIFY;
list->next = notify_list;
list->head = (void *)ph->context; /* reuse this pointer */
break;
case 0x6a68e08: /* users */
if (is_session (ph->context))
{
list->type = LIST_USERS;
list->head = list->next = userlist_flat_list (ph->context);
fe_userlist_set_selected (ph->context);
break;
} /* fall through */
default:
g_free (list);
return NULL;
}
return list;
}
void
hexchat_list_free (hexchat_plugin *ph, hexchat_list *xlist)
{
if (xlist->type == LIST_USERS)
g_slist_free (xlist->head);
g_free (xlist);
}
int
hexchat_list_next (hexchat_plugin *ph, hexchat_list *xlist)
{
if (xlist->next == NULL)
return 0;
xlist->pos = xlist->next;
xlist->next = xlist->pos->next;
/* NOTIFY LIST: Find the entry which matches the context
of the plugin when list_get was originally called. */
if (xlist->type == LIST_NOTIFY)
{
xlist->notifyps = notify_find_server_entry (xlist->pos->data,
((session *)xlist->head)->server);
if (!xlist->notifyps)
return 0;
}
return 1;
}
const char * const *
hexchat_list_fields (hexchat_plugin *ph, const char *name)
{
static const char * const dcc_fields[] =
{
"iaddress32","icps", "sdestfile","sfile", "snick", "iport",
"ipos", "iposhigh", "iresume", "iresumehigh", "isize", "isizehigh", "istatus", "itype", NULL
};
static const char * const channels_fields[] =
{
"schannel", "schannelkey", "schanmodes", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes",
"snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers",
NULL
};
static const char * const ignore_fields[] =
{
"iflags", "smask", NULL
};
static const char * const notify_fields[] =
{
"iflags", "snetworks", "snick", "toff", "ton", "tseen", NULL
};
static const char * const users_fields[] =
{
"saccount", "iaway", "shost", "tlasttalk", "snick", "sprefix", "srealname", "iselected", NULL
};
static const char * const list_of_lists[] =
{
"channels", "dcc", "ignore", "notify", "users", NULL
};
switch (str_hash (name))
{
case 0x556423d0: /* channels */
return channels_fields;
case 0x183c4: /* dcc */
return dcc_fields;
case 0xb90bfdd2: /* ignore */
return ignore_fields;
case 0xc2079749: /* notify */
return notify_fields;
case 0x6a68e08: /* users */
return users_fields;
case 0x6236395: /* lists */
return list_of_lists;
}
return NULL;
}
time_t
hexchat_list_time (hexchat_plugin *ph, hexchat_list *xlist, const char *name)
{
guint32 hash = str_hash (name);
gpointer data;
switch (xlist->type)
{
case LIST_NOTIFY:
if (!xlist->notifyps)
return (time_t) -1;
switch (hash)
{
case 0x1ad6f: /* off */
return xlist->notifyps->lastoff;
case 0xddf: /* on */
return xlist->notifyps->laston;
case 0x35ce7b: /* seen */
return xlist->notifyps->lastseen;
}
break;
case LIST_USERS:
data = xlist->pos->data;
switch (hash)
{
case 0xa9118c42: /* lasttalk */
return ((struct User *)data)->lasttalk;
}
}
return (time_t) -1;
}
const char *
hexchat_list_str (hexchat_plugin *ph, hexchat_list *xlist, const char *name)
{
guint32 hash = str_hash (name);
gpointer data = ph->context;
int type = LIST_CHANNELS;
/* a NULL xlist is a shortcut to current "channels" context */
if (xlist)
{
data = xlist->pos->data;
type = xlist->type;
}
switch (type)
{
case LIST_CHANNELS:
switch (hash)
{
case 0x2c0b7d03: /* channel */
return ((session *)data)->channel;
case 0x8cea5e7c: /* channelkey */
return ((session *)data)->channelkey;
case 0x5716ab1e: /* chanmodes */
return ((session*)data)->server->chanmodes;
case 0x577e0867: /* chantypes */
return ((session *)data)->server->chantypes;
case 0x38b735af: /* context */
return data; /* this is a session * */
case 0x6de15a2e: /* network */
return server_get_network (((session *)data)->server, FALSE);
case 0x8455e723: /* nickprefixes */
return ((session *)data)->server->nick_prefixes;
case 0x829689ad: /* nickmodes */
return ((session *)data)->server->nick_modes;
case 0xca022f43: /* server */
return ((session *)data)->server->servername;
}
break;
case LIST_DCC:
switch (hash)
{
case 0x3d9ad31e: /* destfile */
return ((struct DCC *)data)->destfile;
case 0x2ff57c: /* file */
return ((struct DCC *)data)->file;
case 0x339763: /* nick */
return ((struct DCC *)data)->nick;
}
break;
case LIST_IGNORE:
switch (hash)
{
case 0x3306ec: /* mask */
return ((struct ignore *)data)->mask;
}
break;
case LIST_NOTIFY:
switch (hash)
{
case 0x4e49ec05: /* networks */
return ((struct notify *)data)->networks;
case 0x339763: /* nick */
return ((struct notify *)data)->name;
}
break;
case LIST_USERS:
switch (hash)
{
case 0xb9d38a2d: /* account */
return ((struct User *)data)->account;
case 0x339763: /* nick */
return ((struct User *)data)->nick;
case 0x30f5a8: /* host */
return ((struct User *)data)->hostname;
case 0xc594b292: /* prefix */
return ((struct User *)data)->prefix;
case 0xccc6d529: /* realname */
return ((struct User *)data)->realname;
}
break;
}
return NULL;
}
int
hexchat_list_int (hexchat_plugin *ph, hexchat_list *xlist, const char *name)
{
guint32 hash = str_hash (name);
gpointer data = ph->context;
int channel_flag;
int channel_flags[CHANNEL_FLAG_COUNT];
int channel_flags_used = 0;
int type = LIST_CHANNELS;
/* a NULL xlist is a shortcut to current "channels" context */
if (xlist)
{
data = xlist->pos->data;
type = xlist->type;
}
switch (type)
{
case LIST_DCC:
switch (hash)
{
case 0x34207553: /* address32 */
return ((struct DCC *)data)->addr;
case 0x181a6: /* cps */
{
gint64 cps = ((struct DCC *)data)->cps;
if (cps <= INT_MAX)
{
return (int) cps;
}
return INT_MAX;
}
case 0x349881: /* port */
return ((struct DCC *)data)->port;
case 0x1b254: /* pos */
return ((struct DCC *)data)->pos & 0xffffffff;
case 0xe8a945f6: /* poshigh */
return (((struct DCC *)data)->pos >> 32) & 0xffffffff;
case 0xc84dc82d: /* resume */
return ((struct DCC *)data)->resumable & 0xffffffff;
case 0xded4c74f: /* resumehigh */
return (((struct DCC *)data)->resumable >> 32) & 0xffffffff;
case 0x35e001: /* size */
return ((struct DCC *)data)->size & 0xffffffff;
case 0x3284d523: /* sizehigh */
return (((struct DCC *)data)->size >> 32) & 0xffffffff;
case 0xcacdcff2: /* status */
return ((struct DCC *)data)->dccstat;
case 0x368f3a: /* type */
return ((struct DCC *)data)->type;
}
break;
case LIST_IGNORE:
switch (hash)
{
case 0x5cfee87: /* flags */
return ((struct ignore *)data)->type;
}
break;
case LIST_CHANNELS:
switch (hash)
{
case 0xd1b: /* id */
return ((struct session *)data)->server->id;
case 0x5cfee87: /* flags */
channel_flags[0] = ((struct session *)data)->server->connected;
channel_flags[1] = ((struct session *)data)->server->connecting;
channel_flags[2] = ((struct session *)data)->server->is_away;
channel_flags[3] = ((struct session *)data)->server->end_of_motd;
channel_flags[4] = ((struct session *)data)->server->have_whox;
channel_flags[5] = ((struct session *)data)->server->have_idmsg;
channel_flags[6] = ((struct session *)data)->text_hidejoinpart;
channel_flags[7] = ((struct session *)data)->text_hidejoinpart == SET_DEFAULT;
channel_flags[8] = ((struct session *)data)->alert_beep;
channel_flags[9] = ((struct session *)data)->alert_beep == SET_DEFAULT;
channel_flags[10] = 0; /* unused for historical reasons */
channel_flags[11] = ((struct session *)data)->text_logging;
channel_flags[12] = ((struct session *)data)->text_logging == SET_DEFAULT;
channel_flags[13] = ((struct session *)data)->text_scrollback;
channel_flags[14] = ((struct session *)data)->text_scrollback == SET_DEFAULT;
channel_flags[15] = ((struct session *)data)->text_strip;
channel_flags[16] = ((struct session *)data)->text_strip == SET_DEFAULT;
channel_flags[17] = ((struct session *)data)->alert_tray;
channel_flags[18] = ((struct session *)data)->alert_tray == SET_DEFAULT;
channel_flags[19] = ((struct session *)data)->alert_taskbar;
channel_flags[20] = ((struct session *)data)->alert_taskbar == SET_DEFAULT;
channel_flags[21] = ((struct session *)data)->alert_balloon;
channel_flags[22] = ((struct session *)data)->alert_balloon == SET_DEFAULT;
/* Set flags */
for (channel_flag = 0; channel_flag < CHANNEL_FLAG_COUNT; ++channel_flag) {
if (channel_flags[channel_flag]) {
channel_flags_used |= 1 << channel_flag;
}
}
return channel_flags_used;
case 0x1a192: /* lag */
return ((struct session *)data)->server->lag;
case 0x1916144c: /* maxmodes */
return ((struct session *)data)->server->modes_per_line;
case 0x66f1911: /* queue */
return ((struct session *)data)->server->sendq_len;
case 0x368f3a: /* type */
return ((struct session *)data)->type;
case 0x6a68e08: /* users */
return ((struct session *)data)->total;
}
break;
case LIST_NOTIFY:
if (!xlist->notifyps)
return -1;
switch (hash)
{
case 0x5cfee87: /* flags */
return xlist->notifyps->ison;
}
case LIST_USERS:
switch (hash)
{
case 0x2de2ee: /* away */
return ((struct User *)data)->away;
case 0x4705f29b: /* selected */
return ((struct User *)data)->selected;
}
break;
}
return -1;
}
void *
hexchat_plugingui_add (hexchat_plugin *ph, const char *filename,
const char *name, const char *desc,
const char *version, char *reserved)
{
#ifdef USE_PLUGIN
ph = plugin_list_add (NULL, g_strdup (filename), g_strdup (name), g_strdup (desc),
g_strdup (version), NULL, NULL, TRUE, TRUE);
fe_pluginlist_update ();
#endif
return ph;
}
void
hexchat_plugingui_remove (hexchat_plugin *ph, void *handle)
{
#ifdef USE_PLUGIN
plugin_free (handle, FALSE, FALSE);
#endif
}
int
hexchat_emit_print (hexchat_plugin *ph, const char *event_name, ...)
{
va_list args;
/* currently only 4 because no events use more than 4.
This can be easily expanded without breaking the API. */
char *argv[4] = {NULL, NULL, NULL, NULL};
int i = 0;
va_start (args, event_name);
while (1)
{
argv[i] = va_arg (args, char *);
if (!argv[i])
break;
i++;
if (i >= 4)
break;
}
i = text_emit_by_name ((char *)event_name, ph->context, (time_t) 0,
argv[0], argv[1], argv[2], argv[3]);
va_end (args);
return i;
}
int
hexchat_emit_print_attrs (hexchat_plugin *ph, hexchat_event_attrs *attrs,
const char *event_name, ...)
{
va_list args;
/* currently only 4 because no events use more than 4.
This can be easily expanded without breaking the API. */
char *argv[4] = {NULL, NULL, NULL, NULL};
int i = 0;
va_start (args, event_name);
while (1)
{
argv[i] = va_arg (args, char *);
if (!argv[i])
break;
i++;
if (i >= 4)
break;
}
i = text_emit_by_name ((char *)event_name, ph->context, attrs->server_time_utc,
argv[0], argv[1], argv[2], argv[3]);
va_end (args);
return i;
}
char *
hexchat_gettext (hexchat_plugin *ph, const char *msgid)
{
/* so that plugins can use HexChat's internal gettext strings. */
/* e.g. The EXEC plugin uses this on Windows. */
return _(msgid);
}
void
hexchat_send_modes (hexchat_plugin *ph, const char **targets, int ntargets, int modes_per_line, char sign, char mode)
{
char tbuf[514]; /* modes.c needs 512 + null */
send_channel_modes (ph->context, tbuf, (char **)targets, 0, ntargets, sign, mode, modes_per_line);
}
char *
hexchat_strip (hexchat_plugin *ph, const char *str, int len, int flags)
{
return strip_color ((char *)str, len, flags);
}
void
hexchat_free (hexchat_plugin *ph, void *ptr)
{
g_free (ptr);
}
static int
hexchat_pluginpref_set_str_real (hexchat_plugin *pl, const char *var, const char *value, int mode) /* mode: 0 = delete, 1 = save */
{
FILE *fpIn;
int fhOut;
int prevSetting;
char *confname;
char *confname_tmp;
char *escaped_value;
char *buffer;
char *buffer_tmp;
char line_buffer[512]; /* the same as in cfg_put_str */
char *line_bufp = line_buffer;
char *canon;
canon = g_strdup (pl->name);
canonalize_key (canon);
confname = g_strdup_printf ("addon_%s.conf", canon);
g_free (canon);
confname_tmp = g_strdup_printf ("%s.new", confname);
fhOut = hexchat_open_file (confname_tmp, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
fpIn = hexchat_fopen_file (confname, "r", 0);
if (fhOut == -1) /* unable to save, abort */
{
g_free (confname);
g_free (confname_tmp);
if (fpIn)
fclose (fpIn);
return 0;
}
else if (fpIn == NULL) /* no previous config file, no parsing */
{
if (mode)
{
escaped_value = g_strescape (value, NULL);
buffer = g_strdup_printf ("%s = %s\n", var, escaped_value);
g_free (escaped_value);
write (fhOut, buffer, strlen (buffer));
g_free (buffer);
close (fhOut);
buffer = g_build_filename (get_xdir (), confname, NULL);
g_free (confname);
buffer_tmp = g_build_filename (get_xdir (), confname_tmp, NULL);
g_free (confname_tmp);
#ifdef WIN32
g_unlink (buffer);
#endif
if (g_rename (buffer_tmp, buffer) == 0)
{
g_free (buffer);
g_free (buffer_tmp);
return 1;
}
else
{
g_free (buffer);
g_free (buffer_tmp);
return 0;
}
}
else
{
/* mode = 0, we want to delete but the config file and thus the given setting does not exist, we're ready */
close (fhOut);
g_free (confname);
g_free (confname_tmp);
return 1;
}
}
else /* existing config file, preserve settings and find & replace current var value if any */
{
prevSetting = 0;
while (fscanf (fpIn, " %511[^\n]", line_bufp) != EOF) /* read whole lines including whitespaces */
{
buffer_tmp = g_strdup_printf ("%s ", var); /* add one space, this way it works against var - var2 checks too */
if (strncmp (buffer_tmp, line_buffer, strlen (var) + 1) == 0) /* given setting already exists */
{
if (mode) /* overwrite the existing matching setting if we are in save mode */
{
escaped_value = g_strescape (value, NULL);
buffer = g_strdup_printf ("%s = %s\n", var, escaped_value);
g_free (escaped_value);
}
else /* erase the setting in delete mode */
{
buffer = g_strdup ("");
}
prevSetting = 1;
}
else
{
buffer = g_strdup_printf ("%s\n", line_buffer); /* preserve the existing different settings */
}
write (fhOut, buffer, strlen (buffer));
g_free (buffer);
g_free (buffer_tmp);
}
fclose (fpIn);
if (!prevSetting && mode) /* var doesn't exist currently, append if we're in save mode */
{
escaped_value = g_strescape (value, NULL);
buffer = g_strdup_printf ("%s = %s\n", var, escaped_value);
g_free (escaped_value);
write (fhOut, buffer, strlen (buffer));
g_free (buffer);
}
close (fhOut);
buffer = g_build_filename (get_xdir (), confname, NULL);
g_free (confname);
buffer_tmp = g_build_filename (get_xdir (), confname_tmp, NULL);
g_free (confname_tmp);
#ifdef WIN32
g_unlink (buffer);
#endif
if (g_rename (buffer_tmp, buffer) == 0)
{
g_free (buffer);
g_free (buffer_tmp);
return 1;
}
else
{
g_free (buffer);
g_free (buffer_tmp);
return 0;
}
}
}
int
hexchat_pluginpref_set_str (hexchat_plugin *pl, const char *var, const char *value)
{
return hexchat_pluginpref_set_str_real (pl, var, value, 1);
}
static int
hexchat_pluginpref_get_str_real (hexchat_plugin *pl, const char *var, char *dest, int dest_len)
{
char *confname, *canon, *cfg, *unescaped_value;
char buf[512];
canon = g_strdup (pl->name);
canonalize_key (canon);
confname = g_strdup_printf ("%s%caddon_%s.conf", get_xdir(), G_DIR_SEPARATOR, canon);
g_free (canon);
if (!g_file_get_contents (confname, &cfg, NULL, NULL))
{
g_free (confname);
return 0;
}
g_free (confname);
if (!cfg_get_str (cfg, var, buf, sizeof(buf)))
{
g_free (cfg);
return 0;
}
unescaped_value = g_strcompress (buf);
g_strlcpy (dest, unescaped_value, dest_len);
g_free (unescaped_value);
g_free (cfg);
return 1;
}
int
hexchat_pluginpref_get_str (hexchat_plugin *pl, const char *var, char *dest)
{
/* All users of this must ensure dest is >= 512... */
return hexchat_pluginpref_get_str_real (pl, var, dest, 512);
}
int
hexchat_pluginpref_set_int (hexchat_plugin *pl, const char *var, int value)
{
char buffer[12];
g_snprintf (buffer, sizeof (buffer), "%d", value);
return hexchat_pluginpref_set_str_real (pl, var, buffer, 1);
}
int
hexchat_pluginpref_get_int (hexchat_plugin *pl, const char *var)
{
char buffer[12];
if (hexchat_pluginpref_get_str_real (pl, var, buffer, sizeof(buffer)))
{
int ret = atoi (buffer);
/* 0 could be success or failure, who knows */
if (ret == 0 && *buffer != '0')
return -1;
return ret;
}
else
{
return -1;
}
}
int
hexchat_pluginpref_delete (hexchat_plugin *pl, const char *var)
{
return hexchat_pluginpref_set_str_real (pl, var, 0, 0);
}
int
hexchat_pluginpref_list (hexchat_plugin *pl, char* dest)
{
FILE *fpIn;
char confname[64];
char buffer[512]; /* the same as in cfg_put_str */
char *bufp = buffer;
char *token;
token = g_strdup (pl->name);
canonalize_key (token);
sprintf (confname, "addon_%s.conf", token);
g_free (token);
fpIn = hexchat_fopen_file (confname, "r", 0);
if (fpIn == NULL) /* no existing config file, no parsing */
{
return 0;
}
else /* existing config file, get list of settings */
{
strcpy (dest, ""); /* clean up garbage */
while (fscanf (fpIn, " %511[^\n]", bufp) != EOF) /* read whole lines including whitespaces */
{
token = strtok (buffer, "=");
g_strlcat (dest, g_strchomp (token), 4096); /* Dest must not be smaller than this */
g_strlcat (dest, ",", 4096);
}
fclose (fpIn);
}
return 1;
}