From 153b5e0858f690e1d1a913f04768b627809204f9 Mon Sep 17 00:00:00 2001 From: Leon Henrik Plickat Date: Sun, 7 Jan 2024 06:24:23 +0100 Subject: [PATCH] load script after initial registry burst This makes the logic of installing idle handlers and layout handlers a bit cleaner and allows us to send wayland request from the script in the future. --- README.md | 16 +++- doc/riverguile.1 | 204 +++++++++++++++++++++++++++------------- src/call-exit-handler.c | 2 +- src/call-exit-handler.h | 2 +- src/load-script.c | 56 +++++++++-- src/output.c | 20 ++-- src/output.h | 3 +- src/riverguile.c | 104 ++++++++++---------- src/riverguile.h | 17 +++- src/seat.c | 27 ++++-- src/seat.h | 5 +- 11 files changed, 304 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 8c1c32a..ad3c7b0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # riverguile -Layout generator for the [river](https://github.com/riverwm/river) Wayland -server which uses [Guile Scheme](https://www.gnu.org/software/guile/) for -layouts. Its layout namespace is `riverguile`. +Scripting layer for the [river](https://github.com/riverwm/river) Wayland +server using [Guile Scheme](https://www.gnu.org/software/guile/). + +Send commands to river and install handlers for various events, inclusing +layout events. Uppon launch, riverguile tries to load a scheme script and checks the following paths for it in the given order: @@ -17,9 +19,15 @@ is available, which can be used to install handlers to certain events. Check the man page for more information. Here is an example `layout.scm` file replicating rivertiles behavior of having -a main window and a stack of windows aside: +a main window and a stack of windows aside while also automatically locking +the screen after five minutes of inactivity. ```scheme +(install-handler 'idle:300 (lambda (event) + (cond ((eq? event 'idle) (system "swaylock &") + (system "backlight.sh set-to 40")) + ((eq? event 'resume) (system "backlight.sh set-to 100"))))) + (define split 0.55) (define (layout:rows n x y w h) diff --git a/doc/riverguile.1 b/doc/riverguile.1 index f919cb7..1a489f1 100644 --- a/doc/riverguile.1 +++ b/doc/riverguile.1 @@ -2,7 +2,7 @@ . .SH NAME .P -riverguile \- scheme powered layout generator for river +riverguile \- scheme powered scripting layer for river . . .SH SYNOPSIS @@ -12,16 +12,17 @@ riverguile \- scheme powered layout generator for river . .SH DESCRIPTION .P -Layout generator for the +Scripting layer for the .BR river (1) -Wayland server which allows users to define layouts using guile scheme functions. -. +Wayland server. +Allows the user to send commands to the Wayland server (probably river) and +install handlers for events from a scheme script. .P -The layout namespace is \fBriverguile\fB. -. +By default, riverguile will exit after the script has been loaded and evaluated. +If certain handlers are installed it will run continously. .P -Uppon launch, riverguile tries to eval a scheme script and checks the following -paths for it in the given order: +Uppon launch, riverguile tries to load the script from the following paths +in the given order: .IP \(bu 2 \fBlayout.scm\fR .IP \(bu 2 @@ -30,80 +31,153 @@ paths for it in the given order: \fB$HOME/.config/river/layout.scm\fR .IP \(bu 2 \fB/etc/riverguile/layout.scm\fR -. .P -In the context of this script, a special procedure +. +. +.SH EVENT HANDLERS +.P +In the context of the script, a special procedure \fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR is available, which can be -used to install handlers to certain events. +used to install event handlers. The parameter \fIkey\fR is a symbol indicating for which event to install the procedure \fIproc\fR. +The following keys are currently evailable: +. .P -The key \fBlayout-demand\fR installs a handler for layout demands, which must -accept five required arguments, which are, in order: The amount of views in the -layout (integer), the available width (integer), the available height (integer), -the currently active tag set (integer representing a bitfield of size 32) -and the global name of the output the layout is needed for (integer). +\fBlayout-demand\fR +.P +.RS +Installing a handler for this key allows the user to provide window layouts. +All limitations of the river-layout-v3 protocol apply. +The server will trigger this event when a new layout is required ("demanded"). +.P +Installing a layout-demand handler will cause riverguile to run continously. +.P +The handler procedure must accept five required arguments, which are, in order: +The amount of views in the layout (integer), the available width (integer), the +available height (integer), the currently active tag set (integer representing a +bitfield of size 32) and the global name of the output the layout is needed for +(integer). The procedure must return a list containing exactly as many lists as there are views in the layout. Each of those sublists must contains exactly four numerical values, which are the x and y coordinates of the window as well as its width and height. -Window positions and dimensions get applied to rivers window list top to bottom. +Window positions and dimensions get applied to the window list top to bottom. +.P Note that the numerical values do not need to be exact, riverguile takes care of rounding and casting for you. -A layout demand handler must be installed, otherwise riverguile will exit. .P -The key \fBuser-command\fR install a handler for user commands, which are the -strings a user can send to layout generators. -This handler procedure must accept three arguments, which are, in order: -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. -.P -The key \fBexit\fR installs a handler which is called when riverguile exits. -The procedure takes no arguments. -. -. -.SH EXAMPLE -.P -This is an example configuration, which installs a layout that simply assigns -each window all usable space and a user command handler that tries to evaluate -all send commands as scheme code. -. +Here is an example of a simple layout-demand handler which simply makes all +windows use all available space: .P .RS .EX -(install-handler 'user-command (lambda (cmd tags output) - (eval-string cmd))) - -(install-handler 'layout-demand (lambda (view-count width height tags output) - (letrec ((iter (lambda (n) - (if (eq? 0 n) - '() - (append (list (list 0 0 width height)) - (iter (1- n))))))) - (iter view-count)))) +(\fBinstall-handler\fR 'layout-demand + (\fBlambda\fR (view-count width height tags output) + (\fBletrec\fR ((iter (\fBlambda\fR (n) + (if (eq? 0 n) + '() + (\fBappend\fR (\fBlist\fR (\fBlist\fR 0 0 width height)) + (iter (1- n))))))) + (iter view-count)))) .EE .RE +.RE +. +.P +\fBuser-command\fR +.P +.RS +User commands are intended to send commands to layout generators, allowing the +user to update parameters of the layout live. +Of course, nothing is stopping you from (ab-)using this event to trigger +arbitrary scheme code on keypresses or on outside events, or from simply not +using it at all. +After a user-command has been received, the server can will trigger a +layout-demand if there are visible windows. +.P +Installing a user-command handler will \fInot\fR cause riverguile to run continously. +This event is an extension to the layout-demand event and as such it is invalid +to install a user-command handler without also installing a layout-demant +handler. +.P +The handler procedure must accept three arguments, which are, in order: +The command (string), the currently active tags (integer representing a +bitfield of size 32) and the global name of the output (integer). +.P +Here is an example of a simple user-command handler which simply evaluates the +string as scheme code: +.P +.RS +.EX +(\fBinstall-handler\fR 'user-command + (\fBlambda\fR (cmd tags output) + (\fBeval-string\fR cmd))) +.EE +.RE +.P +Note that this is not necessarily good practice, but serves as a decent example. +.RE +. +.P +\fBidle:X\fR +.P +.RS +A handler installed for this key will be triggered after the system has been +idle for \fIX\fR seconds and once more once the system is no longer idle. +.P +Installing a layout-demand handler will cause riverguile to run continously. +Multiple idle handlers can be installed. +.P +Idle state is server policy and may depend on a multitude of factors, but +usually maps directly to the usage activity of input devices by the user. +Certain programs may inhibit idle state, like for example video players. +.P +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 system is no +longer idle. +.P +Here is an example which will dim the screen after two minutes of inactiviy +and lock it after fice: +.P +.RS +.EX +(\fBinstall-handler\fR 'idle:120 + (\fBlambda\fR (event) + (\fBcond\fR ((\fBeq?\fR event 'idle) (\fBsystem\fR "light -S 20")) + ((\fBeq?\fR event 'resume) (\fBsystem\fR "light -S 100"))))) + +(\fBinstall-handler\fR 'idle:300 + (\fBlambda\fR (event) + (if (\fBeq?\fR event 'idle) (\fBsystem\fR "swaylock &")))) +.EE +.RE +.P +Note: All idle events relate to the first advetised seat. +As of now, river only supports a single seat anyway. +.RE +. +.P +\fBexit\fR +.RE +.RS +This key allows you to installs a handler which is called when riverguile exits. +.P +The procedure takes no arguments. +.P +Here is an example which adds a message to the system log on exit: +.P +.RS +.EX +(\fBinstall-handler\fR 'exit + (\fBlambda\fR () + (\fBsystem\fR "logger 'goodbye from riverguile'"))) +.EE +.RE +.RE + . . .SH SEE ALSO diff --git a/src/call-exit-handler.c b/src/call-exit-handler.c index de6a68a..986b091 100644 --- a/src/call-exit-handler.c +++ b/src/call-exit-handler.c @@ -9,7 +9,7 @@ static void *call_exit_handler_inner (void *data) return scm_call_0(context.exit_handler); } -void *call_exit_handler (void) +void *call_exit_handler (void *data) { assert(context.exit_handler != NULL); diff --git a/src/call-exit-handler.h b/src/call-exit-handler.h index 6e8d088..f796205 100644 --- a/src/call-exit-handler.h +++ b/src/call-exit-handler.h @@ -1,6 +1,6 @@ #ifndef RIVERGUILE_CALL_EXIT_HANDLER_H #define RIVERGUILE_CALL_EXIT_HANDLER_H -void *call_exit_handler (void); +void *call_exit_handler (void* data); #endif diff --git a/src/load-script.c b/src/load-script.c index b2e9323..e3baa0d 100644 --- a/src/load-script.c +++ b/src/load-script.c @@ -5,6 +5,7 @@ #include #include "riverguile.h" +#include "output.h" #include "seat.h" /** @@ -75,19 +76,58 @@ static SCM install_handler (SCM key, SCM proc) } if ( scm_is_eq(scm_from_utf8_symbol("layout-demand"), key) == 1 ) + { + if ( context.layout_manager == NULL ) + { + fputs("ERROR: Trying to install layout-demand handler but server does not support river-layout-v3.\n", stderr); + fputs("INFO: This error is not fatal, but means riverguile will not provide any layout.\n", stderr); + return SCM_BOOL_F; + } + + context.mode = CONTINOUS; context.layout_demand_handler = proc; + + /* Configure all outputs to expose layouts. */ + struct Output *output; + wl_list_for_each(output, &context.outputs, link) + output_configure_layout(output); + } else if ( scm_is_eq(scm_from_utf8_symbol("user-command"), key) == 1) + { + /* No need to check if the interface exists since it's only + * used when a layout-demand handler is configured. + */ context.user_command_handler = proc; + } else if ( scm_is_eq(scm_from_utf8_symbol("exit"), key) == 1) context.exit_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 ) { + if ( context.idle_notifier == NULL ) + { + fputs("ERROR: Trying to install idle handler but server does not support ext-idle-notify-v1.\n", stderr); + fputs("INFO: This error is not fatal, but means riverguile will not be able to call any idle handler.\n", stderr); + return SCM_BOOL_F; + } + if ( wl_list_length(&context.seats) == 0 ) + { + fputs("ERROR: Trying to install idle handler but server did not advertise any seats.\n", stderr); + fputs("INFO: This error is not fatal, but means riverguile will not be able to call any idle handler.\n", stderr); + return SCM_BOOL_F; + } + + context.mode = CONTINOUS; + + /* Just use the first seat. River only supports a single one anyway. */ + struct Seat *seat; + wl_list_for_each(seat, &context.seats, link) + break; + uint32_t ms = extract_ms_from_idle_key(key); - struct Idle *idle = calloc(1, sizeof(struct Idle)); - if ( idle == NULL ) + if (!seat_add_idle(seat, proc, ms)) { scm_error_scm( scm_from_utf8_symbol("memory-allocation-error"), @@ -98,10 +138,6 @@ static SCM install_handler (SCM key, SCM proc) ); return SCM_UNSPECIFIED; } - - idle->ms = ms; - idle->handler = proc; - wl_list_insert(&context.unconfigured_idles, &idle->link); } else { @@ -148,13 +184,13 @@ void *load_script (void *data) if ( call_result == NULL ) return (void *)"ERROR: Fatal error while loading layout script.\n"; - 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(context.layout_demand_handler)) == 1); + if ( context.layout_demand_handler != NULL ) + 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); + if ( context.exit_handler != NULL ) + assert(scm_is_true(scm_procedure_p(context.exit_handler)) == 1); return NULL; } diff --git a/src/output.c b/src/output.c index 100fd49..7ba2bec 100644 --- a/src/output.c +++ b/src/output.c @@ -80,18 +80,18 @@ static const struct river_layout_v3_listener layout_listener = { .user_command_tags = layout_handle_user_command_tags, }; -void output_configure (struct Output *output) +void output_configure_layout (struct Output *output) { - if ( context.layout_manager == NULL ) - return; - if (output->configured) - return; + assert(context.layout_manager != NULL); + assert(context.layout_demand_handler != NULL); + if ( output->layout != NULL ) + 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) @@ -105,7 +105,13 @@ struct Output *output_create (struct wl_output *wl_output, uint32_t name) output->name = name; output->wl_output = wl_output; - output_configure(output); + + /* Only bind layout if we need it. Outputs advertised in the initial + * registry burst, before the script is loaded, will always skip this, + * however it is necessary for outputs added later. + */ + if ( context.layout_manager != NULL && context.layout_demand_handler != NULL ) + output_configure_layout(output); return output; } diff --git a/src/output.h b/src/output.h index d4c804c..f0445e7 100644 --- a/src/output.h +++ b/src/output.h @@ -12,7 +12,6 @@ struct Output struct wl_output *wl_output; struct river_layout_v3 *layout; uint32_t name; - bool configured; /* Tags for the next user command. Due to backwards compatability, the * layout protocol sends us the currently active tags for a user command @@ -24,6 +23,6 @@ struct Output struct Output *output_create (struct wl_output *wl_output, uint32_t name); void output_destroy (struct Output *output); -void output_configure (struct Output *output); +void output_configure_layout (struct Output *output); #endif diff --git a/src/riverguile.c b/src/riverguile.c index 78eeea8..c9d4e68 100644 --- a/src/riverguile.c +++ b/src/riverguile.c @@ -118,54 +118,66 @@ static const struct wl_registry_listener registry_listener = { .global_remove = registry_handle_global_remove, }; +static void sync_handle_done (void *, struct wl_callback *, uint32_t); +static const struct wl_callback_listener sync_callback_listener = { + .done = sync_handle_done, +}; + static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data) { + static int i = 0; + if ( i == 1 ) + { + assert(context.mode == ONESHOT); + context.loop = false; + return; + } + wl_callback_destroy(wl_callback); context.sync_callback = NULL; - if ( context.layout_manager == NULL ) + /* Load the script after connecting to the server and binding interfaces + * to allow calling Wayland requests from it. + */ + assert(context.path != NULL); + void *res = scm_with_guile(load_script, (void *)context.path); + if ( res != NULL ) { - fputs("ERROR: Wayland server does not support river-layout-v3.\n", stderr); + fputs((char *)res, stderr); context.ret = EXIT_FAILURE; context.loop = false; return; } - if ( context.idle_notifier == NULL ) + if ( context.layout_demand_handler == NULL + && context.user_command_handler != 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); - } + fputs("ERROR: Installing user-command handler without installing a layout-demand handler is not allowed.\n", stderr); + fputs("INFO: This error is not fatal, but means riverguile will not provide any layout.\n", stderr); } - else + + switch (context.mode) { - struct Seat *seat; - wl_list_for_each(seat, &context.seats, link) + case ONESHOT: + /* Oneshot mode. Sync again so we are sure that all commands + * have been send, then exit. + */ + assert(i == 0); + i++; + context.sync_callback = wl_display_sync(context.wl_display); + wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL); + fputs("INFO: No handlers installed: Riverguile will exit.\n", stderr); break; - struct Idle *idle, *tmp_i; - wl_list_for_each_safe(idle, tmp_i, &context.unconfigured_idles, link) - idle_configure(idle, seat); + case CONTINOUS: + fputs("INFO: At least one handler installed: Riverguile will run continously.\n", stderr); + break; } - - /* 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, &context.outputs, link) - output_configure(output); } -static const struct wl_callback_listener sync_callback_listener = { - .done = sync_handle_done, -}; - static void handle_interrupt (int signum) { - fputs("Killed 💀\n", stderr); + fputs("INFO: Killed 💀\n", stderr); context.loop = false; longjmp(skip_main_loop, 1); } @@ -211,7 +223,6 @@ static void handle_error (int signum) kill(getpid(), signum); } - static char *formatted_buffer (const char *fmt, ...) { /* Determine length of formatted text. */ @@ -286,36 +297,29 @@ static char *get_script_path (void) if ( path != NULL ) free(path); - fputs("ERROR: No layout script found.\n", stderr); + fputs("ERROR: No script found.\n", stderr); return NULL; } int main(int argc, char *argv[]) { + fputs("INFO: Welcome to riverguile!\n", stderr); + signal(SIGSEGV, handle_error); signal(SIGFPE, handle_error); signal(SIGINT, handle_interrupt); 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 ) + context.path = get_script_path(); + if ( context.path == NULL ) { context.ret = EXIT_FAILURE; goto early_exit; } - - void *res = scm_with_guile(load_script, (void *)path); - if ( res != NULL ) - { - fputs((char *)res, stderr); - context.ret = EXIT_FAILURE; - context.loop = false; - goto early_exit; - } + fprintf(stderr, "INFO: Found script: %s\n", context.path); /* We query the display name here instead of letting wl_display_connect() * figure it out itself, because libwayland (for legacy reasons) falls @@ -348,13 +352,14 @@ int main(int argc, char *argv[]) while ( context.loop && wl_display_dispatch(context.wl_display) > 0 ); if ( context.exit_handler != NULL ) - call_exit_handler(); - - 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); + void *res = scm_with_guile(call_exit_handler, NULL); + if ( res != NULL ) + { + fputs((char *)res, stderr); + context.ret = EXIT_FAILURE; + context.loop = false; + } } struct Output *output, *tmp_o; @@ -382,9 +387,10 @@ int main(int argc, char *argv[]) wl_display_disconnect(context.wl_display); early_exit: - if ( path != NULL ) - free(path); + if ( context.path != NULL ) + free(context.path); + fputs("INFO: Exiting.\n", stderr); return context.ret; } diff --git a/src/riverguile.h b/src/riverguile.h index 7a6b315..1e68e7c 100644 --- a/src/riverguile.h +++ b/src/riverguile.h @@ -7,12 +7,26 @@ #include "seat.h" +enum Riverguile_mode +{ + /* Riverguile is used only for configuring river and will exit after + * the init script has been loaded. + */ + ONESHOT = 0, + + /* Riverguile need to run continous, because we have handlers installed + * f.e. for layouts or idle. + */ + CONTINOUS, +}; + struct Context { SCM layout_demand_handler; SCM user_command_handler; SCM exit_handler; + enum Riverguile_mode mode; bool loop; int ret; @@ -25,8 +39,7 @@ struct Context 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; + char *path; }; extern struct Context context; diff --git a/src/seat.c b/src/seat.c index b779dea..6e5c604 100644 --- a/src/seat.c +++ b/src/seat.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -20,7 +21,9 @@ static void idle_notification_handle_idled (void *data, .idle = idle, .event = IDLE, }; - call_idle_handler(¶ms); + void *res = scm_with_guile(call_idle_handler, ¶ms); + if ( res != NULL ) + fputs((char *)res, stderr); } static void idle_notification_handle_resumed (void *data, @@ -33,7 +36,9 @@ static void idle_notification_handle_resumed (void *data, .idle = idle, .event = RESUME, }; - call_idle_handler(¶ms); + void *res = scm_with_guile(call_idle_handler, ¶ms); + if ( res != NULL ) + fputs((char *)res, stderr); } static const struct ext_idle_notification_v1_listener idle_notification_listener = { @@ -41,28 +46,34 @@ static const struct ext_idle_notification_v1_listener idle_notification_listener .resumed = idle_notification_handle_resumed, }; -void idle_configure (struct Idle *idle, struct Seat *seat) +bool seat_add_idle (struct Seat *seat, SCM proc, uint32_t ms) { - assert(idle->seat == NULL); + assert(context.idle_notifier != NULL); + + struct Idle *idle = calloc(1, sizeof(struct Idle)); + if ( idle == NULL ) + return false; + idle->ms = ms; + idle->seat = seat; + idle->handler = proc; 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; + + return true; } -void idle_destroy (struct Idle *idle) +static 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? diff --git a/src/seat.h b/src/seat.h index dfe2172..4d83abe 100644 --- a/src/seat.h +++ b/src/seat.h @@ -1,6 +1,7 @@ #ifndef RIVERGUILE_SEAT_H #define RIVERGUILE_SEAT_H +#include #include #include @@ -24,8 +25,6 @@ struct Idle 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); +bool seat_add_idle (struct Seat *seat, SCM proc, uint32_t ms); #endif