X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=build%2Fdialog.c;fp=build%2Fdialog.c;h=ee8a2a8a669c61157eb4ccdced3ca442ca807858;hp=0000000000000000000000000000000000000000;hb=6c9943707d4f33035830eba0587a61a34eaecbc2;hpb=af88821a172c4dfd138b91b2a5148ae50b502fa2 diff --git a/build/dialog.c b/build/dialog.c new file mode 100644 index 0000000..ee8a2a8 --- /dev/null +++ b/build/dialog.c @@ -0,0 +1,578 @@ + +/*] Copyright (c) 2011, Charles McGarvey [******************************* +**] All rights reserved. +* +* vi:ts=4 sw=4 tw=75 +* +* Distributable under the terms and conditions of the 2-clause BSD license; +* see the file COPYING for a complete text of the license. +* +**************************************************************************/ + +#define LUA_DIALOG_NAME "dialog" +#define LUA_DIALOG_VERSION "1.0" + + +#include +#include +#include +#include + +#include +#include + +#define LUA_LIB +#include "lua.h" +#include "lauxlib.h" + + +#ifdef NDEBUG +#define printarray(A) +#else +#include +static void printarray(const char* argv[]) +{ + printf("%s", argv[0]); + int i; for (i = 1; argv[i]; ++i) printf(" %s", argv[i]); +} +#endif + + +/** + * Fork and execute a command with arguments and optionally get a file + * descriptor connected to one of the child's own file descriptors. The + * process id of the child is returned, or -1 on error. If fd is not NULL, + * a pipe will be created and connected to *fd. If *fd is 0, then *fd will + * be set to a write file descriptor connected to the child's standard + * input. If *fd is not 0, then *fd will be set to a read file descriptor + * set connected to the specified file descriptor of the child. In either + * case, the caller has the responsibility to close fd when it is no longer + * needed. + */ +static pid_t myexec(const char* command, char* const argv[], int* fd) +{ + int parentFd = -1; + int p[2]; + pid_t child; + + if (fd) + { + if (pipe(p) != 0) return -1; + parentFd = (*fd == 0); + } + if (!(child = fork())) + { + if (fd) + { + close(p[parentFd]); + if (dup2(p[!parentFd], *fd) == -1) _exit(127); + } + execv(command, argv); + _exit(127); + } + if (child == -1) + { + if (parentFd != -1) + { + close(p[0]); close(p[1]); + } + return -1; + } + if (parentFd != -1) + { + close(p[!parentFd]); + *fd = p[parentFd]; + } + return child; +} + +/** + * Wait on a child process. Returns the exit status of the process if the + * child terminates normally, or -1 on error or abnormal child termination. + */ +static int mywait(pid_t pid) +{ + int status; + if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) return -1; + return WEXITSTATUS(status); +} + + +/** + * Read from a file descriptor until EOF and push contents to the top of + * the Lua stack. Closes the file descriptor afterward. + */ +static void pushstream(lua_State* L, int fd) +{ + luaL_Buffer B; + luaL_buffinit(L, &B); + + char buffer[BUFSIZ]; + ssize_t bytes; + while ((bytes = read(fd, buffer, sizeof(buffer)))) + { + if (bytes == -1) break; + luaL_addlstring(&B, buffer, bytes); + } + + close(fd); + luaL_pushresult(&B); +} + +/** + * Write some arbitrary bytes to a file descriptor, appending a newline. + */ +static void writelstring(int fd, const char* str, size_t len) +{ + ssize_t bytes; + while ((bytes = write(fd, str, len))) + { + if (bytes == -1) break; + str += bytes; + len -= bytes; + } + bytes = write(fd, "\n", 1); +} + +/** + * Write a NULL-terminate string to a file descriptor. Uses writelstring. + */ +static void writestring(int fd, const char* str) +{ + writelstring(fd, str, strlen(str)); +} + +/** + * Write a Lua value, that is able to be converted to a string, to a file + * descriptor. + */ +static void tostream(lua_State* L, int index, int fd) +{ + size_t len = 0; + const char* buffer = lua_tolstring(L, index, &len); + writelstring(fd, buffer, len); +} + + +/** + * Add one or more strings to the end of a NULL-terminated array of + * strings. The parameter list itself should also be terminated with a + * NULL. The terminating NULL of the array of strings will be moved back + * as needed. It is the responsibility of the caller to assert there is + * enough room in the array so that an overflow doesn't occur. Strings are + * not duplicated as they are added to the array. + */ +static void addstrings(const char* argv[], ...) +{ + va_list ap; + int i; for (i = 0; argv[i]; ++i); + va_start(ap, argv); + const char* arg = va_arg(ap, const char*); + while (arg) + { + argv[i++] = arg; + arg = va_arg(ap, const char*); + } + va_end(ap); + argv[i] = NULL; +} + +/** + * Search an array of strings for a particular string, returning the index + * where the first equivalent string is found or -1 if it wasn't found. + */ +static int searchstrings(const char* argv[], const char* str) +{ + int i; for (i = 0; argv[i]; ++i) if (strcmp(argv[i], str) == 0) return i; + return -1; +} + + +/** + * Add the command and backtitle to the argument list from the Lua state. + */ +static void addcommand(lua_State* L, const char* argv[]) +{ + lua_getglobal(L, LUA_DIALOG_NAME); + lua_getfield(L, -1, "command"); + lua_getfield(L, -2, "title"); + addstrings(argv, lua_tostring(L, -2), NULL); + if (lua_isstring(L, -1)) + { + addstrings(argv, "--backtitle", lua_tostring(L, -1), NULL); + } + lua_pop(L, 3); +} + +/** + * Add the height and width to the argument list from the Lua state. + */ +static int addsize(lua_State* L, const char* argv[], int extra, int n) +{ + int m = n; + lua_getglobal(L, LUA_DIALOG_NAME); + if (lua_isnumber(L, m)) lua_pushvalue(L, m++); + else lua_getfield(L, -1, "height"); + if (lua_isnumber(L, m)) lua_pushvalue(L, m++); + else lua_getfield(L, -2, "width"); + addstrings(argv, lua_tostring(L, -2), lua_tostring(L, -1), NULL); + if (extra) addstrings(argv, lua_tostring(L, -2), NULL); + lua_pop(L, 3); + return m - n; +} + +/** + * Add the dialog title to the argument list from the Lua state. The title + * should be at index 1 on the stack, and an error is thrown if it is not + * found. + */ +static void addtitle(lua_State* L, const char* argv[]) +{ + addstrings(argv, "--title", luaL_checkstring(L, 1), NULL); +} + +/** + * Add the caption to the argument list from the Lua state. The caption + * should be at index 2 on the stack, and an error is thrown if it is not + * found. + */ +static void addcaption(lua_State* L, const char* argv[]) +{ + addstrings(argv, luaL_optstring(L, 2, ""), NULL); +} + +/** + * Get the string found at a certain index on the Lua stack, returned in + * the text argument. Returns whether the text was set. + */ +static int getstring(lua_State* L, const char* argv[], int n, const char** text) +{ + if (lua_type(L, n) != LUA_TSTRING) return 0; + *text = lua_tostring(L, n); + return 1; +} + +/** + * Add extra arguments to the argument list from the Lua state. The extra + * arguments exist in a table where the key is a flag and its value (if it + * is a string) is added as a flag option. + */ +static int addextra(lua_State* L, const char* argv[], int n) +{ + if (!lua_istable(L, n)) return 0; + lua_pushnil(L); + while (lua_next(L, n)) + { + addstrings(argv, lua_tostring(L, -2), NULL); + if (lua_isstring(L, -1)) addstrings(argv, lua_tostring(L, -1), NULL); + lua_pop(L, 1); + } + return 1; +} + +/** + * Add the default item to the argument list from the Lua stack for a menu + * command. Returns whether or not the arguments were found and added. If + * the value on the stack is nil, no arguments are added to the list, but + * the return value is still 1. + */ +static int addselected(lua_State* L, const char* argv[], int n) +{ + if (lua_isnil(L, n)) return 1; + if (!lua_isstring(L, n)) return 0; + addstrings(argv, "--default-item", lua_tostring(L, n), NULL); + return 1; +} + +/** + * Add menu items to the argument list from the Lua stack. They should + * exist in a table at index 3 of a menu command. Each item should be a + * table itself with two or three strings, depending on whether --item-help + * exists in the argument list. + */ +static void addmenuitems(lua_State* L, const char* argv[]) +{ + int fields = 2; + if (searchstrings(argv, "--item-help") != -1) fields = 3; + + if (!lua_istable(L, 3)) luaL_argerror(L, 3, "menu items"); + int i; for (i = 1;; ++i) + { + lua_pushinteger(L, i); + lua_gettable(L, 3); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + else if (lua_istable(L, -1)) + { + int subtable = lua_gettop(L); + lua_pushnil(L); + int j; for (j = 0; j < fields; ++j) + { + if (!lua_next(L, subtable)) luaL_argerror(L, 3, "not enough fields"); + addstrings(argv, lua_tostring(L, -1), NULL); + lua_pop(L, 1); + } + lua_pop(L, 1); + } + else + { + if (fields == 2) addstrings(argv, "", "", NULL); + else addstrings(argv, "", "", "", NULL); + } + lua_pop(L, 1); + } +} + +/** + * Fill out the argument list from the Lua state according to the standard + * 3 or 4 argument dialog command format. + */ +static void addargs(lua_State* L, const char* command, int nargs, const char* argv[]) +{ + assert((nargs == 3 || nargs == 4) && "nargs should be 3 or 4"); + + addcommand(L, argv); + addtitle(L, argv); + const char* text = NULL; + int n = 3; + if (nargs == 4) n += getstring(L, argv, n, &text); + n += addextra(L, argv, n); + addstrings(argv, command, NULL); + addcaption(L, argv); + addsize(L, argv, 0, n); + if (text) addstrings(argv, text, NULL); +} + + +/** + * Close a gauge dialog if one is running. If a gauge is in progress, the + * status code will be pushed onto the stack and 1 is returned; otherwise, + * 0 is returned. + */ +static void closegauge(lua_State* L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid"); + lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd"); + if (!lua_isnumber(L, -2) || !lua_isnumber(L, -1)) + { + lua_pop(L, 2); + return; + } + pid_t pid = lua_tointeger(L, -2); + int fd = lua_tointeger(L, -1); + lua_pop(L, 2); + + lua_pushnil(L); + lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid"); + lua_pushnil(L); + lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd"); + + close(fd); + int code = mywait(pid); + if (code == -1) luaL_error(L, "dialog killed abnormally"); +} + +/** + * Updates the progress of a gauge dialog. Takes a number argument in the + * range of 0-100 or 0.0-1.0 as the progress. Optionally takes a string as + * the second argument which causes the caption to change. The last call + * to this function should be with no arguments to end the gauge dialog. + * Returns nothing except the last call which returns the status code of + * the dialog which should always be OK. + */ +static int updategauge(lua_State* L) +{ + if (!lua_isnumber(L, 1)) + { + closegauge(L); + return 0; + } + + lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd"); + if (!lua_isnumber(L, -1)) return 0; + int fd = lua_tointeger(L, -1); + + lua_Number percent = lua_tonumber(L, 1); + if (0.0 <= percent && percent <= 1.0) percent *= 100.0; + lua_pushinteger(L, (lua_Integer)percent); + lua_replace(L, 1); + if (lua_isstring(L, 2)) + { + writestring(fd, "XXX"); + tostream(L, 1, fd); + tostream(L, 2, fd); + writestring(fd, "XXX"); + } + else + { + tostream(L, 1, fd); + } + return 0; +} + +/** + * Display a gauge dialog. Required arguments are dialog title and + * caption. Optional arguments include a table of extra flags. Returns a + * new function which can be used to update the progress. + */ +static int dialog_gauge(lua_State* L) +{ + const char* argv[40] = {NULL}; + closegauge(L); + addargs(L, "--gauge", 3, argv); + + int fd = 0; + pid_t pid = myexec(argv[0], (char* const*)argv, &fd); + if (pid == -1) luaL_error(L, "dialog failed to execute"); + lua_pushinteger(L, pid); + lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid"); + lua_pushinteger(L, fd); + lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd"); + lua_pushcfunction(L, updategauge); + return 1; +} +/** + * Display a menu dialog. Required arguments are dialog title, caption, + * and table of menu items. Optional arguments are selected item and table + * of extra flags. + */ +static int dialog_menu(lua_State* L) +{ + const char* argv[1024] = {NULL}; + closegauge(L); + + addcommand(L, argv); + addtitle(L, argv); + int n = 4; + n += addselected(L, argv, n); + n += addextra(L, argv, n); + addstrings(argv, "--menu", NULL); + addcaption(L, argv); + addsize(L, argv, 1, n); + addmenuitems(L, argv); + + printarray(argv); + + int fd = 2; + pid_t pid = myexec(argv[0], (char* const*)argv, &fd); + if (pid == -1) luaL_error(L, "dialog failed to execute"); + int code = mywait(pid); + if (code == -1) luaL_error(L, "dialog killed abnormally"); + lua_pushinteger(L, code); + pushstream(L, fd); + return 2; +} + +/** + * Display a message dialog. Required arguments are dialog title and + * caption. Optional arguments include a table of extra flags. + */ +static int dialog_msgbox(lua_State* L) +{ + const char* argv[40] = {NULL}; + closegauge(L); + addargs(L, "--msgbox", 3, argv); + + pid_t pid = myexec(argv[0], (char* const*)argv, NULL); + if (pid == -1) luaL_error(L, "dialog failed to execute"); + int code = mywait(pid); + if (code == -1) luaL_error(L, "dialog killed abnormally"); + lua_pushinteger(L, code); + return 1; +} + +/** + * Display an input dialog. Required arguments are dialog title and + * caption. Optional arguments are the default text and a table of extra + * flags. + */ +static int dialog_inputbox(lua_State* L) +{ + const char* argv[40] = {NULL}; + closegauge(L); + addargs(L, "--inputbox", 4, argv); + + int fd = 2; + pid_t pid = myexec(argv[0], (char* const*)argv, &fd); + if (pid == -1) luaL_error(L, "dialog failed to execute"); + int code = mywait(pid); + if (code == -1) luaL_error(L, "dialog killed abnormally"); + lua_pushinteger(L, code); + pushstream(L, fd); + return 2; +} + +/** + * Display a yes/no dialog. Required arguments are dialog title and + * caption. Optional arguments include a table of extra flags. + */ +static int dialog_yesno(lua_State* L) +{ + const char* argv[40] = {NULL}; + closegauge(L); + addargs(L, "--yesno", 3, argv); + + pid_t pid = myexec(argv[0], (char* const*)argv, NULL); + if (pid == -1) luaL_error(L, "dialog failed to execute"); + int code = mywait(pid); + if (code == -1) luaL_error(L, "dialog killed abnormally"); + lua_pushinteger(L, code); + return 1; +} + + +/** + * Register the module functions and find a dialog command in the path. + */ +LUALIB_API int luaopen_dialog(lua_State* L) +{ + const struct luaL_Reg dialog_funcs[] = { + {"gauge", dialog_gauge}, + {"inputbox", dialog_inputbox}, + {"menu", dialog_menu}, + {"msgbox", dialog_msgbox}, + {"yesno", dialog_yesno}, + {NULL, NULL} + }; + luaL_register(L, LUA_DIALOG_NAME, dialog_funcs); + + const char* names[] = {getenv("DIALOG"), "dialog", "cdialog"}; + int i; for (i = 0; i < 3; ++i) + { + if (names[i]) + { + char* path = strdup(getenv("PATH")); + char* token; char** paths = &path; + while ((token = strsep(paths, ":"))) + { + luaL_Buffer B; + luaL_buffinit(L, &B); + luaL_addstring(&B, token); + luaL_addstring(&B, "/"); + luaL_addstring(&B, names[i]); + luaL_pushresult(&B); + if (access(lua_tostring(L, -1), X_OK) == 0) + { + lua_setfield(L, -2, "command"); + goto break2; + } + lua_pop(L, 1); + } + free(path); + } + } + luaL_error(L, "cannot find dialog executable in the path; set DIALOG"); + +break2: + + lua_pushinteger(L, 0); + lua_setfield(L, -2, "height"); + lua_pushinteger(L, 0); + lua_setfield(L, -2, "width"); + + return 1; +} +