--- a/CMakeLists.txt Fri Apr 02 20:00:20 2010 +0300
+++ b/CMakeLists.txt Sun Apr 04 16:43:49 2010 +0300
@@ -38,7 +38,7 @@
${MCABBER_LIBRARY_DIRS})
## Define targets
-add_library(lua MODULE main.c util.c)
+add_library(lua MODULE lua.c util.c)
## Set up compiler
configure_file(config.h.in config.h ESCAPE_QUOTES)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lua.c Sun Apr 04 16:43:49 2010 +0300
@@ -0,0 +1,1791 @@
+
+/* Copyright 2009 Myhailo Danylenko
+
+This file is part of mcabber-lua.
+
+mcabber-lua 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, see <http://www.gnu.org/licenses/>. */
+
+#include <glib.h>
+#include <gmodule.h> // g_module_*
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include <stdio.h>
+#include <stdlib.h> // getenv
+#include <string.h> // strcmp
+
+#include <mcabber/logprint.h> // scr_log_print
+#include <mcabber/screen.h> // scr_Beep, scr_WriteIncomingMessage
+#include <mcabber/hbuf.h> // HBB_PREFIX_INFO
+#include <mcabber/commands.h> // process_command, cmd_add, cmd_del
+#include <mcabber/xmpp.h> // xmpp_getstatus, xmpp_getstatusmsg, lconnection
+#include <mcabber/xmpp_helper.h> // xmpp_add_feature, xmpp_del_feature
+#include <mcabber/roster.h> // imstatus2char, foreach_buddy, buddy_*, current_buddy, BUDDATA, ROSTER_TYPE_*
+#include <mcabber/utils.h> // from_utf8, jidtodisp
+#include <mcabber/hooks.h> // hk_add_handler, hk_del_handler
+#include <mcabber/settings.h> // settings_set, settings_del, settings_get
+#include <mcabber/compl.h> // compl_new_category, compl_add_category_word, compl_del_category_word
+#include <mcabber/events.h> // evs_*
+#include <mcabber/modules.h> // module_info_t
+
+#include "config.h"
+#include "util.h"
+
+// module description
+static void mlua_init (void);
+static void mlua_uninit (void);
+
+#ifdef LLM_LOG_HANDLER
+#define DESCRIPTION ( \
+ "Lua scripting interface\n" \
+ "Recognizes options lua_init_file, lua_hook_function and lua_lm_debug\n" \
+ "Provides command /lua" )
+#else
+#define DESCRIPTION ( \
+ "Lua scripting interface\n" \
+ "Recognizes options lua_init_file and lua_hook_function\n" \
+ "Provides command /lua" )
+#endif
+
+static module_info_t info_lua_experimental = {
+ .branch = "experimental",
+ .api = 12,
+ .version = PROJECT_VERSION,
+ .description = DESCRIPTION,
+ .requires = NULL,
+ .init = mlua_init,
+ .uninit = mlua_uninit,
+ .next = NULL,
+};
+
+module_info_t info_lua = {
+ .branch = "dev",
+ .api = 11,
+ .version = PROJECT_VERSION,
+ .description = DESCRIPTION,
+ .requires = NULL,
+ .init = mlua_init,
+ .uninit = mlua_uninit,
+ .next = &info_lua_experimental,
+};
+
+// global lua state object, necessary for uninitialization function
+static lua_State *lua = NULL;
+
+// caller sould g_free result
+static char *mcabber_config_filename (const char *file)
+{
+ const char *home = getenv ("HOME");
+ if (!home)
+ return NULL;
+ return g_strconcat (home, "/.mcabber/", file ? file : "", NULL);
+}
+
+/// print
+/// Prints its arguments to log with default priority.
+/// A: something, ...
+static int lua_global_print (lua_State *L)
+{
+ int top = lua_gettop (L);
+ int i;
+ luaL_Buffer B;
+ luaL_buffinit (L, &B);
+ for (i = 1; i <= top; i++) {
+ int type = lua_type (L, i);
+ if (i > 1)
+ luaL_addchar (&B, '\t');
+ if (type == LUA_TSTRING) {
+ size_t len;
+ const char *str = lua_tolstring (L, i, &len);
+ luaL_addlstring (&B, str, len);
+ } else if (type == LUA_TNUMBER)
+ luaL_addstring (&B, lua_tostring (L, i)); // XXX: modifies
+ else if (type == LUA_TBOOLEAN) {
+ if (lua_toboolean (L, i))
+ luaL_addstring (&B, "true");
+ else
+ luaL_addstring (&B, "false");
+ } else if (type == LUA_TNIL)
+ luaL_addstring (&B, "nil");
+ else {
+ char xbuf[9];
+ luaL_addstring (&B, luaL_typename (L, i));
+ luaL_addstring (&B, ": 0x");
+ snprintf (&xbuf[0], 9, "%08x", (int) lua_topointer (L, i));
+ luaL_addlstring (&B, xbuf, 8); // XXX
+ }
+ }
+ luaL_pushresult (&B);
+
+ scr_log_print (LPRINT_LOGNORM | LPRINT_NOTUTF8, lua_tostring (L, -1));
+ return 0;
+}
+
+/// dopath
+/// Loads lua file from default location.
+/// XXX: g_filename_from_utf8?
+/// A: string (filename, without ".lua")
+/// R: string (error message, optional)
+static int lua_global_dopath (lua_State *L)
+{
+ const char *name = luaL_checkstring (L, 1);
+ size_t size = lua_objlen (L, 1);
+ char *path;
+ int ret = 0;
+ if (size > 4 && !strncmp (name + size - 4, ".lua", 4))
+ path = mcabber_config_filename (name);
+ else {
+ char *fname = g_strconcat (name, ".lua", NULL);
+ path = mcabber_config_filename (fname);
+ g_free (fname);
+ }
+
+ if ((ret = luaL_loadfile (L, path)))
+ scr_log_print (LPRINT_LOGNORM, "lua: Unable to compile file %s: %s", path, lua_tostring (L, -1));
+ else if ((ret = lua_pcall (L, 0, LUA_MULTRET, 0)))
+ scr_log_print (LPRINT_LOGNORM, "lua: Runtime error in file %s: %s", path, lua_tostring (L, -1));
+ g_free (path);
+
+ if (ret)
+ return 1;
+ else
+ return 0;
+}
+
+/// yes or no ansvers
+/// G:
+static const string2enum_t lua_yesno[] = {
+ { "1", 1 },
+ { "0", 0 },
+ { "enable", 1 },
+ { "disable", 0 },
+ { "true", 1 },
+ { "false", 0 },
+ { "on", 1 },
+ { "off", 0 },
+ { "yes", 1 },
+ { "no", 0 },
+ { NULL, -1 },
+};
+
+/// main.yesno
+/// According to yes or no ansvers returns true or false.
+/// If ansver is not recognized, returns nil.
+/// A: anything (string expected)
+/// R: boolean or nil
+static int lua_main_yesno (lua_State *L)
+{
+ int type = lua_type (L, 1);
+ if (type == LUA_TSTRING) {
+ int ret = luaL_checkenum (L, 1, lua_yesno);
+ if (ret == -1)
+ lua_pushnil (L);
+ else
+ lua_pushboolean (L, ret);
+ } else if (type == LUA_TNUMBER)
+ lua_pushboolean (L, lua_tointeger (L, 1));
+ else if (type != LUA_TBOOLEAN)
+ lua_pushnil (L);
+ return 1;
+}
+
+/// log print type
+/// G:
+static const string2enum_t lua_lprint[] = {
+ { "normal", LPRINT_NORMAL },
+ { "log", LPRINT_LOG },
+ { "debug", LPRINT_DEBUG },
+ { "notutf0", LPRINT_NOTUTF8 },
+ { NULL, 0 },
+};
+
+/// roster type
+/// G:
+static const string2enum_t lua_roster_type[] = {
+ { "user", ROSTER_TYPE_USER },
+ { "group", ROSTER_TYPE_GROUP },
+ { "agent", ROSTER_TYPE_AGENT },
+ { "room", ROSTER_TYPE_ROOM },
+ { "special", ROSTER_TYPE_SPECIAL },
+ { NULL, 0 },
+};
+
+/// main.log
+/// Prints message to log.
+/// Note: most likely you need notutf8 flag enabled.
+/// A: log print type, message, message...
+static int lua_main_log (lua_State *L)
+{
+ int type = luaL_checkenum_multi (L, 1, lua_lprint);
+ lua_concat (L, lua_gettop (L) - 1);
+ scr_log_print (type, lua_tostring (L, -1));
+ return 0;
+}
+
+// expects table on top
+static void lua_options_callback (char *key, char *value, lua_State *L)
+{
+ char *loc = from_utf8 (key);
+ lua_pushstring (L, loc);
+ g_free (loc);
+ loc = from_utf8 (value);
+ lua_pushstring (L, loc);
+ g_free (loc);
+ lua_settable (L, -3);
+}
+
+/// main.option
+/// Sets or gets value of mcabber option.
+/// You can specify nil as a value to delete option.
+/// If you omit option name, it returns hash table of all options.
+/// A: string (option name, optional), string (value, optional)
+/// R: string (value, optional)
+static int lua_main_option (lua_State *L)
+{
+ int top = lua_gettop (L);
+ if (top > 0) {
+ char *name = to_utf8 (luaL_checkstring (L, 1));
+ if (top > 1) { // Set
+ if (lua_type (L, 2) == LUA_TNIL) // Unset
+ settings_del (SETTINGS_TYPE_OPTION, name);
+ else { // Set
+ char *value = to_utf8 (luaL_checkstring (L, 2));
+ settings_set (SETTINGS_TYPE_OPTION, name, value);
+ g_free (value);
+ }
+ g_free (name);
+ return 0;
+ } else { // Get
+ char *value = from_utf8 (settings_get (SETTINGS_TYPE_OPTION, name));
+ if (value) {
+ lua_pushstring (L, value);
+ g_free (value);
+ } else
+ lua_pushnil (L);
+ g_free (name);
+ return 1;
+ }
+ } else { // List
+ lua_newtable (L);
+ settings_foreach (SETTINGS_TYPE_OPTION, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
+ return 1;
+ }
+}
+
+/// main.alias
+/// Sets or gets alias.
+/// You can specify nil as a command to delete alias.
+/// If you omit alias name, it will return hash table of all aliases.
+/// A: string (alias name, optional), string (command, optional)
+/// R: string (command, optional)
+static int lua_main_alias (lua_State *L)
+{
+ int top = lua_gettop (L);
+ if (top > 0) {
+ char *name = to_utf8 (luaL_checkstring (L, 1));
+ if (top > 1) { // Set
+ if (lua_type (L, 2) == LUA_TNIL) { // Unset
+ settings_del (SETTINGS_TYPE_ALIAS, name);
+ compl_del_category_word (COMPL_CMD, name);
+ } else { // Set
+ char *value = to_utf8 (luaL_checkstring (L, 2));
+ if (!settings_get (SETTINGS_TYPE_ALIAS, name))
+ compl_add_category_word (COMPL_CMD, name);
+ settings_set (SETTINGS_TYPE_ALIAS, name, value);
+ g_free (value);
+ }
+ g_free (name);
+ return 0;
+ } else { // Get
+ char *value = from_utf8 (settings_get (SETTINGS_TYPE_ALIAS, name));
+ if (value) {
+ lua_pushstring (L, value);
+ g_free (value);
+ } else
+ lua_pushnil (L);
+ g_free (name);
+ return 1;
+ }
+ } else { // List
+ lua_newtable (L);
+ settings_foreach (SETTINGS_TYPE_ALIAS, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
+ return 1;
+ }
+}
+
+/// main.bind
+/// Sets or gets binding.
+/// You can specify nil as a command to unbind key.
+/// If you omit keycode, it will return hash table of all bindings.
+/// A: string (keycode, optional), string (command, optional)
+/// R: string (command, optional)
+static int lua_main_binding (lua_State *L)
+{
+ int top = lua_gettop (L);
+ if (top > 0) {
+ // just to be sure...
+ char *name = to_utf8 (luaL_checkstring (L, 1));
+ if (top > 1) { // Set
+ if (lua_type (L, 2) == LUA_TNIL) // Unset
+ settings_del (SETTINGS_TYPE_BINDING, name);
+ else { // Set
+ char *value = to_utf8 (luaL_checkstring (L, 2));
+ settings_set (SETTINGS_TYPE_BINDING, name, value);
+ g_free (value);
+ }
+ g_free (name);
+ return 0;
+ } else { // Get
+ char *value = from_utf8 (settings_get (SETTINGS_TYPE_BINDING, name));
+ if (value) {
+ lua_pushstring (L, value);
+ g_free (value);
+ } else
+ lua_pushnil (L);
+ g_free (name);
+ return 1;
+ }
+ } else { // List
+ lua_newtable (L);
+ settings_foreach (SETTINGS_TYPE_BINDING, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
+ return 1;
+ }
+}
+
+/// main.fileoption
+/// Gets option, expanding it as filename.
+/// A: string (option name)
+/// R: string (expanded option value) or nil
+static int lua_main_fileoption (lua_State *L)
+{
+ char *fname = expand_filename (settings_opt_get (luaL_checkstring (L, 1)));
+ if (fname) {
+ lua_pushstring (L, fname);
+ g_free (fname);
+ } else
+ lua_pushnil (L);
+ return 1;
+}
+
+#ifdef LLM_CONNECTION_ENABLE
+/// main.connection
+/// Returns lightuserdata of mcabber's loudmouth connection.
+/// This can be very useful with lua-loudmouth, and not much otherwise.
+/// R: lightuserdata or nil
+static int lua_main_connection (lua_State *L)
+{
+ if (xmpp_is_online ())
+ lua_pushlightuserdata (L, lconnection);
+ else
+ lua_pushnil (L);
+ return 1;
+}
+#endif
+
+/// main.print_info
+/// Prints a system message to buddy's window.
+/// A: string (jid), string (message)
+static int lua_main_print_info (lua_State *L)
+{
+ char *jid = to_utf8 (luaL_checkstring (L, 1));
+ char *to = jidtodisp (jid);
+ char *mesg = to_utf8 (luaL_checkstring (L, 2));
+ scr_write_incoming_message (to, mesg, 0, HBB_PREFIX_INFO, 0);
+ g_free (mesg);
+ g_free (to);
+ g_free (jid);
+ return 0;
+}
+
+/// main.beep
+/// Beeps with system speaker.
+static int lua_main_beep (lua_State *L)
+{
+ scr_beep ();
+ return 0;
+}
+
+/// main.run
+/// Runs specified mcabber command.
+/// A: string
+static int lua_main_run (lua_State *L)
+{
+ process_command (luaL_checkstring (L, 1), TRUE);
+ return 0;
+}
+
+/// main.status
+/// Returns your current status.
+/// R: string (status letter), string (status message)
+static int lua_main_status (lua_State *L)
+{
+ char *sm = from_utf8 (xmpp_getstatusmsg ());
+ lua_pushlstring (L, &imstatus2char[xmpp_getstatus ()], 1);
+ lua_pushstring (L, sm);
+ g_free (sm);
+ return 2;
+}
+
+// expects table on top
+static void lua_rosterlist_callback (gpointer buddy, lua_State *L)
+{
+ char *jid = from_utf8 (buddy_getjid (buddy));
+ lua_pushnumber (L, lua_objlen (L, -1) + 1);
+ lua_pushstring (L, jid);
+ lua_settable (L, -3);
+ g_free (jid);
+}
+
+/// main.roster
+/// Returns array of jids of buddies in roster.
+/// R: table
+static int lua_main_roster (lua_State *L)
+{
+ lua_newtable (L);
+ foreach_buddy (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM, (void (*) (gpointer buddy, void *data)) lua_rosterlist_callback, L);
+ return 1;
+}
+
+/// main.current_buddy
+/// Returns jid of current selected buddy or sets current buddy to buddy with specified jid.
+/// A: string (optional)
+/// R: string (optional)
+static int lua_main_current_buddy (lua_State *L)
+{
+ if (lua_gettop (L) > 0) { // Set
+ // XXX: we need not convert to utf, RS works on jids/names in locale charset,
+ // but will jidtodisp always correctly work on such tings?
+ char *jid = jidtodisp (luaL_checkstring (L, 1));
+ scr_roster_search (jid);
+ g_free (jid);
+ return 0;
+ } else { // Get
+ char *jid = from_utf8 (buddy_getjid (BUDDATA (current_buddy)));
+ lua_pushstring (L, jid);
+ g_free (jid);
+ return 1;
+ }
+}
+
+/// main.full_jid
+/// Returns full jid (with current resource) of specified buddy (or current, if not specified).
+/// Note, that if there are no resources online, it will return just what it got.
+/// A: string (jid, optional)
+/// R: string (jid)
+static int lua_main_full_jid (lua_State *L)
+{
+ GList *buddy;
+ GSList *resources;
+ GSList *resource;
+ if (lua_gettop (L) > 0) {
+ char *jid = from_utf8 (luaL_checkstring (L, 1));
+ buddy = buddy_search_jid (jid);
+ g_free (jid);
+ } else
+ buddy = current_buddy;
+ if (!buddy)
+ return 0;
+ resources = buddy_getresources (BUDDATA (buddy));
+ if (!resources) {
+ char *loc = from_utf8 (buddy_getjid (BUDDATA (buddy)));
+ lua_pushstring (L, loc);
+ g_free (loc);
+ } else {
+ char *jid = from_utf8 (buddy_getjid (BUDDATA (buddy)));
+ char *res = from_utf8 (g_slist_last (resources)->data);
+ lua_pushfstring (L, "%s%c%s", jid, JID_RESOURCE_SEPARATOR, res);
+ for (resource = resources; resource; resource = g_slist_next (resource))
+ g_free (resource->data);
+ g_slist_free (resources);
+ g_free (jid);
+ g_free (res);
+ }
+ return 1;
+}
+
+typedef struct {
+ lua_State *L;
+ gpointer buddy;
+} lua_state_and_buddy_t; // :)
+
+/// resources table
+/// Hash table with resource name as keys and another hash tables as values.
+/// Inner tables contain resource-specific information: priority, status and message.
+static void lua_buddy_resources_callback (gpointer resource, lua_state_and_buddy_t *d)
+{
+ char *loc = from_utf8 (resource);
+ lua_pushstring (d->L, loc);
+ g_free (loc);
+ lua_createtable (d->L, 0, 3);
+ lua_pushstring (d->L, "priority");
+ lua_pushnumber (d->L, buddy_getresourceprio (d->buddy, resource));
+ lua_settable (d->L, -3);
+ lua_pushstring (d->L, "status");
+ lua_pushlstring (d->L, &imstatus2char[buddy_getstatus (d->buddy, resource)], 1);
+ lua_settable (d->L, -3);
+ lua_pushstring (d->L, "message");
+ loc = from_utf8 (buddy_getstatusmsg (d->buddy, resource));
+ lua_pushstring (d->L, loc);
+ g_free (loc);
+ lua_settable (d->L, -3);
+ lua_settable (d->L, -3);
+ g_free (resource);
+}
+
+/// main.buddy_info
+/// Returns a hash table with information on specified buddy.
+/// Table contains fields type, name, onserver and resources (which points to resources table).
+/// A: string (jid)
+/// R: table
+static int lua_main_buddy_info (lua_State *L)
+{
+ char *loc = to_utf8 (luaL_checkstring (L, 1));
+ char *jid = jidtodisp (loc);
+ GSList *buddy = roster_find (jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
+ GSList *resources;
+ lua_state_and_buddy_t snb;
+ g_free (jid);
+ g_free (loc);
+
+ if (!buddy) {
+ lua_pushnil (L);
+ return 1;
+ }
+
+ lua_createtable (L, 0, 3);
+ lua_pushstring (L, "type");
+ luaL_pushenum (L, buddy_gettype (BUDDATA (buddy)), lua_roster_type);
+ lua_settable (L, -3);
+ lua_pushstring (L, "name");
+ loc = from_utf8 (buddy_getname (BUDDATA (buddy)));
+ lua_pushstring (L, loc);
+ g_free (loc);
+ lua_settable (L, -3);
+ lua_pushstring (L, "onserver");
+ lua_pushboolean (L, buddy_getonserverflag (BUDDATA (buddy)));
+ lua_settable (L, -3);
+ lua_pushstring (L, "resources");
+ lua_createtable (L, 0, 0);
+ snb.L = L;
+ snb.buddy = BUDDATA (buddy);
+ resources = buddy_getresources (BUDDATA (buddy));
+ g_slist_foreach (buddy_getresources (BUDDATA (buddy)), (GFunc) lua_buddy_resources_callback, &snb);
+ g_slist_free (resources);
+ lua_settable (L, -3);
+
+ return 1;
+}
+
+// XMPP DISCO FEATURES
+
+GSList *lua_added_features = NULL;
+
+/// main.add_feature
+/// Adds xmlns to disco#info features list.
+/// A: string (xmlns)
+static int lua_main_add_feature (lua_State *L)
+{
+ char *xmlns = to_utf8 (luaL_checkstring (L, 1));
+ xmpp_add_feature (xmlns);
+ lua_added_features = g_slist_prepend (lua_added_features, xmlns);
+ return 0;
+}
+
+/// main.del_feature
+/// Removes xmlns from disco#info features list.
+/// A: stirng (xmlns)
+static int lua_main_del_feature (lua_State *L)
+{
+ char *xmlns = to_utf8 (luaL_checkstring (L, 1));
+ GSList *el = g_slist_find_custom (lua_added_features, xmlns, (GCompareFunc) strcmp);
+ xmpp_del_feature (xmlns);
+ if (el) {
+ g_free (el->data);
+ lua_added_features = g_slist_delete_link (lua_added_features, el);
+ }
+ return 0;
+}
+
+// MCABBER EVENTS
+
+/// event context
+/// Enum, indicating what exactly caused event function firing.
+/// G:
+static const string2enum_t lua_event_context[] = {
+ { "timeout", EVS_CONTEXT_TIMEOUT },
+ { "cancel", EVS_CONTEXT_CANCEL },
+ { "reject", EVS_CONTEXT_REJECT },
+ { "accept", EVS_CONTEXT_ACCEPT },
+ { NULL, 0 },
+};
+
+typedef struct {
+ lua_State *L;
+ int reference;
+ int evid;
+} lua_event_callback_t;
+
+static GSList *lua_events = NULL;
+
+static void lua_event_callback_destroy_notify (gpointer udata)
+{
+ lua_event_callback_t *cb = udata;
+
+ luaL_unref (cb -> L, LUA_REGISTRYINDEX, cb->reference);
+ luaL_unref (cb -> L, LUA_REGISTRYINDEX, cb->evid);
+ luaL_free (cb -> L, cb);
+}
+
+/// event function
+/// Function to be called, when some event state change occurs
+/// A: event context, string (event args)
+/// R: boolean (if event shoud be preserved)
+static gboolean lua_event_callback (guint context, const gchar *arg, gpointer userdata)
+{
+ lua_event_callback_t *cb = userdata;
+
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ luaL_pushenum (cb->L, context, lua_event_context);
+ lua_pushstring (cb -> L, arg);
+ if (lua_pcall (cb->L, 2, 1, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Event callback execution error: %s", lua_tostring (cb->L, -1));
+ lua_pop (cb->L, 1);
+ }
+
+ if (lua_toboolean (cb -> L, -1))
+ return TRUE;
+ else {
+ lua_events = g_slist_remove (lua_events, cb); // XXX
+ return FALSE;
+ }
+}
+
+/// main.event
+/// Creates new event. If called without arguments, returns event id list.
+/// A: event function (optional), string (event id), string (description, optional), integer (expiration timeout, optional)
+/// R: string (event id) or nothing (creation error) or table (list of event names)
+static int lua_main_event (lua_State *L)
+{
+ int top = lua_gettop (L);
+ if (top > 0) { // Create
+ lua_event_callback_t *cb;
+ const char *evid = NULL;
+ int timeout = 0;
+ const char *desc = NULL;
+ luaL_argcheck (L, lua_type (L, 1) == LUA_TFUNCTION, 1, "event function expected");
+
+ if (top > 1) {
+ evid = luaL_checkstring (L, 2);
+ if (top > 2) {
+ timeout = luaL_checkinteger (L, 3);
+ if (top > 2) {
+ desc = luaL_checkstring (L, 4);
+ lua_pop (L, 3);
+ } else
+ lua_pop (L, 2);
+ } else
+ lua_pop (L, 1);
+ }
+
+ lua_pushvalue (L, 1); // XXX
+ cb = luaL_malloc (L, sizeof (lua_event_callback_t));
+ cb -> L = L;
+ cb -> reference = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb -> evid = LUA_NOREF;
+ lua_events = g_slist_prepend (lua_events, cb);
+
+ evid = evs_new (desc, evid, timeout, lua_event_callback, cb, lua_event_callback_destroy_notify);
+ if (!evid) {
+ lua_events = g_slist_remove (lua_events, cb); // XXX
+ return 0;
+ }
+
+ lua_pushstring (L, evid);
+ lua_pushvalue (L, -1);
+ cb -> evid = luaL_ref (L, LUA_REGISTRYINDEX); // XXX
+ return 1;
+
+ } else { // List
+ GSList *events = evs_geteventslist ();
+ GSList *event;
+
+ lua_newtable (L);
+ for (event = events; event; event = g_slist_next (event)) {
+ lua_pushstring (L, event->data);
+ luaL_ref (L, -2);
+ }
+ g_slist_free (events);
+
+ return 1;
+ }
+}
+
+// MCABBER COMMANDS
+
+/// completion type
+/// Built-it completion types can be specified as string, instead of id.
+/// G:
+static string2enum_t lua_completion_type[] = { // not const, we need to modify yesno
+ { "cmd", COMPL_CMD },
+ { "jid", COMPL_JID },
+ { "urljid", COMPL_URLJID },
+ { "name", COMPL_NAME },
+ { "status", COMPL_STATUS },
+ { "filename", COMPL_FILENAME },
+ { "roster", COMPL_ROSTER },
+ { "buffer", COMPL_BUFFER },
+ { "group", COMPL_GROUP },
+ { "groupname", COMPL_GROUPNAME },
+ { "multiline", COMPL_MULTILINE },
+ { "room", COMPL_ROOM },
+ { "resource", COMPL_RESOURCE },
+ { "auth", COMPL_AUTH },
+ { "request", COMPL_REQUEST },
+ { "events", COMPL_EVENTS },
+ { "eventsid", COMPL_EVENTSID },
+ { "pgp", COMPL_PGP },
+ { "color", COMPL_COLOR },
+ { "otr", COMPL_OTR },
+ { "ortpolicy", COMPL_OTRPOLICY },
+ { "yesno", 0 },
+ { NULL, 0 },
+};
+#define MLUA_YESNO_POS ( 21 )
+
+typedef struct {
+ int reference;
+ int parse_args;
+ lua_State *L;
+} lua_command_callback_t;
+
+static GSList *lua_added_commands = NULL;
+
+static GSList *lua_added_categories = NULL;
+
+// returns true if string contains errors - unclosed quotes or unvalued option
+/// command arguments table
+/// It can parse barewords (with escapes), double-quoted strings (with escapes), single-quoted strings (without escapes), options and arguments.
+/// Arguments are separated only by whitespace, so, consequential quoted strings or barewords are one argument.
+/// This strings are equal:
+/// * ab\ cd\'e\\f\"
+/// * "ab cd'e\\f\""
+/// * 'ab cd'\''e\f"'
+/// * ab" cd'"'e\f"'
+/// Returned table have option names as keys, option values as values, and arguments as sequential members. -- option is supported.
+/// Example: "-t jid -m 9 -- -aa bb cc" will result in { t = 'jid', m = 9, '-aa', 'bb', 'cc' }
+/// Implementation notes:
+/// * All options should be before any arguments. First non-option argument disables options recognizing.
+/// * EOL is a cutting edge, that can cut much earlier, than you expect. Non-closed quoted strings lose leading quote and option without value loses its leading minus.
+/// * Escape character just before EOL is preserved.
+static int luaL_pushargs (lua_State *L, const char *args)
+{
+ const char *p = args;
+ luaL_Buffer buf;
+ int option = 0;
+ int options = 1;
+
+ lua_newtable (L);
+ luaL_buffinit (L, &buf);
+ while (*p) {
+ if (*p == ' ') {
+ ++p;
+ continue;
+ }
+ if (*p == '"') { // soft quote
+ const char *start = ++p;
+ while (*p) {
+ if (*p == '\\') { // escape symbol
+ luaL_addlstring (&buf, start, p - start);
+ start = ++p;
+ if (*p) // skip symbol
+ ++p;
+ else // add last \ in line
+ luaL_addchar (&buf, '\\');
+ } else if (*p == '"') // quotation end
+ break;
+ else
+ ++p;
+ }
+ luaL_addlstring (&buf, start, p - start); // XXX: eats quote on eol
+ if (*p)
+ ++p;
+ else
+ return 1;
+ } else if (*p == '\'') { // no-escape quote
+ const char *start = ++p;
+ while (*p && *p != '\'')
+ p++;
+ luaL_addlstring (&buf, start, p - start); // XXX: eats quote on eol
+ if (*p)
+ ++p;
+ else
+ return 1;
+ } else { // bareword
+ const char *start = p;
+ while (*p) {
+ if (*p == '\\') {
+ luaL_addlstring (&buf, start, p - start);
+ start = ++p;
+ if (*p) // skip symbol
+ ++p;
+ else // add last \ in line
+ luaL_addchar (&buf, '\\');
+ } else if (*p == ' ' || *p == '\'' || *p == '"')
+ break;
+ else
+ ++p;
+ }
+ luaL_addlstring (&buf, start, p - start);
+ }
+
+ if ((!*p) || *p == ' ') {
+ const char *result;
+ luaL_pushresult (&buf);
+
+ result = lua_tostring (L, -1);
+ if (options && !option && *result == '-') { // option
+ if (*(result+1) == '-' && !*(result+2)) { // end of options
+ lua_pop (L, 1);
+ options = 0;
+ } else { // option name
+ lua_pushstring (L, result + 1);
+ lua_remove (L, -2);
+ option = 1;
+ }
+ } else if (option) { // opion value
+ lua_settable (L, -3);
+ option = 0;
+ } else { // argument
+ options = 0;
+ luaL_ref (L, -2);
+ }
+
+ luaL_buffinit (L, &buf);
+ }
+ }
+
+ if (option) {
+ luaL_ref (L, -2); // XXX: eats minus on eol
+ return 1;
+ }
+
+ return 0;
+}
+
+/// command function
+/// Function to handle newly registered command.
+/// Argument type passed depends on how command is registered.
+/// A: string (arguments) or command arguments table
+static void lua_main_command_handler (char *args, lua_command_callback_t *cb)
+{
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+
+ if (cb->parse_args)
+ luaL_pushargs (cb->L, args);
+ else
+ lua_pushstring (cb->L, args);
+
+ if (lua_pcall (cb->L, 1, 0, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Command execution error: %s", lua_tostring (cb->L, -1));
+ lua_pop (cb->L, 1);
+ }
+}
+
+/// main.parse_args
+/// Function to parse command argument string to command arguments table.
+/// A: string
+/// R: table
+static int lua_main_parse_args (lua_State *L)
+{
+ luaL_pushargs (L, luaL_checkstring (L, 1));
+ return 1;
+}
+
+/// main.add_category
+/// Adds completion category.
+/// A: table (values are used as words for completion, optional)
+/// R: integer (category id, in fact completion type) or nil
+static int lua_main_add_category (lua_State *L)
+{
+ guint cid;
+
+ if (lua_gettop (L) > 0) {
+ luaL_argcheck (L, lua_type (L, 1) == LUA_TTABLE, 1, "table expected");
+ cid = compl_new_category ();
+ if (cid) {
+ lua_pushnil (L);
+ while (lua_next (L, 1)) {
+ char *word = to_utf8 (luaL_checkstring (L, -1));
+ if (word) {
+ compl_add_category_word (cid, word);
+ g_free (word);
+ }
+ lua_pop (L, 1);
+ }
+ }
+ } else
+ cid = compl_new_category ();
+
+ if (cid) {
+ lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
+ lua_pushinteger (L, cid);
+ } else
+ lua_pushnil (L);
+
+ return 1;
+}
+
+/// main.del_category
+/// Removes completion category.
+/// A: integer (category id)
+static int lua_main_del_category (lua_State *L)
+{
+ guint cid = luaL_checkinteger (L, 1);
+ compl_del_category (cid);
+ lua_added_categories = g_slist_remove (lua_added_categories, (gpointer) cid);
+ return 0;
+}
+
+/// main.add_completion
+/// Adds word to a completion list.
+/// A: integer (completion group id), string (word)
+static int lua_main_add_completion (lua_State *L)
+{
+ guint cid = luaL_checkinteger (L, 1);
+ char *word = to_utf8 (luaL_checkstring (L, 2)); // XXX
+ compl_add_category_word (cid, word);
+ g_free (word);
+ return 0;
+}
+
+/// main.del_completion
+/// Removes word from a completion list.
+/// A: integer (completion group id), string (word)
+static int lua_main_del_completion (lua_State *L)
+{
+ guint cid = luaL_checkinteger (L, 1);
+ char *word = to_utf8 (luaL_checkstring (L, 2)); // XXX
+ compl_del_category_word (cid, word);
+ g_free (word);
+ return 0;
+}
+
+/// main.command
+/// Associates or breaks association between mcabber command name and lua function.
+/// To unregister command omit function argument.
+/// If you specify a third argument, table (even empty), function will return completion group id or nothing.
+/// You can also specify a string name (see completion type) instead of table, for non-builtin, you can just pass integer id.
+/// Note, that for now there are no way to unregister completion group, so, resources can be exausted easily.
+/// Also note, that it ignores keys in a completion table.
+/// A: string (command name), command function (optional), boolean (parse args flag, optional), table (completions, optional)/completion type (or integer comletion group id, optional)
+/// R: completion type (integer completion group id or string for builtin types, optional)
+static int lua_main_command (lua_State *L)
+{
+ const char *name = luaL_checkstring (L, 1); // XXX: to_utf8? looks like no :/
+ lua_command_callback_t *cb;
+ int top = lua_gettop (L);
+ if (top > 1) { // Register
+ guint cid = 0;
+ int parse = 0;
+ luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+
+ if (top > 2) { // parse flag provided
+ parse = lua_toboolean (L, 3);
+
+ if (top > 3) { // Completions provided
+ if (lua_type (L, 4) == LUA_TTABLE) {
+ cid = compl_new_category ();
+ if (cid) {
+ lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
+ lua_pushnil (L);
+ while (lua_next (L, 4)) {
+ char *word = to_utf8 (luaL_checkstring (L, -1));
+ compl_add_category_word (cid, word);
+ lua_pop (L, 1);
+ g_free (word);
+ }
+ }
+ } else
+ cid = luaL_checkenum (L, 4, lua_completion_type);
+ }
+ }
+
+ cb = luaL_malloc (L, sizeof (lua_command_callback_t));
+ lua_pushvalue (L, 2);
+ cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb->parse_args = parse;
+ cb->L = L;
+ cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
+
+ lua_added_commands = g_slist_prepend (lua_added_commands, g_strdup (name));
+
+ if (cid) {
+ luaL_pushenum (L, cid, lua_completion_type);
+ return 1;
+ }
+ } else { // Unregister
+ GSList *el = g_slist_find_custom (lua_added_commands, name, (GCompareFunc) g_strcmp0);
+
+ cb = cmd_del (name);
+ if (cb) {
+ luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ luaL_free (cb->L, cb);
+ }
+
+ if (el) {
+ g_free (el->data);
+ lua_added_commands = g_slist_delete_link (lua_added_commands, el);
+ }
+ }
+ return 0;
+}
+
+// TIMER
+
+typedef struct {
+ int reference;
+ guint source;
+ lua_State *L;
+} lua_timer_callback_t;
+
+static GSList *lua_timers = NULL;
+
+static void lua_timer_callback_destroy (lua_timer_callback_t *cb)
+{
+ luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ lua_timers = g_slist_remove (lua_timers, (gpointer) cb->source);
+ luaL_free (cb->L, cb);
+}
+
+/// timer function
+/// Function, that will be called periodically until it returns false.
+/// R: boolean
+static gboolean lua_timer_callback (lua_timer_callback_t *cb)
+{
+ int ret;
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ if (lua_pcall (cb->L, 0, 1, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Timer callback execution error: %s", lua_tostring (cb->L, -1));
+ lua_pop (cb->L, 1);
+ return FALSE;
+ }
+ ret = lua_toboolean (cb->L, -1);
+ lua_pop (cb->L, 1);
+ return ret;
+}
+
+/// main.timer
+/// Creates new timer function, that will be called periodically.
+/// A: integer (interval, seconds), timer function
+static int lua_main_timer (lua_State *L)
+{
+ int interval = luaL_checkint (L, 1);
+ guint source;
+ lua_timer_callback_t *cb;
+ luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+
+ cb = luaL_malloc (L, sizeof (lua_timer_callback_t));
+ cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb->L = L;
+
+ source = g_timeout_add_seconds_full (MLUA_SOURCE_PRIORITY, interval, (GSourceFunc) lua_timer_callback, cb, (GDestroyNotify) lua_timer_callback_destroy);
+ cb->source = source;
+ lua_timers = g_slist_prepend (lua_timers, (gpointer) source);
+
+ return 0;
+}
+
+// BACKGROUND PIPE READING
+
+typedef struct {
+ lua_State *L;
+ guint source;
+ FILE *fd;
+ int reference;
+} lua_bgread_callback_t;
+
+static GSList *lua_bgreads = NULL;
+
+static gchar lua_bgread_buffer[MLUA_BGREAD_BUFFER];
+
+static void lua_bgread_callback_destroy (lua_bgread_callback_t *cb)
+{
+ luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ pclose (cb->fd); // Not necessary?
+ lua_bgreads = g_slist_remove (lua_bgreads, (gpointer) cb->source);
+ luaL_free (cb->L, cb);
+}
+
+/// background reading function
+/// Function, that processes output from pipe in asynchroneous way.
+/// A: string (data) or nil (eof)
+/// R: boolean (false if reading should be terminated)
+static gboolean lua_bgread_callback (GIOChannel *source, GIOCondition condition, lua_bgread_callback_t *cb)
+{
+ int ret = TRUE;
+
+ if (condition | G_IO_IN) { // data
+ while (TRUE) {
+ gsize read = 0;
+ g_io_channel_read_chars (source, lua_bgread_buffer, MLUA_BGREAD_BUFFER, &read, NULL);
+ if (!read) // exhausted
+ break;
+
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ lua_pushlstring (cb->L, lua_bgread_buffer, read);
+ if (lua_pcall (cb->L, 1, 1, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Bgread callback execution error: %s", lua_tostring (cb->L, -1));
+ lua_pop (cb->L, 1);
+ return FALSE;
+ }
+ ret = lua_toboolean (cb->L, -1);
+ lua_pop (cb->L, 1);
+ if (!ret) // enough
+ return FALSE;
+ }
+ }
+
+ if (condition & ~G_IO_IN) { // err or hup
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ lua_pushnil (cb->L);
+ if (lua_pcall (cb->L, 1, 1, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Bgread callback execution error: %s", lua_tostring (cb->L, -1));
+ lua_pop (cb->L, 1);
+ return FALSE;
+ }
+ ret = lua_toboolean (cb->L, -1);
+ lua_pop (cb->L, 1);
+ }
+
+ return ret;
+}
+
+/// main.bgread
+/// Runs specified command and passes its output to a given function.
+/// A: string (command), background reading function
+static int lua_main_bgread (lua_State *L)
+{
+ const char *command = luaL_checkstring (L, 1);
+ lua_bgread_callback_t *cb;
+ FILE *fd;
+ GIOChannel *channel;
+ const char *charset = NULL;
+ guint source;
+ luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+
+ fd = popen (command, "r");
+ if (!fd) {
+ lua_pushstring (L, "Error opening pipe");
+ lua_error (L);
+ }
+
+ channel = g_io_channel_unix_new (fileno (fd));
+ // We, most likely, need this,
+ // But we cannot use this,
+ // It will block.
+ //if (!g_get_charset (&charset))
+ g_io_channel_set_encoding (channel, charset, NULL);
+ g_io_channel_set_buffered (channel, FALSE);
+ g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
+ g_io_channel_set_close_on_unref (channel, TRUE);
+
+ cb = luaL_malloc (L, sizeof (lua_bgread_callback_t));
+ cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb->L = L;
+ cb->fd = fd;
+
+ source = g_io_add_watch_full (channel, MLUA_SOURCE_PRIORITY, G_IO_IN|G_IO_HUP|G_IO_ERR, (GIOFunc) lua_bgread_callback, cb, (GDestroyNotify) lua_bgread_callback_destroy);
+ cb->source = source;
+ lua_bgreads = g_slist_prepend (lua_bgreads, (gpointer) source);
+
+ // unref?
+
+ return 0;
+}
+
+// HOOKÂ HANDLING
+
+typedef struct {
+ lua_State *L; // lua environment for handler use
+ int nameref; // reference to hook name string
+ int cbref; // reference to hook handler function
+ int selfref; // self-reference to object
+ guint hid; // hook id for object destruction
+} lua_hook_t;
+
+/// hook handler result
+/// What to do with hook processing afterwards
+/// G:
+static const string2enum_t lua_hook_handler_result[] = {
+ { "proceed", HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS },
+ { "stop", HOOK_HANDLER_RESULT_NO_MORE_HANDLER },
+ { "drop", HOOK_HANDLER_RESULT_NO_MORE_HANDLER_DROP_DATA },
+ { NULL, HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS },
+};
+
+/// hook handler priority
+/// Feel free to specify just a number instead of some preset value.
+/// G:
+static const string2enum_t lua_hook_handler_priority[] = {
+ { "high", G_PRIORITY_HIGH },
+ { "default", G_PRIORITY_DEFAULT },
+ { "idle-high", G_PRIORITY_HIGH_IDLE },
+ { "idle", G_PRIORITY_DEFAULT_IDLE },
+ { "low", G_PRIORITY_LOW },
+ { NULL, G_PRIORITY_DEFAULT },
+};
+
+/// hook function
+/// Function to be called, when hook will be processsed.
+/// XXX: we can provide object as argument, but is this necessary?
+/// A: table (arguments hash)
+/// R: hook handler result
+static guint lua_hook_cb (const gchar *hookid, hk_arg_t *args, gpointer data)
+{
+ lua_hook_t *cb = data;
+ hk_arg_t *arg = args;
+ guint ret = HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ lua_State *L = cb -> L;
+
+ if (cb -> cbref == LUA_NOREF)
+ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+ lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> cbref);
+ if (!lua_isfunction (L, -1)) {
+ lua_pop (L, 1);
+ return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+
+ lua_newtable (L);
+ lua_pushliteral (L, "hook");
+ lua_pushstring (L, hookid);
+ lua_settable (L, -3);
+ while (arg->name != NULL) {
+ char *name = from_utf8 (arg->name);
+ char *value = from_utf8 (arg->value);
+ lua_pushstring (L, name);
+ lua_pushstring (L, value);
+ lua_settable (L, -3);
+ g_free (name);
+ g_free (value);
+ arg++;
+ }
+
+ if (lua_pcall (L, 1, 1, 0)) {
+ scr_log_print (LPRINT_NORMAL, "lua: Error in hook handler: %s", lua_tostring (L, -1));
+ lua_pop (L, 1);
+ } else {
+ switch (lua_type (L, -1)) {
+ case LUA_TSTRING:
+ case LUA_TNUMBER:
+ ret = luaL_checkenum (L, -1, lua_hook_handler_result);
+ break;
+ default:
+ ret = HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ break;
+ }
+ lua_pop (L, 1);
+ }
+
+ return ret;
+}
+
+/// main.hook
+/// Installs hook handler, returns an object, that you need to keep until
+/// hook handling is no more needed.
+/// A: string (hook name), hook function, integer (priority, optional)
+/// R: userdata (hook object)
+static int lua_main_hook (lua_State *L)
+{
+ const char *hook_name = luaL_checkstring (L, 1);
+ int priority = G_PRIORITY_DEFAULT;
+ lua_hook_t *cb;
+
+ luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+
+ if (lua_gettop (L) > 2)
+ priority = luaL_checkenum (L, 3, lua_hook_handler_priority);
+
+ cb = lua_newuserdata (L, sizeof (lua_hook_t));
+ luaL_getmetatable (L, "mcabber.hook");
+ lua_setmetatable (L, -2);
+
+ lua_pushvalue (L, -1);
+ cb -> selfref = luaL_ref (L, LUA_REGISTRYINDEX);
+ lua_pushvalue (L, 1);
+ cb -> nameref = luaL_ref (L, LUA_REGISTRYINDEX);
+ lua_pushvalue (L, 2);
+ cb -> cbref = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb -> L = L;
+ cb -> hid = hk_add_handler (lua_hook_cb, hook_name, priority, cb);
+
+ return 1;
+}
+
+static void lua_mcabber_unregister_hook (lua_State *L, lua_hook_t *cb)
+{
+ const char *name;
+
+ if (!cb -> hid || cb -> nameref == LUA_NOREF)
+ return;
+
+ lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
+ name = lua_tostring (L, -1);
+ if (name) {
+ hk_del_handler (name, cb -> hid);
+ cb -> hid = 0;
+ }
+
+ lua_pop (L, 1);
+}
+
+/// hook:del
+/// Unregisters given hook handler from mcabber. Object will be destroyed later.
+static int lua_mcabber_hook_del (lua_State *L)
+{
+ lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.hook");
+ luaL_argcheck (L, cb != NULL, 1, "mcabber hook object expected");
+ lua_mcabber_unregister_hook (L, cb);
+ if (cb -> selfref != LUA_NOREF) {
+ luaL_unref (L, LUA_REGISTRYINDEX, cb -> selfref);
+ cb -> selfref = LUA_NOREF;
+ }
+ return 0;
+}
+
+static int lua_mcabber_hook_gc (lua_State *L)
+{
+ lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.hook");
+ luaL_argcheck (L, cb != NULL, 1, "mcabber hook object expected");
+ lua_mcabber_unregister_hook (L, cb);
+ if (cb -> nameref != LUA_NOREF)
+ luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
+ if (cb -> cbref != LUA_NOREF)
+ luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
+ return 0;
+}
+
+static const luaL_Reg lua_mcabber_hook_reg_m[] = {
+ { "del", lua_mcabber_hook_del },
+ { "__gc", lua_mcabber_hook_gc },
+ { NULL, NULL },
+};
+
+static void lua_hook_init (lua_State *L)
+{
+ luaL_newmetatable (L, "mcabber.hook");
+ lua_pushvalue (L, -1);
+ lua_setfield (L, -2, "__index");
+ luaL_register (L, NULL, lua_mcabber_hook_reg_m);
+ lua_pop (L, 1);
+}
+
+#if 0
+// OPTION GUARDS
+
+GSList *lua_installed_guards = NULL;
+
+typedef struct {
+ lua_State *L; // lua environment for handler use
+ int nameref; // reference to key name string
+ int cbref; // reference to guard function
+// int objref; // self_reference to object
+ guint hid; // hook id for object destruction
+} lua_guard_t;
+
+/// guard function
+/// Function to be called, when option changes it's value.
+/// Old option value is still accessible through main.option.
+/// A: string (key), string (new value)
+/// R: string (value to save in hash table)
+static gchar *lua_guard_cb (const gchar *key, const gchar *value)
+{
+ lua_guard_t *cb = ;// FIXME
+ lua_State *L = cb -> L;
+
+ if (cb -> cbref == LUA_NOREF)
+ return g_strdup (value);
+
+ lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> cbref);
+ if (!lua_isfunction (L, -1)) {
+ lua_pop (L, 1);
+ return g_strdup (value);
+ }
+
+ lua_pushstring (L, key);
+ lua_psuhstring (L, value);
+
+ if (lua_pcall (L, 2, 1, 0)) {
+ scr_log_print (LPRINT_NORMAL, "lua: Error in hook handler: %s", lua_tostring (L, -1));
+ lua_pop (L, 1);
+ return g_strdup (value);
+ }
+
+ return g_strdup (lua_tostring (L, -1));
+}
+
+/// main.guard
+/// Installs option guard for given option. Returns guard object, that
+/// should be kept around as long, as guard is needed.
+/// A: string (option name), guard function
+/// R: userdata (guard object)
+static int lua_main_guard (lua_State *L)
+{
+ const char *name = luaL_checkstring (L, 1);
+ int priority = G_PRIORITY_DEFAULT;
+ lua_guard_t *cb;
+
+ luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+
+ cb = lua_newuserdata (L, sizeof (lua_guard_t));
+ luaL_getmetatable (L, "mcabber.guard");
+ lua_setmetatable (L, -2);
+
+ lua_pushvalue (L, 1);
+ cb -> nameref = luaL_ref (L, LUA_REGISTRYINDEX);
+ lua_pushvalue (L, 2);
+ cb -> cbref = luaL_ref (L, LUA_REGISTRYINDEX);
+ cb -> L = L;
+
+ settings_set_guard (name, lua_guard_cb)
+
+ return 1;
+}
+
+static void lua_mcabber_unregister_guard (lua_State *L, lua_guard_t *cb)
+{
+ const char *name;
+
+ if (cb -> nameref == LUA_NOREF)
+ return;
+
+ lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
+ name = lua_tostring (L, -1);
+ if (name) {
+ settings_del_guard (name);
+ luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
+ cb -> nameref = LUA_NOREF;
+ }
+
+ lua_pop (L, 1);
+}
+
+/// guard:del
+/// Unregisters given option guard from mcabber. Object will be destroyed later.
+static int lua_mcabber_guard_del (lua_State *L)
+{
+ lua_guard_t *cb = luaL_checkudata (L, 1, "mcabber.guard");
+ luaL_argcheck (L, cb != NULL, 1, "mcabber guard object expected");
+ lua_mcabber_unregister_guard (L, cb);
+ return 0;
+}
+
+static int lua_mcabber_guard_gc (lua_State *L)
+{
+ lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.guard");
+ luaL_argcheck (L, cb != NULL, 1, "mcabber guard object expected");
+ lua_mcabber_unregister_guard (L, cb);
+ if (cb -> cbref != LUA_NOREF)
+ luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
+ return 0;
+}
+
+static const luaL_Reg lua_mcabber_hook_reg_m[] = {
+ { "del", lua_mcabber_guard_del },
+ { "__gc", lua_mcabber_guard_gc },
+ { NULL, NULL },
+};
+
+static void lua_guard_init (lua_State *L)
+{
+ luaL_newmetatable (L, "mcabber.guard");
+ lua_pushvalue (L, -1);
+ lua_setfield (L, -2, "__index");
+ luaL_register (L, NULL, lua_mcabber_guard_reg_m);
+ lua_pop (L, 1);
+}
+
+static void lua_guard_uninit (lua_State *L)
+{
+}
+#endif
+
+// MAIN INITIALIZATION CODE
+
+#ifdef LLM_LOG_HANDLER
+// FIXME: this should not be here
+guint lua_lm_log_handler_id;
+
+void lua_lm_log_handler (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer ignore)
+{
+ if (settings_opt_get_int ("lua_lm_debug"))
+ scr_log_print (LPRINT_LOGNORM, "%s: %s", domain, message);
+}
+#endif
+
+static void do_lua(char *arg, lua_State *L)
+{
+ if (luaL_loadbuffer (L, arg, strlen (arg), "line")) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Compilation error: %s", lua_tostring (L, -1));
+ lua_pop (L, 1);
+ return;
+ }
+
+ if (lua_pcall (L, 0, 0, 0)) {
+ scr_log_print (LPRINT_NORMAL, "lua: Runtime error: %s", lua_tostring(L, -1));
+ lua_pop (L, 1);
+ return;
+ }
+}
+
+static void *lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
+ if (nsize == 0) {
+ g_free (ptr);
+ return NULL;
+ } else
+ return g_realloc (ptr, nsize);
+}
+
+#define reg(NAME) \
+ { #NAME, lua_main_##NAME },
+static const luaL_Reg lua_reg_main[] = {
+ reg ( yesno )
+#ifdef LLM_CONNECTION_ENABLE
+ reg ( connection )
+#endif
+ reg ( log )
+ reg ( option )
+ reg ( alias )
+ reg ( binding )
+ reg ( fileoption )
+ reg ( add_feature )
+ reg ( del_feature )
+ reg ( event )
+ reg ( parse_args )
+ reg ( add_category )
+ reg ( del_category )
+ reg ( add_completion )
+ reg ( del_completion )
+ reg ( command )
+ reg ( print_info )
+ reg ( beep )
+ reg ( run )
+ reg ( status )
+ reg ( roster )
+ reg ( current_buddy )
+ reg ( full_jid )
+ reg ( buddy_info )
+ reg ( timer )
+ reg ( alias )
+ reg ( binding )
+ reg ( fileoption )
+ reg ( add_feature )
+ reg ( del_feature )
+ reg ( event )
+ reg ( parse_args )
+ reg ( add_category )
+ reg ( del_category )
+ reg ( add_completion )
+ reg ( del_completion )
+ reg ( command )
+ reg ( print_info )
+ reg ( beep )
+ reg ( run )
+ reg ( status )
+ reg ( roster )
+ reg ( current_buddy )
+ reg ( full_jid )
+ reg ( buddy_info )
+ reg ( timer )
+ reg ( bgread )
+ reg ( hook )
+ { NULL, NULL },
+};
+#undef reg
+
+const gchar *g_module_check_init (GModule *module)
+{
+ lua = lua_newstate (lua_alloc, NULL);
+ if (!lua)
+ return "Lua initialization error";
+ else
+ return NULL;
+}
+
+void g_module_unload (GModule *module)
+{
+ if (lua) {
+ lua_close (lua);
+ lua = NULL;
+ }
+}
+
+static void mlua_init (void)
+{
+ luaL_openlibs (lua);
+
+ luaL_register (lua, "main", lua_reg_main);
+ lua_pop (lua, 1); // XXX
+ lua_register (lua, "dopath", lua_global_dopath);
+ lua_register (lua, "print", lua_global_print );
+
+ {
+ int cid = compl_new_category ();
+
+ if (cid) {
+ const string2enum_t *word = lua_yesno;
+ lua_completion_type[MLUA_YESNO_POS].value = cid;
+ lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
+ while (word->string) {
+ compl_add_category_word (cid, word->string);
+ ++word;
+ }
+ }
+ }
+
+ cmd_add ("lua", "Evaluate lua string", 0, 0, (void (*) (char *p)) do_lua, lua);
+
+#ifdef LLM_LOG_HANDLER
+ // FIXME: this should not be here.
+ lua_lm_log_handler_id = g_log_set_handler ("lua-lm", G_LOG_LEVEL_MASK, (GLogFunc) lua_lm_log_handler, NULL);
+#endif
+
+ lua_hook_init (lua);
+
+ {
+ char *initfile = expand_filename (settings_opt_get ("lua_init_filename"));
+
+ if (!initfile)
+ scr_log_print (LPRINT_LOGNORM, "lua: Cannot determine config file name");
+ else {
+ if (luaL_loadfile(lua, initfile)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Unable to compile rc file: %s", lua_tostring (lua, -1));
+ lua_pop (lua, 1);
+ } else if (lua_pcall (lua, 0, LUA_MULTRET, 0)) {
+ scr_log_print (LPRINT_LOGNORM, "lua: Runtime error in rc file: %s", lua_tostring(lua, -1));
+ lua_pop (lua, 1);
+ } else
+ scr_log_print (LPRINT_LOGNORM, "lua: Loaded %s", initfile);
+ g_free (initfile);
+ }
+ }
+
+ {
+ hk_arg_t args[] = {
+ { NULL, NULL },
+ };
+ hk_run_handlers("hook-lua-start", args);
+ }
+}
+
+static void lua_events_cancel (gpointer data, gpointer ignore)
+{
+ lua_event_callback_t *cb = data;
+ const char *evid;
+ if (cb->evid == LUA_NOREF)
+ return;
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->evid);
+ evid = lua_tostring (cb ->L, -1);
+ evs_callback (evid, EVS_CONTEXT_CANCEL, "Module unloading");
+}
+
+static void lua_events_destroy (gpointer data, gpointer ignore)
+{
+ lua_event_callback_t *cb = data;
+ const char *evid;
+ if (cb->evid == LUA_NOREF)
+ return;
+ lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->evid);
+ evid = lua_tostring (cb ->L, -1);
+ evs_del (evid);
+}
+
+static void lua_bgreads_destroy (guint source, gpointer ignore)
+{
+ g_source_remove (source);
+}
+
+static void lua_timers_destroy (guint source, gpointer ignore)
+{
+ g_source_remove (source);
+}
+
+static void lua_features_destroy (char *xmlns, gpointer ignore)
+{
+ xmpp_del_feature (xmlns);
+ g_free (xmlns);
+}
+
+static void lua_commands_destroy (char *name, gpointer ignore)
+{
+ lua_command_callback_t *cb = cmd_del (name);
+ if (cb) {
+ luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
+ luaL_free (cb->L, cb);
+ }
+ g_free (name);
+}
+
+static void lua_categories_destroy (guint id, gpointer ignore)
+{
+ compl_del_category (id);
+}
+
+static void mlua_uninit (void)
+{
+ if (lua) {
+ hk_arg_t args[] = {
+ { NULL, NULL },
+ };
+ hk_run_handlers ("hook-lua-quit", args);
+
+ // hook handlers will be unregistered upon objects destruction
+
+ g_slist_foreach (lua_bgreads, (GFunc) lua_bgreads_destroy, NULL);
+ g_slist_free (lua_bgreads);
+ lua_bgreads = NULL;
+
+ g_slist_foreach (lua_timers, (GFunc) lua_timers_destroy, NULL);
+ g_slist_free (lua_timers);
+ lua_timers = NULL;
+
+ g_slist_foreach (lua_events, (GFunc) lua_events_cancel, NULL);
+ g_slist_foreach (lua_events, (GFunc) lua_events_destroy, NULL);
+ g_slist_free (lua_events);
+ lua_events = NULL;
+
+ g_slist_foreach (lua_added_features, (GFunc) lua_features_destroy, NULL);
+ g_slist_free (lua_added_features);
+ lua_added_features = NULL;
+
+ g_slist_foreach (lua_added_commands, (GFunc) lua_commands_destroy, NULL);
+ g_slist_free (lua_added_commands);
+ lua_added_commands = NULL;
+
+ g_slist_foreach (lua_added_categories, (GFunc) lua_categories_destroy, NULL);
+ g_slist_free (lua_added_categories);
+ lua_added_categories = NULL;
+
+ cmd_del ("lua");
+
+ lua_close (lua);
+ lua = NULL;
+
+#ifdef LLM_LOG_HANDLER
+ // FIXME: shouldn't be here
+ g_log_remove_handler ("lua-lm", lua_lm_log_handler_id);
+#endif
+ }
+}
+
--- a/main.c Fri Apr 02 20:00:20 2010 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1791 +0,0 @@
-
-/* Copyright 2009 Myhailo Danylenko
-
-This file is part of mcabber-lua.
-
-mcabber-lua 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, see <http://www.gnu.org/licenses/>. */
-
-#include <glib.h>
-#include <gmodule.h> // g_module_*
-#include <lua.h>
-#include <lauxlib.h>
-#include <lualib.h>
-#include <stdio.h>
-#include <stdlib.h> // getenv
-#include <string.h> // strcmp
-
-#include <mcabber/logprint.h> // scr_log_print
-#include <mcabber/screen.h> // scr_Beep, scr_WriteIncomingMessage
-#include <mcabber/hbuf.h> // HBB_PREFIX_INFO
-#include <mcabber/commands.h> // process_command, cmd_add, cmd_del
-#include <mcabber/xmpp.h> // xmpp_getstatus, xmpp_getstatusmsg, lconnection
-#include <mcabber/xmpp_helper.h> // xmpp_add_feature, xmpp_del_feature
-#include <mcabber/roster.h> // imstatus2char, foreach_buddy, buddy_*, current_buddy, BUDDATA, ROSTER_TYPE_*
-#include <mcabber/utils.h> // from_utf8, jidtodisp
-#include <mcabber/hooks.h> // hk_add_handler, hk_del_handler
-#include <mcabber/settings.h> // settings_set, settings_del, settings_get
-#include <mcabber/compl.h> // compl_new_category, compl_add_category_word, compl_del_category_word
-#include <mcabber/events.h> // evs_*
-#include <mcabber/modules.h> // module_info_t
-
-#include "config.h"
-#include "util.h"
-
-// module description
-static void mlua_init (void);
-static void mlua_uninit (void);
-
-#ifdef LLM_LOG_HANDLER
-#define DESCRIPTION ( \
- "Lua scripting interface\n" \
- "Recognizes options lua_init_file, lua_hook_function and lua_lm_debug\n" \
- "Provides command /lua" )
-#else
-#define DESCRIPTION ( \
- "Lua scripting interface\n" \
- "Recognizes options lua_init_file and lua_hook_function\n" \
- "Provides command /lua" )
-#endif
-
-static module_info_t info_lua_experimental = {
- .branch = "experimental",
- .api = 12,
- .version = PROJECT_VERSION,
- .description = DESCRIPTION,
- .requires = NULL,
- .init = mlua_init,
- .uninit = mlua_uninit,
- .next = NULL,
-};
-
-module_info_t info_lua = {
- .branch = "dev",
- .api = 11,
- .version = PROJECT_VERSION,
- .description = DESCRIPTION,
- .requires = NULL,
- .init = mlua_init,
- .uninit = mlua_uninit,
- .next = &info_lua_experimental,
-};
-
-// global lua state object, necessary for uninitialization function
-static lua_State *lua = NULL;
-
-// caller sould g_free result
-static char *mcabber_config_filename (const char *file)
-{
- const char *home = getenv ("HOME");
- if (!home)
- return NULL;
- return g_strconcat (home, "/.mcabber/", file ? file : "", NULL);
-}
-
-/// print
-/// Prints its arguments to log with default priority.
-/// A: something, ...
-static int lua_global_print (lua_State *L)
-{
- int top = lua_gettop (L);
- int i;
- luaL_Buffer B;
- luaL_buffinit (L, &B);
- for (i = 1; i <= top; i++) {
- int type = lua_type (L, i);
- if (i > 1)
- luaL_addchar (&B, '\t');
- if (type == LUA_TSTRING) {
- size_t len;
- const char *str = lua_tolstring (L, i, &len);
- luaL_addlstring (&B, str, len);
- } else if (type == LUA_TNUMBER)
- luaL_addstring (&B, lua_tostring (L, i)); // XXX: modifies
- else if (type == LUA_TBOOLEAN) {
- if (lua_toboolean (L, i))
- luaL_addstring (&B, "true");
- else
- luaL_addstring (&B, "false");
- } else if (type == LUA_TNIL)
- luaL_addstring (&B, "nil");
- else {
- char xbuf[9];
- luaL_addstring (&B, luaL_typename (L, i));
- luaL_addstring (&B, ": 0x");
- snprintf (&xbuf[0], 9, "%08x", (int) lua_topointer (L, i));
- luaL_addlstring (&B, xbuf, 8); // XXX
- }
- }
- luaL_pushresult (&B);
-
- scr_log_print (LPRINT_LOGNORM | LPRINT_NOTUTF8, lua_tostring (L, -1));
- return 0;
-}
-
-/// dopath
-/// Loads lua file from default location.
-/// XXX: g_filename_from_utf8?
-/// A: string (filename, without ".lua")
-/// R: string (error message, optional)
-static int lua_global_dopath (lua_State *L)
-{
- const char *name = luaL_checkstring (L, 1);
- size_t size = lua_objlen (L, 1);
- char *path;
- int ret = 0;
- if (size > 4 && !strncmp (name + size - 4, ".lua", 4))
- path = mcabber_config_filename (name);
- else {
- char *fname = g_strconcat (name, ".lua", NULL);
- path = mcabber_config_filename (fname);
- g_free (fname);
- }
-
- if ((ret = luaL_loadfile (L, path)))
- scr_log_print (LPRINT_LOGNORM, "lua: Unable to compile file %s: %s", path, lua_tostring (L, -1));
- else if ((ret = lua_pcall (L, 0, LUA_MULTRET, 0)))
- scr_log_print (LPRINT_LOGNORM, "lua: Runtime error in file %s: %s", path, lua_tostring (L, -1));
- g_free (path);
-
- if (ret)
- return 1;
- else
- return 0;
-}
-
-/// yes or no ansvers
-/// G:
-static const string2enum_t lua_yesno[] = {
- { "1", 1 },
- { "0", 0 },
- { "enable", 1 },
- { "disable", 0 },
- { "true", 1 },
- { "false", 0 },
- { "on", 1 },
- { "off", 0 },
- { "yes", 1 },
- { "no", 0 },
- { NULL, -1 },
-};
-
-/// main.yesno
-/// According to yes or no ansvers returns true or false.
-/// If ansver is not recognized, returns nil.
-/// A: anything (string expected)
-/// R: boolean or nil
-static int lua_main_yesno (lua_State *L)
-{
- int type = lua_type (L, 1);
- if (type == LUA_TSTRING) {
- int ret = luaL_checkenum (L, 1, lua_yesno);
- if (ret == -1)
- lua_pushnil (L);
- else
- lua_pushboolean (L, ret);
- } else if (type == LUA_TNUMBER)
- lua_pushboolean (L, lua_tointeger (L, 1));
- else if (type != LUA_TBOOLEAN)
- lua_pushnil (L);
- return 1;
-}
-
-/// log print type
-/// G:
-static const string2enum_t lua_lprint[] = {
- { "normal", LPRINT_NORMAL },
- { "log", LPRINT_LOG },
- { "debug", LPRINT_DEBUG },
- { "notutf0", LPRINT_NOTUTF8 },
- { NULL, 0 },
-};
-
-/// roster type
-/// G:
-static const string2enum_t lua_roster_type[] = {
- { "user", ROSTER_TYPE_USER },
- { "group", ROSTER_TYPE_GROUP },
- { "agent", ROSTER_TYPE_AGENT },
- { "room", ROSTER_TYPE_ROOM },
- { "special", ROSTER_TYPE_SPECIAL },
- { NULL, 0 },
-};
-
-/// main.log
-/// Prints message to log.
-/// Note: most likely you need notutf8 flag enabled.
-/// A: log print type, message, message...
-static int lua_main_log (lua_State *L)
-{
- int type = luaL_checkenum_multi (L, 1, lua_lprint);
- lua_concat (L, lua_gettop (L) - 1);
- scr_log_print (type, lua_tostring (L, -1));
- return 0;
-}
-
-// expects table on top
-static void lua_options_callback (char *key, char *value, lua_State *L)
-{
- char *loc = from_utf8 (key);
- lua_pushstring (L, loc);
- g_free (loc);
- loc = from_utf8 (value);
- lua_pushstring (L, loc);
- g_free (loc);
- lua_settable (L, -3);
-}
-
-/// main.option
-/// Sets or gets value of mcabber option.
-/// You can specify nil as a value to delete option.
-/// If you omit option name, it returns hash table of all options.
-/// A: string (option name, optional), string (value, optional)
-/// R: string (value, optional)
-static int lua_main_option (lua_State *L)
-{
- int top = lua_gettop (L);
- if (top > 0) {
- char *name = to_utf8 (luaL_checkstring (L, 1));
- if (top > 1) { // Set
- if (lua_type (L, 2) == LUA_TNIL) // Unset
- settings_del (SETTINGS_TYPE_OPTION, name);
- else { // Set
- char *value = to_utf8 (luaL_checkstring (L, 2));
- settings_set (SETTINGS_TYPE_OPTION, name, value);
- g_free (value);
- }
- g_free (name);
- return 0;
- } else { // Get
- char *value = from_utf8 (settings_get (SETTINGS_TYPE_OPTION, name));
- if (value) {
- lua_pushstring (L, value);
- g_free (value);
- } else
- lua_pushnil (L);
- g_free (name);
- return 1;
- }
- } else { // List
- lua_newtable (L);
- settings_foreach (SETTINGS_TYPE_OPTION, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
- return 1;
- }
-}
-
-/// main.alias
-/// Sets or gets alias.
-/// You can specify nil as a command to delete alias.
-/// If you omit alias name, it will return hash table of all aliases.
-/// A: string (alias name, optional), string (command, optional)
-/// R: string (command, optional)
-static int lua_main_alias (lua_State *L)
-{
- int top = lua_gettop (L);
- if (top > 0) {
- char *name = to_utf8 (luaL_checkstring (L, 1));
- if (top > 1) { // Set
- if (lua_type (L, 2) == LUA_TNIL) { // Unset
- settings_del (SETTINGS_TYPE_ALIAS, name);
- compl_del_category_word (COMPL_CMD, name);
- } else { // Set
- char *value = to_utf8 (luaL_checkstring (L, 2));
- if (!settings_get (SETTINGS_TYPE_ALIAS, name))
- compl_add_category_word (COMPL_CMD, name);
- settings_set (SETTINGS_TYPE_ALIAS, name, value);
- g_free (value);
- }
- g_free (name);
- return 0;
- } else { // Get
- char *value = from_utf8 (settings_get (SETTINGS_TYPE_ALIAS, name));
- if (value) {
- lua_pushstring (L, value);
- g_free (value);
- } else
- lua_pushnil (L);
- g_free (name);
- return 1;
- }
- } else { // List
- lua_newtable (L);
- settings_foreach (SETTINGS_TYPE_ALIAS, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
- return 1;
- }
-}
-
-/// main.bind
-/// Sets or gets binding.
-/// You can specify nil as a command to unbind key.
-/// If you omit keycode, it will return hash table of all bindings.
-/// A: string (keycode, optional), string (command, optional)
-/// R: string (command, optional)
-static int lua_main_binding (lua_State *L)
-{
- int top = lua_gettop (L);
- if (top > 0) {
- // just to be sure...
- char *name = to_utf8 (luaL_checkstring (L, 1));
- if (top > 1) { // Set
- if (lua_type (L, 2) == LUA_TNIL) // Unset
- settings_del (SETTINGS_TYPE_BINDING, name);
- else { // Set
- char *value = to_utf8 (luaL_checkstring (L, 2));
- settings_set (SETTINGS_TYPE_BINDING, name, value);
- g_free (value);
- }
- g_free (name);
- return 0;
- } else { // Get
- char *value = from_utf8 (settings_get (SETTINGS_TYPE_BINDING, name));
- if (value) {
- lua_pushstring (L, value);
- g_free (value);
- } else
- lua_pushnil (L);
- g_free (name);
- return 1;
- }
- } else { // List
- lua_newtable (L);
- settings_foreach (SETTINGS_TYPE_BINDING, (void (*)(char *key, char *val, void *ud)) lua_options_callback, L);
- return 1;
- }
-}
-
-/// main.fileoption
-/// Gets option, expanding it as filename.
-/// A: string (option name)
-/// R: string (expanded option value) or nil
-static int lua_main_fileoption (lua_State *L)
-{
- char *fname = expand_filename (settings_opt_get (luaL_checkstring (L, 1)));
- if (fname) {
- lua_pushstring (L, fname);
- g_free (fname);
- } else
- lua_pushnil (L);
- return 1;
-}
-
-#ifdef LLM_CONNECTION_ENABLE
-/// main.connection
-/// Returns lightuserdata of mcabber's loudmouth connection.
-/// This can be very useful with lua-loudmouth, and not much otherwise.
-/// R: lightuserdata or nil
-static int lua_main_connection (lua_State *L)
-{
- if (xmpp_is_online ())
- lua_pushlightuserdata (L, lconnection);
- else
- lua_pushnil (L);
- return 1;
-}
-#endif
-
-/// main.print_info
-/// Prints a system message to buddy's window.
-/// A: string (jid), string (message)
-static int lua_main_print_info (lua_State *L)
-{
- char *jid = to_utf8 (luaL_checkstring (L, 1));
- char *to = jidtodisp (jid);
- char *mesg = to_utf8 (luaL_checkstring (L, 2));
- scr_write_incoming_message (to, mesg, 0, HBB_PREFIX_INFO, 0);
- g_free (mesg);
- g_free (to);
- g_free (jid);
- return 0;
-}
-
-/// main.beep
-/// Beeps with system speaker.
-static int lua_main_beep (lua_State *L)
-{
- scr_beep ();
- return 0;
-}
-
-/// main.run
-/// Runs specified mcabber command.
-/// A: string
-static int lua_main_run (lua_State *L)
-{
- process_command (luaL_checkstring (L, 1), TRUE);
- return 0;
-}
-
-/// main.status
-/// Returns your current status.
-/// R: string (status letter), string (status message)
-static int lua_main_status (lua_State *L)
-{
- char *sm = from_utf8 (xmpp_getstatusmsg ());
- lua_pushlstring (L, &imstatus2char[xmpp_getstatus ()], 1);
- lua_pushstring (L, sm);
- g_free (sm);
- return 2;
-}
-
-// expects table on top
-static void lua_rosterlist_callback (gpointer buddy, lua_State *L)
-{
- char *jid = from_utf8 (buddy_getjid (buddy));
- lua_pushnumber (L, lua_objlen (L, -1) + 1);
- lua_pushstring (L, jid);
- lua_settable (L, -3);
- g_free (jid);
-}
-
-/// main.roster
-/// Returns array of jids of buddies in roster.
-/// R: table
-static int lua_main_roster (lua_State *L)
-{
- lua_newtable (L);
- foreach_buddy (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM, (void (*) (gpointer buddy, void *data)) lua_rosterlist_callback, L);
- return 1;
-}
-
-/// main.current_buddy
-/// Returns jid of current selected buddy or sets current buddy to buddy with specified jid.
-/// A: string (optional)
-/// R: string (optional)
-static int lua_main_current_buddy (lua_State *L)
-{
- if (lua_gettop (L) > 0) { // Set
- // XXX: we need not convert to utf, RS works on jids/names in locale charset,
- // but will jidtodisp always correctly work on such tings?
- char *jid = jidtodisp (luaL_checkstring (L, 1));
- scr_roster_search (jid);
- g_free (jid);
- return 0;
- } else { // Get
- char *jid = from_utf8 (buddy_getjid (BUDDATA (current_buddy)));
- lua_pushstring (L, jid);
- g_free (jid);
- return 1;
- }
-}
-
-/// main.full_jid
-/// Returns full jid (with current resource) of specified buddy (or current, if not specified).
-/// Note, that if there are no resources online, it will return just what it got.
-/// A: string (jid, optional)
-/// R: string (jid)
-static int lua_main_full_jid (lua_State *L)
-{
- GList *buddy;
- GSList *resources;
- GSList *resource;
- if (lua_gettop (L) > 0) {
- char *jid = from_utf8 (luaL_checkstring (L, 1));
- buddy = buddy_search_jid (jid);
- g_free (jid);
- } else
- buddy = current_buddy;
- if (!buddy)
- return 0;
- resources = buddy_getresources (BUDDATA (buddy));
- if (!resources) {
- char *loc = from_utf8 (buddy_getjid (BUDDATA (buddy)));
- lua_pushstring (L, loc);
- g_free (loc);
- } else {
- char *jid = from_utf8 (buddy_getjid (BUDDATA (buddy)));
- char *res = from_utf8 (g_slist_last (resources)->data);
- lua_pushfstring (L, "%s%c%s", jid, JID_RESOURCE_SEPARATOR, res);
- for (resource = resources; resource; resource = g_slist_next (resource))
- g_free (resource->data);
- g_slist_free (resources);
- g_free (jid);
- g_free (res);
- }
- return 1;
-}
-
-typedef struct {
- lua_State *L;
- gpointer buddy;
-} lua_state_and_buddy_t; // :)
-
-/// resources table
-/// Hash table with resource name as keys and another hash tables as values.
-/// Inner tables contain resource-specific information: priority, status and message.
-static void lua_buddy_resources_callback (gpointer resource, lua_state_and_buddy_t *d)
-{
- char *loc = from_utf8 (resource);
- lua_pushstring (d->L, loc);
- g_free (loc);
- lua_createtable (d->L, 0, 3);
- lua_pushstring (d->L, "priority");
- lua_pushnumber (d->L, buddy_getresourceprio (d->buddy, resource));
- lua_settable (d->L, -3);
- lua_pushstring (d->L, "status");
- lua_pushlstring (d->L, &imstatus2char[buddy_getstatus (d->buddy, resource)], 1);
- lua_settable (d->L, -3);
- lua_pushstring (d->L, "message");
- loc = from_utf8 (buddy_getstatusmsg (d->buddy, resource));
- lua_pushstring (d->L, loc);
- g_free (loc);
- lua_settable (d->L, -3);
- lua_settable (d->L, -3);
- g_free (resource);
-}
-
-/// main.buddy_info
-/// Returns a hash table with information on specified buddy.
-/// Table contains fields type, name, onserver and resources (which points to resources table).
-/// A: string (jid)
-/// R: table
-static int lua_main_buddy_info (lua_State *L)
-{
- char *loc = to_utf8 (luaL_checkstring (L, 1));
- char *jid = jidtodisp (loc);
- GSList *buddy = roster_find (jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
- GSList *resources;
- lua_state_and_buddy_t snb;
- g_free (jid);
- g_free (loc);
-
- if (!buddy) {
- lua_pushnil (L);
- return 1;
- }
-
- lua_createtable (L, 0, 3);
- lua_pushstring (L, "type");
- luaL_pushenum (L, buddy_gettype (BUDDATA (buddy)), lua_roster_type);
- lua_settable (L, -3);
- lua_pushstring (L, "name");
- loc = from_utf8 (buddy_getname (BUDDATA (buddy)));
- lua_pushstring (L, loc);
- g_free (loc);
- lua_settable (L, -3);
- lua_pushstring (L, "onserver");
- lua_pushboolean (L, buddy_getonserverflag (BUDDATA (buddy)));
- lua_settable (L, -3);
- lua_pushstring (L, "resources");
- lua_createtable (L, 0, 0);
- snb.L = L;
- snb.buddy = BUDDATA (buddy);
- resources = buddy_getresources (BUDDATA (buddy));
- g_slist_foreach (buddy_getresources (BUDDATA (buddy)), (GFunc) lua_buddy_resources_callback, &snb);
- g_slist_free (resources);
- lua_settable (L, -3);
-
- return 1;
-}
-
-// XMPP DISCO FEATURES
-
-GSList *lua_added_features = NULL;
-
-/// main.add_feature
-/// Adds xmlns to disco#info features list.
-/// A: string (xmlns)
-static int lua_main_add_feature (lua_State *L)
-{
- char *xmlns = to_utf8 (luaL_checkstring (L, 1));
- xmpp_add_feature (xmlns);
- lua_added_features = g_slist_prepend (lua_added_features, xmlns);
- return 0;
-}
-
-/// main.del_feature
-/// Removes xmlns from disco#info features list.
-/// A: stirng (xmlns)
-static int lua_main_del_feature (lua_State *L)
-{
- char *xmlns = to_utf8 (luaL_checkstring (L, 1));
- GSList *el = g_slist_find_custom (lua_added_features, xmlns, (GCompareFunc) strcmp);
- xmpp_del_feature (xmlns);
- if (el) {
- g_free (el->data);
- lua_added_features = g_slist_delete_link (lua_added_features, el);
- }
- return 0;
-}
-
-// MCABBER EVENTS
-
-/// event context
-/// Enum, indicating what exactly caused event function firing.
-/// G:
-static const string2enum_t lua_event_context[] = {
- { "timeout", EVS_CONTEXT_TIMEOUT },
- { "cancel", EVS_CONTEXT_CANCEL },
- { "reject", EVS_CONTEXT_REJECT },
- { "accept", EVS_CONTEXT_ACCEPT },
- { NULL, 0 },
-};
-
-typedef struct {
- lua_State *L;
- int reference;
- int evid;
-} lua_event_callback_t;
-
-static GSList *lua_events = NULL;
-
-static void lua_event_callback_destroy_notify (gpointer udata)
-{
- lua_event_callback_t *cb = udata;
-
- luaL_unref (cb -> L, LUA_REGISTRYINDEX, cb->reference);
- luaL_unref (cb -> L, LUA_REGISTRYINDEX, cb->evid);
- luaL_free (cb -> L, cb);
-}
-
-/// event function
-/// Function to be called, when some event state change occurs
-/// A: event context, string (event args)
-/// R: boolean (if event shoud be preserved)
-static gboolean lua_event_callback (guint context, const gchar *arg, gpointer userdata)
-{
- lua_event_callback_t *cb = userdata;
-
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
- luaL_pushenum (cb->L, context, lua_event_context);
- lua_pushstring (cb -> L, arg);
- if (lua_pcall (cb->L, 2, 1, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Event callback execution error: %s", lua_tostring (cb->L, -1));
- lua_pop (cb->L, 1);
- }
-
- if (lua_toboolean (cb -> L, -1))
- return TRUE;
- else {
- lua_events = g_slist_remove (lua_events, cb); // XXX
- return FALSE;
- }
-}
-
-/// main.event
-/// Creates new event. If called without arguments, returns event id list.
-/// A: event function (optional), string (event id), string (description, optional), integer (expiration timeout, optional)
-/// R: string (event id) or nothing (creation error) or table (list of event names)
-static int lua_main_event (lua_State *L)
-{
- int top = lua_gettop (L);
- if (top > 0) { // Create
- lua_event_callback_t *cb;
- const char *evid = NULL;
- int timeout = 0;
- const char *desc = NULL;
- luaL_argcheck (L, lua_type (L, 1) == LUA_TFUNCTION, 1, "event function expected");
-
- if (top > 1) {
- evid = luaL_checkstring (L, 2);
- if (top > 2) {
- timeout = luaL_checkinteger (L, 3);
- if (top > 2) {
- desc = luaL_checkstring (L, 4);
- lua_pop (L, 3);
- } else
- lua_pop (L, 2);
- } else
- lua_pop (L, 1);
- }
-
- lua_pushvalue (L, 1); // XXX
- cb = luaL_malloc (L, sizeof (lua_event_callback_t));
- cb -> L = L;
- cb -> reference = luaL_ref (L, LUA_REGISTRYINDEX);
- cb -> evid = LUA_NOREF;
- lua_events = g_slist_prepend (lua_events, cb);
-
- evid = evs_new (desc, evid, timeout, lua_event_callback, cb, lua_event_callback_destroy_notify);
- if (!evid) {
- lua_events = g_slist_remove (lua_events, cb); // XXX
- return 0;
- }
-
- lua_pushstring (L, evid);
- lua_pushvalue (L, -1);
- cb -> evid = luaL_ref (L, LUA_REGISTRYINDEX); // XXX
- return 1;
-
- } else { // List
- GSList *events = evs_geteventslist ();
- GSList *event;
-
- lua_newtable (L);
- for (event = events; event; event = g_slist_next (event)) {
- lua_pushstring (L, event->data);
- luaL_ref (L, -2);
- }
- g_slist_free (events);
-
- return 1;
- }
-}
-
-// MCABBER COMMANDS
-
-/// completion type
-/// Built-it completion types can be specified as string, instead of id.
-/// G:
-static string2enum_t lua_completion_type[] = { // not const, we need to modify yesno
- { "cmd", COMPL_CMD },
- { "jid", COMPL_JID },
- { "urljid", COMPL_URLJID },
- { "name", COMPL_NAME },
- { "status", COMPL_STATUS },
- { "filename", COMPL_FILENAME },
- { "roster", COMPL_ROSTER },
- { "buffer", COMPL_BUFFER },
- { "group", COMPL_GROUP },
- { "groupname", COMPL_GROUPNAME },
- { "multiline", COMPL_MULTILINE },
- { "room", COMPL_ROOM },
- { "resource", COMPL_RESOURCE },
- { "auth", COMPL_AUTH },
- { "request", COMPL_REQUEST },
- { "events", COMPL_EVENTS },
- { "eventsid", COMPL_EVENTSID },
- { "pgp", COMPL_PGP },
- { "color", COMPL_COLOR },
- { "otr", COMPL_OTR },
- { "ortpolicy", COMPL_OTRPOLICY },
- { "yesno", 0 },
- { NULL, 0 },
-};
-#define MLUA_YESNO_POS ( 21 )
-
-typedef struct {
- int reference;
- int parse_args;
- lua_State *L;
-} lua_command_callback_t;
-
-static GSList *lua_added_commands = NULL;
-
-static GSList *lua_added_categories = NULL;
-
-// returns true if string contains errors - unclosed quotes or unvalued option
-/// command arguments table
-/// It can parse barewords (with escapes), double-quoted strings (with escapes), single-quoted strings (without escapes), options and arguments.
-/// Arguments are separated only by whitespace, so, consequential quoted strings or barewords are one argument.
-/// This strings are equal:
-/// * ab\ cd\'e\\f\"
-/// * "ab cd'e\\f\""
-/// * 'ab cd'\''e\f"'
-/// * ab" cd'"'e\f"'
-/// Returned table have option names as keys, option values as values, and arguments as sequential members. -- option is supported.
-/// Example: "-t jid -m 9 -- -aa bb cc" will result in { t = 'jid', m = 9, '-aa', 'bb', 'cc' }
-/// Implementation notes:
-/// * All options should be before any arguments. First non-option argument disables options recognizing.
-/// * EOL is a cutting edge, that can cut much earlier, than you expect. Non-closed quoted strings lose leading quote and option without value loses its leading minus.
-/// * Escape character just before EOL is preserved.
-static int luaL_pushargs (lua_State *L, const char *args)
-{
- const char *p = args;
- luaL_Buffer buf;
- int option = 0;
- int options = 1;
-
- lua_newtable (L);
- luaL_buffinit (L, &buf);
- while (*p) {
- if (*p == ' ') {
- ++p;
- continue;
- }
- if (*p == '"') { // soft quote
- const char *start = ++p;
- while (*p) {
- if (*p == '\\') { // escape symbol
- luaL_addlstring (&buf, start, p - start);
- start = ++p;
- if (*p) // skip symbol
- ++p;
- else // add last \ in line
- luaL_addchar (&buf, '\\');
- } else if (*p == '"') // quotation end
- break;
- else
- ++p;
- }
- luaL_addlstring (&buf, start, p - start); // XXX: eats quote on eol
- if (*p)
- ++p;
- else
- return 1;
- } else if (*p == '\'') { // no-escape quote
- const char *start = ++p;
- while (*p && *p != '\'')
- p++;
- luaL_addlstring (&buf, start, p - start); // XXX: eats quote on eol
- if (*p)
- ++p;
- else
- return 1;
- } else { // bareword
- const char *start = p;
- while (*p) {
- if (*p == '\\') {
- luaL_addlstring (&buf, start, p - start);
- start = ++p;
- if (*p) // skip symbol
- ++p;
- else // add last \ in line
- luaL_addchar (&buf, '\\');
- } else if (*p == ' ' || *p == '\'' || *p == '"')
- break;
- else
- ++p;
- }
- luaL_addlstring (&buf, start, p - start);
- }
-
- if ((!*p) || *p == ' ') {
- const char *result;
- luaL_pushresult (&buf);
-
- result = lua_tostring (L, -1);
- if (options && !option && *result == '-') { // option
- if (*(result+1) == '-' && !*(result+2)) { // end of options
- lua_pop (L, 1);
- options = 0;
- } else { // option name
- lua_pushstring (L, result + 1);
- lua_remove (L, -2);
- option = 1;
- }
- } else if (option) { // opion value
- lua_settable (L, -3);
- option = 0;
- } else { // argument
- options = 0;
- luaL_ref (L, -2);
- }
-
- luaL_buffinit (L, &buf);
- }
- }
-
- if (option) {
- luaL_ref (L, -2); // XXX: eats minus on eol
- return 1;
- }
-
- return 0;
-}
-
-/// command function
-/// Function to handle newly registered command.
-/// Argument type passed depends on how command is registered.
-/// A: string (arguments) or command arguments table
-static void lua_main_command_handler (char *args, lua_command_callback_t *cb)
-{
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
-
- if (cb->parse_args)
- luaL_pushargs (cb->L, args);
- else
- lua_pushstring (cb->L, args);
-
- if (lua_pcall (cb->L, 1, 0, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Command execution error: %s", lua_tostring (cb->L, -1));
- lua_pop (cb->L, 1);
- }
-}
-
-/// main.parse_args
-/// Function to parse command argument string to command arguments table.
-/// A: string
-/// R: table
-static int lua_main_parse_args (lua_State *L)
-{
- luaL_pushargs (L, luaL_checkstring (L, 1));
- return 1;
-}
-
-/// main.add_category
-/// Adds completion category.
-/// A: table (values are used as words for completion, optional)
-/// R: integer (category id, in fact completion type) or nil
-static int lua_main_add_category (lua_State *L)
-{
- guint cid;
-
- if (lua_gettop (L) > 0) {
- luaL_argcheck (L, lua_type (L, 1) == LUA_TTABLE, 1, "table expected");
- cid = compl_new_category ();
- if (cid) {
- lua_pushnil (L);
- while (lua_next (L, 1)) {
- char *word = to_utf8 (luaL_checkstring (L, -1));
- if (word) {
- compl_add_category_word (cid, word);
- g_free (word);
- }
- lua_pop (L, 1);
- }
- }
- } else
- cid = compl_new_category ();
-
- if (cid) {
- lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
- lua_pushinteger (L, cid);
- } else
- lua_pushnil (L);
-
- return 1;
-}
-
-/// main.del_category
-/// Removes completion category.
-/// A: integer (category id)
-static int lua_main_del_category (lua_State *L)
-{
- guint cid = luaL_checkinteger (L, 1);
- compl_del_category (cid);
- lua_added_categories = g_slist_remove (lua_added_categories, (gpointer) cid);
- return 0;
-}
-
-/// main.add_completion
-/// Adds word to a completion list.
-/// A: integer (completion group id), string (word)
-static int lua_main_add_completion (lua_State *L)
-{
- guint cid = luaL_checkinteger (L, 1);
- char *word = to_utf8 (luaL_checkstring (L, 2)); // XXX
- compl_add_category_word (cid, word);
- g_free (word);
- return 0;
-}
-
-/// main.del_completion
-/// Removes word from a completion list.
-/// A: integer (completion group id), string (word)
-static int lua_main_del_completion (lua_State *L)
-{
- guint cid = luaL_checkinteger (L, 1);
- char *word = to_utf8 (luaL_checkstring (L, 2)); // XXX
- compl_del_category_word (cid, word);
- g_free (word);
- return 0;
-}
-
-/// main.command
-/// Associates or breaks association between mcabber command name and lua function.
-/// To unregister command omit function argument.
-/// If you specify a third argument, table (even empty), function will return completion group id or nothing.
-/// You can also specify a string name (see completion type) instead of table, for non-builtin, you can just pass integer id.
-/// Note, that for now there are no way to unregister completion group, so, resources can be exausted easily.
-/// Also note, that it ignores keys in a completion table.
-/// A: string (command name), command function (optional), boolean (parse args flag, optional), table (completions, optional)/completion type (or integer comletion group id, optional)
-/// R: completion type (integer completion group id or string for builtin types, optional)
-static int lua_main_command (lua_State *L)
-{
- const char *name = luaL_checkstring (L, 1); // XXX: to_utf8? looks like no :/
- lua_command_callback_t *cb;
- int top = lua_gettop (L);
- if (top > 1) { // Register
- guint cid = 0;
- int parse = 0;
- luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
-
- if (top > 2) { // parse flag provided
- parse = lua_toboolean (L, 3);
-
- if (top > 3) { // Completions provided
- if (lua_type (L, 4) == LUA_TTABLE) {
- cid = compl_new_category ();
- if (cid) {
- lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
- lua_pushnil (L);
- while (lua_next (L, 4)) {
- char *word = to_utf8 (luaL_checkstring (L, -1));
- compl_add_category_word (cid, word);
- lua_pop (L, 1);
- g_free (word);
- }
- }
- } else
- cid = luaL_checkenum (L, 4, lua_completion_type);
- }
- }
-
- cb = luaL_malloc (L, sizeof (lua_command_callback_t));
- lua_pushvalue (L, 2);
- cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
- cb->parse_args = parse;
- cb->L = L;
- cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
-
- lua_added_commands = g_slist_prepend (lua_added_commands, g_strdup (name));
-
- if (cid) {
- luaL_pushenum (L, cid, lua_completion_type);
- return 1;
- }
- } else { // Unregister
- GSList *el = g_slist_find_custom (lua_added_commands, name, (GCompareFunc) g_strcmp0);
-
- cb = cmd_del (name);
- if (cb) {
- luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
- luaL_free (cb->L, cb);
- }
-
- if (el) {
- g_free (el->data);
- lua_added_commands = g_slist_delete_link (lua_added_commands, el);
- }
- }
- return 0;
-}
-
-// TIMER
-
-typedef struct {
- int reference;
- guint source;
- lua_State *L;
-} lua_timer_callback_t;
-
-static GSList *lua_timers = NULL;
-
-static void lua_timer_callback_destroy (lua_timer_callback_t *cb)
-{
- luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
- lua_timers = g_slist_remove (lua_timers, (gpointer) cb->source);
- luaL_free (cb->L, cb);
-}
-
-/// timer function
-/// Function, that will be called periodically until it returns false.
-/// R: boolean
-static gboolean lua_timer_callback (lua_timer_callback_t *cb)
-{
- int ret;
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
- if (lua_pcall (cb->L, 0, 1, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Timer callback execution error: %s", lua_tostring (cb->L, -1));
- lua_pop (cb->L, 1);
- return FALSE;
- }
- ret = lua_toboolean (cb->L, -1);
- lua_pop (cb->L, 1);
- return ret;
-}
-
-/// main.timer
-/// Creates new timer function, that will be called periodically.
-/// A: integer (interval, seconds), timer function
-static int lua_main_timer (lua_State *L)
-{
- int interval = luaL_checkint (L, 1);
- guint source;
- lua_timer_callback_t *cb;
- luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
-
- cb = luaL_malloc (L, sizeof (lua_timer_callback_t));
- cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
- cb->L = L;
-
- source = g_timeout_add_seconds_full (MLUA_SOURCE_PRIORITY, interval, (GSourceFunc) lua_timer_callback, cb, (GDestroyNotify) lua_timer_callback_destroy);
- cb->source = source;
- lua_timers = g_slist_prepend (lua_timers, (gpointer) source);
-
- return 0;
-}
-
-// BACKGROUND PIPE READING
-
-typedef struct {
- lua_State *L;
- guint source;
- FILE *fd;
- int reference;
-} lua_bgread_callback_t;
-
-static GSList *lua_bgreads = NULL;
-
-static gchar lua_bgread_buffer[MLUA_BGREAD_BUFFER];
-
-static void lua_bgread_callback_destroy (lua_bgread_callback_t *cb)
-{
- luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
- pclose (cb->fd); // Not necessary?
- lua_bgreads = g_slist_remove (lua_bgreads, (gpointer) cb->source);
- luaL_free (cb->L, cb);
-}
-
-/// background reading function
-/// Function, that processes output from pipe in asynchroneous way.
-/// A: string (data) or nil (eof)
-/// R: boolean (false if reading should be terminated)
-static gboolean lua_bgread_callback (GIOChannel *source, GIOCondition condition, lua_bgread_callback_t *cb)
-{
- int ret = TRUE;
-
- if (condition | G_IO_IN) { // data
- while (TRUE) {
- gsize read = 0;
- g_io_channel_read_chars (source, lua_bgread_buffer, MLUA_BGREAD_BUFFER, &read, NULL);
- if (!read) // exhausted
- break;
-
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
- lua_pushlstring (cb->L, lua_bgread_buffer, read);
- if (lua_pcall (cb->L, 1, 1, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Bgread callback execution error: %s", lua_tostring (cb->L, -1));
- lua_pop (cb->L, 1);
- return FALSE;
- }
- ret = lua_toboolean (cb->L, -1);
- lua_pop (cb->L, 1);
- if (!ret) // enough
- return FALSE;
- }
- }
-
- if (condition & ~G_IO_IN) { // err or hup
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
- lua_pushnil (cb->L);
- if (lua_pcall (cb->L, 1, 1, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Bgread callback execution error: %s", lua_tostring (cb->L, -1));
- lua_pop (cb->L, 1);
- return FALSE;
- }
- ret = lua_toboolean (cb->L, -1);
- lua_pop (cb->L, 1);
- }
-
- return ret;
-}
-
-/// main.bgread
-/// Runs specified command and passes its output to a given function.
-/// A: string (command), background reading function
-static int lua_main_bgread (lua_State *L)
-{
- const char *command = luaL_checkstring (L, 1);
- lua_bgread_callback_t *cb;
- FILE *fd;
- GIOChannel *channel;
- const char *charset = NULL;
- guint source;
- luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
-
- fd = popen (command, "r");
- if (!fd) {
- lua_pushstring (L, "Error opening pipe");
- lua_error (L);
- }
-
- channel = g_io_channel_unix_new (fileno (fd));
- // We, most likely, need this,
- // But we cannot use this,
- // It will block.
- //if (!g_get_charset (&charset))
- g_io_channel_set_encoding (channel, charset, NULL);
- g_io_channel_set_buffered (channel, FALSE);
- g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
- g_io_channel_set_close_on_unref (channel, TRUE);
-
- cb = luaL_malloc (L, sizeof (lua_bgread_callback_t));
- cb->reference = luaL_ref (L, LUA_REGISTRYINDEX);
- cb->L = L;
- cb->fd = fd;
-
- source = g_io_add_watch_full (channel, MLUA_SOURCE_PRIORITY, G_IO_IN|G_IO_HUP|G_IO_ERR, (GIOFunc) lua_bgread_callback, cb, (GDestroyNotify) lua_bgread_callback_destroy);
- cb->source = source;
- lua_bgreads = g_slist_prepend (lua_bgreads, (gpointer) source);
-
- // unref?
-
- return 0;
-}
-
-// HOOKÂ HANDLING
-
-typedef struct {
- lua_State *L; // lua environment for handler use
- int nameref; // reference to hook name string
- int cbref; // reference to hook handler function
- int selfref; // self-reference to object
- guint hid; // hook id for object destruction
-} lua_hook_t;
-
-/// hook handler result
-/// What to do with hook processing afterwards
-/// G:
-static const string2enum_t lua_hook_handler_result[] = {
- { "proceed", HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS },
- { "stop", HOOK_HANDLER_RESULT_NO_MORE_HANDLER },
- { "drop", HOOK_HANDLER_RESULT_NO_MORE_HANDLER_DROP_DATA },
- { NULL, HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS },
-};
-
-/// hook handler priority
-/// Feel free to specify just a number instead of some preset value.
-/// G:
-static const string2enum_t lua_hook_handler_priority[] = {
- { "high", G_PRIORITY_HIGH },
- { "default", G_PRIORITY_DEFAULT },
- { "idle-high", G_PRIORITY_HIGH_IDLE },
- { "idle", G_PRIORITY_DEFAULT_IDLE },
- { "low", G_PRIORITY_LOW },
- { NULL, G_PRIORITY_DEFAULT },
-};
-
-/// hook function
-/// Function to be called, when hook will be processsed.
-/// XXX: we can provide object as argument, but is this necessary?
-/// A: table (arguments hash)
-/// R: hook handler result
-static guint lua_hook_cb (const gchar *hookid, hk_arg_t *args, gpointer data)
-{
- lua_hook_t *cb = data;
- hk_arg_t *arg = args;
- guint ret = HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
- lua_State *L = cb -> L;
-
- if (cb -> cbref == LUA_NOREF)
- return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
- lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> cbref);
- if (!lua_isfunction (L, -1)) {
- lua_pop (L, 1);
- return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
- }
-
- lua_newtable (L);
- lua_pushliteral (L, "hook");
- lua_pushstring (L, hookid);
- lua_settable (L, -3);
- while (arg->name != NULL) {
- char *name = from_utf8 (arg->name);
- char *value = from_utf8 (arg->value);
- lua_pushstring (L, name);
- lua_pushstring (L, value);
- lua_settable (L, -3);
- g_free (name);
- g_free (value);
- arg++;
- }
-
- if (lua_pcall (L, 1, 1, 0)) {
- scr_log_print (LPRINT_NORMAL, "lua: Error in hook handler: %s", lua_tostring (L, -1));
- lua_pop (L, 1);
- } else {
- switch (lua_type (L, -1)) {
- case LUA_TSTRING:
- case LUA_TNUMBER:
- ret = luaL_checkenum (L, -1, lua_hook_handler_result);
- break;
- default:
- ret = HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
- break;
- }
- lua_pop (L, 1);
- }
-
- return ret;
-}
-
-/// main.hook
-/// Installs hook handler, returns an object, that you need to keep until
-/// hook handling is no more needed.
-/// A: string (hook name), hook function, integer (priority, optional)
-/// R: userdata (hook object)
-static int lua_main_hook (lua_State *L)
-{
- const char *hook_name = luaL_checkstring (L, 1);
- int priority = G_PRIORITY_DEFAULT;
- lua_hook_t *cb;
-
- luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
-
- if (lua_gettop (L) > 2)
- priority = luaL_checkenum (L, 3, lua_hook_handler_priority);
-
- cb = lua_newuserdata (L, sizeof (lua_hook_t));
- luaL_getmetatable (L, "mcabber.hook");
- lua_setmetatable (L, -2);
-
- lua_pushvalue (L, -1);
- cb -> selfref = luaL_ref (L, LUA_REGISTRYINDEX);
- lua_pushvalue (L, 1);
- cb -> nameref = luaL_ref (L, LUA_REGISTRYINDEX);
- lua_pushvalue (L, 2);
- cb -> cbref = luaL_ref (L, LUA_REGISTRYINDEX);
- cb -> L = L;
- cb -> hid = hk_add_handler (lua_hook_cb, hook_name, priority, cb);
-
- return 1;
-}
-
-static void lua_mcabber_unregister_hook (lua_State *L, lua_hook_t *cb)
-{
- const char *name;
-
- if (!cb -> hid || cb -> nameref == LUA_NOREF)
- return;
-
- lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
- name = lua_tostring (L, -1);
- if (name) {
- hk_del_handler (name, cb -> hid);
- cb -> hid = 0;
- }
-
- lua_pop (L, 1);
-}
-
-/// hook:del
-/// Unregisters given hook handler from mcabber. Object will be destroyed later.
-static int lua_mcabber_hook_del (lua_State *L)
-{
- lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.hook");
- luaL_argcheck (L, cb != NULL, 1, "mcabber hook object expected");
- lua_mcabber_unregister_hook (L, cb);
- if (cb -> selfref != LUA_NOREF) {
- luaL_unref (L, LUA_REGISTRYINDEX, cb -> selfref);
- cb -> selfref = LUA_NOREF;
- }
- return 0;
-}
-
-static int lua_mcabber_hook_gc (lua_State *L)
-{
- lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.hook");
- luaL_argcheck (L, cb != NULL, 1, "mcabber hook object expected");
- lua_mcabber_unregister_hook (L, cb);
- if (cb -> nameref != LUA_NOREF)
- luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
- if (cb -> cbref != LUA_NOREF)
- luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
- return 0;
-}
-
-static const luaL_Reg lua_mcabber_hook_reg_m[] = {
- { "del", lua_mcabber_hook_del },
- { "__gc", lua_mcabber_hook_gc },
- { NULL, NULL },
-};
-
-static void lua_hook_init (lua_State *L)
-{
- luaL_newmetatable (L, "mcabber.hook");
- lua_pushvalue (L, -1);
- lua_setfield (L, -2, "__index");
- luaL_register (L, NULL, lua_mcabber_hook_reg_m);
- lua_pop (L, 1);
-}
-
-#if 0
-// OPTION GUARDS
-
-GSList *lua_installed_guards = NULL;
-
-typedef struct {
- lua_State *L; // lua environment for handler use
- int nameref; // reference to key name string
- int cbref; // reference to guard function
-// int objref; // self_reference to object
- guint hid; // hook id for object destruction
-} lua_guard_t;
-
-/// guard function
-/// Function to be called, when option changes it's value.
-/// Old option value is still accessible through main.option.
-/// A: string (key), string (new value)
-/// R: string (value to save in hash table)
-static gchar *lua_guard_cb (const gchar *key, const gchar *value)
-{
- lua_guard_t *cb = ;// FIXME
- lua_State *L = cb -> L;
-
- if (cb -> cbref == LUA_NOREF)
- return g_strdup (value);
-
- lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> cbref);
- if (!lua_isfunction (L, -1)) {
- lua_pop (L, 1);
- return g_strdup (value);
- }
-
- lua_pushstring (L, key);
- lua_psuhstring (L, value);
-
- if (lua_pcall (L, 2, 1, 0)) {
- scr_log_print (LPRINT_NORMAL, "lua: Error in hook handler: %s", lua_tostring (L, -1));
- lua_pop (L, 1);
- return g_strdup (value);
- }
-
- return g_strdup (lua_tostring (L, -1));
-}
-
-/// main.guard
-/// Installs option guard for given option. Returns guard object, that
-/// should be kept around as long, as guard is needed.
-/// A: string (option name), guard function
-/// R: userdata (guard object)
-static int lua_main_guard (lua_State *L)
-{
- const char *name = luaL_checkstring (L, 1);
- int priority = G_PRIORITY_DEFAULT;
- lua_guard_t *cb;
-
- luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
-
- cb = lua_newuserdata (L, sizeof (lua_guard_t));
- luaL_getmetatable (L, "mcabber.guard");
- lua_setmetatable (L, -2);
-
- lua_pushvalue (L, 1);
- cb -> nameref = luaL_ref (L, LUA_REGISTRYINDEX);
- lua_pushvalue (L, 2);
- cb -> cbref = luaL_ref (L, LUA_REGISTRYINDEX);
- cb -> L = L;
-
- settings_set_guard (name, lua_guard_cb)
-
- return 1;
-}
-
-static void lua_mcabber_unregister_guard (lua_State *L, lua_guard_t *cb)
-{
- const char *name;
-
- if (cb -> nameref == LUA_NOREF)
- return;
-
- lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
- name = lua_tostring (L, -1);
- if (name) {
- settings_del_guard (name);
- luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
- cb -> nameref = LUA_NOREF;
- }
-
- lua_pop (L, 1);
-}
-
-/// guard:del
-/// Unregisters given option guard from mcabber. Object will be destroyed later.
-static int lua_mcabber_guard_del (lua_State *L)
-{
- lua_guard_t *cb = luaL_checkudata (L, 1, "mcabber.guard");
- luaL_argcheck (L, cb != NULL, 1, "mcabber guard object expected");
- lua_mcabber_unregister_guard (L, cb);
- return 0;
-}
-
-static int lua_mcabber_guard_gc (lua_State *L)
-{
- lua_hook_t *cb = luaL_checkudata (L, 1, "mcabber.guard");
- luaL_argcheck (L, cb != NULL, 1, "mcabber guard object expected");
- lua_mcabber_unregister_guard (L, cb);
- if (cb -> cbref != LUA_NOREF)
- luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
- return 0;
-}
-
-static const luaL_Reg lua_mcabber_hook_reg_m[] = {
- { "del", lua_mcabber_guard_del },
- { "__gc", lua_mcabber_guard_gc },
- { NULL, NULL },
-};
-
-static void lua_guard_init (lua_State *L)
-{
- luaL_newmetatable (L, "mcabber.guard");
- lua_pushvalue (L, -1);
- lua_setfield (L, -2, "__index");
- luaL_register (L, NULL, lua_mcabber_guard_reg_m);
- lua_pop (L, 1);
-}
-
-static void lua_guard_uninit (lua_State *L)
-{
-}
-#endif
-
-// MAIN INITIALIZATION CODE
-
-#ifdef LLM_LOG_HANDLER
-// FIXME: this should not be here
-guint lua_lm_log_handler_id;
-
-void lua_lm_log_handler (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer ignore)
-{
- if (settings_opt_get_int ("lua_lm_debug"))
- scr_log_print (LPRINT_LOGNORM, "%s: %s", domain, message);
-}
-#endif
-
-static void do_lua(char *arg, lua_State *L)
-{
- if (luaL_loadbuffer (L, arg, strlen (arg), "line")) {
- scr_log_print (LPRINT_LOGNORM, "lua: Compilation error: %s", lua_tostring (L, -1));
- lua_pop (L, 1);
- return;
- }
-
- if (lua_pcall (L, 0, 0, 0)) {
- scr_log_print (LPRINT_NORMAL, "lua: Runtime error: %s", lua_tostring(L, -1));
- lua_pop (L, 1);
- return;
- }
-}
-
-static void *lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
- if (nsize == 0) {
- g_free (ptr);
- return NULL;
- } else
- return g_realloc (ptr, nsize);
-}
-
-#define reg(NAME) \
- { #NAME, lua_main_##NAME },
-static const luaL_Reg lua_reg_main[] = {
- reg ( yesno )
-#ifdef LLM_CONNECTION_ENABLE
- reg ( connection )
-#endif
- reg ( log )
- reg ( option )
- reg ( alias )
- reg ( binding )
- reg ( fileoption )
- reg ( add_feature )
- reg ( del_feature )
- reg ( event )
- reg ( parse_args )
- reg ( add_category )
- reg ( del_category )
- reg ( add_completion )
- reg ( del_completion )
- reg ( command )
- reg ( print_info )
- reg ( beep )
- reg ( run )
- reg ( status )
- reg ( roster )
- reg ( current_buddy )
- reg ( full_jid )
- reg ( buddy_info )
- reg ( timer )
- reg ( alias )
- reg ( binding )
- reg ( fileoption )
- reg ( add_feature )
- reg ( del_feature )
- reg ( event )
- reg ( parse_args )
- reg ( add_category )
- reg ( del_category )
- reg ( add_completion )
- reg ( del_completion )
- reg ( command )
- reg ( print_info )
- reg ( beep )
- reg ( run )
- reg ( status )
- reg ( roster )
- reg ( current_buddy )
- reg ( full_jid )
- reg ( buddy_info )
- reg ( timer )
- reg ( bgread )
- reg ( hook )
- { NULL, NULL },
-};
-#undef reg
-
-const gchar *g_module_check_init (GModule *module)
-{
- lua = lua_newstate (lua_alloc, NULL);
- if (!lua)
- return "Lua initialization error";
- else
- return NULL;
-}
-
-void g_module_unload (GModule *module)
-{
- if (lua) {
- lua_close (lua);
- lua = NULL;
- }
-}
-
-static void mlua_init (void)
-{
- luaL_openlibs (lua);
-
- luaL_register (lua, "main", lua_reg_main);
- lua_pop (lua, 1); // XXX
- lua_register (lua, "dopath", lua_global_dopath);
- lua_register (lua, "print", lua_global_print );
-
- {
- int cid = compl_new_category ();
-
- if (cid) {
- const string2enum_t *word = lua_yesno;
- lua_completion_type[MLUA_YESNO_POS].value = cid;
- lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
- while (word->string) {
- compl_add_category_word (cid, word->string);
- ++word;
- }
- }
- }
-
- cmd_add ("lua", "Evaluate lua string", 0, 0, (void (*) (char *p)) do_lua, lua);
-
-#ifdef LLM_LOG_HANDLER
- // FIXME: this should not be here.
- lua_lm_log_handler_id = g_log_set_handler ("lua-lm", G_LOG_LEVEL_MASK, (GLogFunc) lua_lm_log_handler, NULL);
-#endif
-
- lua_hook_init (lua);
-
- {
- char *initfile = expand_filename (settings_opt_get ("lua_init_filename"));
-
- if (!initfile)
- scr_log_print (LPRINT_LOGNORM, "lua: Cannot determine config file name");
- else {
- if (luaL_loadfile(lua, initfile)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Unable to compile rc file: %s", lua_tostring (lua, -1));
- lua_pop (lua, 1);
- } else if (lua_pcall (lua, 0, LUA_MULTRET, 0)) {
- scr_log_print (LPRINT_LOGNORM, "lua: Runtime error in rc file: %s", lua_tostring(lua, -1));
- lua_pop (lua, 1);
- } else
- scr_log_print (LPRINT_LOGNORM, "lua: Loaded %s", initfile);
- g_free (initfile);
- }
- }
-
- {
- hk_arg_t args[] = {
- { NULL, NULL },
- };
- hk_run_handlers("hook-lua-start", args);
- }
-}
-
-static void lua_events_cancel (gpointer data, gpointer ignore)
-{
- lua_event_callback_t *cb = data;
- const char *evid;
- if (cb->evid == LUA_NOREF)
- return;
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->evid);
- evid = lua_tostring (cb ->L, -1);
- evs_callback (evid, EVS_CONTEXT_CANCEL, "Module unloading");
-}
-
-static void lua_events_destroy (gpointer data, gpointer ignore)
-{
- lua_event_callback_t *cb = data;
- const char *evid;
- if (cb->evid == LUA_NOREF)
- return;
- lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->evid);
- evid = lua_tostring (cb ->L, -1);
- evs_del (evid);
-}
-
-static void lua_bgreads_destroy (guint source, gpointer ignore)
-{
- g_source_remove (source);
-}
-
-static void lua_timers_destroy (guint source, gpointer ignore)
-{
- g_source_remove (source);
-}
-
-static void lua_features_destroy (char *xmlns, gpointer ignore)
-{
- xmpp_del_feature (xmlns);
- g_free (xmlns);
-}
-
-static void lua_commands_destroy (char *name, gpointer ignore)
-{
- lua_command_callback_t *cb = cmd_del (name);
- if (cb) {
- luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
- luaL_free (cb->L, cb);
- }
- g_free (name);
-}
-
-static void lua_categories_destroy (guint id, gpointer ignore)
-{
- compl_del_category (id);
-}
-
-static void mlua_uninit (void)
-{
- if (lua) {
- hk_arg_t args[] = {
- { NULL, NULL },
- };
- hk_run_handlers ("hook-lua-quit", args);
-
- // hook handlers will be unregistered upon objects destruction
-
- g_slist_foreach (lua_bgreads, (GFunc) lua_bgreads_destroy, NULL);
- g_slist_free (lua_bgreads);
- lua_bgreads = NULL;
-
- g_slist_foreach (lua_timers, (GFunc) lua_timers_destroy, NULL);
- g_slist_free (lua_timers);
- lua_timers = NULL;
-
- g_slist_foreach (lua_events, (GFunc) lua_events_cancel, NULL);
- g_slist_foreach (lua_events, (GFunc) lua_events_destroy, NULL);
- g_slist_free (lua_events);
- lua_events = NULL;
-
- g_slist_foreach (lua_added_features, (GFunc) lua_features_destroy, NULL);
- g_slist_free (lua_added_features);
- lua_added_features = NULL;
-
- g_slist_foreach (lua_added_commands, (GFunc) lua_commands_destroy, NULL);
- g_slist_free (lua_added_commands);
- lua_added_commands = NULL;
-
- g_slist_foreach (lua_added_categories, (GFunc) lua_categories_destroy, NULL);
- g_slist_free (lua_added_categories);
- lua_added_categories = NULL;
-
- cmd_del ("lua");
-
- lua_close (lua);
- lua = NULL;
-
-#ifdef LLM_LOG_HANDLER
- // FIXME: shouldn't be here
- g_log_remove_handler ("lua-lm", lua_lm_log_handler_id);
-#endif
- }
-}
-