new API
and code cleanup
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
protocol/*.c
|
||||||
|
protocol/*.h
|
||||||
|
*.o
|
||||||
|
riverguile
|
||||||
11
Makefile
11
Makefile
@@ -7,13 +7,18 @@ MANDIR=$(DATADIR)/man
|
|||||||
|
|
||||||
CFLAGS=-g -Wall -Werror -Wextra -Wpedantic -Wno-unused-parameter $\
|
CFLAGS=-g -Wall -Werror -Wextra -Wpedantic -Wno-unused-parameter $\
|
||||||
-Wno-overlength-strings -Wformat-security -Wformat -Wunused-result $\
|
-Wno-overlength-strings -Wformat-security -Wformat -Wunused-result $\
|
||||||
|
-I protocol $\
|
||||||
$(shell pkg-config --cflags wayland-client) $\
|
$(shell pkg-config --cflags wayland-client) $\
|
||||||
$(shell guile-config compile)
|
$(shell guile-config compile)
|
||||||
LIBS=-lrt $\
|
LIBS=-lrt $\
|
||||||
$(shell pkg-config --libs wayland-client) $\
|
$(shell pkg-config --libs wayland-client) $\
|
||||||
$(shell guile-config link)
|
$(shell guile-config link)
|
||||||
OBJ=riverguile.o river-layout-v3.o
|
OBJ=src/riverguile.o $\
|
||||||
GEN=river-layout-v3.c river-layout-v3.h
|
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
|
all: riverguile
|
||||||
|
|
||||||
@@ -31,7 +36,7 @@ $(OBJ): $(GEN)
|
|||||||
|
|
||||||
install: riverguile
|
install: riverguile
|
||||||
install -D riverguile $(DESTDIR)$(BINDIR)/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:
|
uninstall:
|
||||||
$(RM) $(DESTDIR)$(BINDIR)/riverguile
|
$(RM) $(DESTDIR)$(BINDIR)/riverguile
|
||||||
|
|||||||
97
doc/riverguile.1
Normal file
97
doc/riverguile.1
Normal 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
|
||||||
62
riverguile.1
62
riverguile.1
@@ -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
|
|
||||||
83
src/call-layout-demand-handler.c
Normal file
83
src/call-layout-demand-handler.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
16
src/call-layout-demand-handler.h
Normal file
16
src/call-layout-demand-handler.h
Normal 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
|
||||||
38
src/call-user-command-handler.c
Normal file
38
src/call-user-command-handler.c
Normal 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;
|
||||||
|
}
|
||||||
14
src/call-user-command-handler.h
Normal file
14
src/call-user-command-handler.h
Normal 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
104
src/load-script.c
Normal 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
6
src/load-script.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef RIVERGUILE_LOAD_SCRIPT_H_INCLUDED
|
||||||
|
#define RIVERGUILE_LOAD_SCRIPT_H_INCLUDED
|
||||||
|
|
||||||
|
void *load_script (void *data);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
#include <libguile.h>
|
#include <libguile.h>
|
||||||
|
|
||||||
@@ -21,6 +20,11 @@
|
|||||||
|
|
||||||
#include "river-layout-v3.h"
|
#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;
|
bool loop = true;
|
||||||
int ret = EXIT_SUCCESS;
|
int ret = EXIT_SUCCESS;
|
||||||
|
|
||||||
@@ -33,119 +37,68 @@ struct river_layout_manager_v3 *layout_manager = NULL;
|
|||||||
|
|
||||||
struct wl_list outputs;
|
struct wl_list outputs;
|
||||||
|
|
||||||
struct Output
|
/* 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
|
||||||
struct wl_list link;
|
* that is intended or will disappear in the future. Special care must be taken
|
||||||
struct wl_output *wl_output;
|
* to never call any scm_* function on these while they are NULL.
|
||||||
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.
|
|
||||||
*/
|
*/
|
||||||
static SCM scm_c_get_variable (const char *name)
|
SCM layout_demand_handler = NULL;
|
||||||
{
|
SCM user_command_handler = NULL;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void layout_handle_layout_demand (void *data, struct river_layout_v3 *river_layout_v3,
|
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)
|
uint32_t view_count, uint32_t width, uint32_t height, uint32_t tags, uint32_t serial)
|
||||||
{
|
{
|
||||||
struct Output *output = (struct Output *)data;
|
struct Output *output = (struct Output *)data;
|
||||||
|
|
||||||
struct Layout_demand_parameters params = {
|
struct Call_layout_demand_handler_parameters params = {
|
||||||
.view_count = view_count,
|
.view_count = view_count,
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
.serial = serial,
|
.serial = serial,
|
||||||
|
.tags = tags,
|
||||||
.output = output,
|
.output = output,
|
||||||
};
|
};
|
||||||
|
|
||||||
void *res = scm_with_guile(call_layout_demand_handler, (void *)¶ms);
|
void *res = scm_with_guile(call_layout_demand_handler, (void *)¶ms);
|
||||||
|
|
||||||
if ( res != NULL )
|
if ( res != NULL )
|
||||||
{
|
{
|
||||||
fputs(res, stderr);
|
fputs(res, stderr);
|
||||||
loop = false;
|
fputs("INFO: This error is not fatal.\n", stderr);
|
||||||
ret = EXIT_FAILURE;
|
|
||||||
return;
|
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);
|
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 *)¶ms);
|
||||||
|
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)
|
static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 *river_layout_v3)
|
||||||
{
|
{
|
||||||
fputs("ERROR: Namespace already in use.\n", stderr);
|
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;
|
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 = {
|
static const struct river_layout_v3_listener layout_listener = {
|
||||||
.namespace_in_use = layout_handle_namespace_in_use,
|
.namespace_in_use = layout_handle_namespace_in_use,
|
||||||
.layout_demand = layout_handle_layout_demand,
|
.layout_demand = layout_handle_layout_demand,
|
||||||
.user_command = layout_handle_user_command,
|
.user_command = layout_handle_user_command,
|
||||||
|
.user_command_tags = layout_handle_user_command_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void output_configure (struct Output *output)
|
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 )
|
if ( strcmp(interface, river_layout_manager_v3_interface.name) == 0 )
|
||||||
{
|
{
|
||||||
layout_manager = wl_registry_bind(
|
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 )
|
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)
|
static void handle_interrupt (int signum)
|
||||||
{
|
{
|
||||||
fputs("Killed.\n", stderr);
|
fputs("Killed 💀\n", stderr);
|
||||||
loop = false;
|
loop = false;
|
||||||
longjmp(skip_main_loop, 1);
|
longjmp(skip_main_loop, 1);
|
||||||
}
|
}
|
||||||
@@ -327,30 +269,6 @@ static void handle_error (int signum)
|
|||||||
kill(getpid(), 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, ...)
|
static char *formatted_buffer (const char *fmt, ...)
|
||||||
{
|
{
|
||||||
@@ -437,6 +355,7 @@ int main(int argc, char *argv[])
|
|||||||
signal(SIGINT, handle_interrupt);
|
signal(SIGINT, handle_interrupt);
|
||||||
wl_list_init(&outputs);
|
wl_list_init(&outputs);
|
||||||
|
|
||||||
|
// TODO use argv[1] if present
|
||||||
char *path = get_script_path();
|
char *path = get_script_path();
|
||||||
if ( path == NULL )
|
if ( path == NULL )
|
||||||
{
|
{
|
||||||
@@ -449,6 +368,7 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
fputs((char *)res, stderr);
|
fputs((char *)res, stderr);
|
||||||
ret = EXIT_FAILURE;
|
ret = EXIT_FAILURE;
|
||||||
|
loop = false;
|
||||||
goto early_exit;
|
goto early_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
25
src/types.h
Normal file
25
src/types.h
Normal 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
|
||||||
Reference in New Issue
Block a user