implement ext-idle-notify-v1

This commit is contained in:
Leon Henrik Plickat
2024-01-06 21:25:26 +01:00
parent 25fcd37930
commit 2ccbfb2283
16 changed files with 682 additions and 187 deletions

View File

@@ -14,11 +14,15 @@ LIBS=-lrt $\
$(shell pkg-config --libs wayland-client) $\ $(shell pkg-config --libs wayland-client) $\
$(shell guile-config link) $(shell guile-config link)
OBJ=src/riverguile.o $\ OBJ=src/riverguile.o $\
src/seat.o src/output.o $\
src/call-layout-demand-handler.o $\ src/call-layout-demand-handler.o $\
src/call-user-command-handler.o $\ src/call-user-command-handler.o $\
src/call-idle-handler.o $\
src/load-script.o $\ src/load-script.o $\
protocol/river-layout-v3.o protocol/river-layout-v3.o $\
GEN=protocol/river-layout-v3.c protocol/river-layout-v3.h protocol/ext-idle-notify-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
all: riverguile all: riverguile

View File

@@ -59,6 +59,25 @@ The command (string), the currently active tags (integer representing a
bitfield of size 32) and the global name of the output (integer). bitfield of size 32) and the global name of the output (integer).
This event can be used to change paramters of the layout. This event can be used to change paramters of the layout.
A new layout demand will be send right after this event. A new layout demand will be send right after this event.
.P
The key \fBidle:X\fR installs a handler which is called when the system has been
idle for \fIX\fR seconds.
As an example, use \fBidle:300\fR if you wish to be notified of your system
being idle for five minutes.
Idle in this case relates to user interaction;
The system being idle for five minutes usually means there has been no input
from the user in five minutes.
Idle status however can also be influenced by other factors, for example by
other programs inhibiting it (like video players), special sensors (user
presence sensors) and is generally server-specific policy.
The handler procedure must accept one argument, a symbol indicating the type
of idle event.
This symbol is either \fBidle\fR, indicating the system has been idle for the
configured amount of time, or \fBresume\fR, indicating that the time intervall
of the system being idle is over.
Multiple idle handlers can be installed.
Note: All idle events relate to the first advetised seat.
As of now, river only supports a single seat anyway.
. .
. .
.SH EXAMPLE .SH EXAMPLE

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_idle_notify_v1">
<copyright>
Copyright © 2015 Martin Gräßlin
Copyright © 2022 Simon Ser
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="ext_idle_notifier_v1" version="1">
<description summary="idle notification manager">
This interface allows clients to monitor user idle status.
After binding to this global, clients can create ext_idle_notification_v1
objects to get notified when the user is idle for a given amount of time.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the manager object. All objects created via this interface
remain valid.
</description>
</request>
<request name="get_idle_notification">
<description summary="create a notification object">
Create a new idle notification object.
The notification object has a minimum timeout duration and is tied to a
seat. The client will be notified if the seat is inactive for at least
the provided timeout. See ext_idle_notification_v1 for more details.
A zero timeout is valid and means the client wants to be notified as
soon as possible when the seat is inactive.
</description>
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
</interface>
<interface name="ext_idle_notification_v1" version="1">
<description summary="idle notification">
This interface is used by the compositor to send idle notification events
to clients.
Initially the notification object is not idle. The notification object
becomes idle when no user activity has happened for at least the timeout
duration, starting from the creation of the notification object. User
activity may include input events or a presence sensor, but is
compositor-specific. If an idle inhibitor is active (e.g. another client
has created a zwp_idle_inhibitor_v1 on a visible surface), the compositor
must not make the notification object idle.
When the notification object becomes idle, an idled event is sent. When
user activity starts again, the notification object stops being idle,
a resumed event is sent and the timeout is restarted.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the notification object">
Destroy the notification object.
</description>
</request>
<event name="idled">
<description summary="notification object is idle">
This event is sent when the notification object becomes idle.
It's a compositor protocol error to send this event twice without a
resumed event in-between.
</description>
</event>
<event name="resumed">
<description summary="notification object is no longer idle">
This event is sent when the notification object stops being idle.
It's a compositor protocol error to send this event twice without an
idled event in-between. It's a compositor protocol error to send this
event prior to any idled event.
</description>
</event>
</interface>
</protocol>

36
src/call-idle-handler.c Normal file
View File

@@ -0,0 +1,36 @@
#include <libguile.h>
#include "seat.h"
#include "call-idle-handler.h"
static void *call_idle_handler_inner (void *data)
{
struct Call_idle_handler_parameters *params =
(struct Call_idle_handler_parameters *)data;
SCM event;
switch (params->event)
{
case IDLE: event = scm_from_utf8_symbol("idle"); break;
case RESUME: event = scm_from_utf8_symbol("resume"); break;
}
return scm_call_1(params->idle->handler, event);
}
void *call_idle_handler (void *data)
{
/* Continuation barrier causes stack unwind on exceptions (i.e. errors
* in the user defined ide 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_idle_handler_inner, data
);
if ( call_result == NULL )
return (void *)"ERROR: An exception occured while calling the user-command handler.\n";
return NULL;
}

20
src/call-idle-handler.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef RIVERGUILE_CALL_IDLE_HANDLER_H
#define RIVERGUILE_CALL_IDLE_HANDLER_H
#include "seat.h"
enum Idle_handler_event
{
IDLE,
RESUME,
};
struct Call_idle_handler_parameters
{
struct Idle *idle;
enum Idle_handler_event event;
};
void *call_idle_handler (void *data);
#endif

View File

@@ -1,17 +1,16 @@
#include <assert.h> #include <assert.h>
#include <libguile.h> #include <libguile.h>
#include "riverguile.h"
#include "output.h"
#include "river-layout-v3.h" #include "river-layout-v3.h"
#include "call-layout-demand-handler.h" #include "call-layout-demand-handler.h"
extern SCM layout_demand_handler;
static void *call_layout_demand_handler_inner (void *data) static void *call_layout_demand_handler_inner (void *data)
{ {
struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data; struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data;
return scm_call_5( return scm_call_5(
layout_demand_handler, context.layout_demand_handler,
scm_from_uint32(params->view_count), scm_from_uint32(params->view_count),
scm_from_uint32(params->width), scm_from_uint32(params->width),
scm_from_uint32(params->height), scm_from_uint32(params->height),
@@ -24,8 +23,8 @@ void *call_layout_demand_handler (void *data)
{ {
struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data; struct Call_layout_demand_handler_parameters *params = (struct Call_layout_demand_handler_parameters *)data;
assert(layout_demand_handler != NULL); assert(context.layout_demand_handler != NULL);
assert(scm_is_true(scm_procedure_p(layout_demand_handler)) == 1); assert(scm_is_true(scm_procedure_p(context.layout_demand_handler)) == 1);
/* Continuation barrier causes stack unwind on exceptions (i.e. errors /* Continuation barrier causes stack unwind on exceptions (i.e. errors
* in the user defined layout demand handler) to stop here. Otherwise * in the user defined layout demand handler) to stop here. Otherwise

View File

@@ -3,8 +3,6 @@
#include <libguile.h> #include <libguile.h>
#include "types.h"
struct Call_layout_demand_handler_parameters struct Call_layout_demand_handler_parameters
{ {
uint32_t view_count, width, height, serial, tags; uint32_t view_count, width, height, serial, tags;

View File

@@ -1,16 +1,15 @@
#include <assert.h> #include <assert.h>
#include <libguile.h> #include <libguile.h>
#include "types.h" #include "riverguile.h"
#include "output.h"
#include "call-user-command-handler.h" #include "call-user-command-handler.h"
extern SCM user_command_handler;
static void *call_user_command_handler_inner (void *data) static void *call_user_command_handler_inner (void *data)
{ {
struct Call_user_command_parameters *params = (struct Call_user_command_parameters *)data; struct Call_user_command_parameters *params = (struct Call_user_command_parameters *)data;
return scm_call_3( return scm_call_3(
user_command_handler, context.user_command_handler,
scm_from_utf8_string(params->cmd), scm_from_utf8_string(params->cmd),
scm_from_uint32(params->output->user_command_tags), scm_from_uint32(params->output->user_command_tags),
scm_from_uint32(params->output->name) scm_from_uint32(params->output->name)
@@ -19,8 +18,8 @@ static void *call_user_command_handler_inner (void *data)
void *call_user_command_handler (void *data) void *call_user_command_handler (void *data)
{ {
assert(user_command_handler != NULL); assert(context.user_command_handler != NULL);
assert(scm_is_true(scm_procedure_p(user_command_handler)) == 1); assert(scm_is_true(scm_procedure_p(context.user_command_handler)) == 1);
/* Continuation barrier causes stack unwind on exceptions (i.e. errors /* Continuation barrier causes stack unwind on exceptions (i.e. errors
* in the user defined user-command handler) to stop here. Otherwise * in the user defined user-command handler) to stop here. Otherwise

View File

@@ -1,7 +1,7 @@
#ifndef RIVERGUILE_CALL_USER_COMMAND_HANDLER_H #ifndef RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
#define RIVERGUILE_CALL_USER_COMMAND_HANDLER_H #define RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
#include "types.h" #include "output.h"
struct Call_user_command_parameters struct Call_user_command_parameters
{ {

View File

@@ -1,6 +1,11 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h>
#include <libguile.h> #include <libguile.h>
#include <string.h>
#include "riverguile.h"
#include "seat.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
@@ -12,8 +17,35 @@
#define scm_c_define_gsubr_fix(NAME, REQ, OPT, RST, FN) \ #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); } { const long int ptr = (long int)FN; scm_c_define_gsubr(NAME, REQ, OPT, RST, (void *)ptr); }
extern SCM layout_demand_handler; static uint32_t extract_ms_from_idle_key (SCM key)
extern SCM user_command_handler; {
SCM key_split = scm_string_split(
scm_symbol_to_string(key),
scm_to_char_set(scm_from_utf8_string(":"))
);
if ( scm_to_uint32(scm_length(key_split)) != 2 )
goto error;
SCM maybe_number = scm_string_to_number(scm_cadr(key_split), scm_from_int(10));
if ( scm_is_false(maybe_number) == 1 )
goto error;
int32_t ms = scm_to_int32(maybe_number);
if ( ms < 0 )
goto error;
return (uint32_t)ms * 1000;
error:
scm_error_scm(
scm_from_utf8_symbol("wrong-type-arg"),
scm_from_utf8_string("install-handler"),
scm_from_utf8_string("First argument: Excpected 'idle:<positive integer>."),
SCM_BOOL_F,
scm_list_1(key)
);
return 0;
}
static SCM install_handler (SCM key, SCM proc) static SCM install_handler (SCM key, SCM proc)
{ {
@@ -42,10 +74,33 @@ static SCM install_handler (SCM key, SCM proc)
return SCM_UNSPECIFIED; return SCM_UNSPECIFIED;
} }
if (scm_is_eq(scm_from_utf8_symbol("layout-demand"), key)) if ( scm_is_eq(scm_from_utf8_symbol("layout-demand"), key) == 1 )
layout_demand_handler = proc; context.layout_demand_handler = proc;
else if (scm_is_eq(scm_from_utf8_symbol("user-command"), key)) else if ( scm_is_eq(scm_from_utf8_symbol("user-command"), key) == 1)
user_command_handler = proc; context.user_command_handler = proc;
else if ( scm_is_true(scm_string_prefix_p(scm_from_utf8_string("idle:"), scm_symbol_to_string(key),
scm_from_int(0), scm_from_int(5),
scm_from_int(0), scm_string_length(scm_symbol_to_string(key)))) == 1 )
{
uint32_t ms = extract_ms_from_idle_key(key);
struct Idle *idle = calloc(1, sizeof(struct Idle));
if ( idle == NULL )
{
scm_error_scm(
scm_from_utf8_symbol("memory-allocation-error"),
scm_from_utf8_string("install-handler"),
SCM_BOOL_F,
SCM_BOOL_F,
SCM_BOOL_F
);
return SCM_UNSPECIFIED;
}
idle->ms = ms;
idle->handler = proc;
wl_list_insert(&context.unconfigured_idles, &idle->link);
}
else else
{ {
scm_error_scm( scm_error_scm(
@@ -91,13 +146,13 @@ void *load_script (void *data)
if ( call_result == NULL ) if ( call_result == NULL )
return (void *)"ERROR: Fatal error while loading layout script.\n"; return (void *)"ERROR: Fatal error while loading layout script.\n";
if ( layout_demand_handler == NULL ) if ( context.layout_demand_handler == NULL )
return (void *)"ERROR: No layout demand handler installed.\n"; return (void *)"ERROR: No layout demand handler installed.\n";
/* Checked in the installer functions. */ /* Checked in the installer functions. */
assert(scm_is_true(scm_procedure_p(layout_demand_handler)) == 1); assert(scm_is_true(scm_procedure_p(context.layout_demand_handler)) == 1);
if ( user_command_handler != NULL ) if ( context.user_command_handler != NULL )
assert(scm_is_true(scm_procedure_p(user_command_handler)) == 1); assert(scm_is_true(scm_procedure_p(context.user_command_handler)) == 1);
return NULL; return NULL;
} }

121
src/output.c Normal file
View File

@@ -0,0 +1,121 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libguile.h>
#include <assert.h>
#include "river-layout-v3.h"
#include "riverguile.h"
#include "output.h"
#include "call-layout-demand-handler.h"
#include "call-user-command-handler.h"
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 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);
fputs("INFO: This error is not fatal.\n", stderr);
return;
}
// 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 ( context.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);
context.ret = EXIT_FAILURE;
context.loop = false;
}
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,
.user_command_tags = layout_handle_user_command_tags,
};
void output_configure (struct Output *output)
{
if ( context.layout_manager == NULL )
return;
if (output->configured)
return;
output->layout = river_layout_manager_v3_get_layout(
context.layout_manager, output->wl_output, "riverguile"
);
river_layout_v3_add_listener(output->layout, &layout_listener, output);
output->configured = true;
}
struct Output *output_create (struct wl_output *wl_output, uint32_t name)
{
struct Output *output = calloc(1, sizeof(struct Output));
if ( output == NULL )
{
fprintf(stderr, "ERROR: calloc: %s\n", strerror(errno));
return NULL;
}
output->name = name;
output->wl_output = wl_output;
output_configure(output);
return output;
}
void output_destroy (struct Output *output)
{
if ( output->layout != NULL )
river_layout_v3_destroy(output->layout);
assert(output->wl_output != NULL);
wl_output_destroy(output->wl_output);
free(output);
}

View File

@@ -1,5 +1,5 @@
#ifndef RIVERGUILE_TYPES_H_INCLUDED #ifndef RIVERGUILE_OUTPUT_H
#define RIVERGUILE_TYPES_H_INCLUDED #define RIVERGUILE_OUTPUT_H
#include <stdbool.h> #include <stdbool.h>
#include <wayland-client.h> #include <wayland-client.h>
@@ -22,4 +22,8 @@ struct Output
uint32_t user_command_tags; uint32_t user_command_tags;
}; };
struct Output *output_create (struct wl_output *wl_output, uint32_t name);
void output_destroy (struct Output *output);
void output_configure (struct Output *output);
#endif #endif

View File

@@ -19,168 +19,80 @@
#endif #endif
#include "river-layout-v3.h" #include "river-layout-v3.h"
#include "ext-idle-notify-v1.h"
#include "types.h"
#include "call-layout-demand-handler.h"
#include "call-user-command-handler.h"
#include "load-script.h" #include "load-script.h"
#include "riverguile.h"
#include "output.h"
#include "seat.h"
bool loop = true; struct Context context = {
int ret = EXIT_SUCCESS; /* Handlers are initially NULL instead of SCM_EOL because I am not sure whether
jmp_buf skip_main_loop;
struct wl_display *wl_display = NULL;
struct wl_registry *wl_registry = NULL;
struct wl_callback *sync_callback = NULL;
struct river_layout_manager_v3 *layout_manager = NULL;
struct wl_list outputs;
/* 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 * 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 * 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. * to never call any scm_* function on these while they are NULL.
*/ */
SCM layout_demand_handler = NULL; .layout_demand_handler = NULL,
SCM user_command_handler = NULL; .user_command_handler = NULL,
static void layout_handle_layout_demand (void *data, struct river_layout_v3 *river_layout_v3, .loop = true,
uint32_t view_count, uint32_t width, uint32_t height, uint32_t tags, uint32_t serial) .ret = EXIT_SUCCESS,
{
struct Output *output = (struct Output *)data;
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);
fputs("INFO: This error is not fatal.\n", stderr);
return;
}
// 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);
ret = EXIT_FAILURE;
loop = false;
}
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,
.user_command_tags = layout_handle_user_command_tags,
}; };
static void output_configure (struct Output *output) jmp_buf skip_main_loop;
{
if ( layout_manager == NULL )
return;
if (output->configured)
return;
output->layout = river_layout_manager_v3_get_layout(
layout_manager, output->wl_output, "riverguile"
);
river_layout_v3_add_listener(output->layout, &layout_listener, output);
output->configured = true;
}
static struct Output *output_create (struct wl_output *wl_output)
{
struct Output *output = calloc(1, sizeof(struct Output));
if ( output == NULL )
{
fprintf(stderr, "ERROR: calloc: %s\n", strerror(errno));
return NULL;
}
output->wl_output = wl_output;
output_configure(output);
return output;
}
static void output_destroy (struct Output *output)
{
if ( output->layout != NULL )
river_layout_v3_destroy(output->layout);
assert(output->wl_output != NULL);
wl_output_destroy(output->wl_output);
free(output);
}
static void registry_handle_global (void *data, struct wl_registry *registry, static void registry_handle_global (void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) uint32_t name, const char *interface, uint32_t version)
{ {
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( context.layout_manager = wl_registry_bind(
registry, name, &river_layout_manager_v3_interface, 2 registry, name, &river_layout_manager_v3_interface, 2
); );
} }
else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 )
{
context.idle_notifier = wl_registry_bind(
registry, name, &ext_idle_notifier_v1_interface, 1
);
}
else if ( strcmp(interface, wl_output_interface.name) == 0 ) else if ( strcmp(interface, wl_output_interface.name) == 0 )
{ {
struct wl_output *wl_output = wl_registry_bind( struct wl_output *wl_output = wl_registry_bind(
registry, name, &wl_output_interface, 1 registry, name, &wl_output_interface, 1
); );
struct Output *output = output_create(wl_output); struct Output *output = output_create(wl_output, name);
if ( output == NULL ) if ( output == NULL )
{ {
wl_output_destroy(wl_output); wl_output_destroy(wl_output);
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
loop = false; context.loop = false;
return; return;
} }
wl_list_insert(&outputs, &output->link); wl_list_insert(&context.outputs, &output->link);
}
else if ( strcmp(interface, wl_seat_interface.name) == 0 )
{
struct wl_seat *wl_seat = wl_registry_bind(
registry, name, &wl_seat_interface, 1
);
struct Seat *seat = seat_create(wl_seat, name);
if ( seat == NULL )
{
wl_seat_destroy(wl_seat);
context.ret = EXIT_FAILURE;
context.loop = false;
return;
}
wl_list_insert(&context.seats, &seat->link);
} }
} }
static void registry_handle_global_remove (void *data, struct wl_registry *registry, static void registry_handle_global_remove (void *data, struct wl_registry *registry,
uint32_t name) uint32_t name)
{ {
struct Output *output, *tmp; struct Output *output, *tmp_o;
wl_list_for_each_safe(output, tmp, &outputs, link) wl_list_for_each_safe(output, tmp_o, &context.outputs, link)
{ {
if ( output->name != name ) if ( output->name != name )
continue; continue;
@@ -188,6 +100,16 @@ static void registry_handle_global_remove (void *data, struct wl_registry *regis
output_destroy(output); output_destroy(output);
return; return;
} }
struct Seat *seat, *tmp_s;
wl_list_for_each_safe(seat, tmp_s, &context.seats, link)
{
if ( seat->name != name )
continue;
wl_list_remove(&seat->link);
seat_destroy(seat);
return;
}
} }
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {
@@ -198,22 +120,41 @@ static const struct wl_registry_listener registry_listener = {
static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data) static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data)
{ {
wl_callback_destroy(wl_callback); wl_callback_destroy(wl_callback);
sync_callback = NULL; context.sync_callback = NULL;
if ( layout_manager == NULL ) if ( context.layout_manager == NULL )
{ {
fputs("ERROR: Wayland server does not support river-layout-v3.\n", stderr); fputs("ERROR: Wayland server does not support river-layout-v3.\n", stderr);
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
loop = false; context.loop = false;
return; return;
} }
if ( context.idle_notifier == NULL )
{
if ( wl_list_length(&context.unconfigured_idles) > 0 )
{
fputs("ERROR: Wayland server does not support ext-idle-notify-v1.\n", stderr);
fputs("INFO: This error is not fatal.\n", stderr);
}
}
else
{
struct Seat *seat;
wl_list_for_each(seat, &context.seats, link)
break;
struct Idle *idle, *tmp_i;
wl_list_for_each_safe(idle, tmp_i, &context.unconfigured_idles, link)
idle_configure(idle, seat);
}
/* If outputs were registered before the river_layout_manager is /* If outputs were registered before the river_layout_manager is
* available, they won't have a river_layout, so we need to create * available, they won't have a river_layout, so we need to create
* those here. * those here.
*/ */
struct Output *output; struct Output *output;
wl_list_for_each(output, &outputs, link) wl_list_for_each(output, &context.outputs, link)
output_configure(output); output_configure(output);
} }
@@ -224,7 +165,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; context.loop = false;
longjmp(skip_main_loop, 1); longjmp(skip_main_loop, 1);
} }
@@ -353,13 +294,16 @@ int main(int argc, char *argv[])
signal(SIGSEGV, handle_error); signal(SIGSEGV, handle_error);
signal(SIGFPE, handle_error); signal(SIGFPE, handle_error);
signal(SIGINT, handle_interrupt); signal(SIGINT, handle_interrupt);
wl_list_init(&outputs);
wl_list_init(&context.outputs);
wl_list_init(&context.seats);
wl_list_init(&context.unconfigured_idles);
// TODO use argv[1] if present // TODO use argv[1] if present
char *path = get_script_path(); char *path = get_script_path();
if ( path == NULL ) if ( path == NULL )
{ {
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
goto early_exit; goto early_exit;
} }
@@ -367,8 +311,8 @@ int main(int argc, char *argv[])
if ( res != NULL ) if ( res != NULL )
{ {
fputs((char *)res, stderr); fputs((char *)res, stderr);
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
loop = false; context.loop = false;
goto early_exit; goto early_exit;
} }
@@ -381,44 +325,62 @@ int main(int argc, char *argv[])
if ( display_name == NULL ) if ( display_name == NULL )
{ {
fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr); fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr);
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
goto early_exit; goto early_exit;
} }
wl_display = wl_display_connect(display_name); context.wl_display = wl_display_connect(display_name);
if ( wl_display == NULL ) if ( context.wl_display == NULL )
{ {
fputs("ERROR: Can not connect to wayland display.\n", stderr); fputs("ERROR: Can not connect to wayland display.\n", stderr);
ret = EXIT_FAILURE; context.ret = EXIT_FAILURE;
goto early_exit; goto early_exit;
} }
wl_registry = wl_display_get_registry(wl_display); context.wl_registry = wl_display_get_registry(context.wl_display);
wl_registry_add_listener(wl_registry, &registry_listener, NULL); wl_registry_add_listener(context.wl_registry, &registry_listener, NULL);
sync_callback = wl_display_sync(wl_display); context.sync_callback = wl_display_sync(context.wl_display);
wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL); wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL);
if ( setjmp(skip_main_loop) == 0 ) if ( setjmp(skip_main_loop) == 0 )
while ( loop && wl_display_dispatch(wl_display) > 0 ); while ( context.loop && wl_display_dispatch(context.wl_display) > 0 );
struct Output *output, *tmp; struct Idle *idle, *tmp_i;
wl_list_for_each_safe(output, tmp, &outputs, link) wl_list_for_each_safe(idle, tmp_i, &context.unconfigured_idles, link)
{
wl_list_remove(&idle->link);
idle_destroy(idle);
}
struct Output *output, *tmp_o;
wl_list_for_each_safe(output, tmp_o, &context.outputs, link)
{ {
wl_list_remove(&output->link); wl_list_remove(&output->link);
output_destroy(output); output_destroy(output);
} }
if ( sync_callback != NULL ) struct Seat *seat, *tmp_s;
wl_callback_destroy(sync_callback); wl_list_for_each_safe(seat, tmp_s, &context.seats, link)
if ( wl_registry != NULL ) {
wl_registry_destroy(wl_registry); wl_list_remove(&seat->link);
wl_display_disconnect(wl_display); seat_destroy(seat);
}
if ( context.idle_notifier != NULL )
ext_idle_notifier_v1_destroy(context.idle_notifier);
if ( context.layout_manager != NULL )
river_layout_manager_v3_destroy(context.layout_manager);
if ( context.sync_callback != NULL )
wl_callback_destroy(context.sync_callback);
if ( context.wl_registry != NULL )
wl_registry_destroy(context.wl_registry);
wl_display_disconnect(context.wl_display);
early_exit: early_exit:
if ( path != NULL ) if ( path != NULL )
free(path); free(path);
return ret; return context.ret;
} }

33
src/riverguile.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef RIVERGUILE_RIVERGUILE_H
#define RIVERGUILE_RIVERGUILE_H
#include <stdbool.h>
#include <wayland-client.h>
#include <libguile.h>
#include "seat.h"
struct Context
{
SCM layout_demand_handler;
SCM user_command_handler;
bool loop;
int ret;
struct wl_display *wl_display;
struct wl_registry *wl_registry;
struct wl_callback *sync_callback;
struct river_layout_manager_v3 *layout_manager;
struct ext_idle_notifier_v1 *idle_notifier;
struct wl_list seats;
struct wl_list outputs;
/* Idles are created before we connect to the server and bind seats. */
struct wl_list unconfigured_idles;
};
extern struct Context context;
#endif

112
src/seat.c Normal file
View File

@@ -0,0 +1,112 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "ext-idle-notify-v1.h"
#include "riverguile.h"
#include "seat.h"
#include "call-idle-handler.h"
static void idle_notification_handle_idled (void *data,
struct ext_idle_notification_v1 *ext_idle_notification_v1)
{
struct Idle *idle = (struct Idle *)data;
assert(idle->handler != NULL);
struct Call_idle_handler_parameters params = {
.idle = idle,
.event = IDLE,
};
call_idle_handler(&params);
}
static void idle_notification_handle_resumed (void *data,
struct ext_idle_notification_v1 *ext_idle_notification_v1)
{
struct Idle *idle = (struct Idle *)data;
assert(idle->handler != NULL);
struct Call_idle_handler_parameters params = {
.idle = idle,
.event = RESUME,
};
call_idle_handler(&params);
}
static const struct ext_idle_notification_v1_listener idle_notification_listener = {
.idled = idle_notification_handle_idled,
.resumed = idle_notification_handle_resumed,
};
void idle_configure (struct Idle *idle, struct Seat *seat)
{
assert(idle->seat == NULL);
idle->idle_notification = ext_idle_notifier_v1_get_idle_notification(
context.idle_notifier,
idle->ms,
seat->wl_seat
);
ext_idle_notification_v1_add_listener(
idle->idle_notification,
&idle_notification_listener,
idle
);
wl_list_remove(&idle->link);
wl_list_insert(&seat->idles, &idle->link);
idle->seat = seat;
}
void idle_destroy (struct Idle *idle)
{
// TODO XXX how do we tell guile it is allowed to clean up the handler?
// maybe by entering guile mode one more time at exit?
// we could also define an 'exit handler
if ( idle->idle_notification != NULL )
{
assert(idle->seat != NULL);
ext_idle_notification_v1_destroy(idle->idle_notification);
}
else
{
assert(idle->seat == NULL);
}
free(idle);
}
struct Seat *seat_create (struct wl_seat *wl_seat, uint32_t name)
{
struct Seat *seat = calloc(1, sizeof(struct Seat));
if ( seat == NULL )
{
fprintf(stderr, "ERROR: calloc: %s\n", strerror(errno));
return NULL;
}
seat->name = name;
seat->wl_seat = wl_seat;
wl_list_init(&seat->idles);
return seat;
}
void seat_destroy (struct Seat *seat)
{
struct Idle *idle, *tmp_i;
wl_list_for_each_safe(idle, tmp_i, &seat->idles, link)
{
wl_list_remove(&idle->link);
idle_destroy(idle);
}
assert(seat->wl_seat != NULL);
wl_seat_destroy(seat->wl_seat);
free(seat);
}

31
src/seat.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef RIVERGUILE_SEAT_H
#define RIVERGUILE_SEAT_H
#include <wayland-client.h>
#include <libguile.h>
struct Seat
{
struct wl_list link;
struct wl_seat *wl_seat;
uint32_t name;
struct wl_list idles;
};
struct Idle
{
struct Seat *seat;
struct wl_list link;
struct ext_idle_notification_v1 *idle_notification;
SCM handler;
uint32_t ms;
};
struct Seat *seat_create (struct wl_seat *wl_seat, uint32_t name);
void seat_destroy (struct Seat *seat);
void idle_configure (struct Idle *idle, struct Seat *seat);
void idle_destroy (struct Idle *idle);
#endif