Compare commits

...

15 Commits

Author SHA1 Message Date
c0eda1c23f liberty-xui: circumvent an Nvidia issue
All checks were successful
Alpine 3.22 Success
OpenBSD 7.8 Success
2025-11-29 00:16:49 +01:00
7566f9af82 liberty: comment on pthread_cancel
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-09-21 18:59:16 +02:00
7425355d01 liberty-xui: fix a new Fontconfig warning
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-08-02 18:22:00 +02:00
d8f785eae5 liberty-xdg: don't crash on missing X11 atoms
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
They can be missing in bare configurations, such as Sway + XWayland.
2025-06-04 21:54:04 +02:00
31ae400852 LibertyXDR: update VIM syntax highlight file
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-05-07 19:47:43 +02:00
b69d3f8692 LibertyXDR: add support for default in unions 2025-05-07 19:42:46 +02:00
9a26284a64 wdye: clean up protected calls
Have a common way of catching Lua errors for resource cleanup purposes.
2025-01-15 02:21:23 +01:00
0f20cce9c8 wdye: pass script arguments
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-10 10:58:27 +01:00
017cb1d570 MPD client: tolerate usage while disconnected
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
If the client is password-protected, this will not automagically
make queued up commands work, but it's better than hitting
the poller assertion.
2025-01-08 08:07:46 +01:00
1642d387f3 wdye: rename the self-test
add_subdirectory imports it to parent projects, so be more indicative.
2025-01-08 06:24:05 +01:00
af889b733e wdye: ensure we find our own config.h
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-08 06:14:47 +01:00
51231d84ba wdye: clean up, add process.pid
All checks were successful
OpenBSD 7.5 Success
Alpine 3.20 Success
2025-01-07 03:16:37 +01:00
6c47e384f5 wdye: optionally produce asciicast v2 logs
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
I've been fairly disappointed with asciinema,
but it's slightly better than nothing.
2025-01-06 17:03:54 +01:00
914e743dc4 wdye: don't add the script path on error
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
Lua already provides this for us, including the line number.
2025-01-06 14:40:58 +01:00
37a8f16235 wdye: enable waiting for processes 2025-01-06 14:29:41 +01:00
12 changed files with 281 additions and 62 deletions

View File

@@ -1594,6 +1594,8 @@ mpd_client_parse_kv (char *line, char **value)
static void
mpd_client_update_poller (struct mpd_client *self)
{
if (self->state != MPD_CONNECTED)
return;
poller_fd_set (&self->socket_event,
self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
}

View File

@@ -86,24 +86,30 @@ xdg_xsettings_update (struct xdg_xsettings *self, Display *dpy)
// TODO: We're supposed to lock the server.
// TODO: We're supposed to trap X errors.
char *selection = xstrdup_printf ("_XSETTINGS_S%d", DefaultScreen (dpy));
Window owner
= XGetSelectionOwner (dpy, XInternAtom (dpy, selection, True));
Atom selection_atom = XInternAtom (dpy, selection, True);
free (selection);
if (!selection_atom)
return;
Window owner = XGetSelectionOwner (dpy, selection_atom);
if (!owner)
return;
Atom xsettings_atom = XInternAtom (dpy, "_XSETTINGS_SETTINGS", True);
if (!xsettings_atom)
return;
Atom actual_type = None;
int actual_format = 0;
unsigned long nitems = 0, bytes_after = 0;
unsigned char *buffer = NULL;
Atom xsettings = XInternAtom (dpy, "_XSETTINGS_SETTINGS", True);
int status = XGetWindowProperty (dpy,
owner,
xsettings,
xsettings_atom,
0L,
LONG_MAX,
False,
xsettings,
xsettings_atom,
&actual_type,
&actual_format,
&nitems,
@@ -112,7 +118,7 @@ xdg_xsettings_update (struct xdg_xsettings *self, Display *dpy)
if (status != Success || !buffer)
return;
if (actual_type != xsettings
if (actual_type != xsettings_atom
|| actual_format != 8
|| nitems < 12)
goto fail;

View File

@@ -1269,14 +1269,21 @@ x11_render_label (struct widget *self)
XRenderColor solid = *x11_fg (self), colors[3] = { solid, solid, solid };
colors[2].alpha = 0;
double portion = MIN (1, 2.0 * font->list->font->height / space);
// Nvidia's XRender appears to not add (x, y) to (srcx, srcy),
// and works correctly if we pass self->x as srcx further on.
//
// Instead of special-casing nvidia_drv.so, let's have the gradient
// also extend to the left, in order to make text legible at all.
//
// We could also detect the behaviour experimentally in run-time.
double portion = MIN (1, 2. * font->list->font->height / (self->x + space));
XFixed stops[3] = { 0, XDoubleToFixed (1 - portion), XDoubleToFixed (1) };
XLinearGradient gradient = { {}, { XDoubleToFixed (space), 0 } };
XLinearGradient gradient = { {}, { XDoubleToFixed (self->x + space), 0 } };
// Note that this masking is a very expensive operation.
Picture source =
XRenderCreateLinearGradient (g_xui.dpy, &gradient, stops, colors, 3);
x11_font_render (font, PictOpOver, source, -self->x, 0, self->x, self->y,
x11_font_render (font, PictOpOver, source, 0, 0, self->x, self->y,
self->text);
XRenderFreePicture (g_xui.dpy, source);
}
@@ -1884,6 +1891,8 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
if (!(g_xui.dpy = XkbOpenDisplay
(NULL, &g_xui.xkb_base_event_code, NULL, NULL, NULL, NULL)))
exit_fatal ("cannot open display");
if (!XftInit (NULL))
print_warning ("Fontconfig initialization failed");
if (!XftDefaultHasRender (g_xui.dpy))
exit_fatal ("XRender is not supported");
if (!(g_xui.x11_im = XOpenIM (g_xui.dpy, NULL, NULL, NULL)))
@@ -1912,8 +1921,6 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
g_xui.x11_xsettings = xdg_xsettings_make ();
xdg_xsettings_update (&g_xui.x11_xsettings, g_xui.dpy);
if (!FcInit ())
print_warning ("Fontconfig initialization failed");
if (!(g_xui.xft_fonts = x11_font_open (0)))
exit_fatal ("cannot open a font");

View File

@@ -1209,7 +1209,10 @@ async_make (struct async_manager *manager)
}
/// Only allowed from the main thread once the job has been started but before
/// the results have been dispatched
/// the results have been dispatched.
///
/// Note that it may in practice lead to memory leakage, although that's
/// an implementation issue: https://eissing.org/icing/posts/rip_pthread_cancel/
static void
async_cancel (struct async *self)
{

View File

@@ -93,10 +93,12 @@ and always-present field, which must be a tag *enum*:
case CAR: void;
case LORRY: i8 axles;
case PLANE: i8 engines;
default: void;
};
All possible enumeration values must be named, and there is no *case*
fall-through.
There is no *case* fall-through.
Unless *default* is present, only the listed enumeration values are valid.
Any *default* must currently be empty.
Framing
-------

View File

@@ -8,7 +8,7 @@ syn region libertyxdrBlockComment start=+/[*]+ end=+[*]/+
syn match libertyxdrComment "//.*"
syn match libertyxdrIdentifier "\<[[:alpha:]][[:alnum:]_]*\>"
syn match libertyxdrNumber "\<0\>\|\(-\|\<\)[1-9][[:digit:]]*\>"
syn keyword libertyxdrKeyword const enum struct union switch case
syn keyword libertyxdrKeyword const enum struct union switch case default
syn keyword libertyxdrType bool u8 u16 u32 u64 i8 i16 i32 i64 string void
let b:current_syntax = "libertyxdr"

View File

@@ -25,5 +25,7 @@ struct Struct {
union Onion switch (Enum tag) {
case NOTHING:
void;
default:
void;
} o;
};

View File

@@ -1,6 +1,6 @@
# lxdrgen.awk: an XDR-derived code generator for network protocols.
#
# Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name>
# Copyright (c) 2022 - 2025, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: env LC_ALL=C awk -f lxdrgen.awk -f lxdrgen-{c,go,mjs}.awk \
@@ -218,7 +218,7 @@ function defstruct( name, d, cg) {
}
function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
unseen, exhaustive) {
unseen, defaulted, exhaustive) {
delete cg[0]
delete scg[0]
delete d[0]
@@ -249,9 +249,22 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
if (!unseen[tagvalue]--)
fatal("no such value or duplicate case: " tagtype "." tagvalue)
codegen_struct_tag(tag, scg)
} else if (accept("default")) {
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
expect(accept(":"))
if (defaulted)
fatal("duplicate default")
tagvalue = ""
defaulted = 1
} else if (tagvalue) {
if (readfield(d))
codegen_struct_field(d, scg)
} else if (defaulted) {
if (readfield(d))
fatal("default must not contain fields")
} else {
fatal("union fields must fall under a case")
}
@@ -259,11 +272,17 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
# Unseen cases are simply not recognized/allowed.
# Unseen cases are only recognized/allowed when default is present.
exhaustive = 1
for (i in unseen)
if (i && unseen[i])
if (i && unseen[i]) {
if (defaulted) {
codegen_struct_tag(tag, scg)
codegen_union_struct(name, i, cg, scg)
} else {
exhaustive = 0
}
}
Types[name] = "union"
codegen_union(name, cg, exhaustive)

View File

@@ -22,7 +22,7 @@ option (WITH_CURSES "Offer terminal sequences using Curses" "${CURSES_FOUND}")
# -liconv may or may not be a part of libc
find_path (iconv_INCLUDE_DIRS iconv.h)
include_directories ("${PROJECT_BINARY_DIR}" ${iconv_INCLUDE_DIRS})
include_directories (BEFORE "${PROJECT_BINARY_DIR}" ${iconv_INCLUDE_DIRS})
file (CONFIGURE OUTPUT "${PROJECT_BINARY_DIR}/config.h" CONTENT [[
#define PROGRAM_NAME "${PROJECT_NAME}"
#define PROGRAM_VERSION "${PROJECT_VERSION}"
@@ -38,5 +38,5 @@ if (WITH_CURSES)
target_link_libraries (wdye PUBLIC ${CURSES_LIBRARIES})
endif ()
add_test (NAME simple COMMAND wdye "${PROJECT_SOURCE_DIR}/test.lua")
add_test (NAME wdye COMMAND wdye "${PROJECT_SOURCE_DIR}/test.lua")
include (CTest)

View File

@@ -18,7 +18,16 @@ expect(cat:regex {"A(.*)3", nocase=true, function (p)
assert(p[1] == "bc12", "wrong regex group #1")
end})
assert(not cat:wait (true), "process reports exiting early")
-- Send EOF (^D), test method chaining.
cat:send("Closing...\r"):send("\004")
local v = expect(cat:eof {true},
cat:default {.5, function (p) error "expected EOF, got a timeout" end})
assert(cat.pid > 0, "process has no ID")
local s1, exit, signal = cat:wait ()
assert(s1 == 0 and exit == 0 and not signal, "unexpected exit status")
assert(cat.pid < 0, "process still has an ID")
local s2 = cat:wait (true)
assert(s1 == s2, "exit status not remembered")

View File

@@ -31,7 +31,7 @@ The *env* map may be used to override environment variables, notably _TERM_.
Variables evaluating to _false_ will be removed from the environment.
The program's whole process group receives SIGKILL when the _process_
is garbage-collected.
is garbage-collected, unless *wait* has collected the process group leader.
wdye.expect ([pattern1, ...])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -57,6 +57,10 @@ PROCESS.buffer
~~~~~~~~~~~~~~
A string with the _process_' current read buffer contents.
PROCESS.pid
~~~~~~~~~~~
An integer with the _process_' process ID, or -1 if *wait* has collected it.
PROCESS.term
~~~~~~~~~~~~
A table with the _process_' *terminfo*(5) capabilities,
@@ -94,6 +98,16 @@ PROCESS:eof {[notransfer=true, ] [value1, ...]}
Returns a new end-of-file _pattern_, which matches the entire read buffer
contents once the child process closes the terminal.
PROCESS:wait ([nowait])
~~~~~~~~~~~~~~~~~~~~~~~
Waits for the program to terminate, and returns three values:
a combined status as used by `$?` in shells,
an exit status, and a termination signal number.
One of the latter two values will be _nil_, as appropriate.
When the *nowait* option is _true_, the function returns immediately.
If the process hasn't terminated yet, the function then returns no values.
PROCESS:default {[timeout, ] [notransfer=true, ] [value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new _pattern_ combining *wdye.timeout* with *eof*.
@@ -116,6 +130,12 @@ Example
rot13:send "Hello\r"
expect(rot13:exact {"Uryyb\r"})
Environment
-----------
*WDYE_LOGGING*::
When this environment variable is present, *wdye* produces asciicast v2
files for every spawned program, in the current working directory.
Reporting bugs
--------------
Use https://git.janouch.name/p/liberty to report bugs, request features,

View File

@@ -175,7 +175,7 @@ pty_fork (int *ptrfdm, char **slave_name,
char *pts_name = NULL;
if ((fdm = ptym_open (&pts_name)) < 0)
{
error_set (e, "cant open master pty: %s", strerror (errno));
error_set (e, "can't open master pty: %s", strerror (errno));
return -1;
}
if (slave_name != NULL)
@@ -194,7 +194,7 @@ pty_fork (int *ptrfdm, char **slave_name,
if (setsid () < 0)
exit_fatal ("setsid: %s", strerror (errno));
if ((fds = ptys_open (pts_name)) < 0)
exit_fatal ("cant open slave pty: %s", strerror (errno));
exit_fatal ("can't open slave pty: %s", strerror (errno));
xclose (fdm);
#if defined BSD
@@ -222,6 +222,45 @@ pty_fork (int *ptrfdm, char **slave_name,
return pid;
}
// --- JSON --------------------------------------------------------------------
static void
write_json_string (FILE *output, const char *s, size_t len)
{
fputc ('"', output);
for (const char *last = s, *end = s + len; s != end; last = s)
{
// Here is where you realize the asciicast format is retarded for using
// JSON at all. (Consider multibyte characters at read() boundaries.)
int32_t codepoint = utf8_decode (&s, end - s);
if (codepoint < 0)
{
s++;
fprintf (output, "\\uFFFD");
continue;
}
switch (codepoint)
{
break; case '"': fprintf (output, "\\\"");
break; case '\\': fprintf (output, "\\\\");
break; case '\b': fprintf (output, "\\b");
break; case '\f': fprintf (output, "\\f");
break; case '\n': fprintf (output, "\\n");
break; case '\r': fprintf (output, "\\r");
break; case '\t': fprintf (output, "\\t");
break; default:
if (!utf8_validate_cp (codepoint))
fprintf (output, "\\uFFFD");
else if (codepoint < 32)
fprintf (output, "\\u%04X", codepoint);
else
fwrite (last, 1, s - last, output);
}
}
fputc ('"', output);
}
// --- Global state ------------------------------------------------------------
static struct
@@ -362,7 +401,7 @@ xlua_pattern_index (lua_State *L)
if (!strcmp (key, "process"))
lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_process);
else
return luaL_argerror (L, 2, "not a readable property");
return luaL_error (L, "not a readable property: %s", key);
return 1;
}
@@ -372,8 +411,8 @@ xlua_pattern_index (lua_State *L)
case PATTERN_REGEX:
{
const regmatch_t *m = self->matches + group;
if (group < 0 || group > self->regex->re_nsub
|| m->rm_so < 0 || m->rm_eo < 0 || m->rm_eo > self->input.len)
if (group < 0 || (size_t) group > self->regex->re_nsub
|| m->rm_so < 0 || m->rm_eo < 0 || (size_t) m->rm_eo > self->input.len)
lua_pushnil (L);
else
lua_pushlstring (L,
@@ -389,7 +428,7 @@ xlua_pattern_index (lua_State *L)
lua_pushlstring (L, self->input.str, self->input.len);
return 1;
default:
return luaL_argerror (L, 2, "indexing unavailable for this pattern");
return luaL_argerror (L, 1, "indexing unavailable for this pattern");
}
}
@@ -431,9 +470,13 @@ static luaL_Reg xlua_pattern_table[] =
struct process
{
int terminal_fd; ///< Process stdin/stdout/stderr
pid_t pid; ///< Process ID
pid_t pid; ///< Process ID or -1 if collected
int ref_term; ///< Terminal information
struct str buffer; ///< Terminal input buffer
int status; ///< Process status iff pid is -1
int64_t start; ///< Start timestamp (Unix msec)
FILE *asciicast; ///< asciicast script dump
};
static struct process *
@@ -461,6 +504,8 @@ xlua_process_gc (lua_State *L)
kill (-self->pid, SIGKILL);
luaL_unref (L, LUA_REGISTRYINDEX, self->ref_term);
str_free (&self->buffer);
if (self->asciicast)
fclose (self->asciicast);
return 0;
}
@@ -474,10 +519,12 @@ xlua_process_index (lua_State *L)
if (!strcmp (key, "buffer"))
lua_pushlstring (L, self->buffer.str, self->buffer.len);
else if (!strcmp (key, "pid"))
lua_pushinteger (L, self->pid);
else if (!strcmp (key, "term"))
lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_term);
else
return luaL_argerror (L, 2, "not a readable property");
return luaL_error (L, "not a readable property: %s", key);
return 1;
}
@@ -497,8 +544,16 @@ xlua_process_send (lua_State *L)
ssize_t written = write (self->terminal_fd, arg, len);
if (written == -1)
return luaL_error (L, "write failed: %s", strerror (errno));
else if (written != len)
else if (written != (ssize_t) len)
return luaL_error (L, "write failed: %s", "short write");
if (self->asciicast)
{
double timestamp = (clock_msec () - self->start) / 1000.;
fprintf (self->asciicast, "[%f, \"i\", ", timestamp);
write_json_string (self->asciicast, arg, len);
fprintf (self->asciicast, "]\n");
}
}
lua_pushvalue (L, 1);
return 1;
@@ -507,7 +562,7 @@ xlua_process_send (lua_State *L)
static int
xlua_process_regex (lua_State *L)
{
struct process *self = luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
(void) luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
luaL_checktype (L, 2, LUA_TTABLE);
if (lua_gettop (L) != 2)
return luaL_error (L, "too many arguments");
@@ -540,7 +595,7 @@ xlua_process_regex (lua_State *L)
static int
xlua_process_exact (lua_State *L)
{
struct process *self = luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
(void) luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
luaL_checktype (L, 2, LUA_TTABLE);
if (lua_gettop (L) != 2)
return luaL_error (L, "too many arguments");
@@ -565,7 +620,7 @@ xlua_process_exact (lua_State *L)
static int
xlua_process_eof (lua_State *L)
{
struct process *self = luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
(void) luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
luaL_checktype (L, 2, LUA_TTABLE);
if (lua_gettop (L) != 2)
return luaL_error (L, "too many arguments");
@@ -581,7 +636,7 @@ xlua_process_eof (lua_State *L)
static int
xlua_process_default (lua_State *L)
{
struct process *self = luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
(void) luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
luaL_checktype (L, 2, LUA_TTABLE);
if (lua_gettop (L) != 2)
return luaL_error (L, "too many arguments");
@@ -598,6 +653,54 @@ xlua_process_default (lua_State *L)
return 1;
}
static int
xlua_process_wait (lua_State *L)
{
struct process *self = luaL_checkudata (L, 1, XLUA_PROCESS_METATABLE);
bool nowait = luaL_opt(L, lua_toboolean, 2, false);
if (lua_gettop (L) > 2)
return luaL_error (L, "too many arguments");
int status = self->status;
restart:
if (self->pid != -1)
{
int options = 0;
if (nowait)
options |= WNOHANG;
pid_t pid = waitpid (self->pid, &status, options);
if (!pid)
return 0;
if (pid < 0)
{
if (errno == EINTR)
goto restart;
return luaL_error (L, "waitpid: %s", strerror (errno));
}
// We lose the ability to reliably kill the whole process group.
self->status = status;
self->pid = -1;
}
if (WIFEXITED (status))
{
lua_pushinteger (L, WEXITSTATUS (status));
lua_pushinteger (L, WEXITSTATUS (status));
lua_pushnil (L);
return 3;
}
if (WIFSIGNALED (status))
{
lua_pushinteger (L, 128 + WTERMSIG (status));
lua_pushnil (L);
lua_pushinteger (L, WTERMSIG (status));
return 3;
}
return 0;
}
static bool
process_feed (struct process *self)
{
@@ -618,6 +721,14 @@ process_feed (struct process *self)
return false;
}
if (self->asciicast)
{
double timestamp = (clock_msec () - self->start) / 1000.;
fprintf (self->asciicast, "[%f, \"o\", ", timestamp);
write_json_string (self->asciicast, buf, n);
fprintf (self->asciicast, "]\n");
}
// TODO(p): Add match_max processing, limiting the buffer size.
str_append_data (&self->buffer, buf, n);
return n > 0;
@@ -632,6 +743,7 @@ static luaL_Reg xlua_process_table[] =
{ "exact", xlua_process_exact },
{ "eof", xlua_process_eof },
{ "default", xlua_process_default },
{ "wait", xlua_process_wait },
{ NULL, NULL }
};
@@ -778,10 +890,11 @@ environ_map_serialize (struct str_map *env, struct strv *envv)
static int
spawn_protected (lua_State *L)
{
struct spawn_context *ctx = lua_touserdata (L, 1);
struct spawn_context *ctx = lua_touserdata (L, lua_upvalueindex (1));
luaL_checktype (L, 1, LUA_TTABLE);
// Step 1: Prepare process environment.
if (xlua_getfield (L, 2, "environ", LUA_TTABLE, true))
if (xlua_getfield (L, 1, "environ", LUA_TTABLE, true))
{
environ_map_update (&ctx->env, L);
lua_pop (L, 1);
@@ -801,11 +914,11 @@ spawn_protected (lua_State *L)
#endif
// Step 3: Prepare process command line.
size_t argc = lua_rawlen (L, 2);
size_t argc = lua_rawlen (L, 1);
for (size_t i = 1; i <= argc; i++)
{
lua_pushinteger (L, i);
lua_rawget (L, 2);
lua_rawget (L, 1);
const char *arg = lua_tostring (L, -1);
if (!arg)
return luaL_error (L, "spawn arguments must be strings");
@@ -838,9 +951,17 @@ spawn_protected (lua_State *L)
}
process->ref_term = luaL_ref (L, LUA_REGISTRYINDEX);
struct winsize ws = { .ws_row = 24, .ws_col = 80 };
if ((entry = str_map_find (&ctx->term, "lines"))
&& entry->kind == TERMINFO_NUMERIC)
ws.ws_row = entry->numeric;
if ((entry = str_map_find (&ctx->term, "columns"))
&& entry->kind == TERMINFO_NUMERIC)
ws.ws_col = entry->numeric;
// Step 5: Spawn the process, which gets a new process group.
process->pid =
pty_fork (&process->terminal_fd, NULL, NULL, NULL, &ctx->error);
pty_fork (&process->terminal_fd, NULL, NULL, &ws, &ctx->error);
if (process->pid < 0)
{
return luaL_error (L, "failed to spawn %s: %s",
@@ -851,9 +972,32 @@ spawn_protected (lua_State *L)
execvpe (ctx->argv.vector[0], ctx->argv.vector, ctx->envv.vector);
print_error ("failed to spawn %s: %s",
ctx->argv.vector[0], strerror (errno));
// Or we could figure out when exactly to use statuses 126 and 127.
_exit (EXIT_FAILURE);
}
// Step 6: Create a log file.
if (getenv ("WDYE_LOGGING"))
{
const char *name = ctx->argv.vector[0];
const char *last_slash = strrchr (name, '/');
if (last_slash)
name = last_slash + 1;
char *path = xstrdup_printf ("%s-%s.%d.cast",
PROGRAM_NAME, name, (int) process->pid);
if (!(process->asciicast = fopen (path, "w")))
print_warning ("%s: %s", path, strerror (errno));
free (path);
}
process->start = clock_msec ();
if (process->asciicast)
{
fprintf (process->asciicast, "{\"version\": 2, "
"\"width\": %u, \"height\": %u, \"env\": {\"TERM\": \"%s\"}}\n",
ws.ws_col, ws.ws_row, term);
}
set_cloexec (process->terminal_fd);
return 1;
}
@@ -861,23 +1005,24 @@ spawn_protected (lua_State *L)
static int
xlua_spawn (lua_State *L)
{
luaL_checktype (L, 1, LUA_TTABLE);
lua_pushcfunction (L, xlua_error_handler);
lua_pushcfunction (L, spawn_protected);
lua_insert (L, 1);
struct spawn_context ctx = {};
lua_pushlightuserdata (L, &ctx);
lua_pushcclosure (L, spawn_protected, 1);
lua_insert (L, 2);
// There are way too many opportunities for Lua to throw,
// so maintain a context to clean up in one go.
struct spawn_context ctx = spawn_context_make ();
lua_pushlightuserdata (L, &ctx);
lua_rotate (L, 1, -1);
int result = lua_pcall (L, 2, 1, -4);
ctx = spawn_context_make ();
int result = lua_pcall (L, lua_gettop (L) - 2, 1, 1);
spawn_context_free (&ctx);
if (result)
return lua_error (L);
// Remove the error handler ("good programming practice").
lua_remove (L, -2);
lua_remove (L, 1);
return 1;
}
@@ -940,7 +1085,7 @@ expect_prepare_pattern (struct expect_context *ctx, struct pattern *p)
{
str_reset (&p->input);
if (p->kind == PATTERN_REGEX)
for (int i = 0; i <= p->regex->re_nsub; i++)
for (size_t i = 0; i <= p->regex->re_nsub; i++)
p->matches[i] = (regmatch_t) { .rm_so = -1, .rm_eo = -1 };
if (p->kind == PATTERN_REGEX
@@ -1027,7 +1172,7 @@ pattern_match (struct pattern *self)
if (regexec (self->regex, buffer->str,
self->regex->re_nsub + 1, self->matches, flags))
{
for (int i = 0; i <= self->regex->re_nsub; i++)
for (size_t i = 0; i <= self->regex->re_nsub; i++)
self->matches[i] = (regmatch_t) { .rm_so = -1, .rm_eo = -1 };
return false;
}
@@ -1233,9 +1378,9 @@ xlua_panic (lua_State *L)
int
main (int argc, char *argv[])
{
if (argc != 2)
if (argc < 2)
{
fprintf (stderr, "Usage: %s program.lua\n", argv[0]);
fprintf (stderr, "Usage: %s program.lua [args...]\n", argv[0]);
return 1;
}
@@ -1256,16 +1401,20 @@ main (int argc, char *argv[])
luaL_setfuncs (g.L, xlua_pattern_table, 0);
lua_pop (g.L, 1);
const char *path = argv[1];
luaL_checkstack (g.L, argc, NULL);
lua_pushcfunction (g.L, xlua_error_handler);
if (luaL_loadfile (g.L, path)
|| lua_pcall (g.L, 0, 0, -2))
{
print_error ("%s: %s", path, lua_tostring (g.L, -1));
lua_pop (g.L, 1);
lua_close (g.L);
return 1;
}
if (luaL_loadfile (g.L, strcmp (argv[1], "-") ? argv[1] : NULL))
goto error;
for (int i = 2; i < argc; i++)
lua_pushstring (g.L, argv[i]);
if (lua_pcall (g.L, argc - 2, 0, 1))
goto error;
lua_close (g.L);
return 0;
error:
print_error ("%s", lua_tostring (g.L, -1));
lua_close (g.L);
return 1;
}