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