]> Dogcows Code - chaz/yoink/blob - build/dialog.c
import stlplus 3.7
[chaz/yoink] / build / dialog.c
1
2 /*] Copyright (c) 2011, Charles McGarvey [**********************************
3 **] All rights reserved.
4 *
5 * Distributable under the terms and conditions of the 2-clause BSD license;
6 * see the file COPYING for a complete text of the license.
7 *
8 *****************************************************************************/
9
10 #define LUA_DIALOG_NAME "dialog"
11 #define LUA_DIALOG_VERSION "1.0"
12
13
14 #include <assert.h>
15 #include <stdarg.h>
16 #include <stdlib.h>
17 #include <string.h>
18
19 #include <sys/wait.h>
20 #include <unistd.h>
21
22 #define LUA_LIB
23 #include "lua.h"
24 #include "lauxlib.h"
25
26
27 #ifdef NDEBUG
28 #define printarray(A)
29 #else
30 #include <stdio.h>
31 static void printarray(const char* argv[])
32 {
33 printf("%s", argv[0]);
34 int i; for (i = 1; argv[i]; ++i) printf(" %s", argv[i]);
35 }
36 #endif
37
38
39 /**
40 * Fork and execute a command with arguments and optionally get a file
41 * descriptor connected to one of the child's own file descriptors. The
42 * process id of the child is returned, or -1 on error. If fd is not NULL, a
43 * pipe will be created and connected to *fd. If *fd is 0, then *fd will be
44 * set to a write file descriptor connected to the child's standard input. If
45 * *fd is not 0, then *fd will be set to a read file descriptor set connected
46 * to the specified file descriptor of the child. In either case, the caller
47 * has the responsibility to close fd when it is no longer needed.
48 */
49 static pid_t myexec(const char* command, char* const argv[], int* fd)
50 {
51 int parentFd = -1;
52 int p[2];
53 pid_t child;
54
55 if (fd) {
56 if (pipe(p) != 0) return -1;
57 parentFd = (*fd == 0);
58 }
59 if (!(child = fork())) {
60 if (fd) {
61 close(p[parentFd]);
62 if (dup2(p[!parentFd], *fd) == -1) _exit(127);
63 }
64 execv(command, argv);
65 _exit(127);
66 }
67 if (child == -1) {
68 if (parentFd != -1)
69 close(p[0]); close(p[1]);
70 return -1;
71 }
72 if (parentFd != -1) {
73 close(p[!parentFd]);
74 *fd = p[parentFd];
75 }
76 return child;
77 }
78
79 /**
80 * Wait on a child process. Returns the exit status of the process if the
81 * child terminates normally, or -1 on error or abnormal child termination.
82 */
83 static int mywait(pid_t pid)
84 {
85 int status;
86 if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) return -1;
87 return WEXITSTATUS(status);
88 }
89
90
91 /**
92 * Read from a file descriptor until EOF and push contents to the top of the
93 * Lua stack. Closes the file descriptor afterward.
94 */
95 static void pushstream(lua_State* L, int fd)
96 {
97 luaL_Buffer B;
98 luaL_buffinit(L, &B);
99
100 char buffer[BUFSIZ];
101 ssize_t bytes;
102 while ((bytes = read(fd, buffer, sizeof(buffer)))) {
103 if (bytes == -1) break;
104 luaL_addlstring(&B, buffer, bytes);
105 }
106
107 close(fd);
108 luaL_pushresult(&B);
109 }
110
111 /**
112 * Write some arbitrary bytes to a file descriptor, appending a newline.
113 */
114 static void writelstring(int fd, const char* str, size_t len)
115 {
116 ssize_t bytes;
117 while ((bytes = write(fd, str, len))) {
118 if (bytes == -1) break;
119 str += bytes;
120 len -= bytes;
121 }
122 bytes = write(fd, "\n", 1);
123 }
124
125 /**
126 * Write a NULL-terminate string to a file descriptor. Uses writelstring.
127 */
128 static void writestring(int fd, const char* str)
129 {
130 writelstring(fd, str, strlen(str));
131 }
132
133 /**
134 * Write a Lua value, that is able to be converted to a string, to a file
135 * descriptor.
136 */
137 static void tostream(lua_State* L, int index, int fd)
138 {
139 size_t len = 0;
140 const char* buffer = lua_tolstring(L, index, &len);
141 writelstring(fd, buffer, len);
142 }
143
144
145 /**
146 * Add one or more strings to the end of a NULL-terminated array of
147 * strings. The parameter list itself should also be terminated with a
148 * NULL. The terminating NULL of the array of strings will be moved back
149 * as needed. It is the responsibility of the caller to assert there is
150 * enough room in the array so that an overflow doesn't occur. Strings are
151 * not duplicated as they are added to the array.
152 */
153 static void addstrings(const char* argv[], ...)
154 {
155 va_list ap;
156 int i; for (i = 0; argv[i]; ++i);
157 va_start(ap, argv);
158 const char* arg = va_arg(ap, const char*);
159 while (arg) {
160 argv[i++] = arg;
161 arg = va_arg(ap, const char*);
162 }
163 va_end(ap);
164 argv[i] = NULL;
165 }
166
167 /**
168 * Search an array of strings for a particular string, returning the index
169 * where the first equivalent string is found or -1 if it wasn't found.
170 */
171 static int searchstrings(const char* argv[], const char* str)
172 {
173 int i; for (i = 0; argv[i]; ++i) if (strcmp(argv[i], str) == 0) return i;
174 return -1;
175 }
176
177
178 /**
179 * Add the command and backtitle to the argument list from the Lua state.
180 */
181 static void addcommand(lua_State* L, const char* argv[])
182 {
183 lua_getglobal(L, LUA_DIALOG_NAME);
184 lua_getfield(L, -1, "command");
185 lua_getfield(L, -2, "title");
186 addstrings(argv, lua_tostring(L, -2), NULL);
187 if (lua_isstring(L, -1)) {
188 addstrings(argv, "--backtitle", lua_tostring(L, -1), NULL);
189 }
190 lua_pop(L, 3);
191 }
192
193 /**
194 * Add the height and width to the argument list from the Lua state.
195 */
196 static int addsize(lua_State* L, const char* argv[], int extra, int n)
197 {
198 int m = n;
199 lua_getglobal(L, LUA_DIALOG_NAME);
200 if (lua_isnumber(L, m)) lua_pushvalue(L, m++);
201 else lua_getfield(L, -1, "height");
202 if (lua_isnumber(L, m)) lua_pushvalue(L, m++);
203 else lua_getfield(L, -2, "width");
204 addstrings(argv, lua_tostring(L, -2), lua_tostring(L, -1), NULL);
205 if (extra) addstrings(argv, lua_tostring(L, -2), NULL);
206 lua_pop(L, 3);
207 return m - n;
208 }
209
210 /**
211 * Add the dialog title to the argument list from the Lua state. The title
212 * should be at index 1 on the stack, and an error is thrown if it is not
213 * found.
214 */
215 static void addtitle(lua_State* L, const char* argv[])
216 {
217 addstrings(argv, "--title", luaL_checkstring(L, 1), NULL);
218 }
219
220 /**
221 * Add the caption to the argument list from the Lua state. The caption
222 * should be at index 2 on the stack, and an error is thrown if it is not
223 * found.
224 */
225 static void addcaption(lua_State* L, const char* argv[])
226 {
227 addstrings(argv, luaL_optstring(L, 2, ""), NULL);
228 }
229
230 /**
231 * Get the string found at a certain index on the Lua stack, returned in
232 * the text argument. Returns whether the text was set.
233 */
234 static int getstring(lua_State* L, const char* argv[], int n, const char** text)
235 {
236 if (lua_type(L, n) != LUA_TSTRING) return 0;
237 *text = lua_tostring(L, n);
238 return 1;
239 }
240
241 /**
242 * Add extra arguments to the argument list from the Lua state. The extra
243 * arguments exist in a table where the key is a flag and its value (if it
244 * is a string) is added as a flag option.
245 */
246 static int addextra(lua_State* L, const char* argv[], int n)
247 {
248 if (!lua_istable(L, n)) return 0;
249 lua_pushnil(L);
250 while (lua_next(L, n)) {
251 addstrings(argv, lua_tostring(L, -2), NULL);
252 if (lua_isstring(L, -1)) addstrings(argv, lua_tostring(L, -1), NULL);
253 lua_pop(L, 1);
254 }
255 return 1;
256 }
257
258 /**
259 * Add the default item to the argument list from the Lua stack for a menu
260 * command. Returns whether or not the arguments were found and added. If
261 * the value on the stack is nil, no arguments are added to the list, but
262 * the return value is still 1.
263 */
264 static int addselected(lua_State* L, const char* argv[], int n)
265 {
266 if (lua_isnil(L, n)) return 1;
267 if (!lua_isstring(L, n)) return 0;
268 addstrings(argv, "--default-item", lua_tostring(L, n), NULL);
269 return 1;
270 }
271
272 /**
273 * Add menu items to the argument list from the Lua stack. They should
274 * exist in a table at index 3 of a menu command. Each item should be a
275 * table itself with two or three strings, depending on whether --item-help
276 * exists in the argument list.
277 */
278 static void addmenuitems(lua_State* L, const char* argv[])
279 {
280 int fields = 2;
281 if (searchstrings(argv, "--item-help") != -1) fields = 3;
282
283 if (!lua_istable(L, 3)) luaL_argerror(L, 3, "menu items");
284 int i; for (i = 1;; ++i) {
285 lua_pushinteger(L, i);
286 lua_gettable(L, 3);
287 if (lua_isnil(L, -1)) {
288 lua_pop(L, 1);
289 break;
290 }
291 else if (lua_istable(L, -1)) {
292 int subtable = lua_gettop(L);
293 lua_pushnil(L);
294 int j; for (j = 0; j < fields; ++j) {
295 if (!lua_next(L, subtable)) luaL_argerror(L, 3, "not enough fields");
296 addstrings(argv, lua_tostring(L, -1), NULL);
297 lua_pop(L, 1);
298 }
299 lua_pop(L, 1);
300 }
301 else {
302 if (fields == 2) addstrings(argv, "", "", NULL);
303 else addstrings(argv, "", "", "", NULL);
304 }
305 lua_pop(L, 1);
306 }
307 }
308
309 /**
310 * Fill out the argument list from the Lua state according to the standard
311 * 3 or 4 argument dialog command format.
312 */
313 static void addargs(lua_State* L, const char* command, int nargs, const char* argv[])
314 {
315 assert((nargs == 3 || nargs == 4) && "nargs should be 3 or 4");
316
317 addcommand(L, argv);
318 addtitle(L, argv);
319 const char* text = NULL;
320 int n = 3;
321 if (nargs == 4) n += getstring(L, argv, n, &text);
322 n += addextra(L, argv, n);
323 addstrings(argv, command, NULL);
324 addcaption(L, argv);
325 addsize(L, argv, 0, n);
326 if (text) addstrings(argv, text, NULL);
327 }
328
329
330 /**
331 * Close a gauge dialog if one is running. If a gauge is in progress, the
332 * status code will be pushed onto the stack and 1 is returned; otherwise,
333 * 0 is returned.
334 */
335 static void closegauge(lua_State* L)
336 {
337 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
338 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
339 if (!lua_isnumber(L, -2) || !lua_isnumber(L, -1)) {
340 lua_pop(L, 2);
341 return;
342 }
343 pid_t pid = lua_tointeger(L, -2);
344 int fd = lua_tointeger(L, -1);
345 lua_pop(L, 2);
346
347 lua_pushnil(L);
348 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
349 lua_pushnil(L);
350 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
351
352 close(fd);
353 int code = mywait(pid);
354 if (code == -1) luaL_error(L, "dialog killed abnormally");
355 }
356
357 /**
358 * Updates the progress of a gauge dialog. Takes a number argument in the
359 * range of 0-100 or 0.0-1.0 as the progress. Optionally takes a string as
360 * the second argument which causes the caption to change. The last call
361 * to this function should be with no arguments to end the gauge dialog.
362 * Returns nothing except the last call which returns the status code of
363 * the dialog which should always be OK.
364 */
365 static int updategauge(lua_State* L)
366 {
367 if (!lua_isnumber(L, 1)) {
368 closegauge(L);
369 return 0;
370 }
371
372 lua_getfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
373 if (!lua_isnumber(L, -1)) return 0;
374 int fd = lua_tointeger(L, -1);
375
376 lua_Number percent = lua_tonumber(L, 1);
377 if (0.0 <= percent && percent <= 1.0) percent *= 100.0;
378 lua_pushinteger(L, (lua_Integer)percent);
379 lua_replace(L, 1);
380 if (lua_isstring(L, 2)) {
381 writestring(fd, "XXX");
382 tostream(L, 1, fd);
383 tostream(L, 2, fd);
384 writestring(fd, "XXX");
385 }
386 else {
387 tostream(L, 1, fd);
388 }
389 return 0;
390 }
391
392 /**
393 * Display a gauge dialog. Required arguments are dialog title and
394 * caption. Optional arguments include a table of extra flags. Returns a
395 * new function which can be used to update the progress.
396 */
397 static int dialog_gauge(lua_State* L)
398 {
399 const char* argv[40] = {NULL};
400 closegauge(L);
401 addargs(L, "--gauge", 3, argv);
402
403 int fd = 0;
404 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
405 if (pid == -1) luaL_error(L, "dialog failed to execute");
406 lua_pushinteger(L, pid);
407 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_pid");
408 lua_pushinteger(L, fd);
409 lua_setfield(L, LUA_REGISTRYINDEX, "dialog_gauge_fd");
410 lua_pushcfunction(L, updategauge);
411 return 1;
412 }
413 /**
414 * Display a menu dialog. Required arguments are dialog title, caption,
415 * and table of menu items. Optional arguments are selected item and table
416 * of extra flags.
417 */
418 static int dialog_menu(lua_State* L)
419 {
420 const char* argv[1024] = {NULL};
421 closegauge(L);
422
423 addcommand(L, argv);
424 addtitle(L, argv);
425 int n = 4;
426 n += addselected(L, argv, n);
427 n += addextra(L, argv, n);
428 addstrings(argv, "--menu", NULL);
429 addcaption(L, argv);
430 addsize(L, argv, 1, n);
431 addmenuitems(L, argv);
432
433 printarray(argv);
434
435 int fd = 2;
436 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
437 if (pid == -1) luaL_error(L, "dialog failed to execute");
438 int code = mywait(pid);
439 if (code == -1) luaL_error(L, "dialog killed abnormally");
440 lua_pushinteger(L, code);
441 pushstream(L, fd);
442 return 2;
443 }
444
445 /**
446 * Display a message dialog. Required arguments are dialog title and
447 * caption. Optional arguments include a table of extra flags.
448 */
449 static int dialog_msgbox(lua_State* L)
450 {
451 const char* argv[40] = {NULL};
452 closegauge(L);
453 addargs(L, "--msgbox", 3, argv);
454
455 pid_t pid = myexec(argv[0], (char* const*)argv, NULL);
456 if (pid == -1) luaL_error(L, "dialog failed to execute");
457 int code = mywait(pid);
458 if (code == -1) luaL_error(L, "dialog killed abnormally");
459 lua_pushinteger(L, code);
460 return 1;
461 }
462
463 /**
464 * Display an input dialog. Required arguments are dialog title and
465 * caption. Optional arguments are the default text and a table of extra
466 * flags.
467 */
468 static int dialog_inputbox(lua_State* L)
469 {
470 const char* argv[40] = {NULL};
471 closegauge(L);
472 addargs(L, "--inputbox", 4, argv);
473
474 int fd = 2;
475 pid_t pid = myexec(argv[0], (char* const*)argv, &fd);
476 if (pid == -1) luaL_error(L, "dialog failed to execute");
477 int code = mywait(pid);
478 if (code == -1) luaL_error(L, "dialog killed abnormally");
479 lua_pushinteger(L, code);
480 pushstream(L, fd);
481 return 2;
482 }
483
484 /**
485 * Display a yes/no dialog. Required arguments are dialog title and
486 * caption. Optional arguments include a table of extra flags.
487 */
488 static int dialog_yesno(lua_State* L)
489 {
490 const char* argv[40] = {NULL};
491 closegauge(L);
492 addargs(L, "--yesno", 3, argv);
493
494 pid_t pid = myexec(argv[0], (char* const*)argv, NULL);
495 if (pid == -1) luaL_error(L, "dialog failed to execute");
496 int code = mywait(pid);
497 if (code == -1) luaL_error(L, "dialog killed abnormally");
498 lua_pushinteger(L, code);
499 return 1;
500 }
501
502
503 /**
504 * Register the module functions and find a dialog command in the path.
505 */
506 LUALIB_API int luaopen_dialog(lua_State* L)
507 {
508 const struct luaL_Reg dialog_funcs[] = {
509 {"gauge", dialog_gauge},
510 {"inputbox", dialog_inputbox},
511 {"menu", dialog_menu},
512 {"msgbox", dialog_msgbox},
513 {"yesno", dialog_yesno},
514 {NULL, NULL}
515 };
516 luaL_register(L, LUA_DIALOG_NAME, dialog_funcs);
517
518 const char* names[] = {getenv("DIALOG"), "dialog", "cdialog"};
519 int i; for (i = 0; i < 3; ++i) {
520 if (names[i]) {
521 char* path = strdup(getenv("PATH"));
522 char* token; char** paths = &path;
523 while ((token = strsep(paths, ":"))) {
524 luaL_Buffer B;
525 luaL_buffinit(L, &B);
526 luaL_addstring(&B, token);
527 luaL_addstring(&B, "/");
528 luaL_addstring(&B, names[i]);
529 luaL_pushresult(&B);
530 if (access(lua_tostring(L, -1), X_OK) == 0) {
531 lua_setfield(L, -2, "command");
532 goto break2;
533 }
534 lua_pop(L, 1);
535 }
536 free(path);
537 }
538 }
539 luaL_error(L, "cannot find dialog executable in the path; set DIALOG");
540
541 break2:
542
543 lua_pushinteger(L, 0);
544 lua_setfield(L, -2, "height");
545 lua_pushinteger(L, 0);
546 lua_setfield(L, -2, "width");
547
548 return 1;
549 }
550
This page took 0.05192 seconds and 4 git commands to generate.