add river-control-unstable-v1 support

This commit is contained in:
Leon Henrik Plickat
2024-01-08 11:47:00 +01:00
parent 153b5e0858
commit 4ae3654e35
6 changed files with 214 additions and 9 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_control_unstable_v1">
<copyright>
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.
</copyright>
<interface name="zriver_control_v1" version="1">
<description summary="run compositor commands">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_control object">
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.
</description>
</request>
<request name="add_argument">
<description summary="add an argument to the current command">
Arguments are stored by the server in the order they were sent until
the run_command request is made.
</description>
<arg name="argument" type="string" summary="the argument to add"/>
</request>
<request name="run_command">
<description summary="run the current command">
Execute the command built up using the add_argument request for the
given seat.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
summary="callback object"/>
</request>
</interface>
<interface name="zriver_command_callback_v1" version="1">
<description summary="callback object">
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.
</description>
<event name="success" type="destructor">
<description summary="command successful">
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.
</description>
<arg name="output" type="string" summary="the output of the command"/>
</event>
<event name="failure" type="destructor">
<description summary="command failed">
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.
</description>
<arg name="failure_message" type="string"
summary="a message explaining why failure occurred"/>
</event>
</interface>
</protocol>

View File

@@ -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

View File

@@ -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 )

View File

@@ -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;