Switch to object-based command handling
Hopefully, I have not introduced bugs here
/* Copyright 2009 Myhailo Danylenko
This file is part of mcabber-lua.
mcabber-lua is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <glib.h>
#include <gmodule.h> // g_module_*
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <stdlib.h> // getenv
#include <string.h> // strcmp
#include <mcabber/logprint.h> // scr_log_print
#include <mcabber/screen.h> // scr_Beep, scr_WriteIncomingMessage
#include <mcabber/hbuf.h> // HBB_PREFIX_INFO
#include <mcabber/commands.h> // process_command, cmd_add, cmd_del
#include <mcabber/xmpp.h> // xmpp_getstatus, xmpp_getstatusmsg, lconnection
#include <mcabber/xmpp_helper.h> // xmpp_add_feature, xmpp_del_feature
#include <mcabber/roster.h> // imstatus2char, foreach_buddy, buddy_*, current_buddy, BUDDATA, ROSTER_TYPE_*
#include <mcabber/utils.h> // from_utf8, jidtodisp
#include <mcabber/hooks.h> // hk_add_handler, hk_del_handler
#include <mcabber/settings.h> // settings_set, settings_del, settings_get
#include <mcabber/compl.h> // compl_new_category, compl_add_category_word, compl_del_category_word
#include <mcabber/events.h> // evs_*
#include <mcabber/modules.h> // module_info_t
#include <mcabber/api.h> // mcabber_branch, mcabber_api_version
#include <mcabber/main.h> // mcabber_version
#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 = 27,
.version = PROJECT_VERSION,
.description = DESCRIPTION,
.requires = NULL,
.init = mlua_init,
.uninit = mlua_uninit,
.next = NULL,
};
static module_info_t info_lua_dev = {
.branch = "dev",
.api = 13,
.version = PROJECT_VERSION,
.description = DESCRIPTION,
.requires = NULL,
.init = mlua_init,
.uninit = mlua_uninit,
.next = &info_lua_experimental,
};
module_info_t info_lua = {
.branch = "0.10.0",
.api = 1,
.version = PROJECT_VERSION,
.description = DESCRIPTION,
.requires = NULL,
.init = mlua_init,
.uninit = mlua_uninit,
.next = &info_lua_dev,
};
// 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;
}
/// main.version
/// Returns information about mcabber version
/// R: table
static int lua_main_version (lua_State *L)
{
lua_createtable (L, 0, 3);
lua_pushstring (L, mcabber_branch);
lua_setfield (L, -2, "branch");
lua_pushinteger (L, mcabber_api_version);
lua_setfield (L, -2, "api");
{
gchar *version = mcabber_version ();
lua_pushstring (L, version);
g_free (version);
}
lua_setfield (L, -2, "version");
lua_pushliteral (L, MCABBER_BRANCH);
lua_setfield (L, -2, "build_branch");
lua_pushinteger (L, MCABBER_API_VERSION);
lua_setfield (L, -2, "build_api");
lua_pushinteger (L, MCABBER_API_MIN);
lua_setfield (L, -2, "build_api_min");
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 cbref;
int parse_args;
#ifdef HAVE_CMD_ID
gpointer cmid;
#else
int nameref;
#endif
int selfref;
lua_State *L;
} lua_command_t;
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_t *cb)
{
lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->cbref);
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 mcabber command name and lua function.
/// As a completion 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, boolean (parse args flag, optional), table (completions, optional)/completion type (or integer comletion group id, optional)
/// R: userdata (command object)
static int lua_main_command (lua_State *L)
{
const char *name = luaL_checkstring (L, 1); // XXX: to_utf8? looks like no :/
lua_command_t *cb;
int top = lua_gettop (L);
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 = lua_newuserdata (L, sizeof (lua_command_t));
luaL_getmetatable (L, "mcabber.command");
lua_setmetatable (L, -2);
lua_pushvalue (L, 2);
cb -> cbref = luaL_ref (L, LUA_REGISTRYINDEX);
lua_pushvalue (L, -1);
cb -> selfref = luaL_ref (L, LUA_REGISTRYINDEX);
cb -> parse_args = parse;
cb -> L = L;
#ifdef HAVE_CMD_ID
cb -> cmid = cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
#else
lua_pushvalue (L, 1);
cb -> nameref = luaL_ref (L, LUA_REGISTRYINDEX);
cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
#endif
return 1;
}
static void lua_mcabber_unregister_command (lua_State *L, lua_command_t *cb)
{
#ifdef HAVE_CMD_ID
if (cb -> cmid)
cmd_del (cb -> cmid);
#else
const char *name;
if (cb -> nameref == LUA_NOREF)
return;
lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
name = lua_tostring (L, -1);
cmd_del (name);
lua_pop (L, 1);
#endif
}
/// command:del
/// Unregisters given command from mcabber. Object will be destroyed later.
static int lua_mcabber_command_del (lua_State *L)
{
lua_command_t *cb = luaL_checkudata (L, 1, "mcabber.command");
luaL_argcheck (L, cb != NULL, 1, "mcabber command object expected");
lua_mcabber_unregister_command (L, cb);
if (cb -> selfref != LUA_NOREF) {
luaL_unref (L, LUA_REGISTRYINDEX, cb -> selfref);
cb -> selfref = LUA_NOREF;
}
return 0;
}
static int lua_mcabber_command_gc (lua_State *L)
{
lua_command_t *cb = luaL_checkudata (L, 1, "mcabber.command");
luaL_argcheck (L, cb != NULL, 1, "mcabber command object expected");
lua_mcabber_unregister_command (L, cb);
#ifndef HAVE_CMD_ID
if (cb -> nameref != LUA_NOREF)
luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
#endif
if (cb -> cbref != LUA_NOREF)
luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
return 0;
}
static const luaL_Reg lua_mcabber_command_reg_m[] = {
{ "del", lua_mcabber_command_del },
{ "__gc", lua_mcabber_command_gc },
{ NULL, NULL },
};
static void lua_command_init (lua_State *L)
{
luaL_newmetatable (L, "mcabber.command");
lua_pushvalue (L, -1);
lua_setfield (L, -2, "__index");
luaL_register (L, NULL, lua_mcabber_command_reg_m);
lua_pop (L, 1);
}
// 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 ( version )
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);
lua_command_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_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 and commands 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;
cmd_del ("lua");
lua_close (lua);
lua = NULL;
g_slist_foreach (lua_added_categories, (GFunc) lua_categories_destroy, NULL);
g_slist_free (lua_added_categories);
lua_added_categories = NULL;
#ifdef LLM_LOG_HANDLER
// FIXME: shouldn't be here
g_log_remove_handler ("lua-lm", lua_lm_log_handler_id);
#endif
}
}