diff --git a/Makefile b/Makefile
index 45b4148..0accb45 100644
--- a/Makefile
+++ b/Makefile
@@ -14,11 +14,15 @@ LIBS=-lrt $\
$(shell pkg-config --libs wayland-client) $\
$(shell guile-config link)
OBJ=src/riverguile.o $\
+ src/seat.o src/output.o $\
src/call-layout-demand-handler.o $\
src/call-user-command-handler.o $\
+ src/call-idle-handler.o $\
src/load-script.o $\
- protocol/river-layout-v3.o
-GEN=protocol/river-layout-v3.c protocol/river-layout-v3.h
+ protocol/river-layout-v3.o $\
+ 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
diff --git a/doc/riverguile.1 b/doc/riverguile.1
index 842d0c3..a9bba37 100644
--- a/doc/riverguile.1
+++ b/doc/riverguile.1
@@ -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).
This event can be used to change paramters of the layout.
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
diff --git a/protocol/ext-idle-notify-v1.xml b/protocol/ext-idle-notify-v1.xml
new file mode 100644
index 0000000..6fe97d7
--- /dev/null
+++ b/protocol/ext-idle-notify-v1.xml
@@ -0,0 +1,102 @@
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ Destroy the manager object. All objects created via this interface
+ remain valid.
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+ Destroy the notification object.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
diff --git a/src/call-idle-handler.c b/src/call-idle-handler.c
new file mode 100644
index 0000000..7dbcc11
--- /dev/null
+++ b/src/call-idle-handler.c
@@ -0,0 +1,36 @@
+#include
+
+#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;
+}
diff --git a/src/call-idle-handler.h b/src/call-idle-handler.h
new file mode 100644
index 0000000..20822a4
--- /dev/null
+++ b/src/call-idle-handler.h
@@ -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
diff --git a/src/call-layout-demand-handler.c b/src/call-layout-demand-handler.c
index fd9ac3a..3e5bcf7 100644
--- a/src/call-layout-demand-handler.c
+++ b/src/call-layout-demand-handler.c
@@ -1,17 +1,16 @@
#include
#include
+#include "riverguile.h"
+#include "output.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,
+ context.layout_demand_handler,
scm_from_uint32(params->view_count),
scm_from_uint32(params->width),
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;
- assert(layout_demand_handler != NULL);
- assert(scm_is_true(scm_procedure_p(layout_demand_handler)) == 1);
+ assert(context.layout_demand_handler != NULL);
+ assert(scm_is_true(scm_procedure_p(context.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
diff --git a/src/call-layout-demand-handler.h b/src/call-layout-demand-handler.h
index 53ed063..5710e00 100644
--- a/src/call-layout-demand-handler.h
+++ b/src/call-layout-demand-handler.h
@@ -3,8 +3,6 @@
#include
-#include "types.h"
-
struct Call_layout_demand_handler_parameters
{
uint32_t view_count, width, height, serial, tags;
diff --git a/src/call-user-command-handler.c b/src/call-user-command-handler.c
index e3abf5b..50f1dd3 100644
--- a/src/call-user-command-handler.c
+++ b/src/call-user-command-handler.c
@@ -1,16 +1,15 @@
#include
#include
-#include "types.h"
+#include "riverguile.h"
+#include "output.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,
+ context.user_command_handler,
scm_from_utf8_string(params->cmd),
scm_from_uint32(params->output->user_command_tags),
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)
{
- assert(user_command_handler != NULL);
- assert(scm_is_true(scm_procedure_p(user_command_handler)) == 1);
+ assert(context.user_command_handler != NULL);
+ assert(scm_is_true(scm_procedure_p(context.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
diff --git a/src/call-user-command-handler.h b/src/call-user-command-handler.h
index 5c7d24f..1527cca 100644
--- a/src/call-user-command-handler.h
+++ b/src/call-user-command-handler.h
@@ -1,7 +1,7 @@
#ifndef RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
#define RIVERGUILE_CALL_USER_COMMAND_HANDLER_H
-#include "types.h"
+#include "output.h"
struct Call_user_command_parameters
{
diff --git a/src/load-script.c b/src/load-script.c
index bc90a9b..1fa8b33 100644
--- a/src/load-script.c
+++ b/src/load-script.c
@@ -1,6 +1,11 @@
#include
#include
+#include
#include
+#include
+
+#include "riverguile.h"
+#include "seat.h"
/**
* 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) \
{ 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 uint32_t extract_ms_from_idle_key (SCM key)
+{
+ 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:."),
+ SCM_BOOL_F,
+ scm_list_1(key)
+ );
+ return 0;
+}
static SCM install_handler (SCM key, SCM proc)
{
@@ -42,10 +74,33 @@ static SCM install_handler (SCM key, SCM 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;
+ if ( scm_is_eq(scm_from_utf8_symbol("layout-demand"), key) == 1 )
+ context.layout_demand_handler = proc;
+ else if ( scm_is_eq(scm_from_utf8_symbol("user-command"), key) == 1)
+ 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
{
scm_error_scm(
@@ -91,13 +146,13 @@ void *load_script (void *data)
if ( call_result == NULL )
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";
/* 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);
+ assert(scm_is_true(scm_procedure_p(context.layout_demand_handler)) == 1);
+ if ( context.user_command_handler != NULL )
+ assert(scm_is_true(scm_procedure_p(context.user_command_handler)) == 1);
return NULL;
}
diff --git a/src/output.c b/src/output.c
new file mode 100644
index 0000000..100fd49
--- /dev/null
+++ b/src/output.c
@@ -0,0 +1,121 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 *)¶ms);
+ 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 *)¶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)
+{
+ 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);
+}
+
diff --git a/src/types.h b/src/output.h
similarity index 69%
rename from src/types.h
rename to src/output.h
index d3880d2..d4c804c 100644
--- a/src/types.h
+++ b/src/output.h
@@ -1,5 +1,5 @@
-#ifndef RIVERGUILE_TYPES_H_INCLUDED
-#define RIVERGUILE_TYPES_H_INCLUDED
+#ifndef RIVERGUILE_OUTPUT_H
+#define RIVERGUILE_OUTPUT_H
#include
#include
@@ -22,4 +22,8 @@ struct Output
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
diff --git a/src/riverguile.c b/src/riverguile.c
index 9ff852b..37a2c05 100644
--- a/src/riverguile.c
+++ b/src/riverguile.c
@@ -19,168 +19,80 @@
#endif
#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 "riverguile.h"
+#include "output.h"
+#include "seat.h"
-bool loop = true;
-int ret = EXIT_SUCCESS;
+struct Context context = {
+ /* 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
+ * 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.
+ */
+ .layout_demand_handler = NULL,
+ .user_command_handler = NULL,
-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
- * 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.
- */
-SCM layout_demand_handler = NULL;
-SCM user_command_handler = NULL;
-
-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 *)¶ms);
- 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 *)¶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)
-{
- 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,
+ .loop = true,
+ .ret = EXIT_SUCCESS,
};
-static void output_configure (struct Output *output)
-{
- 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);
-}
+jmp_buf skip_main_loop;
static void registry_handle_global (void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
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
);
}
+ 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 )
{
struct wl_output *wl_output = wl_registry_bind(
registry, name, &wl_output_interface, 1
);
- struct Output *output = output_create(wl_output);
+ struct Output *output = output_create(wl_output, name);
if ( output == NULL )
{
wl_output_destroy(wl_output);
- ret = EXIT_FAILURE;
- loop = false;
+ context.ret = EXIT_FAILURE;
+ context.loop = false;
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,
uint32_t name)
{
- struct Output *output, *tmp;
- wl_list_for_each_safe(output, tmp, &outputs, link)
+ struct Output *output, *tmp_o;
+ wl_list_for_each_safe(output, tmp_o, &context.outputs, link)
{
if ( output->name != name )
continue;
@@ -188,6 +100,16 @@ static void registry_handle_global_remove (void *data, struct wl_registry *regis
output_destroy(output);
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 = {
@@ -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)
{
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);
- ret = EXIT_FAILURE;
- loop = false;
+ context.ret = EXIT_FAILURE;
+ context.loop = false;
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
* available, they won't have a river_layout, so we need to create
* those here.
*/
struct Output *output;
- wl_list_for_each(output, &outputs, link)
+ wl_list_for_each(output, &context.outputs, link)
output_configure(output);
}
@@ -224,7 +165,7 @@ static const struct wl_callback_listener sync_callback_listener = {
static void handle_interrupt (int signum)
{
fputs("Killed 💀\n", stderr);
- loop = false;
+ context.loop = false;
longjmp(skip_main_loop, 1);
}
@@ -353,13 +294,16 @@ int main(int argc, char *argv[])
signal(SIGSEGV, handle_error);
signal(SIGFPE, handle_error);
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
char *path = get_script_path();
if ( path == NULL )
{
- ret = EXIT_FAILURE;
+ context.ret = EXIT_FAILURE;
goto early_exit;
}
@@ -367,8 +311,8 @@ int main(int argc, char *argv[])
if ( res != NULL )
{
fputs((char *)res, stderr);
- ret = EXIT_FAILURE;
- loop = false;
+ context.ret = EXIT_FAILURE;
+ context.loop = false;
goto early_exit;
}
@@ -381,44 +325,62 @@ int main(int argc, char *argv[])
if ( display_name == NULL )
{
fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr);
- ret = EXIT_FAILURE;
+ context.ret = EXIT_FAILURE;
goto early_exit;
}
- wl_display = wl_display_connect(display_name);
- if ( wl_display == NULL )
+ context.wl_display = wl_display_connect(display_name);
+ if ( context.wl_display == NULL )
{
fputs("ERROR: Can not connect to wayland display.\n", stderr);
- ret = EXIT_FAILURE;
+ context.ret = EXIT_FAILURE;
goto early_exit;
}
- wl_registry = wl_display_get_registry(wl_display);
- wl_registry_add_listener(wl_registry, ®istry_listener, NULL);
+ context.wl_registry = wl_display_get_registry(context.wl_display);
+ wl_registry_add_listener(context.wl_registry, ®istry_listener, NULL);
- sync_callback = wl_display_sync(wl_display);
- wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL);
+ context.sync_callback = wl_display_sync(context.wl_display);
+ wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL);
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;
- wl_list_for_each_safe(output, tmp, &outputs, link)
+ struct Idle *idle, *tmp_i;
+ 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);
output_destroy(output);
}
- if ( sync_callback != NULL )
- wl_callback_destroy(sync_callback);
- if ( wl_registry != NULL )
- wl_registry_destroy(wl_registry);
- wl_display_disconnect(wl_display);
+ struct Seat *seat, *tmp_s;
+ wl_list_for_each_safe(seat, tmp_s, &context.seats, link)
+ {
+ wl_list_remove(&seat->link);
+ 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:
if ( path != NULL )
free(path);
- return ret;
+ return context.ret;
}
diff --git a/src/riverguile.h b/src/riverguile.h
new file mode 100644
index 0000000..24b67e8
--- /dev/null
+++ b/src/riverguile.h
@@ -0,0 +1,33 @@
+#ifndef RIVERGUILE_RIVERGUILE_H
+#define RIVERGUILE_RIVERGUILE_H
+
+#include
+#include
+#include
+
+#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
diff --git a/src/seat.c b/src/seat.c
new file mode 100644
index 0000000..b779dea
--- /dev/null
+++ b/src/seat.c
@@ -0,0 +1,112 @@
+#include
+#include
+#include
+#include
+#include
+
+#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(¶ms);
+}
+
+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(¶ms);
+}
+
+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);
+}
+
diff --git a/src/seat.h b/src/seat.h
new file mode 100644
index 0000000..dfe2172
--- /dev/null
+++ b/src/seat.h
@@ -0,0 +1,31 @@
+#ifndef RIVERGUILE_SEAT_H
+#define RIVERGUILE_SEAT_H
+
+#include
+#include
+
+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