From 4ae3654e352afda7cbcf8648ae6e93720dfe233e Mon Sep 17 00:00:00 2001 From: Leon Henrik Plickat Date: Mon, 8 Jan 2024 11:47:00 +0100 Subject: [PATCH] add river-control-unstable-v1 support --- Makefile | 6 +- doc/riverguile.1 | 52 +++++++++++++--- protocol/river-control-unstable-v1.xml | 85 ++++++++++++++++++++++++++ src/load-script.c | 69 +++++++++++++++++++++ src/riverguile.c | 10 +++ src/riverguile.h | 1 + 6 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 protocol/river-control-unstable-v1.xml diff --git a/Makefile b/Makefile index 1530d2e..37fd28b 100644 --- a/Makefile +++ b/Makefile @@ -21,9 +21,11 @@ OBJ=src/riverguile.o $\ src/call-exit-handler.o $\ src/load-script.o $\ protocol/river-layout-v3.o $\ - protocol/ext-idle-notify-v1.o + protocol/ext-idle-notify-v1.o $\ + protocol/river-control-unstable-v1.o GEN=protocol/river-layout-v3.c protocol/river-layout-v3.h $\ - protocol/ext-idle-notify-v1.c protocol/ext-idle-notify-v1.h + protocol/ext-idle-notify-v1.c protocol/ext-idle-notify-v1.h $\ + protocol/river-control-unstable-v1.c protocol/river-control-unstable-v1.h all: riverguile diff --git a/doc/riverguile.1 b/doc/riverguile.1 index 1a489f1..3a3cc5c 100644 --- a/doc/riverguile.1 +++ b/doc/riverguile.1 @@ -23,22 +23,60 @@ If certain handlers are installed it will run continously. .P Uppon launch, riverguile tries to load the script from the following paths in the given order: -.IP \(bu 2 +.IP \(bu 2 \fBlayout.scm\fR -.IP \(bu 2 +.IP \(bu 2 \fB$XDG_CONFIG_HOME/river/layout.scm\fR -.IP \(bu 2 +.IP \(bu 2 \fB$HOME/.config/river/layout.scm\fR -.IP \(bu 2 +.IP \(bu 2 \fB/etc/riverguile/layout.scm\fR +. +. +.SH COMMANDS .P +Riverguile exposes the special procedure +\fB(riverctl \fR\fIfirst\fR . \fIrest\fR\fB)\fR, which can be used to send +commands to the server in a similar fashion to +.BR riverctl (1). +All arguments must be strings. +.P +Here is an example of using this procedure to instruct river to spawn an +instance of the +.BR foot (1) +terminal emulator: +.P +.RS +.EX +(\fBriverctl\fR "spawn" "foot") +.EE +.RE +.P +To make using this procedure more ergonomic you likely want to wrap it in a +macro like this: +.P +.RS +.EX +(\fBdefine-syntax\fR R + (\fBsyntax-rules\fR () + ((R first) (riverctl (\fBsymbol->string\fR 'first))) + ((R first . rest) (\fBapply\fR riverctl + (\fBmap\fR \fBsymbol->string\fR + (\fBappend\fR '(first) 'rest)))))) + +(R spawn foot) +(R spawn \fB#{\fRnotify-send notification!\fB}#\fR) +.EE +.RE +.P +This macro can of course be further modified to suit your specific needs. . . .SH EVENT HANDLERS .P -In the context of the script, a special procedure -\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR is available, which can be -used to install event handlers. +Riverguile exposes the special procedure +\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR, which can be used to install +event handlers. The parameter \fIkey\fR is a symbol indicating for which event to install the procedure \fIproc\fR. The following keys are currently evailable: diff --git a/protocol/river-control-unstable-v1.xml b/protocol/river-control-unstable-v1.xml new file mode 100644 index 0000000..aa5fc4d --- /dev/null +++ b/protocol/river-control-unstable-v1.xml @@ -0,0 +1,85 @@ + + + + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + + + This interface allows clients to run compositor commands and receive a + success/failure response with output or a failure message respectively. + + Each command is built up in a series of add_argument requests and + executed with a run_command request. The first argument is the command + to be run. + + A complete list of commands should be made available in the man page of + the compositor. + + + + + This request indicates that the client will not use the + river_control object any more. Objects that have been created + through this instance are not affected. + + + + + + Arguments are stored by the server in the order they were sent until + the run_command request is made. + + + + + + + Execute the command built up using the add_argument request for the + given seat. + + + + + + + + + This object is created by the run_command request. Exactly one of the + success or failure events will be sent. This object will be destroyed + by the compositor after one of the events is sent. + + + + + Sent when the command has been successfully received and executed by + the compositor. Some commands may produce output, in which case the + output argument will be a non-empty string. + + + + + + + Sent when the command could not be carried out. This could be due to + sending a non-existent command, no command, not enough arguments, too + many arguments, invalid arguments, etc. + + + + + diff --git a/src/load-script.c b/src/load-script.c index e3baa0d..b20ee52 100644 --- a/src/load-script.c +++ b/src/load-script.c @@ -8,6 +8,8 @@ #include "output.h" #include "seat.h" +#include "river-control-unstable-v1.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 @@ -155,6 +157,72 @@ static SCM install_handler (SCM key, SCM proc) return SCM_BOOL_T; } +static SCM riverctl (SCM first, SCM rest) +{ + if ( context.river_control == NULL ) + { + fputs("ERROR: User script attempts to send river command but river-control-unstable-v1 not available.\n", stderr); + fputs("INFO: This error is not fatal, however riverguile will not be able to send any commands to river.\n", stderr); + return SCM_BOOL_F; + } + if ( wl_list_length(&context.seats) == 0 ) + { + fputs("ERROR: User script attempts to send river command but server did not advertise any seats.\n", stderr); + fputs("INFO: This error is not fatal, however riverguile will not be able to send any commands to river.\n", stderr); + return SCM_BOOL_F; + } + + if ( scm_is_string(first) != 1 ) + goto error; + + assert(scm_is_true(scm_list_p(rest)) == 1); + const uint32_t rest_len = scm_to_uint32(scm_length(rest)); + for (uint32_t i = 0; i < rest_len; i++) + if ( scm_is_string(scm_list_ref(rest, scm_from_uint32(i))) != 1 ) + goto error; + + char *first_owned = scm_to_utf8_stringn(first, 0); + zriver_control_v1_add_argument(context.river_control, first_owned); + free(first_owned); + + for (uint32_t i = 0; i < rest_len; i++) + { + char *owned = scm_to_utf8_stringn(scm_list_ref(rest, scm_from_uint32(i)), 0); + zriver_control_v1_add_argument(context.river_control, owned); + free(owned); + } + + /* Just use the first seat. River only supports a single one anyway. */ + struct Seat *seat; + wl_list_for_each(seat, &context.seats, link) + break; + + /* While river does tell us about the status of the command, I do not + * want to make use of that in riverguile for multiple reasons. + * For one, the reporting is of course async, meaning we could not raise + * a guile error in response to failed commands, even if we wanted to. + * Riverguile could allow to install a handler for the callback, however + * I do not sett the use. There are two riverctl commands that return + * useful data as a string, however using that for scripting would be + * very hacky and I prefer dedicated well designed interfaces. So we + * just destroy the callback before it even fires. + */ + zriver_command_callback_v1_destroy( + zriver_control_v1_run_command(context.river_control, seat->wl_seat) + ); + + return SCM_BOOL_T; +error: + scm_error_scm( + scm_from_utf8_symbol("wrong-type-arg"), + scm_from_utf8_string("riverctl"), + scm_from_utf8_string("All arguments must be strings."), + SCM_BOOL_F, + SCM_BOOL_F + ); + return SCM_UNSPECIFIED; +} + static void *load_script_inner (void *data) { const char *path = (char *)data; @@ -172,6 +240,7 @@ void *load_script (void *data) /* Note: All guile objects are garbage collected. */ scm_c_define_gsubr_fix("install-handler", 2, 0, 0, install_handler); + scm_c_define_gsubr_fix("riverctl", 1, 0, 1, riverctl); /* Continuation barrier causes stack unwind on exceptions to stop here. * Otherwise the entire stack created by scm_with_guile() would be diff --git a/src/riverguile.c b/src/riverguile.c index c9d4e68..53805ee 100644 --- a/src/riverguile.c +++ b/src/riverguile.c @@ -20,6 +20,7 @@ #include "river-layout-v3.h" #include "ext-idle-notify-v1.h" +#include "river-control-unstable-v1.h" #include "load-script.h" #include "call-exit-handler.h" @@ -35,6 +36,7 @@ struct Context context = { */ .layout_demand_handler = NULL, .user_command_handler = NULL, + .exit_handler = NULL, .loop = true, .ret = EXIT_SUCCESS, @@ -51,6 +53,12 @@ static void registry_handle_global (void *data, struct wl_registry *registry, registry, name, &river_layout_manager_v3_interface, 2 ); } + else if ( strcmp(interface, zriver_control_v1_interface.name) == 0 ) + { + context.river_control = wl_registry_bind( + registry, name, &zriver_control_v1_interface, 1 + ); + } else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 ) { context.idle_notifier = wl_registry_bind( @@ -380,6 +388,8 @@ int main(int argc, char *argv[]) ext_idle_notifier_v1_destroy(context.idle_notifier); if ( context.layout_manager != NULL ) river_layout_manager_v3_destroy(context.layout_manager); + if ( context.river_control != NULL ) + zriver_control_v1_destroy(context.river_control); if ( context.sync_callback != NULL ) wl_callback_destroy(context.sync_callback); if ( context.wl_registry != NULL ) diff --git a/src/riverguile.h b/src/riverguile.h index 1e68e7c..85684cd 100644 --- a/src/riverguile.h +++ b/src/riverguile.h @@ -35,6 +35,7 @@ struct Context struct wl_callback *sync_callback; struct river_layout_manager_v3 *layout_manager; struct ext_idle_notifier_v1 *idle_notifier; + struct zriver_control_v1 *river_control; struct wl_list seats; struct wl_list outputs;