add river-control-unstable-v1 support
This commit is contained in:
6
Makefile
6
Makefile
@@ -21,9 +21,11 @@ OBJ=src/riverguile.o $\
|
|||||||
src/call-exit-handler.o $\
|
src/call-exit-handler.o $\
|
||||||
src/load-script.o $\
|
src/load-script.o $\
|
||||||
protocol/river-layout-v3.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 $\
|
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
|
all: riverguile
|
||||||
|
|
||||||
|
|||||||
@@ -23,22 +23,60 @@ If certain handlers are installed it will run continously.
|
|||||||
.P
|
.P
|
||||||
Uppon launch, riverguile tries to load the script from the following paths
|
Uppon launch, riverguile tries to load the script from the following paths
|
||||||
in the given order:
|
in the given order:
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
\fBlayout.scm\fR
|
\fBlayout.scm\fR
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
\fB$XDG_CONFIG_HOME/river/layout.scm\fR
|
\fB$XDG_CONFIG_HOME/river/layout.scm\fR
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
\fB$HOME/.config/river/layout.scm\fR
|
\fB$HOME/.config/river/layout.scm\fR
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
\fB/etc/riverguile/layout.scm\fR
|
\fB/etc/riverguile/layout.scm\fR
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH COMMANDS
|
||||||
.P
|
.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
|
.SH EVENT HANDLERS
|
||||||
.P
|
.P
|
||||||
In the context of the script, a special procedure
|
Riverguile exposes the special procedure
|
||||||
\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR is available, which can be
|
\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR, which can be used to install
|
||||||
used to install event handlers.
|
event handlers.
|
||||||
The parameter \fIkey\fR is a symbol indicating for which event to install
|
The parameter \fIkey\fR is a symbol indicating for which event to install
|
||||||
the procedure \fIproc\fR.
|
the procedure \fIproc\fR.
|
||||||
The following keys are currently evailable:
|
The following keys are currently evailable:
|
||||||
|
|||||||
85
protocol/river-control-unstable-v1.xml
Normal file
85
protocol/river-control-unstable-v1.xml
Normal 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>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "output.h"
|
#include "output.h"
|
||||||
#include "seat.h"
|
#include "seat.h"
|
||||||
|
|
||||||
|
#include "river-control-unstable-v1.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ISO C forbids casting a function pointer to a void pointer because on some
|
* 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
|
* 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;
|
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)
|
static void *load_script_inner (void *data)
|
||||||
{
|
{
|
||||||
const char *path = (char *)data;
|
const char *path = (char *)data;
|
||||||
@@ -172,6 +240,7 @@ void *load_script (void *data)
|
|||||||
/* Note: All guile objects are garbage collected. */
|
/* Note: All guile objects are garbage collected. */
|
||||||
|
|
||||||
scm_c_define_gsubr_fix("install-handler", 2, 0, 0, install_handler);
|
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.
|
/* Continuation barrier causes stack unwind on exceptions to stop here.
|
||||||
* Otherwise the entire stack created by scm_with_guile() would be
|
* Otherwise the entire stack created by scm_with_guile() would be
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "river-layout-v3.h"
|
#include "river-layout-v3.h"
|
||||||
#include "ext-idle-notify-v1.h"
|
#include "ext-idle-notify-v1.h"
|
||||||
|
#include "river-control-unstable-v1.h"
|
||||||
|
|
||||||
#include "load-script.h"
|
#include "load-script.h"
|
||||||
#include "call-exit-handler.h"
|
#include "call-exit-handler.h"
|
||||||
@@ -35,6 +36,7 @@ struct Context context = {
|
|||||||
*/
|
*/
|
||||||
.layout_demand_handler = NULL,
|
.layout_demand_handler = NULL,
|
||||||
.user_command_handler = NULL,
|
.user_command_handler = NULL,
|
||||||
|
.exit_handler = NULL,
|
||||||
|
|
||||||
.loop = true,
|
.loop = true,
|
||||||
.ret = EXIT_SUCCESS,
|
.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
|
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 )
|
else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 )
|
||||||
{
|
{
|
||||||
context.idle_notifier = wl_registry_bind(
|
context.idle_notifier = wl_registry_bind(
|
||||||
@@ -380,6 +388,8 @@ int main(int argc, char *argv[])
|
|||||||
ext_idle_notifier_v1_destroy(context.idle_notifier);
|
ext_idle_notifier_v1_destroy(context.idle_notifier);
|
||||||
if ( context.layout_manager != NULL )
|
if ( context.layout_manager != NULL )
|
||||||
river_layout_manager_v3_destroy(context.layout_manager);
|
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 )
|
if ( context.sync_callback != NULL )
|
||||||
wl_callback_destroy(context.sync_callback);
|
wl_callback_destroy(context.sync_callback);
|
||||||
if ( context.wl_registry != NULL )
|
if ( context.wl_registry != NULL )
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ struct Context
|
|||||||
struct wl_callback *sync_callback;
|
struct wl_callback *sync_callback;
|
||||||
struct river_layout_manager_v3 *layout_manager;
|
struct river_layout_manager_v3 *layout_manager;
|
||||||
struct ext_idle_notifier_v1 *idle_notifier;
|
struct ext_idle_notifier_v1 *idle_notifier;
|
||||||
|
struct zriver_control_v1 *river_control;
|
||||||
|
|
||||||
struct wl_list seats;
|
struct wl_list seats;
|
||||||
struct wl_list outputs;
|
struct wl_list outputs;
|
||||||
|
|||||||
Reference in New Issue
Block a user