and code cleanup
This commit is contained in:
Leon Henrik Plickat
2023-12-30 03:43:12 +01:00
parent 2e409596cf
commit ccfaa03839
13 changed files with 446 additions and 196 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
protocol/*.c
protocol/*.h
*.o
riverguile

View File

@@ -7,13 +7,18 @@ MANDIR=$(DATADIR)/man
CFLAGS=-g -Wall -Werror -Wextra -Wpedantic -Wno-unused-parameter $\
-Wno-overlength-strings -Wformat-security -Wformat -Wunused-result $\
-I protocol $\
$(shell pkg-config --cflags wayland-client) $\
$(shell guile-config compile)
LIBS=-lrt $\
$(shell pkg-config --libs wayland-client) $\
$(shell guile-config link)
OBJ=riverguile.o river-layout-v3.o
GEN=river-layout-v3.c river-layout-v3.h
OBJ=src/riverguile.o $\
src/call-layout-demand-handler.o $\
src/call-user-command-handler.o $\
src/load-script.o $\
protocol/river-layout-v3.o
GEN=protocol/river-layout-v3.c protocol/river-layout-v3.h
all: riverguile
@@ -31,7 +36,7 @@ $(OBJ): $(GEN)
install: riverguile
install -D riverguile $(DESTDIR)$(BINDIR)/riverguile
install -m 644 -D riverguile.1 $(DESTDIR)$(MANDIR)/man1/riverguile.1
install -m 644 -D doc/riverguile.1 $(DESTDIR)$(MANDIR)/man1/riverguile.1
uninstall:
$(RM) $(DESTDIR)$(BINDIR)/riverguile

97
doc/riverguile.1 Normal file
View File

@@ -0,0 +1,97 @@
.TH RIVERGUILE 1 2023-11-25 "git.sr.ht/~leon_plickat/riverguile" "General Commands Manual"
.
.SH NAME
.P
riverguile \- scheme powered layout generator for river
.
.
.SH SYNOPSIS
.SY riverguile
.YS
.
.
.SH DESCRIPTION
.P
Layout generator for the
.BR river (1)
Wayland server which allows users to define layouts using guile scheme functions.
.
.P
The layout namespace is \fBriverguile\fB.
.
.P
Uppon launch, riverguile tries to eval a scheme script and checks the following
paths for it in the given order:
.IP \(bu 2
\fBlayout.scm\fR
.IP \(bu 2
\fB$XDG_CONFIG_HOME/river/layout.scm\fR
.IP \(bu 2
\fB$HOME/.config/river/layout.scm\fR
.IP \(bu 2
\fB/etc/riverguile/layout.scm\fR
.
.P
In the context of this script, a special procedure
\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR is available, which can be
used to install handlers to certain events.
The parameter \fIkey\fR is a symbol indicating for which event to install
the procedure \fIproc\fR.
.P
The key \fBlayout-demand\fR installs a handler for layout demands, which must
accept five required arguments, which are, in order: The amount of views in the
layout (integer), the available width (integer), the available height (integer),
the currently active tag set (integer representing a bitfield of size 32)
and the global name of the output the layout is needed for (integer).
The procedure must return a list containing exactly as many lists as there are
views in the layout.
Each of those sublists must contains exactly four numerical values, which are
the x and y coordinates of the window as well as its width and height.
Window positions and dimensions get applied to rivers window list top to bottom.
Note that the numerical values do not need to be exact, riverguile takes care
of rounding and casting for you.
.P
The key \fBuser-command\fR install a handler for user commands, which are the
strings a user can send to layout generators.
This handler procedure must accept three arguments, which are, in order:
The command (string), the currently active tags (integer representing a
bitfield of size 32) and the global name of the output (integer).
This event can be used to change paramters of the layout.
A new layout demand will be send right after this event.
.
.
.SH EXAMPLE
.P
This is an example configuration, which installs a layout that simply assigns
each window all usable space and a user command handler that tries to evaluate
all send commands as scheme code.
.
.P
.RS
.EX
(install-handler 'user-command (lambda (cmd tags output)
(eval-string cmd)))
(install-handler 'layout-demand (lambda (view-count width height tags output)
(letrec ((iter (lambda (n)
(if (eq? 0 n)
'()
(append (list (list 0 0 width height))
(iter (1- n)))))))
(iter view-count))))
.EE
.RE
.
.
.SH SEE ALSO
.BR river (1),
.BR riverctl (1),
.BR rivertile (1),
.BR guile (1)
.
.
.SH AUTHOR
.P
.MT leonhenrik.plickat@stud.uni-goettingen.de
Leon Henrik Plickat
.ME

View File

@@ -1,62 +0,0 @@
.TH RIVERGUILE 1 2023-11-25 "git.sr.ht/~leon_plickat/riverguile" "General Commands Manual"
.
.SH NAME
.P
riverguile \- scheme powered layout generator for river
.
.
.SH SYNOPSIS
.SY riverguile
.YS
.
.
.SH DESCRIPTION
.P
Layout generator for the
.BR river (1)
Wayland server which allows users to define layouts using guile scheme functions.
.
.P
The layout namespace is `riverguile`.
.
.P
Uppon launch, riverguile tries to eval a scheme script and checks the following
paths for it in the given order:
.IP \(bu 2
\fBlayout.scm\fR
.IP \(bu 2
\fB$XDG_CONFIG_HOME/river/layout.scm\fR
.IP \(bu 2
\fB$HOME/.config/river/layout.scm\fR
.IP \(bu 2
\fB/etc/riverguile/layout.scm\fR
.
.P
This script must contain the definition of a function
\fB(layout-demand-handler view-count usable-width usable-height)\fR and may
contain any other code the user desires.
The function must return a list containing exactly as many lists as \fBview-count\fR.
Each of those sublists must contains exactly four numerical values, which are
the x and y coordinates of the window as well as its width and height.
Window positions and dimensions get applied to rivers window list top to bottom.
.
.P
Note that the numerical values do not need to be exact, riverguile takes care
of rounding and casting for you.
.
.P
If you send riverguile a layout-command, it will try to eval it as scheme code.
.
.
.SH SEE ALSO
.BR river (1),
.BR riverctl (1),
.BR rivertile (1),
.BR guile (1)
.
.
.SH AUTHOR
.P
.MT leonhenrik.plickat@stud.uni-goettingen.de
Leon Henrik Plickat
.ME

View File

@@ -0,0 +1,83 @@
#include <assert.h>
#include <libguile.h>
#include "river-layout-v3.h"
#include "call-layout-demand-handler.h"
extern SCM layout_demand_handler;
static void *call_layout_demand_handler_inner (void *data)
{
struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data;
return scm_call_5(
layout_demand_handler,
scm_from_uint32(params->view_count),
scm_from_uint32(params->width),
scm_from_uint32(params->height),
scm_from_uint32(params->tags),
scm_from_uint32(params->output->name)
);
}
void *call_layout_demand_handler (void *data)
{
struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data;
assert(layout_demand_handler != NULL);
assert(scm_is_true(scm_procedure_p(layout_demand_handler)) == 1);
/* Continuation barrier causes stack unwind on exceptions (i.e. errors
* in the user defined layout demand handler) to stop here. Otherwise
* the entire stack created by scm_with_guile() would be unwound. This
* makes responding to exceptions nicer.
*/
SCM call_result = scm_c_with_continuation_barrier(
call_layout_demand_handler_inner, data
);
if ( call_result == NULL )
return (void *)"ERROR: An exception occured while calling the layout demand handler.\n";
/* Check integrity of data before firing off any Wayland requests. */
if ( scm_is_false(scm_list_p(call_result)) == 1 )
return (void *)"ERROR: layout-demand-handler did not return a list.\n";
if ( params->view_count != scm_to_uint32(scm_length(call_result)) )
return (void *)"ERROR: Length of list returned by layout-demand-handler does not match view count.\n";
for (uint32_t i = 0; i < params->view_count; i++)
{
SCM elem = scm_list_ref(call_result, scm_from_uint32(i));
if ( scm_is_false(scm_list_p(elem)) == 1 )
return (void *)"ERROR: View dimensions list is not a list.\n";
if ( scm_to_uint32(scm_length(elem)) != 4 )
return (void *)"ERROR: View dimensions list length does not equal four (x, y, width, height).\n";
for (uint8_t j = 0; j < 4; j++)
{
SCM list_elem = scm_list_ref(elem, scm_from_int(j));
if ( scm_is_false(scm_number_p(list_elem)) == 1 )
return (void *)"ERROR: Encountered non-numerical view dimensions.\n";
}
}
for (uint32_t i = 0; i < params->view_count; i++)
{
SCM elem = scm_list_ref(call_result, scm_from_uint32(i));
uint32_t x = scm_to_uint32(scm_inexact_to_exact(
scm_round_number(scm_list_ref(elem, scm_from_int(0)))));
uint32_t y = scm_to_uint32(scm_inexact_to_exact(
scm_round_number(scm_list_ref(elem, scm_from_int(1)))));
uint32_t w = scm_to_uint32(scm_inexact_to_exact(
scm_round_number(scm_list_ref(elem, scm_from_int(2)))));
uint32_t h = scm_to_uint32(scm_inexact_to_exact(
scm_round_number(scm_list_ref(elem, scm_from_int(3)))));
river_layout_v3_push_view_dimensions(
params->output->layout,
x, y, w, h,
params->serial
);
}
return NULL;
}

View File

@@ -0,0 +1,16 @@
#ifndef RIVERGUILE_CALL_LAYOUT_DEMAND_HANDLER_H
#define RIVERGUILE_CALL_LAYOUT_DEMAND_HANDLER_H
#include <libguile.h>
#include "types.h"
struct Call_layout_demand_handler_parameters
{
uint32_t view_count, width, height, serial, tags;
struct Output *output;
};
void *call_layout_demand_handler (void *data);
#endif

View File

@@ -0,0 +1,38 @@
#include <assert.h>
#include <libguile.h>
#include "types.h"
#include "call-user-command-handler.h"
extern SCM user_command_handler;
static void *call_user_command_handler_inner (void *data)
{
struct Call_user_command_parameters *params = (struct Call_user_command_parameters *)data;
return scm_call_3(
user_command_handler,
scm_from_utf8_string(params->cmd),
scm_from_uint32(params->output->user_command_tags),
scm_from_uint32(params->output->name)
);
}
void *call_user_command_handler (void *data)
{
assert(user_command_handler != NULL);
assert(scm_is_true(scm_procedure_p(user_command_handler)) == 1);
/* Continuation barrier causes stack unwind on exceptions (i.e. errors
* in the user defined user-command handler) to stop here. Otherwise
* the entire stack created by scm_with_guile() would be unwound. This
* makes responding to exceptions nicer.
*/
SCM call_result = scm_c_with_continuation_barrier(
call_user_command_handler_inner, data
);
if ( call_result == NULL )
return (void *)"ERROR: An exception occured while calling the user-command handler.\n";
return NULL;
}

View File

@@ -0,0 +1,14 @@
#ifndef RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
#define RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
#include "types.h"
struct Call_user_command_parameters
{
struct Output *output;
const char *cmd;
};
void *call_user_command_handler (void *data);
#endif

104
src/load-script.c Normal file
View File

@@ -0,0 +1,104 @@
#include <assert.h>
#include <stdio.h>
#include <libguile.h>
/**
* ISO C forbids casting a function pointer to a void pointer because on some
* architectures they have different sizes. However scm_c_define_gsubr() wants
* a function pointer as a void pointer, which trips -Wpedantic. Compiling C
* without pedantic errors is not a reasonable option in my opinion, so instead
* we'll have to resort to this hack.
*/
#define scm_c_define_gsubr_fix(NAME, REQ, OPT, RST, FN) \
{ const long int ptr = (long int)FN; scm_c_define_gsubr(NAME, REQ, OPT, RST, (void *)ptr); }
extern SCM layout_demand_handler;
extern SCM user_command_handler;
static SCM install_handler (SCM key, SCM proc)
{
if ( scm_is_false(scm_symbol_p(key)) == 1 )
{
scm_error_scm(
scm_from_utf8_symbol("wrong-type-arg"),
scm_from_utf8_string("install-handler"),
scm_from_utf8_string("First argument must be a symbol."),
SCM_BOOL_F,
scm_list_1(key)
);
return SCM_UNSPECIFIED;
}
// TODO check if the procedure has the right amount of arguments.
if ( scm_is_false(scm_procedure_p(proc)) == 1 )
{
scm_error_scm(
scm_from_utf8_symbol("wrong-type-arg"),
scm_from_utf8_string("install-handler"),
scm_from_utf8_string("Second argument must be a procedure."),
SCM_BOOL_F,
scm_list_1(proc)
);
return SCM_UNSPECIFIED;
}
if (scm_is_eq(scm_from_utf8_symbol("layout-demand"), key))
layout_demand_handler = proc;
else if (scm_is_eq(scm_from_utf8_symbol("user-command"), key))
user_command_handler = proc;
else
{
scm_error_scm(
// TODO should this be 'misc-error instead?
scm_from_utf8_symbol("out-of-range"),
scm_from_utf8_string("install-handler"),
scm_from_utf8_string("Unknown key: ~A"),
scm_list_1(key),
scm_list_1(key)
);
return SCM_UNSPECIFIED;
}
return SCM_BOOL_T;
}
static void *load_script_inner (void *data)
{
const char *path = (char *)data;
/* scm_primitive_load_path() searches guiles load-path when encountering
* a relative path. That should never happen here though.
*/
assert(path[0] == '/');
return scm_primitive_load_path(scm_from_utf8_string(path));
}
void *load_script (void *data)
{
/* Note: All guile objects are garbage collected. */
scm_c_define_gsubr_fix("install-handler", 2, 0, 0, install_handler);
/* Continuation barrier causes stack unwind on exceptions to stop here.
* Otherwise the entire stack created by scm_with_guile() would be
* unwound. This makes responding to exceptions nicer.
*/
SCM call_result = scm_c_with_continuation_barrier(
load_script_inner, data
);
if ( call_result == NULL )
return (void *)"ERROR: Fatal error while loading layout script.\n";
if ( layout_demand_handler == NULL )
return (void *)"ERROR: No layout demand handler installed.\n";
/* Checked in the installer functions. */
assert(scm_is_true(scm_procedure_p(layout_demand_handler)) == 1);
if ( user_command_handler != NULL )
assert(scm_is_true(scm_procedure_p(user_command_handler)) == 1);
return NULL;
}

6
src/load-script.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef RIVERGUILE_LOAD_SCRIPT_H_INCLUDED
#define RIVERGUILE_LOAD_SCRIPT_H_INCLUDED
void *load_script (void *data);
#endif

View File

@@ -8,7 +8,6 @@
#include <errno.h>
#include <assert.h>
#include <setjmp.h>
#include <wayland-client.h>
#include <libguile.h>
@@ -21,6 +20,11 @@
#include "river-layout-v3.h"
#include "types.h"
#include "call-layout-demand-handler.h"
#include "call-user-command-handler.h"
#include "load-script.h"
bool loop = true;
int ret = EXIT_SUCCESS;
@@ -33,119 +37,68 @@ struct river_layout_manager_v3 *layout_manager = NULL;
struct wl_list outputs;
struct Output
{
struct wl_list link;
struct wl_output *wl_output;
struct river_layout_v3 *layout;
uint32_t name;
bool configured;
};
struct Layout_demand_parameters
{
uint32_t view_count, width, height, serial;
struct Output *output;
};
static bool scm2uint(uint32_t *x, SCM s)
{
if ( scm_is_false(scm_number_p(s)) == 1 )
return false;
*x = scm_to_uint32(scm_inexact_to_exact(scm_round_number(s)));
return true;
}
/**
* Try to find a variable named 'name' and returns it, otherwise #f is returned.
/* Handlers are initially NULL instead of SCM_EOL because I am not sure whether
* scm_null_p() is safe to use outside of a guile context and if yes, whether
* that is intended or will disappear in the future. Special care must be taken
* to never call any scm_* function on these while they are NULL.
*/
static SCM scm_c_get_variable (const char *name)
{
return scm_module_variable(
scm_current_module(),
scm_string_to_symbol(
scm_from_utf8_string(name)
)
);
}
static void *call_layout_demand_handler (void *data)
{
struct Layout_demand_parameters *params = (struct Layout_demand_parameters *)data;
SCM layout_demand_handler = scm_c_get_variable("layout-demand-handler");
if ( scm_is_false(layout_demand_handler) == 1 )
{
ret = EXIT_FAILURE;
loop = false;
return (void *)"ERROR: layout-demand-handler no longer exists.\n";
}
SCM call_result = scm_call_3(
scm_variable_ref(layout_demand_handler),
scm_from_uint32(params->view_count),
scm_from_uint32(params->width),
scm_from_uint32(params->height)
);
if ( scm_is_false(scm_list_p(call_result)) == 1 )
return (void *)"ERROR: layout-demand-handler did not return a list.\n";
if ( params->view_count != scm_to_uint32(scm_length(call_result)) )
return (void *)"ERROR: Length of list returned by layout-demand-handler does not match view count.\n";
for (uint32_t i = 0; i < params->view_count; i++)
{
SCM elem = scm_list_ref(call_result, scm_from_uint32(i));
if ( scm_is_false(scm_list_p(elem)) == 1 )
return (void *)"ERROR: View dimensions list is not a list.\n";
if ( scm_to_uint32(scm_length(elem)) != 4 )
return (void *)"ERROR: View dimensions list length does not equal four (x, y, width, height).\n";
uint32_t x, y, w, h;
if ( !scm2uint(&x, scm_list_ref(elem, scm_from_int(0)))
|| !scm2uint(&y, scm_list_ref(elem, scm_from_int(1)))
|| !scm2uint(&w, scm_list_ref(elem, scm_from_int(2)))
|| !scm2uint(&h, scm_list_ref(elem, scm_from_int(3))) )
return "ERROR: Encountered non-numerical view dimensions.\n";
river_layout_v3_push_view_dimensions(
params->output->layout,
x, y, w, h,
params->serial
);
}
return NULL;
}
SCM layout_demand_handler = NULL;
SCM user_command_handler = NULL;
static void layout_handle_layout_demand (void *data, struct river_layout_v3 *river_layout_v3,
uint32_t view_count, uint32_t width, uint32_t height, uint32_t tags, uint32_t serial)
{
struct Output *output = (struct Output *)data;
struct Layout_demand_parameters params = {
struct Call_layout_demand_handler_parameters params = {
.view_count = view_count,
.width = width,
.height = height,
.serial = serial,
.tags = tags,
.output = output,
};
void *res = scm_with_guile(call_layout_demand_handler, (void *)&params);
if ( res != NULL )
{
fputs(res, stderr);
loop = false;
ret = EXIT_FAILURE;
fputs("INFO: This error is not fatal.\n", stderr);
return;
}
// TODO if 'layout-name' variable exists, use it.
// TODO allow setting layout name from user installed layout demand handler
river_layout_v3_commit(output->layout, "(🐦)", serial);
}
static void layout_handle_user_command (void *data,
struct river_layout_v3 *river_layout, const char *command)
{
if ( user_command_handler == NULL )
return;
struct Output *output = (struct Output *)data;
struct Call_user_command_parameters params = {
.cmd = command,
.output = output,
};
void *res = scm_with_guile(call_user_command_handler, (void *)&params);
if ( res != NULL )
{
fputs(res, stderr);
fputs("INFO: This error is not fatal.\n", stderr);
}
}
static void layout_handle_user_command_tags (void *data,
struct river_layout_v3 *river_layout, uint32_t tags)
{
struct Output *output = (struct Output *)data;
output->user_command_tags = tags;
}
static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 *river_layout_v3)
{
fputs("ERROR: Namespace already in use.\n", stderr);
@@ -153,22 +106,11 @@ static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 *
loop = false;
}
static void *exec_user_command (void *str)
{
return (void *)scm_c_eval_string((char *)str);
}
static void layout_handle_user_command (void *data,
struct river_layout_v3 *river_layout_manager_v3,
const char *command)
{
(void)scm_with_guile(exec_user_command, (void *)command);
}
static const struct river_layout_v3_listener layout_listener = {
.namespace_in_use = layout_handle_namespace_in_use,
.layout_demand = layout_handle_layout_demand,
.user_command = layout_handle_user_command,
.namespace_in_use = layout_handle_namespace_in_use,
.layout_demand = layout_handle_layout_demand,
.user_command = layout_handle_user_command,
.user_command_tags = layout_handle_user_command_tags,
};
static void output_configure (struct Output *output)
@@ -214,7 +156,7 @@ static void registry_handle_global (void *data, struct wl_registry *registry,
if ( strcmp(interface, river_layout_manager_v3_interface.name) == 0 )
{
layout_manager = wl_registry_bind(
registry, name, &river_layout_manager_v3_interface, 1
registry, name, &river_layout_manager_v3_interface, 2
);
}
else if ( strcmp(interface, wl_output_interface.name) == 0 )
@@ -281,7 +223,7 @@ static const struct wl_callback_listener sync_callback_listener = {
static void handle_interrupt (int signum)
{
fputs("Killed.\n", stderr);
fputs("Killed 💀\n", stderr);
loop = false;
longjmp(skip_main_loop, 1);
}
@@ -327,30 +269,6 @@ static void handle_error (int signum)
kill(getpid(), signum);
}
static void *load_script (void *data)
{
/* Note: All guile objects are garbage collected. */
const char *path = (char *)data;
/* scm_primitive_load_path() searches guiles load-path when encountering
* a relative path. That should never happen here though.
*/
assert(path[0] == '/');
scm_primitive_load_path(scm_from_utf8_string(path));
/* Check if the handler has been defined. */
SCM layout_demand_handler = scm_c_get_variable("layout-demand-handler");
if ( scm_is_false(layout_demand_handler) == 1 )
{
ret = EXIT_FAILURE;
loop = false;
return (void *)"ERROR: Script does not define layout-demand-handler.\n";
}
return NULL;
}
static char *formatted_buffer (const char *fmt, ...)
{
@@ -437,6 +355,7 @@ int main(int argc, char *argv[])
signal(SIGINT, handle_interrupt);
wl_list_init(&outputs);
// TODO use argv[1] if present
char *path = get_script_path();
if ( path == NULL )
{
@@ -449,6 +368,7 @@ int main(int argc, char *argv[])
{
fputs((char *)res, stderr);
ret = EXIT_FAILURE;
loop = false;
goto early_exit;
}

25
src/types.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef RIVERGUILE_TYPES_H_INCLUDED
#define RIVERGUILE_TYPES_H_INCLUDED
#include <stdbool.h>
#include <wayland-client.h>
#include "river-layout-v3.h"
struct Output
{
struct wl_list link;
struct wl_output *wl_output;
struct river_layout_v3 *layout;
uint32_t name;
bool configured;
/* Tags for the next user command. Due to backwards compatability, the
* layout protocol sends us the currently active tags for a user command
* in a separate event before the actual user command. This event is
* guaranteed to be received.
*/
uint32_t user_command_tags;
};
#endif