diff options
-rw-r--r-- | src/Makefile.in | 10 | ||||
-rw-r--r-- | src/acconfig.h | 7 | ||||
-rw-r--r-- | src/comm.c | 4 | ||||
-rw-r--r-- | src/configure.in | 12 | ||||
-rw-r--r-- | src/drafts/screen_internal | 95 | ||||
-rw-r--r-- | src/drafts/scripting | 276 | ||||
-rw-r--r-- | src/help.c | 2 | ||||
-rw-r--r-- | src/lua.c | 1034 | ||||
-rw-r--r-- | src/process.c | 282 | ||||
-rw-r--r-- | src/screen.c | 72 | ||||
-rw-r--r-- | src/screen.h | 1 | ||||
-rw-r--r-- | src/script.c | 290 | ||||
-rw-r--r-- | src/script.h | 89 | ||||
-rw-r--r-- | src/scripts/cmdcallback.lua | 44 | ||||
-rw-r--r-- | src/scripts/findwindow.lua | 19 | ||||
-rw-r--r-- | src/window.h | 7 |
16 files changed, 2222 insertions, 22 deletions
diff --git a/src/Makefile.in b/src/Makefile.in index 2ae57d0..58b961a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -29,11 +29,11 @@ ETCSCREENRC = $(prefix)/etc/screenrc SCREENENCODINGS = $(datadir)/screen/utf8encodings CC = @CC@ -CFLAGS = @CFLAGS@ +CFLAGS = @CFLAGS@ @LUA_CFLAGS@ CPPFLAGS = @CPPFLAGS@ -DETCSCREENRC='"$(ETCSCREENRC)"' \ -DSCREENENCODINGS='"$(SCREENENCODINGS)"' LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ +LIBS = @LIBS@ @LUA_LIBS@ CPP=@CPP@ CPP_DEPEND=$(CC) -MM @@ -62,13 +62,15 @@ CFILES= screen.c ansi.c fileio.c mark.c misc.c resize.c socket.c \ termcap.c input.c attacher.c pty.c process.c display.c comm.c \ kmapdef.c acls.c braille.c braille_tsi.c logfile.c layer.c \ sched.c teln.c nethack.c encoding.c canvas.c layout.c viewport.c \ - list_display.c list_generic.c list_window.c + list_display.c list_generic.c list_window.c \ + lua.c script.c OFILES= screen.o ansi.o fileio.o mark.o misc.o resize.o socket.o \ search.o tty.o term.o window.o utmp.o loadav.o putenv.o help.o \ termcap.o input.o attacher.o pty.o process.o display.o comm.o \ kmapdef.o acls.o braille.o braille_tsi.o logfile.o layer.o \ list_generic.o list_display.o list_window.o \ - sched.o teln.o nethack.o encoding.o canvas.o layout.o viewport.o + sched.o teln.o nethack.o encoding.o canvas.o layout.o viewport.o \ + lua.o script.o all: screen diff --git a/src/acconfig.h b/src/acconfig.h index 2e46985..585de3d 100644 --- a/src/acconfig.h +++ b/src/acconfig.h @@ -140,6 +140,7 @@ * Syntax: screen //telnet host [port] * define RXVT_OSC if you want support for rxvts special * change fgcolor/bgcolor/bgpicture sequences + * define SCRIPT to add scripting support to screen. */ #undef SIMPLESCREEN #ifndef SIMPLESCREEN @@ -161,8 +162,14 @@ # define COLORS16 # define ZMODEM # define BLANKER_PRG +# define SCRIPT #endif /* SIMPLESCREEN */ +/*Include the binding you would like to use.*/ +#ifdef SCRIPT +#define LUA_BINDING +#endif + #undef BUILTIN_TELNET #undef RXVT_OSC #undef COLORS256 @@ -56,6 +56,7 @@ struct comm comms[RC_LAST + 1] = #ifdef MULTIUSER { "addacl", ARGS_1234 }, #endif + { "alias", ARGS_12|ARGS_ORMORE }, { "allpartial", NEED_DISPLAY|ARGS_1 }, { "altscreen", ARGS_01 }, { "at", ARGS_2|ARGS_ORMORE }, @@ -283,6 +284,9 @@ struct comm comms[RC_LAST + 1] = { "reset", NEED_FORE|ARGS_0 }, { "resize", NEED_DISPLAY|ARGS_0|ARGS_ORMORE }, { "screen", ARGS_0|ARGS_ORMORE }, +#ifdef SCRIPT + { "script", ARGS_2|ARGS_ORMORE }, +#endif #ifdef COPY_PASTE { "scrollback", NEED_FORE|ARGS_1 }, #endif diff --git a/src/configure.in b/src/configure.in index bd29de2..4cf9376 100644 --- a/src/configure.in +++ b/src/configure.in @@ -1292,6 +1292,18 @@ fi dnl Ptx bug workaround -- insert -lc after -ltermcap test -n "$seqptx" && LIBS="-ltermcap -lc -lsocket -linet -lnsl -lsec -lseq" +PKG_CHECK_MODULES(LUA, [lua5.1], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + +*** You need to have lua5.1 (http://www.lua.org/) installed. +*** If you don't want support for lua scripting, please use the official repository +*** (http://git.savannah.gnu.org/gitweb/?p=screen.git;a=summary). +])]) + +AC_SUBST(LUA_CFLAGS) +AC_SUBST(LUA_LIBS) + AC_TRY_RUN(main(){exit(0);},,AC_MSG_ERROR(Can't run the compiler - internal error. Sorry.)) AC_OUTPUT(Makefile doc/Makefile, [[ diff --git a/src/drafts/screen_internal b/src/drafts/screen_internal new file mode 100644 index 0000000..8299986 --- /dev/null +++ b/src/drafts/screen_internal @@ -0,0 +1,95 @@ +I. Disclaimer + +This document includes some of my personal understanding about the internal +structure of GNU Screen. It's written to serve as a remainder to myself. I +hope that it can help those not familiar with GNU Screen already. But be aware +of any possible mistake in this document. + + +II. Architecture + +GNU Screen works in a client-server way. The two participants are called +front-end and back-end respectively, sharing the same code base. When an +instance of Screen launches, the command line arguments determines the role +and the corresponding action. + +Front-end send requests to back-end through a named-pipe or socket. And the +back-end gives feed back by signal. The requests include attach, detach, +resize, window create and command execution. The front-end does nothing more +than send requests and act upon responses. When there are no such events, it +simply sleeps to kill it's boring time. All other works are handled by the +back-end. + + +III. Important objects + +There are several kinds of objects in Screen. Here are some of them. + +A. Display + +A Display stands for an attached display area. It corresponds to the real user +tty that the attaching front-end runs in. Since there can be multiple users +(or multiple attach from the same user), the possibly multiple displays are +chained together in a single linked-list 'displays'. An Display object +contains many global statuses and flags (terminal modes & flags etc.), also +includes some events such as tty read & write. + +B. Canvas, Viewport & Layout + +Canvas is a place to draw contents on. They logically belong to a specific +display. However, a display can be further divided to several sub-regions, +using the split command. As a result, the regions need corresponding canvases. +Moreover, the way that a display is organised can be saved by Layout object. +All Layout objects are linked together as a list. The active layout used by +Display is stored with it. The canvas in a display that has input focus is +called forecv. + +To track the Canvases in a Display, Screen uses a two-dimensional link list. +One list link (slnext & slprev) together all adjacent Canvases that are +spitted in the same direction. The other is the stacking direction, or the +Z-axis in 3D graphics. All such adjacent Canvases shares one container Canvas, +which is inserted when a split with different direction is about to happen. +Each Canvas points to it's container using slback and a container points to +one of its random child using slprep. This structure actually link the +Canvases together as a tree, which is very helpful when doing free and resize. + +Finally, to ease the clipping when drawing in the canvas, there is also a +viewport object to track the boundary of each canvas. (But why not the canvas +itself?) + +C. Window & Layer + +Each Window in screen stands for a pseudo-terminal (or telnet connection, +depending on the type of window) that runs inside screen. All relevant +statuses and events are stored in this object. Many Windows can be shown in +Display at once, the one that has input focus is called the fore Window. + +To be shown in Canvases, a Window needs a object called Layer. A Layer can be +drawn on multiple Canvases, either in one Display or in different Displays. So +it also make sense to call it 'view'. However, the name Layer tells us that +it's stackable. One can add an overlay to the existing one, such as the help +page. Each Layer contains an operation function structure, providing important +operation to process the input, draw line, clear, redraw, resize, abort and +restore etc. + +E users & ACL + +TODO + +IV. Event dispatching. + +Screen uses asynchronous IO to handle all inputs/outputs in all ttys. Such +asynchronous events, IO related or not, are organized as events, registered to +a central event list and got scheduled by a scheduler. + +The scheduler loops indefinitely. In each turn, it waits for some events to go +ready using the Select() system call. There are three types of events, +READ/WRITE, TIMEOUT and ALWAYS. The READ/WRITE events are mostly used to carry +data between outer tty and ptys within screen. And the TIMEOUT events are +mostly used to do periodically update. Different from other kinds of events, +the TIMEOUT event is one-off, and should be re-scheduled if periodic +activation is desired. Note that the TIMEOUT event has lower priority than +READ/WRITE events, and the timeout specified is never adjusted to compensate +the elapsed time. As a result, the period of activation should typically +longer then specified. + diff --git a/src/drafts/scripting b/src/drafts/scripting new file mode 100644 index 0000000..a126906 --- /dev/null +++ b/src/drafts/scripting @@ -0,0 +1,276 @@ +=================== +The design +=================== + +I. Key Factors +There are several aspects to deal with when adding scripting support to +Screen: +1. The set of information to expose. +2. The command interface to provide. +3. The hook on interesting events in Screen. +4. The ability to do run asynchronously to Screen itself. + +A. Query & control interface + +Currently, screen provides a way to control a running session through the -X +command switch, which sends the specified command through the sever socket. +It's quite useful and can already do automation to some degree, (e.g. [1]). +However, the problem is that, there is no way to get enough feedback to make +decision in your script in such a scheme. The first job is to provide an +interface for scripts to query internal status of Screen. The second job is to +provide an interface to control Screen, with reasonable feedback. + +B. Event hooking + +When the internal status of Screen changed, we say that an event happened. +Events are also interesting to scripts, but are not covered by the query +interface. A hook / callback system is needed to notify scripts about +interested events. With the callback that triggers before an event, the script +should be able to stop the event from happening. + +C. Asynchronous scripting + +So far, with the described scripting support, we can write small functions +that provide enhanced functionality to a screen command, or write event +handler that handles interested events. But we are restricted to finish our +work quickly. The problem is that, Screen itself is a single threaded +application, utilizing asynchronous I/O to provide services to all windows +running inside. So you simply can not do time-consuming work in any of the +processing path, including the scripts. + +Sometimes we may need to wait for something in a script, such as emulating +Expect in a Screen script [2]. The core of this use case is to wait for an +output pattern and react upon it. One possible way is to provide an event +callback for it. But it's not a pleasant way to do a series of interaction. +A natural solution is to make the script able to run asynchronously to Screen +itself. + +[1] http://github.com/rblackwe/yapc--na-2007--making-an-ajax-gui-for-gnu-screen +[2] http://lists.gnu.org/archive/html/screen-users/2009-05/msg00006.html + +II. The Screen interface + +Screen needs to provide a user interface to source and run scripts. + +script source [-async|-a] [-binding|-b <binding>] script. + + This command sources the specified script. This command can be used several + times to source multiple scripts. Use the -async switch if the + script is supposed to run asynchronously , e.g. it needs to wait for + external events. Asynchronous scripts running mode needs to acquire a + lock before playing on Screen itself. As a result, it pays more overhead + than synch ones. Moreover, asynchronous scripts may be isolated from other + scripts and may not share any information with them. In other words, normal + scripts may share the same context. (Note: the isolation between scripts may + be implementation dependent. Which is more desirable?) + +script call func arg1 arg2 ... + + Call functions defined by scripts. If the same function are defined in + multiple scripting context, the last one applies. Call to normal script + returns synchronously. And call to asynchronous one will return immediately. + +Special invoke interface, such as those embedded in caption/hstatus format +string. + +III. The scripting interface + +There are several kinds of objects that may be interested to scripts. + +A. Display + +Stands for a user interface. + +---------- +Properties: +---------- + +tty: + mode: read only. + type: String. + description: The tty that this attach runs on. + +term: + mode: read only. + type: String. + description: The TERM env of that tty. + +fore: + mode: read only. + type: Window. + description: The fore window. + +other: + mode: read only. + type: Window. + description: List of windows other than the fore. + +width: + mode: read only. + type: Int. + description: As the name suggests + +height: + mode: read only. + type: Int. + description: As the name suggests + +user: + mode: read only. + type: User. + description: The owner of this display. + +layout: + mode: read only. + type: Layout. + description: Active layout on this display. + + +---------- +Methods: +---------- + +get_canvases: Get a list of canvases on this display. + +---------- +Events: +---------- + +on_resize: Triggered after the geometry of the display is changed. + +B. Window + +Stands for the sub-terminal(PTY, telnet etc) that processes runs on. + +---------- +Properties: +---------- + +title: + mode: read write + type: String. + description: The title of the window. + +number: + mode: read only + type: int. + description: The index in the window slots. + +dir: + mode: read only + type: String. + description: The initial directory of the first + application (typically the shell) in that window. + +tty: + mode: read only + type: String + description: the associated terminal device for that window. + +pid: + mode: read only + type: int + description: the pid of of the slave end of the pty. + +group: + mode: read only + type: Window + description: The window group we belongs to. (*This seems in-active?*) + +bell: + mode: read only + type: int + description: The bell status of the window. + +---------- +Methods: +---------- + +int get_monitor_status(bool activity); + Returns the status of the specified monitor type. Either activity or silence. + When the silence monitor is active, the threshold is returned. + +void set_monitor_status(bool activity, int status); + Set the status of activity/silence monitor. The number of status for the + silence monitor is the seconds of threshold. + +void stuff(string buf); + put the string into the input buffer. + +int waitfor(string pattern); + Waiting for a specified output pattern. The pattern is limited to plain text. + NOTICE: This is an asynchronous method call and can only be called in + asynchronous mode. + +---------- +Events +---------- + +on_activity +on_silence +before_winchange +on_winchange +before_resize +on_resize + +C. User + +-------- +Property: +-------- + +name: + mode: read only + type: String. + description: The login name. + +uid: + mode: read only + type: int + description: The index in the ACL bit fields. + +esc: +metaesc: + mode: read only + type: int + description: The escape character + +umask: + mode: read only + type: String. + description: The access for the window created by this user to other users. + The result will be in a form of 'rwx'. + +D. Screen + + This is a pseudo object standing for the whole screen object. All other + objects can be obtained starting from this one. + +-------- +Methods: +-------- +windows +displays +command +windowbyname + +input +get input from user. + + +=================== +The Implementation +=================== + +Bindings are in fact script interpretors. We can have several different +language bindings at the same time, with each registered at the compiling time +and loaded (initialized) dynamically at runtime. It's an bridge between +scripts and screen itself. + +--------------- +Binding related. +--------------- + +--------------- +Binding independent +--------------- @@ -182,7 +182,7 @@ struct action *ktabp; for (key = 0; key < 256 + KMAP_KEYS; key++) { n = ktabp[key].nr; - if (n == RC_ILLEGAL) + if (n == RC_ILLEGAL || n > RC_LAST) continue; if (ktabp[key].args == noargs) { diff --git a/src/lua.c b/src/lua.c new file mode 100644 index 0000000..7b3ec71 --- /dev/null +++ b/src/lua.c @@ -0,0 +1,1034 @@ +/* Lua scripting support + * + * Copyright (c) 2008 Sadrul Habib Chowdhury (sadrul@users.sf.net) + * Copyright (c) 2009 + * Sadrul Habib Chowdhury (sadrul@users.sf.net) + * Rui Guo (firemeteor.guo@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + **************************************************************** + */ +#include <sys/types.h> +#include "config.h" +#include "screen.h" +#include <sys/stat.h> +#include <unistd.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "extern.h" +#include "logfile.h" + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +static int StackDump(lua_State *L, const char *message) +{ + FILE *f = fopen("/tmp/debug/stack", "ab"); + int i = lua_gettop(L); + if (message) + fprintf(f, "%s", message); + while (i) + { + int t = lua_type(L, i); + switch (t) + { + case LUA_TSTRING: + fprintf(f, "String: %s\n", lua_tostring(L, i)); + break; + + case LUA_TBOOLEAN: + fprintf(f, "Boolean: %s\n", lua_toboolean(L, i) ? "true" : "false"); + break; + + case LUA_TNUMBER: + fprintf(f, "Number: %g\n", lua_tonumber(L, i)); + break; + + default: + fprintf(f, "Type: %s\n", lua_typename(L, t)); + } + i--; + } + if (message) + fprintf(f, "----\n"); + fclose(f); + return 0; +} + +extern struct win *windows, *fore; +extern struct display *displays, *display; +extern struct LayFuncs WinLf; +extern struct layer *flayer; + +static int LuaDispatch(void *handler, const char *params, va_list va); +static int LuaRegEvent(lua_State *L); +static int LuaUnRegEvent(lua_State *L); + +enum +{ + LUA_HANDLER_TYPE_N = 1, + LUA_HANDLER_TYPE_F +}; + +struct lua_handler +{ + int type; + union + { + char *name; + int reference; + } u; +}; + +/** Template {{{ */ + +#define CHECK_TYPE(name, type) \ +static type * \ +check_##name(lua_State *L, int index) \ +{ \ + type **var; \ + luaL_checktype(L, index, LUA_TUSERDATA); \ + var = (type **) luaL_checkudata(L, index, #name); \ + if (!var || !*var) \ + luaL_typerror(L, index, #name); \ + return *var; \ +} + +#define PUSH_TYPE(name, type) \ +static void \ +push_##name(lua_State *L, type **t) \ +{ \ + if (!t || !*t) \ + lua_pushnil(L); \ + else \ + { \ + type **r; \ + r = (type **)lua_newuserdata(L, sizeof(type *)); \ + *r = *t; \ + luaL_getmetatable(L, #name); \ + lua_setmetatable(L,-2); \ + } \ +} + +/* Much of the following template comes from: + * http://lua-users.org/wiki/BindingWithMembersAndMethods + */ + +static int get_int (lua_State *L, void *v) +{ + lua_pushinteger (L, *(int*)v); + return 1; +} + +static int set_int (lua_State *L, void *v) +{ + *(int*)v = luaL_checkint(L, 3); + return 0; +} + +static int get_number (lua_State *L, void *v) +{ + lua_pushnumber(L, *(lua_Number*)v); + return 1; +} + +static int set_number (lua_State *L, void *v) +{ + *(lua_Number*)v = luaL_checknumber(L, 3); + return 0; +} + +static int get_string (lua_State *L, void *v) +{ + lua_pushstring(L, (char*)v ); + return 1; +} + +static int set_string (lua_State *L, void *v) +{ + *(const char**)v = luaL_checkstring(L, 3); + return 0; +} + +typedef int (*Xet_func) (lua_State *L, void *v); + +/* member info for get and set handlers */ +struct Xet_reg +{ + const char *name; /* member name */ + Xet_func func; /* get or set function for type of member */ + size_t offset; /* offset of member within the struct */ + int (*absolute)(lua_State *); +}; + +static void Xet_add (lua_State *L, const struct Xet_reg *l) +{ + if (!l) + return; + for (; l->name; l++) + { + lua_pushstring(L, l->name); + lua_pushlightuserdata(L, (void*)l); + lua_settable(L, -3); + } +} + +static int Xet_call (lua_State *L) +{ + /* for get: stack has userdata, index, lightuserdata */ + /* for set: stack has userdata, index, value, lightuserdata */ + const struct Xet_reg *m = (const struct Xet_reg *)lua_touserdata(L, -1); /* member info */ + lua_pop(L, 1); /* drop lightuserdata */ + luaL_checktype(L, 1, LUA_TUSERDATA); + if (m->absolute) + return m->absolute(L); + return m->func(L, *(char**)lua_touserdata(L, 1) + m->offset); +} + +static int index_handler (lua_State *L) +{ + /* stack has userdata, index */ + lua_pushvalue(L, 2); /* dup index */ + lua_rawget(L, lua_upvalueindex(1)); /* lookup member by name */ + if (!lua_islightuserdata(L, -1)) + { + lua_pop(L, 1); /* drop value */ + lua_pushvalue(L, 2); /* dup index */ + lua_gettable(L, lua_upvalueindex(2)); /* else try methods */ + if (lua_isnil(L, -1)) /* invalid member */ + luaL_error(L, "cannot get member '%s'", lua_tostring(L, 2)); + return 1; + } + return Xet_call(L); /* call get function */ +} + +static int newindex_handler (lua_State *L) +{ + /* stack has userdata, index, value */ + lua_pushvalue(L, 2); /* dup index */ + lua_rawget(L, lua_upvalueindex(1)); /* lookup member by name */ + if (!lua_islightuserdata(L, -1)) /* invalid member */ + luaL_error(L, "cannot set member '%s'", lua_tostring(L, 2)); + return Xet_call(L); /* call set function */ +} + +static int +struct_register(lua_State *L, const char *name, const luaL_reg fn_methods[], const luaL_reg meta_methods[], + const struct Xet_reg setters[], const struct Xet_reg getters[]) +{ + int metatable, methods; + + /* create methods table & add it to the table of globals */ + luaL_register(L, name, fn_methods); + methods = lua_gettop(L); + + /* create metatable & add it to the registry */ + luaL_newmetatable(L, name); + luaL_register(L, 0, meta_methods); /* fill metatable */ + + /* To identify the type of object */ + lua_pushstring(L, "_objname"); + lua_pushstring(L, name); + lua_settable(L, -3); + + metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methods); /* dup methods table*/ + lua_rawset(L, metatable); /* hide metatable: + metatable.__metatable = methods */ + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, metatable); /* upvalue index 1 */ + Xet_add(L, getters); /* fill metatable with getters */ + lua_pushvalue(L, methods); /* upvalue index 2 */ + lua_pushcclosure(L, index_handler, 2); + lua_rawset(L, metatable); /* metatable.__index = index_handler */ + + lua_pushliteral(L, "__newindex"); + lua_newtable(L); /* table for members you can set */ + Xet_add(L, setters); /* fill with setters */ + lua_pushcclosure(L, newindex_handler, 1); + lua_rawset(L, metatable); /* metatable.__newindex = newindex_handler */ + + lua_pop(L, 1); /* drop metatable */ + return 1; /* return methods on the stack */ +} + +/** }}} */ + +/** Window {{{ */ + +PUSH_TYPE(window, struct win) + +CHECK_TYPE(window, struct win) + +static int get_window(lua_State *L, void *v) +{ + push_window(L, (struct win **)v); + return 1; +} + +static int +window_change_title(lua_State *L) +{ + struct win *w = check_window(L, 1); + unsigned int len; + const char *title = luaL_checklstring(L, 2, &len); + ChangeAKA(w, (char *)title, len); + return 0; +} + +static int +window_get_monitor_status(lua_State *L) +{ + struct win *w = check_window(L, 1); + int activity = luaL_checkint(L, 2); + if (activity) + /*monitor*/ + lua_pushinteger(L, w->w_monitor != MON_OFF); + else + /*silence*/ + lua_pushinteger(L, w->w_silence == SILENCE_ON ? w->w_silencewait: 0); + + return 1; +} + +static const luaL_reg window_methods[] = { + {"get_monitor_status", window_get_monitor_status}, + {"hook", LuaRegEvent}, + {0, 0} +}; + +static int +window_tostring(lua_State *L) +{ + char str[128]; + struct win *w = check_window(L, 1); + snprintf(str, sizeof(str), "{window #%d: '%s'}", w->w_number, w->w_title); + lua_pushstring(L, str); + return 1; +} + +static int +window_equality(lua_State *L) +{ + struct win *w1 = check_window(L, 1), *w2 = check_window(L, 2); + lua_pushboolean(L, (w1 == w2 || (w1 && w2 && w1->w_number == w2->w_number))); + return 1; +} + +static const luaL_reg window_metamethods[] = { + {"__tostring", window_tostring}, + {"__eq", window_equality}, + {0, 0} +}; + +static const struct Xet_reg window_setters[] = { + {"title", 0, 0, window_change_title/*absolute setter*/}, + {0, 0} +}; + +static const struct Xet_reg window_getters[] = { + {"title", get_string, offsetof(struct win, w_title) + 8}, + {"number", get_int, offsetof(struct win, w_number)}, + {"dir", get_string, offsetof(struct win, w_dir)}, + {"tty", get_string, offsetof(struct win, w_tty)}, + {"pid", get_int, offsetof(struct win, w_pid)}, + {"group", get_window, offsetof(struct win, w_group)}, + {"bell", get_int, offsetof(struct win, w_bell)}, + {0, 0} +}; + + +/** }}} */ + +/** AclUser {{{ */ + +PUSH_TYPE(user, struct acluser) + +CHECK_TYPE(user, struct acluser) + +static int +get_user(lua_State *L, void *v) +{ + push_user(L, (struct acluser **)v); + return 1; +} + +static const luaL_reg user_methods[] = { + {0, 0} +}; + +static int +user_tostring(lua_State *L) +{ + char str[128]; + struct acluser *u = check_user(L, 1); + snprintf(str, sizeof(str), "{user '%s'}", u->u_name); + lua_pushstring(L, str); + return 1; +} + +static const luaL_reg user_metamethods[] = { + {"__tostring", user_tostring}, + {0, 0} +}; + +static const struct Xet_reg user_setters[] = { + {0, 0} +}; + +static const struct Xet_reg user_getters[] = { + {"name", get_string, offsetof(struct acluser, u_name)}, + {"password", get_string, offsetof(struct acluser, u_password)}, + {0, 0} +}; + +/** }}} */ + +/** Canvas {{{ */ + +PUSH_TYPE(canvas, struct canvas) + +CHECK_TYPE(canvas, struct canvas) + +static int +get_canvas(lua_State *L, void *v) +{ + push_canvas(L, (struct canvas **)v); + return 1; +} + +static int +canvas_select(lua_State *L) +{ + struct canvas *c = check_canvas(L, 1); + if (!display || D_forecv == c) + return 0; + SetCanvasWindow(c, Layer2Window(c->c_layer)); + D_forecv = c; + + /* XXX: the following all is duplicated from process.c:DoAction. + * Should these be in some better place? + */ + ResizeCanvas(&D_canvas); + RecreateCanvasChain(); + RethinkDisplayViewports(); + ResizeLayersToCanvases(); /* redisplays */ + fore = D_fore = Layer2Window(D_forecv->c_layer); + flayer = D_forecv->c_layer; +#ifdef RXVT_OSC + if (D_xtermosc[2] || D_xtermosc[3]) + { + Activate(-1); + break; + } +#endif + RefreshHStatus(); +#ifdef RXVT_OSC + RefreshXtermOSC(); +#endif + flayer = D_forecv->c_layer; + CV_CALL(D_forecv, LayRestore();LaySetCursor()); + WindowChanged(0, 'F'); + return 1; +} + +static const luaL_reg canvas_methods[] = { + {"select", canvas_select}, + {0, 0} +}; + +static const luaL_reg canvas_metamethods[] = { + {0, 0} +}; + +static const struct Xet_reg canvas_setters[] = { + {0, 0} +}; + +static int +canvas_get_window(lua_State *L) +{ + struct canvas *c = check_canvas(L, 1); + struct win *win = Layer2Window(c->c_layer); + if (win) + push_window(L, &win); + else + lua_pushnil(L); + return 1; +} + +static const struct Xet_reg canvas_getters[] = { + {"next", get_canvas, offsetof(struct canvas, c_next)}, + {"xoff", get_int, offsetof(struct canvas, c_xoff)}, + {"yoff", get_int, offsetof(struct canvas, c_yoff)}, + {"xs", get_int, offsetof(struct canvas, c_xs)}, + {"ys", get_int, offsetof(struct canvas, c_ys)}, + {"xe", get_int, offsetof(struct canvas, c_xe)}, + {"ye", get_int, offsetof(struct canvas, c_ye)}, + {"window", 0, 0, canvas_get_window}, + {0, 0} +}; + +/** }}} */ + +/** Layout {{{ */ + +PUSH_TYPE(layout, struct layout) +CHECK_TYPE(layout, struct layout) + +static const struct Xet_reg layout_getters[] = { + {0,0} +}; + +static int +get_layout(lua_State *L, void *v) +{ + push_layout(L, (struct layout **)v); + return 1; +} + +/** }}} */ + +/** Display {{{ */ + +PUSH_TYPE(display, struct display) + +CHECK_TYPE(display, struct display) + +static int +display_get_canvases(lua_State *L) +{ + struct display *d; + struct canvas *iter; + int count; + + d = check_display(L, 1); + lua_newtable(L); + for (iter = d->d_cvlist, count = 0; iter; iter = iter->c_next, count++) { + lua_pushinteger(L, count); + push_canvas(L, &iter); + lua_settable(L, -3); + } + + return 1; +} + +static const luaL_reg display_methods[] = { + {"get_canvases", display_get_canvases}, + {0, 0} +}; + +static int +display_tostring(lua_State *L) +{ + char str[128]; + struct display *d = check_display(L, 1); + snprintf(str, sizeof(str), "{display: tty = '%s', term = '%s'}", d->d_usertty, d->d_termname); + lua_pushstring(L, str); + return 1; +} + +static const luaL_reg display_metamethods[] = { + {"__tostring", display_tostring}, + {0, 0} +}; + +static const struct Xet_reg display_setters[] = { + {0, 0} +}; + +static const struct Xet_reg display_getters[] = { + {"tty", get_string, offsetof(struct display, d_usertty)}, + {"term", get_string, offsetof(struct display, d_termname)}, + {"fore", get_window, offsetof(struct display, d_fore)}, + {"other", get_window, offsetof(struct display, d_other)}, + {"width", get_int, offsetof(struct display, d_width)}, + {"height", get_int, offsetof(struct display, d_height)}, + {"user", get_user, offsetof(struct display, d_user)}, + {"layout", get_layout, offsetof(struct display, d_layout)}, + {0, 0} +}; + +/** }}} */ + +/** Screen {{{ */ + +static int +screen_get_windows(lua_State *L) +{ + struct win *iter; + int count; + + lua_newtable(L); + for (iter = windows, count = 0; iter; iter = iter->w_next, count++) { + lua_pushinteger(L, iter->w_number); + push_window(L, &iter); + lua_settable(L, -3); + } + + return 1; +} + +static int +screen_get_displays(lua_State *L) +{ + struct display *iter; + int count; + + lua_newtable(L); + for (iter = displays, count = 0; iter; iter = iter->d_next, count++) { + lua_pushinteger(L, count); + push_display(L, &iter); + lua_settable(L, -3); + } + + return 1; +} + +static int +screen_get_display(lua_State *L) +{ + push_display(L, &display); + return 1; +} + +static int +screen_exec_command(lua_State *L) +{ + const char *command; + unsigned int len; + + command = luaL_checklstring(L, 1, &len); + if (command) + RcLine((char *)command, len); + + return 0; +} + +static int +screen_append_msg(lua_State *L) +{ + const char *msg, *color; + int len; + msg = luaL_checklstring(L, 1, &len); + if (lua_isnil(L, 2)) + color = NULL; + else + color = luaL_checklstring(L, 2, &len); + AppendWinMsgRend(msg, color); + return 0; +} + +static const luaL_reg screen_methods[] = { + {"windows", screen_get_windows}, + {"displays", screen_get_displays}, + {"display", screen_get_display}, + {"command", screen_exec_command}, + {"append_msg", screen_append_msg}, + {"hook", LuaRegEvent}, + {"unhook", LuaUnRegEvent}, + {0, 0} +}; + +static const luaL_reg screen_metamethods[] = { + {0, 0} +}; + +static const struct Xet_reg screen_setters[] = { + {0, 0} +}; + +static const struct Xet_reg screen_getters[] = { + {0, 0} +}; + +/** }}} */ + +/** Public functions {{{ */ +static lua_State *L; +int LuaInit(void) +{ + L = luaL_newstate(); + + luaL_openlibs(L); + +#define REGISTER(X) struct_register(L, #X , X ## _methods, X##_metamethods, X##_setters, X##_getters) + + REGISTER(screen); + REGISTER(window); + REGISTER(display); + REGISTER(user); + REGISTER(canvas); + + return 0; +} + +/* An error message on top of the stack. */ +static void +LuaShowErr(lua_State *L) +{ + struct display *d = display; + unsigned int len; + const char *message = luaL_checklstring(L, -1, &len); + LMsg(0, "%s", message ? message : "Unknown error"); + lua_pop(L, 1); + display = d; +} + +struct fn_def +{ + void (*push_fn)(lua_State *, void*); + void *value; +}; + +static int +LuaCallProcess(const char *name, struct fn_def defs[]) +{ + int argc = 0; + + lua_getfield(L, LUA_GLOBALSINDEX, name); + if (lua_isnil(L, -1)) + return 0; + for (argc = 0; defs[argc].push_fn; argc++) + defs[argc].push_fn(L, defs[argc].value); + if (lua_pcall(L, argc, 0, 0) == LUA_ERRRUN && lua_isstring(L, -1)) + { + LuaShowErr(L); + return 0; + } + return 1; +} + +int LuaSource(const char *file, int async) +{ + if (!L) + return 0; + struct stat st; + if (stat(file, &st) == -1) + Msg(errno, "Error loading lua script file '%s'", file); + else + { + int len = strlen(file); + if (len < 4 || strncmp(file + len - 4, ".lua", 4) != 0) + return 0; + if (luaL_dofile(L, file) && lua_isstring(L, -1)) + { + LuaShowErr(L); + } + return 1; + } + return 0; +} + +int LuaFinit(void) +{ + if (!L) + return 0; + lua_close(L); + L = (lua_State*)0; + return 0; +} + +int +LuaProcessCaption(const char *caption, struct win *win, int len) +{ + if (!L) + return 0; + struct fn_def params[] = { + {lua_pushstring, caption}, + {push_window, &win}, + {lua_pushinteger, len}, + {NULL, NULL} + }; + return LuaCallProcess("process_caption", params); +} + +static void +push_stringarray(lua_State *L, char **args) +{ + int i; + lua_newtable(L); + for (i = 1; args && *args; i++) { + lua_pushinteger(L, i); + lua_pushstring(L, *args++); + lua_settable(L, -3); + } +} + +int +LuaPushParams(lua_State *L, const char *params, va_list va) +{ + int num = 0; + while (*params) + { + switch (*params) + { + case 's': + lua_pushstring(L, va_arg(va, char *)); + break; + case 'S': + push_stringarray(L, va_arg(va, char **)); + break; + case 'i': + lua_pushinteger(L, va_arg(va, int)); + break; + case 'd': + { + struct display *d = va_arg(va, struct display *); + push_display(L, &d); + break; + } + } + params++; + num++; + } + return num; +} + +int LuaCall(char *func, char **argv) +{ + int argc; + if (!L) + return 0; + + StackDump(L, "Before LuaCall\n"); + lua_getfield(L, LUA_GLOBALSINDEX, func); + if (lua_isnil(L, -1)) + { + lua_pushstring(L, "Could not find the script function\n"); + LuaShowErr(L); + return 0; + } + for (argc = 0; *argv; argv++, argc++) + { + lua_pushstring(L, *argv); + } + if (lua_pcall(L, argc, 0, 0) == LUA_ERRRUN) + { + if(lua_isstring(L, -1)) + { + StackDump(L, "After LuaCall\n"); + LuaShowErr(L); + return 0; + } + } + return 1; +} + +static int +LuaDispatch(void *handler, const char *params, va_list va) +{ + struct lua_handler *lh = handler; + int argc, retvalue; + + if (lh->type == LUA_HANDLER_TYPE_N) + lua_getfield(L, LUA_GLOBALSINDEX, lh->u.name); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, lh->u.reference); + + if (lua_isnil(L, -1)) + return 0; + argc = LuaPushParams(L, params, va); + + if (lua_pcall(L, argc, 1, 0) == LUA_ERRRUN && lua_isstring(L, -1)) + { + StackDump(L, "After LuaDispatch\n"); + LuaShowErr(L); + return 0; + } + retvalue = lua_tonumber(L, -1); + lua_pop(L, 1); + return retvalue; +} + +int LuaForeWindowChanged(void) +{ + struct fn_def params[] = { + {push_display, &display}, + {push_window, display ? &D_fore : &fore}, + {NULL, NULL} + }; + if (!L) + return 0; + return LuaCallProcess("fore_changed", params); +} + +/* +int +LuaCommandExecuted(const char *command, const char **args, int argc) +{ + if (!L) + return 0; + struct fn_def params[] = { + {lua_pushstring, command}, + {push_stringarray, args}, + {NULL, NULL} + }; + return LuaCallProcess("command_executed", params); +}*/ + +#define SEVNAME_MAX 30 +static int +LuaRegEvent(lua_State *L) +{ + /* signature: hook(obj, event, handler, priv); + * or: hook(event, handler, priv) + * returns: A ticket for later unregister. */ + int idx = 1, argc = lua_gettop(L); + int priv = 31; /* Default privilege */ + struct lua_handler lh; + + char *obj = NULL; + const char *objname = "global"; + + static char evbuf[SEVNAME_MAX]; + const char *event; + + struct script_event *sev; + + StackDump(L, "Before RegEvent\n"); + + /* Identify the object, if specified */ + if (luaL_getmetafield(L, 1, "_objname")) + { + objname = luaL_checkstring(L, -1); + lua_pop(L, 1); + if (!strcmp("screen", objname)) + objname = "global"; + else + obj = *(char **)lua_touserdata(L, 1); + idx++; + } + + event = luaL_checkstring(L, idx++); + snprintf(evbuf, SEVNAME_MAX, "%s_%s", objname, event); + + if (lua_isfunction(L, idx)) + { + lua_pushvalue(L, idx); + lh.u.reference = luaL_ref(L, LUA_REGISTRYINDEX); + lh.type = LUA_HANDLER_TYPE_F; + idx++; + } + else + { + lh.type = LUA_HANDLER_TYPE_N; + lh.u.name = SaveStr(luaL_checkstring(L, idx++)); + } + + StackDump(L, "In RegEvent\n"); + + if (idx <= argc) + priv = luaL_checkinteger(L, idx); + + sev = object_get_event(obj, evbuf); + if (sev) + { + struct listener *l; + l = (struct listener *)malloc(sizeof(struct listener)); + if (!l) + return luaL_error(L, "Out of memory"); + l->handler = &lh; + l->priv = priv; + l->dispatcher = LuaDispatch; + if (register_listener(sev, l)) + { + free(l); + if (lh.type == LUA_HANDLER_TYPE_N) + return luaL_error(L, "Handler %s has already been registered", lh.u.name); + else + return luaL_error(L, "Handler has already been registered"); + } + /* Return the handler for un-register */ + l->handler = malloc(sizeof(struct lua_handler)); + memcpy(l->handler, &lh, sizeof(struct lua_handler)); + lua_pushlightuserdata(L, l); + } + else + return luaL_error(L, "Invalid event specified: %s for object %s", event, objname); + + StackDump(L, "After RegEvent\n"); + return 1; +} + +static int +LuaUnRegEvent(lua_State *L) +{ + /* signature: unhook([obj], ticket) + * returns: true of success, false otherwise */ + int idx = 1; + struct listener *l; + + /* If the param is not as expected */ + if (!lua_islightuserdata(L, idx)) + { + /* Then it should be the userdata to be ignore, but if not ... */ + if (!lua_isuserdata(L, idx)) + luaL_checktype(L, idx, LUA_TLIGHTUSERDATA); + idx++; + luaL_checktype(L, idx, LUA_TLIGHTUSERDATA); + } + + l = (struct listener*)lua_touserdata(L, idx++); + + /* Validate the listener structure */ + if (!l || !l->handler) + { + /* invalid */ + lua_pushboolean(L,0); + } + else + { + struct lua_handler *lh = l->handler; + if (lh->type == LUA_HANDLER_TYPE_N) + Free(lh->u.name); + Free(l->handler); + unregister_listener(l); + lua_pushboolean(L, 1); + } + + return 1; +} + +/** }}} */ + +struct ScriptFuncs LuaFuncs = +{ + LuaForeWindowChanged, + LuaProcessCaption +}; + +struct binding lua_binding = +{ + "lua", /*name*/ + 0, /*inited*/ + 0, /*registered*/ + LuaInit, + LuaFinit, + LuaCall, + LuaSource, + 0, /*b_next*/ + &LuaFuncs +}; + diff --git a/src/process.c b/src/process.c index da9188d..6c8cba7 100644 --- a/src/process.c +++ b/src/process.c @@ -171,6 +171,12 @@ static struct action *FindKtab __P((char *, int)); static void SelectFin __P((char *, int, char *)); static void SelectLayoutFin __P((char *, int, char *)); +/* Alias */ +static void AddAlias __P((const char *name, const char *val , char **args, int *argl, int count)); +static struct alias * FindAlias __P((const char *name)); +static struct alias * FindAliasnr __P((int)); +static void DelAlias __P((const char *name)); +static int DoAlias __P((const char *, char **, int *)); extern struct layer *flayer; extern struct display *display, *displays; @@ -248,6 +254,20 @@ struct digraph int value; }; +/* + * Command aliases. + */ +struct alias { + int nr; + char *name; /* Name of the alias */ + int cmdnr; /* Number of the command this is alias for */ + char **args; /* The argument list for the command */ + int *argl; + struct alias *next; +}; +struct alias *g_aliases_list = NULL; + + /* digraph table taken from old vim and rfc1345 */ static struct digraph digraphs[MAX_DIGRAPH + 1] = { {' ', ' ', 160}, /* */ @@ -1156,11 +1176,24 @@ int key; struct acluser *user; user = display ? D_user : users; + + if (nr > RC_LAST) + { + struct alias *alias = FindAliasnr(nr); + if (alias) + { + DoAlias(alias->name, act->args, act->argl); + return; + } + nr = RC_ILLEGAL; + } + if (nr == RC_ILLEGAL) { debug1("key '%c': No action\n", key); return; } + n = comms[nr].flags; /* Commands will have a CAN_QUERY flag, depending on whether they have * something to return on a query. For example, 'windows' can return a result, @@ -2645,6 +2678,12 @@ int key; break; case RC_SLEEP: break; /* Already handled */ + case RC_ALIAS: + if (argc == 1) + DelAlias(args[0]); + else + AddAlias(args[0], args[1], args+1, argl+1, argc - 1); + break; case RC_TERM: s = NULL; if (ParseSaveStr(act, &s)) @@ -3282,10 +3321,15 @@ int key; { if ((i = FindCommnr(args[1])) == RC_ILLEGAL) { - OutputMsg(0, "%s: bind: unknown command '%s'", rc_name, args[1]); - break; + struct alias *alias = FindAlias(args[1]); + if (!alias) + { + OutputMsg(0, "%s: bind: unknown command or alias '%s'", rc_name, args[1]); + break; + } + i = alias->nr; } - if (CheckArgNum(i, args + 2) < 0) + if (i <= RC_LAST && CheckArgNum(i, args + 2) < 0) break; ClearAction(&ktabp[n]); SaveAction(ktabp + n, i, args + 2, argl + 2); @@ -3413,10 +3457,15 @@ int key; { if ((newnr = FindCommnr(args[1])) == RC_ILLEGAL) { - OutputMsg(0, "%s: bindkey: unknown command '%s'", rc_name, args[1]); - break; + struct alias *alias = FindAlias(args[1]); + if (!alias) + { + OutputMsg(0, "%s: bindkey: unknown command or alias '%s'", rc_name, args[1]); + break; + } + newnr = alias->nr; } - if (CheckArgNum(newnr, args + 2) < 0) + if (newnr <= RC_LAST && CheckArgNum(newnr, args + 2) < 0) break; ClearAction(newact); SaveAction(newact, newnr, args + 2, argl + 2); @@ -3953,9 +4002,15 @@ int key; #endif break; - case RC_SOURCE: - do_source(*args); - break; + case RC_SOURCE: + do_source(*args); + break; + +#ifdef SCRIPT + case RC_SCRIPT: + ScriptCmd(argc, (const char **) args); + break; +#endif #ifdef MULTIUSER case RC_SU: @@ -4177,10 +4232,15 @@ int key; { if ((i = FindCommnr(args[1])) == RC_ILLEGAL) { - OutputMsg(0, "%s: idle: unknown command '%s'", rc_name, args[1]); - break; + struct alias *alias = FindAlias(args[1]); + if (!alias) + { + OutputMsg(0, "%s: idle: unknown command or alias '%s'", rc_name, args[1]); + break; + } + i = alias->nr; } - if (CheckArgNum(i, args + 2) < 0) + if (i <= RC_LAST && CheckArgNum(i, args + 2) < 0) break; ClearAction(&idleaction); SaveAction(&idleaction, i, args + 2, argl + 2); @@ -4461,6 +4521,12 @@ int key; #endif break; } + +#ifdef SCRIPT + if (nr < RC_LAST) + trigger_sevent(&globalevents.cmdexecuted, comms[nr].name, args); +#endif + if (display != odisplay) { for (display = displays; display; display = display->d_next) @@ -4470,14 +4536,66 @@ int key; } #undef OutputMsg +static int +DoAlias(name, args, argl) +const char *name; +char **args; +int *argl; +{ + char **mergeds; + int *mergedl; + int count, i; + struct alias *alias = FindAlias(name); + + if (alias == NULL) + return 0; + + count = 0; + for (; args && args[count]; count++) + ; + for (i = 0; alias->args && alias->args[i]; i++, count++) + ; + ++count; + + if ((mergeds = malloc(count * sizeof(char *))) == 0) + return 0; + if ((mergedl = malloc(count * sizeof(int))) == 0) + { + free(mergeds); + return 0; + } + for (count = 0; alias->args && alias->args[count]; count++) + { + mergeds[count] = alias->args[count]; + mergedl[count] = alias->argl[count]; + } + for (i = 0; args && args[i]; i++, count++) + { + mergeds[count] = args[i]; + mergedl[count] = argl[i]; + } + mergeds[count] = 0; + mergedl[count] = 0 ; + + DoCommand(mergeds, mergedl); + + free(mergeds); + free(mergedl); + return 1; +} + void -DoCommand(argv, argl) +DoCommand(argv, argl) char **argv; int *argl; { struct action act; const char *cmd = *argv; + /* Alias? */ + if (DoAlias(*argv, argv + 1, argl + 1)) + return; + act.quiet = 0; /* For now, we actually treat both 'supress error' and 'suppress normal message' as the * same, and ignore all messages on either flag. If we wanted to do otherwise, we would @@ -5133,6 +5251,9 @@ struct win *wi; if (wi) WindowChanged(wi, 'u'); flayer = D_forecv->c_layer; +#ifdef SCRIPT + ScriptForeWindowChanged(); +#endif /* Activate called afterwards, so no RefreshHStatus needed */ } @@ -6535,6 +6656,141 @@ char *presel; return wi; } +/** + * Add an alias + */ +void +AddAlias(name, value, args, argl, count) +const char *name; +const char *value; +char **args; +int *argl; +int count; +{ + struct alias *nalias = NULL; + static next_command = RC_LAST; + int nr; + + /* Make sure we don't already have this alias name defined. */ + if (FindAlias(name) != NULL) + { + Msg(0, "alias already defined: %s", name); + return; + } + + /* Make sure the alias maps to something */ + if ((nr = FindCommnr((char *)value)) == RC_ILLEGAL) + { + struct alias *alias = FindAlias(value); + if (!alias) + { + Msg(0, "%s: could not find command or alias '%s'", rc_name, value); + return; + } + nr = alias->nr; + } + + nalias = (struct alias *)calloc(1, sizeof(struct alias)); + + /* store it */ + nalias->next = NULL; + nalias->name = SaveStr(name); + nalias->cmdnr = nr; + if (count > 0) + { + nalias->args = SaveArgs(args); + nalias->argl = calloc(count + 1, sizeof(int)); + while (count--) + nalias->argl[count] = argl[count]; + } + nalias->nr = ++next_command; + + /* Add to head */ + nalias->next = g_aliases_list; + g_aliases_list = nalias; +} + + +/** + * Find an alias by name. + */ +static struct alias * +FindAlias(name) +const char *name; +{ + struct alias *t = g_aliases_list; + + while(t != NULL) + { + if ((t->name != NULL) && + (strcmp(t->name, name) == 0)) + return t; + + t = t->next; + } + + return NULL; +} + +/** + * Find an alias by number. + */ +static struct alias * +FindAliasnr(nr) +int nr; +{ + struct alias *t; + for (t = g_aliases_list; t; t = t->next) + { + if (t->nr == nr) + return t; + } + + return NULL; +} + +/** + * Delete an alias + */ +void +DelAlias(name) +const char *name; +{ + /* Find the previous alias */ + struct alias *cur = g_aliases_list; + struct alias **pcur = &g_aliases_list; + + while (cur != NULL) + { + if ((cur->name != NULL) && + (strcmp(cur->name, name) == 0)) + { + struct alias *found = cur; + int c; + + /* remove this one from the chain. */ + *pcur = found->next; + + free(found->name); + if (found->args) + { + for (c = 0; found->args[c]; c++) + free(found->args[c]); + free(found->args); + free(found->argl); + } + free(found); + + Msg(0, "alias %s removed", name); + return; + } + pcur = &cur->next; + cur = cur->next; + } + + Msg(0, "alias %s not found", name); +} + #if 0 /* sorted list of all commands */ diff --git a/src/screen.c b/src/screen.c index 3dde3b4..ba5c259 100644 --- a/src/screen.c +++ b/src/screen.c @@ -388,6 +388,11 @@ char **av; #ifdef MULTIUSER char *sockp; #endif + +#ifdef SCRIPT + char *script_file = 0; +#endif + char *sty = 0; #if (defined(AUX) || defined(_AUX_SOURCE)) && defined(POSIX) @@ -754,6 +759,13 @@ char **av; nwin_options.encoding = nwin_options.encoding == -1 ? UTF8 : 0; break; #endif + case 'u': + if (--ac == 0) + exit_with_usage(myname, "Specify lua script file with -u", NULL); + if (script_file) + free(script_file); + script_file = SaveStr(*++av); + break; default: exit_with_usage(myname, "Unknown option %s", --ap); } @@ -1345,6 +1357,17 @@ char **av; ServerSocket = MakeServerSocket(); InitKeytab(); + +#ifdef SCRIPT + LoadBindings(); + if (script_file) + { + ScriptSource(script_file); + free(script_file); + script_file = 0; + } +#endif + #ifdef ETCSCREENRC # ifdef ALLOW_SYSSCREENRC if ((ap = getenv("SYSSCREENRC"))) @@ -1803,6 +1826,11 @@ int i; signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_IGN); debug1("Finit(%d);\n", i); + +#ifdef SCRIPT + FinalizeBindings(); +#endif + while (windows) { struct win *p = windows; @@ -1926,6 +1954,7 @@ int mode; case D_DETACH: AddStrSock("detached"); sign = SIG_BYE; + trigger_sevent(&globalevents.detached, display, 0x0); break; #ifdef BSDJOBS case D_STOP: @@ -1936,6 +1965,7 @@ int mode; case D_REMOTE: AddStrSock("remote detached"); sign = SIG_BYE; + trigger_sevent(&globalevents.detached, display, 0x1); break; #endif #ifdef POW_DETACH @@ -1947,6 +1977,7 @@ int mode; AddStr("\r\n"); } sign = SIG_POWER_BYE; + trigger_sevent(&globalevents.detached, display, 0x2); break; #ifdef REMOTE_DETACH case D_REMOTE_POWER: @@ -1957,6 +1988,7 @@ int mode; AddStr("\r\n"); } sign = SIG_POWER_BYE; + trigger_sevent(&globalevents.detached, display, 0x1 | 0x2); /* Remote and power */ break; #endif #endif @@ -2473,6 +2505,30 @@ time_t now; return bt->result; } +void +AppendWinMsgRend(str, color) +char *str, *color; +{ + char *p; + int r = -1; + if (winmsg_numrend >= MAX_WINMSG_REND) + return; + p = winmsg_buf + strlen(winmsg_buf); + if (color) + { + if (*color != '-') + { + r = ParseAttrColor(color, 0, 0); + if (r == -1) + return; + } + winmsg_rend[winmsg_numrend] = r; + winmsg_rendpos[winmsg_numrend] = p - winmsg_buf; + winmsg_numrend++; + } + strncpy(p, str, winmsg_buf + sizeof(winmsg_buf) - p); +} + int AddWinMsgRend(str, r) const char *str; @@ -2518,17 +2574,25 @@ int rec; int truncper = 0; int trunclong = 0; struct backtick *bt; - + if (winmsg_numrend >= 0) winmsg_numrend = 0; else winmsg_numrend = -winmsg_numrend; - + + *p = '\0'; +#ifdef SCRIPT + if (ScriptProcessCaption(str, win, padlen)) + return winmsg_buf; +#endif + if (!display) + return winmsg_buf; + tick = 0; tm = 0; ctrl = 0; gettimeofday(&now, NULL); - for (; *s && (l = winmsg_buf + MAXSTR - 1 - p) > 0; s++, p++) + for (s = str; *s && (l = winmsg_buf + MAXSTR - 1 - p) > 0; s++, p++) { *p = *s; if (ctrl) @@ -3076,7 +3140,7 @@ int start, max; if (start-- > 0) s++; else - PUTCHARLP(*s++); + PUTCHARLP(*s++); } } r = winmsg_rend[i]; diff --git a/src/screen.h b/src/screen.h index 5c93f32..ff361df 100644 --- a/src/screen.h +++ b/src/screen.h @@ -48,6 +48,7 @@ #include "comm.h" #include "layer.h" #include "term.h" +#include "script.h" #ifdef DEBUG diff --git a/src/script.c b/src/script.c new file mode 100644 index 0000000..2b09a9c --- /dev/null +++ b/src/script.c @@ -0,0 +1,290 @@ +/* Copyright (c) 2008 Sadrul Habib Chowdhury (sadrul@users.sf.net) + * 2009 Rui Guo (firemeteor.guo@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + **************************************************************** + */ + +#include "config.h" +#include "screen.h" +#include <stddef.h> + +/*Binding structure & functions*/ + +struct binding *bindings = NULL; + +static void +register_binding (struct binding *new_binding) +{ + if (!new_binding->registered) + { + new_binding->b_next = bindings; + bindings = new_binding; + new_binding->registered = 1; + } +} + +#ifdef LUA_BINDING +extern struct binding lua_binding; +#endif + +void LoadBindings(void) +{ +#ifdef LUA_BINDING + register_binding(&lua_binding); +#endif +} + +void +FinalizeBindings (void) +{ + struct binding *binding=bindings; + while(binding) + { + if (binding->inited) + binding->bd_Finit(); + binding = binding->b_next; + } +} + +void +ScriptSource(int argc, const char **argv) +{ + int ret = 0; + int async = 0; + const char *bd_select = 0, *script; + struct binding *binding = bindings; + + /* Parse the commandline options + * sourcescript [-async|-a] [-binding|-b <binding>] script + */ + while (*argv && **argv == '-') + { + /* check for (-a | -async) */ + const char *arg = *argv; + if ((arg[1] == 'a' && !arg[2]) + || strcmp(arg, "-async") == 0) + async = 1; + /* check for (-b | -binding) */ + else if ((arg[1] == 'b' && !arg[2]) + || strcmp(arg, "-binding") == 0) { + argv++; + bd_select = *argv; + } + argv++; + } + script = *argv; + + while (binding) { + if (!bd_select || strcmp(bd_select, binding->name) == 0) { + /*dynamically initialize the binding*/ + if (!binding->inited) + { + binding->bd_Init(); + binding->inited = 1; + } + + /*and source the script*/ + if (ret = binding->bd_Source(script, async)) + break; + } + binding = binding->b_next; + } + if (!ret) + LMsg(1, "Could not source specified script %s", script); +} + +int +ScriptCall(const char *func, const char **argv) +{ + /*TODO*/ + return LuaCall(func, argv); +} + +void +ScriptCmd(int argc, const char **argv) +{ + const char * sub = *argv; + argv++;argc--; + if (!strcmp(sub, "call")) + ScriptCall(*argv, argv+1); + else if (!strcmp(sub, "source")) + ScriptSource(argc, argv); +} + +/* Event notification handling */ + +struct gevents globalevents; + +/* To add a new event, introduce a field for that event to the object in + * question, and don't forget to put an descriptor here. NOTE: keep the + * name field sorted in alphabet order, the searching relies on it. + * + * the params string specifies the expected parameters. The length of the + * string equals to the number of parameters. Each char specifies the type of + * the parameter, with its meaning similar to those in printf(). + * + * s: string (char *) + * S: string array (char **) + * i: signed int + * d: display + */ + +struct sev_description { + char *name; + char *params; + int offset; +} event_table[] = { + /* Global events */ + {"global_cmdexecuted", "sS", offsetof(struct gevents, cmdexecuted)}, + {"global_detached", "di", offsetof(struct gevents, detached)}, + /* The command "detach" triggers both 'cmdexecuted' and 'detached' events. + However, we need the 'detached' event to trigger callbacks from remote detaches. + */ + + /* Window events */ + {"window_resize", "", offsetof(struct win, w_sev.resize)}, + {"window_can_resize", "", offsetof(struct win, w_sev.canresize)} +}; + +/* Get the event queue with the given name in the obj. If the obj is NULL, + * global events are searched. If no event is found, a NULL is returned. + */ +struct script_event * +object_get_event(char *obj, const char *name) +{ + int lo, hi, n, cmp; + if (!obj) + obj = (char *)&globalevents; + + lo = 0; + n = hi = sizeof(event_table) / sizeof(struct sev_description); + while (lo < hi) + { + int half; + half = (lo + hi) >> 1; + cmp = strcmp(name, event_table[half].name); + if (cmp > 0) + lo = half + 1; + else + hi = half; + } + + if (lo >= n || strcmp(name, event_table[lo].name)) + return 0; + else + { + /*found an entry.*/ + struct script_event *res; + res = (struct script_event *) (obj + event_table[lo].offset); + /*Setup the parameter record.*/ + res->params = event_table[lo].params; + return res; + } +} + +/* Put a listener in a proper position in the chain + * according to the privlege. + * Not insert duplicate entry. return zero if successful.*/ +int +register_listener(struct script_event *ev, struct listener *l) +{ + unsigned int priv = l->priv; + struct listener *p, *iter = &ev->listeners; + +#if 0 + while(iter->chain && priv >= iter->chain->priv) + { + iter = iter->chain; + /* return if duplicate found*/ + if (iter->handler == l->handler + && iter->dispatcher == l->dispatcher) + return 1; + } +#endif + p = iter; + + l->chain = p->chain; + l->prev = p; + if (p->chain) + p->chain->prev = l; + p->chain = l; + return 0; +} + +void +unregister_listener(struct listener *l) +{ + struct listener *p = l->prev; + p->chain = l->chain; + if (l->chain) + l->chain->prev = p; + l->chain = l->prev = 0; + l->handler = 0; + free(l); +} + +/* Trigger event with given parameters.*/ +int +trigger_sevent(struct script_event *ev, VA_DOTS) +{ + int res = 0; + struct listener *chain; + char *params; + VA_LIST(va); + /*invalid or un-registered event structure*/ + if (!ev || !ev->params) + return 0; + + /*process the chain in order, stop if any of the handler returns true.*/ + chain = ev->listeners.chain; + params = ev->params; + while (chain) + { + VA_START(va, ev); + res = chain->dispatcher(chain->handler, params, va); + VA_END(va); + if (res) + break; + chain = chain->chain; + } + + return res; +} + +#define ALL_SCRIPTS(fn, params, stop) do { \ + struct binding *iter; \ + for (iter = bindings; iter; iter = iter->b_next) \ + { \ + if (iter->fns && iter->fns->fn && (ret = (iter->fns->fn params)) && stop) \ + break; \ + } \ +} while (0) + +void ScriptForeWindowChanged(void) +{ + int ret; + ALL_SCRIPTS(sf_ForeWindowChanged, (), 0); +} + +int ScriptProcessCaption(const char *str, struct win *win, int len) +{ + int ret = 0; + ALL_SCRIPTS(sf_ProcessCaption, (str, win, len), 1); + return ret; +} + diff --git a/src/script.h b/src/script.h new file mode 100644 index 0000000..c268eb5 --- /dev/null +++ b/src/script.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2008 Sadrul Habib Chowdhury (sadrul@users.sf.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + **************************************************************** + * $Id$ FAU + */ +#ifndef SCRIPT_H +#define SCRIPT_H +struct win; + +/*Obsolete*/ +struct ScriptFuncs +{ + int (*sf_ForeWindowChanged) __P((void)); + int (*sf_ProcessCaption) __P((const char *, struct win *, int len)); +}; + +/***Language binding***/ +struct binding +{ + char * name; + int inited; + int registered; + int (*bd_Init) __P((void)); + int (*bd_Finit) __P((void)); + int (*bd_call) __P((char *func, char **argv)); + /*Returns zero on failure, non zero on success*/ + int (*bd_Source) __P((const char *, int)); + struct binding *b_next; + struct ScriptFuncs *fns; +}; + +void LoadBindings(void); +void FinializeBindings(void); +void ScriptCmd __P((int argc, const char **argv)); + +/***Script events***/ + +struct script_event; +/* Script event listener */ +struct listener +{ + /*Binding dependent event handler data*/ + void *handler; + + /* dispatcher provided by the binding. + * The return value is significant: + * a non-zero value will stop further + * notification to the rest of the chain.*/ + int (*dispatcher) __P((void *handler, const char *params, va_list va)); + + /* smaller means higher privilege.*/ + unsigned int priv; + struct listener *chain; + struct listener *prev; +}; + +/* the script_event structure needs to be zeroed before using. + * embeding this structure directly into screen objects will do the job, as + * long as the objects are created from calloc() call.*/ +struct script_event +{ + /* expected parameter description of this event. */ + char *params; + struct listener listeners; +}; +struct script_event* object_get_event __P((char *obj, const char *name)); +int trigger_sevent(struct script_event *ev, VA_DOTS); + +struct gevents { + struct script_event cmdexecuted; + struct script_event detached; +}; +extern struct gevents globalevents; +#endif diff --git a/src/scripts/cmdcallback.lua b/src/scripts/cmdcallback.lua new file mode 100644 index 0000000..54fa4eb --- /dev/null +++ b/src/scripts/cmdcallback.lua @@ -0,0 +1,44 @@ +--[[ For now, this sample function will simply record all the commands executed ]]-- + +ticket1 = screen.hook("cmdexecuted", function(name, args) + os.execute('mkdir -p /tmp/debug') + local f = io.open('/tmp/debug/22', 'a') + f:write("Command executed: " .. name) + + for i, c in pairs(args) do + f:write(" " .. c) + end + + f:write("\n") + f:close() + return 0 + end) + +function cmd(name, args) + os.execute('mkdir -p /tmp/debug') + local f = io.open('/tmp/debug/11', 'a') + f:write("Command executed: " .. name) + + for i, c in pairs(args) do + f:write(" " .. c) + end + + f:write("\n") + f:close() + return 0 +end + +ticket2 = screen.hook("cmdexecuted", "cmd") + +function unhook() + if ticket1 ~= nil then + screen.unhook(ticket1) + ticket1 = nil + end + + if ticket2 ~= nil then + screen.unhook(ticket2) + ticket2 = nil + end +end + diff --git a/src/scripts/findwindow.lua b/src/scripts/findwindow.lua new file mode 100644 index 0000000..ff393df --- /dev/null +++ b/src/scripts/findwindow.lua @@ -0,0 +1,19 @@ +function find_window(name) + display = screen.display() + canvases = display:get_canvases() + for i, c in pairs(canvases) do + w = c.window + if w ~= nil and (w.title == name or tostring(w.number) == name) then c:select() return end + end + + -- Try partial matches, just like 'select' + for i, c in pairs(canvases) do + w = c.window + if w ~= nil and w.title:sub(1, name:len()) == name then c:select() return end + end + + -- We didn't find the desired window in any canvas + -- So switch to the window in the current canvas instead + screen.command("select " .. name) +end + diff --git a/src/window.h b/src/window.h index 1f70f39..e5b0d75 100644 --- a/src/window.h +++ b/src/window.h @@ -297,6 +297,13 @@ struct win #else int w_exitstatus; #endif +#ifdef SCRIPT + struct + { + struct script_event resize; + struct script_event canresize; + } w_sev; /*For Script events. */ +#endif }; |