# HG changeset patch # User Myhailo Danylenko # Date 1270388629 -10800 # Node ID 2cb96eae301a5321766728439c6f851f813debf4 # Parent f3d9d9e67ee4d16d813b040bff7a7f44d4873020 Move main.c to lua.c diff -r f3d9d9e67ee4 -r 2cb96eae301a CMakeLists.txt --- 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) diff -r f3d9d9e67ee4 -r 2cb96eae301a lua.c --- /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 . */ + +#include +#include // g_module_* +#include +#include +#include +#include +#include // getenv +#include // strcmp + +#include // scr_log_print +#include // scr_Beep, scr_WriteIncomingMessage +#include // HBB_PREFIX_INFO +#include // process_command, cmd_add, cmd_del +#include // xmpp_getstatus, xmpp_getstatusmsg, lconnection +#include // xmpp_add_feature, xmpp_del_feature +#include // imstatus2char, foreach_buddy, buddy_*, current_buddy, BUDDATA, ROSTER_TYPE_* +#include // from_utf8, jidtodisp +#include // hk_add_handler, hk_del_handler +#include // settings_set, settings_del, settings_get +#include // compl_new_category, compl_add_category_word, compl_del_category_word +#include // evs_* +#include // 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 + } +} + diff -r f3d9d9e67ee4 -r 2cb96eae301a main.c --- 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 . */ - -#include -#include // g_module_* -#include -#include -#include -#include -#include // getenv -#include // strcmp - -#include // scr_log_print -#include // scr_Beep, scr_WriteIncomingMessage -#include // HBB_PREFIX_INFO -#include // process_command, cmd_add, cmd_del -#include // xmpp_getstatus, xmpp_getstatusmsg, lconnection -#include // xmpp_add_feature, xmpp_del_feature -#include // imstatus2char, foreach_buddy, buddy_*, current_buddy, BUDDATA, ROSTER_TYPE_* -#include // from_utf8, jidtodisp -#include // hk_add_handler, hk_del_handler -#include // settings_set, settings_del, settings_get -#include // compl_new_category, compl_add_category_word, compl_del_category_word -#include // evs_* -#include // 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 - } -} -