diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cce9f84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +protocol/*.c +protocol/*.h +*.o +riverguile diff --git a/Makefile b/Makefile index 3655e6b..45b4148 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,18 @@ MANDIR=$(DATADIR)/man CFLAGS=-g -Wall -Werror -Wextra -Wpedantic -Wno-unused-parameter $\ -Wno-overlength-strings -Wformat-security -Wformat -Wunused-result $\ + -I protocol $\ $(shell pkg-config --cflags wayland-client) $\ $(shell guile-config compile) LIBS=-lrt $\ $(shell pkg-config --libs wayland-client) $\ $(shell guile-config link) -OBJ=riverguile.o river-layout-v3.o -GEN=river-layout-v3.c river-layout-v3.h +OBJ=src/riverguile.o $\ + src/call-layout-demand-handler.o $\ + src/call-user-command-handler.o $\ + src/load-script.o $\ + protocol/river-layout-v3.o +GEN=protocol/river-layout-v3.c protocol/river-layout-v3.h all: riverguile @@ -31,7 +36,7 @@ $(OBJ): $(GEN) install: riverguile install -D riverguile $(DESTDIR)$(BINDIR)/riverguile - install -m 644 -D riverguile.1 $(DESTDIR)$(MANDIR)/man1/riverguile.1 + install -m 644 -D doc/riverguile.1 $(DESTDIR)$(MANDIR)/man1/riverguile.1 uninstall: $(RM) $(DESTDIR)$(BINDIR)/riverguile diff --git a/doc/riverguile.1 b/doc/riverguile.1 new file mode 100644 index 0000000..1eba733 --- /dev/null +++ b/doc/riverguile.1 @@ -0,0 +1,97 @@ +.TH RIVERGUILE 1 2023-11-25 "git.sr.ht/~leon_plickat/riverguile" "General Commands Manual" +. +.SH NAME +.P +riverguile \- scheme powered layout generator for river +. +. +.SH SYNOPSIS +.SY riverguile +.YS +. +. +.SH DESCRIPTION +.P +Layout generator for the +.BR river (1) +Wayland server which allows users to define layouts using guile scheme functions. +. +.P +The layout namespace is \fBriverguile\fB. +. +.P +Uppon launch, riverguile tries to eval a scheme script and checks the following +paths for it in the given order: +.IP \(bu 2 +\fBlayout.scm\fR +.IP \(bu 2 +\fB$XDG_CONFIG_HOME/river/layout.scm\fR +.IP \(bu 2 +\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 +\fB(install-handler \fR\fIkey\fR \fIproc\fR\fB)\fR is available, which can be +used to install handlers to certain events. +The parameter \fIkey\fR is a symbol indicating for which event to install +the procedure \fIproc\fR. +.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). +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. +Note that the numerical values do not need to be exact, riverguile takes care +of rounding and casting for you. +.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. +. +. +.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. +. +.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)))) +.EE +.RE +. +. +.SH SEE ALSO +.BR river (1), +.BR riverctl (1), +.BR rivertile (1), +.BR guile (1) +. +. +.SH AUTHOR +.P +.MT leonhenrik.plickat@stud.uni-goettingen.de +Leon Henrik Plickat +.ME diff --git a/river-layout-v3.xml b/protocol/river-layout-v3.xml similarity index 100% rename from river-layout-v3.xml rename to protocol/river-layout-v3.xml diff --git a/riverguile.1 b/riverguile.1 deleted file mode 100644 index 026c607..0000000 --- a/riverguile.1 +++ /dev/null @@ -1,62 +0,0 @@ -.TH RIVERGUILE 1 2023-11-25 "git.sr.ht/~leon_plickat/riverguile" "General Commands Manual" -. -.SH NAME -.P -riverguile \- scheme powered layout generator for river -. -. -.SH SYNOPSIS -.SY riverguile -.YS -. -. -.SH DESCRIPTION -.P -Layout generator for the -.BR river (1) -Wayland server which allows users to define layouts using guile scheme functions. -. -.P -The layout namespace is `riverguile`. -. -.P -Uppon launch, riverguile tries to eval a scheme script and checks the following -paths for it in the given order: -.IP \(bu 2 -\fBlayout.scm\fR -.IP \(bu 2 -\fB$XDG_CONFIG_HOME/river/layout.scm\fR -.IP \(bu 2 -\fB$HOME/.config/river/layout.scm\fR -.IP \(bu 2 -\fB/etc/riverguile/layout.scm\fR -. -.P -This script must contain the definition of a function -\fB(layout-demand-handler view-count usable-width usable-height)\fR and may -contain any other code the user desires. -The function must return a list containing exactly as many lists as \fBview-count\fR. -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. -. -.P -Note that the numerical values do not need to be exact, riverguile takes care -of rounding and casting for you. -. -.P -If you send riverguile a layout-command, it will try to eval it as scheme code. -. -. -.SH SEE ALSO -.BR river (1), -.BR riverctl (1), -.BR rivertile (1), -.BR guile (1) -. -. -.SH AUTHOR -.P -.MT leonhenrik.plickat@stud.uni-goettingen.de -Leon Henrik Plickat -.ME diff --git a/src/call-layout-demand-handler.c b/src/call-layout-demand-handler.c new file mode 100644 index 0000000..fd9ac3a --- /dev/null +++ b/src/call-layout-demand-handler.c @@ -0,0 +1,83 @@ +#include +#include + +#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, + scm_from_uint32(params->view_count), + scm_from_uint32(params->width), + scm_from_uint32(params->height), + scm_from_uint32(params->tags), + scm_from_uint32(params->output->name) + ); +} + +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); + + /* Continuation barrier causes stack unwind on exceptions (i.e. errors + * in the user defined layout demand 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_layout_demand_handler_inner, data + ); + + if ( call_result == NULL ) + return (void *)"ERROR: An exception occured while calling the layout demand handler.\n"; + + /* Check integrity of data before firing off any Wayland requests. */ + if ( scm_is_false(scm_list_p(call_result)) == 1 ) + return (void *)"ERROR: layout-demand-handler did not return a list.\n"; + if ( params->view_count != scm_to_uint32(scm_length(call_result)) ) + return (void *)"ERROR: Length of list returned by layout-demand-handler does not match view count.\n"; + for (uint32_t i = 0; i < params->view_count; i++) + { + SCM elem = scm_list_ref(call_result, scm_from_uint32(i)); + if ( scm_is_false(scm_list_p(elem)) == 1 ) + return (void *)"ERROR: View dimensions list is not a list.\n"; + if ( scm_to_uint32(scm_length(elem)) != 4 ) + return (void *)"ERROR: View dimensions list length does not equal four (x, y, width, height).\n"; + for (uint8_t j = 0; j < 4; j++) + { + SCM list_elem = scm_list_ref(elem, scm_from_int(j)); + if ( scm_is_false(scm_number_p(list_elem)) == 1 ) + return (void *)"ERROR: Encountered non-numerical view dimensions.\n"; + } + } + + for (uint32_t i = 0; i < params->view_count; i++) + { + SCM elem = scm_list_ref(call_result, scm_from_uint32(i)); + uint32_t x = scm_to_uint32(scm_inexact_to_exact( + scm_round_number(scm_list_ref(elem, scm_from_int(0))))); + uint32_t y = scm_to_uint32(scm_inexact_to_exact( + scm_round_number(scm_list_ref(elem, scm_from_int(1))))); + uint32_t w = scm_to_uint32(scm_inexact_to_exact( + scm_round_number(scm_list_ref(elem, scm_from_int(2))))); + uint32_t h = scm_to_uint32(scm_inexact_to_exact( + scm_round_number(scm_list_ref(elem, scm_from_int(3))))); + + river_layout_v3_push_view_dimensions( + params->output->layout, + x, y, w, h, + params->serial + ); + } + + return NULL; +} + diff --git a/src/call-layout-demand-handler.h b/src/call-layout-demand-handler.h new file mode 100644 index 0000000..53ed063 --- /dev/null +++ b/src/call-layout-demand-handler.h @@ -0,0 +1,16 @@ +#ifndef RIVERGUILE_CALL_LAYOUT_DEMAND_HANDLER_H +#define RIVERGUILE_CALL_LAYOUT_DEMAND_HANDLER_H + +#include + +#include "types.h" + +struct Call_layout_demand_handler_parameters +{ + uint32_t view_count, width, height, serial, tags; + struct Output *output; +}; + +void *call_layout_demand_handler (void *data); + +#endif diff --git a/src/call-user-command-handler.c b/src/call-user-command-handler.c new file mode 100644 index 0000000..e3abf5b --- /dev/null +++ b/src/call-user-command-handler.c @@ -0,0 +1,38 @@ +#include +#include + +#include "types.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, + scm_from_utf8_string(params->cmd), + scm_from_uint32(params->output->user_command_tags), + scm_from_uint32(params->output->name) + ); +} + +void *call_user_command_handler (void *data) +{ + assert(user_command_handler != NULL); + assert(scm_is_true(scm_procedure_p(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 + * 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_user_command_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-user-command-handler.h b/src/call-user-command-handler.h new file mode 100644 index 0000000..5c7d24f --- /dev/null +++ b/src/call-user-command-handler.h @@ -0,0 +1,14 @@ +#ifndef RIVERGUILE_CALL_USER_COMMAND_HANDLER_H +#define RIVERGUILE_CALL_USER_COMMAND_HANDLER_H + +#include "types.h" + +struct Call_user_command_parameters +{ + struct Output *output; + const char *cmd; +}; + +void *call_user_command_handler (void *data); + +#endif diff --git a/src/load-script.c b/src/load-script.c new file mode 100644 index 0000000..bc90a9b --- /dev/null +++ b/src/load-script.c @@ -0,0 +1,104 @@ +#include +#include +#include + +/** + * ISO C forbids casting a function pointer to a void pointer because on some + * architectures they have different sizes. However scm_c_define_gsubr() wants + * a function pointer as a void pointer, which trips -Wpedantic. Compiling C + * without pedantic errors is not a reasonable option in my opinion, so instead + * we'll have to resort to this hack. + */ +#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 SCM install_handler (SCM key, SCM proc) +{ + if ( scm_is_false(scm_symbol_p(key)) == 1 ) + { + scm_error_scm( + scm_from_utf8_symbol("wrong-type-arg"), + scm_from_utf8_string("install-handler"), + scm_from_utf8_string("First argument must be a symbol."), + SCM_BOOL_F, + scm_list_1(key) + ); + return SCM_UNSPECIFIED; + } + + // TODO check if the procedure has the right amount of arguments. + if ( scm_is_false(scm_procedure_p(proc)) == 1 ) + { + scm_error_scm( + scm_from_utf8_symbol("wrong-type-arg"), + scm_from_utf8_string("install-handler"), + scm_from_utf8_string("Second argument must be a procedure."), + SCM_BOOL_F, + scm_list_1(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; + else + { + scm_error_scm( + // TODO should this be 'misc-error instead? + scm_from_utf8_symbol("out-of-range"), + scm_from_utf8_string("install-handler"), + scm_from_utf8_string("Unknown key: ~A"), + scm_list_1(key), + scm_list_1(key) + ); + return SCM_UNSPECIFIED; + } + + return SCM_BOOL_T; +} + +static void *load_script_inner (void *data) +{ + const char *path = (char *)data; + + /* scm_primitive_load_path() searches guiles load-path when encountering + * a relative path. That should never happen here though. + */ + assert(path[0] == '/'); + + return scm_primitive_load_path(scm_from_utf8_string(path)); +} + +void *load_script (void *data) +{ + /* Note: All guile objects are garbage collected. */ + + scm_c_define_gsubr_fix("install-handler", 2, 0, 0, install_handler); + + /* Continuation barrier causes stack unwind on exceptions 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( + load_script_inner, data + ); + + if ( call_result == NULL ) + return (void *)"ERROR: Fatal error while loading layout script.\n"; + + if ( 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); + + return NULL; +} + diff --git a/src/load-script.h b/src/load-script.h new file mode 100644 index 0000000..d4b7393 --- /dev/null +++ b/src/load-script.h @@ -0,0 +1,6 @@ +#ifndef RIVERGUILE_LOAD_SCRIPT_H_INCLUDED +#define RIVERGUILE_LOAD_SCRIPT_H_INCLUDED + +void *load_script (void *data); + +#endif diff --git a/riverguile.c b/src/riverguile.c similarity index 72% rename from riverguile.c rename to src/riverguile.c index 2081e05..9ff852b 100644 --- a/riverguile.c +++ b/src/riverguile.c @@ -8,7 +8,6 @@ #include #include #include - #include #include @@ -21,6 +20,11 @@ #include "river-layout-v3.h" +#include "types.h" +#include "call-layout-demand-handler.h" +#include "call-user-command-handler.h" +#include "load-script.h" + bool loop = true; int ret = EXIT_SUCCESS; @@ -33,119 +37,68 @@ struct river_layout_manager_v3 *layout_manager = NULL; struct wl_list outputs; -struct Output -{ - struct wl_list link; - struct wl_output *wl_output; - struct river_layout_v3 *layout; - uint32_t name; - bool configured; -}; - -struct Layout_demand_parameters -{ - uint32_t view_count, width, height, serial; - struct Output *output; -}; - -static bool scm2uint(uint32_t *x, SCM s) -{ - if ( scm_is_false(scm_number_p(s)) == 1 ) - return false; - *x = scm_to_uint32(scm_inexact_to_exact(scm_round_number(s))); - return true; -} - -/** - * Try to find a variable named 'name' and returns it, otherwise #f is returned. +/* 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. */ -static SCM scm_c_get_variable (const char *name) -{ - return scm_module_variable( - scm_current_module(), - scm_string_to_symbol( - scm_from_utf8_string(name) - ) - ); -} - -static void *call_layout_demand_handler (void *data) -{ - struct Layout_demand_parameters *params = (struct Layout_demand_parameters *)data; - - SCM layout_demand_handler = scm_c_get_variable("layout-demand-handler"); - if ( scm_is_false(layout_demand_handler) == 1 ) - { - ret = EXIT_FAILURE; - loop = false; - return (void *)"ERROR: layout-demand-handler no longer exists.\n"; - } - - SCM call_result = scm_call_3( - scm_variable_ref(layout_demand_handler), - scm_from_uint32(params->view_count), - scm_from_uint32(params->width), - scm_from_uint32(params->height) - ); - - if ( scm_is_false(scm_list_p(call_result)) == 1 ) - return (void *)"ERROR: layout-demand-handler did not return a list.\n"; - if ( params->view_count != scm_to_uint32(scm_length(call_result)) ) - return (void *)"ERROR: Length of list returned by layout-demand-handler does not match view count.\n"; - - for (uint32_t i = 0; i < params->view_count; i++) - { - SCM elem = scm_list_ref(call_result, scm_from_uint32(i)); - - if ( scm_is_false(scm_list_p(elem)) == 1 ) - return (void *)"ERROR: View dimensions list is not a list.\n"; - if ( scm_to_uint32(scm_length(elem)) != 4 ) - return (void *)"ERROR: View dimensions list length does not equal four (x, y, width, height).\n"; - - uint32_t x, y, w, h; - if ( !scm2uint(&x, scm_list_ref(elem, scm_from_int(0))) - || !scm2uint(&y, scm_list_ref(elem, scm_from_int(1))) - || !scm2uint(&w, scm_list_ref(elem, scm_from_int(2))) - || !scm2uint(&h, scm_list_ref(elem, scm_from_int(3))) ) - return "ERROR: Encountered non-numerical view dimensions.\n"; - - river_layout_v3_push_view_dimensions( - params->output->layout, - x, y, w, h, - params->serial - ); - } - - return 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 Layout_demand_parameters params = { + 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); - loop = false; - ret = EXIT_FAILURE; + fputs("INFO: This error is not fatal.\n", stderr); return; } - // TODO if 'layout-name' variable exists, use it. + // 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); @@ -153,22 +106,11 @@ static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 * loop = false; } -static void *exec_user_command (void *str) -{ - return (void *)scm_c_eval_string((char *)str); -} - -static void layout_handle_user_command (void *data, - struct river_layout_v3 *river_layout_manager_v3, - const char *command) -{ - (void)scm_with_guile(exec_user_command, (void *)command); -} - 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, + .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) @@ -214,7 +156,7 @@ static void registry_handle_global (void *data, struct wl_registry *registry, if ( strcmp(interface, river_layout_manager_v3_interface.name) == 0 ) { layout_manager = wl_registry_bind( - registry, name, &river_layout_manager_v3_interface, 1 + registry, name, &river_layout_manager_v3_interface, 2 ); } else if ( strcmp(interface, wl_output_interface.name) == 0 ) @@ -281,7 +223,7 @@ static const struct wl_callback_listener sync_callback_listener = { static void handle_interrupt (int signum) { - fputs("Killed.\n", stderr); + fputs("Killed 💀\n", stderr); loop = false; longjmp(skip_main_loop, 1); } @@ -327,30 +269,6 @@ static void handle_error (int signum) kill(getpid(), signum); } -static void *load_script (void *data) -{ - /* Note: All guile objects are garbage collected. */ - - const char *path = (char *)data; - - /* scm_primitive_load_path() searches guiles load-path when encountering - * a relative path. That should never happen here though. - */ - assert(path[0] == '/'); - - scm_primitive_load_path(scm_from_utf8_string(path)); - - /* Check if the handler has been defined. */ - SCM layout_demand_handler = scm_c_get_variable("layout-demand-handler"); - if ( scm_is_false(layout_demand_handler) == 1 ) - { - ret = EXIT_FAILURE; - loop = false; - return (void *)"ERROR: Script does not define layout-demand-handler.\n"; - } - - return NULL; -} static char *formatted_buffer (const char *fmt, ...) { @@ -437,6 +355,7 @@ int main(int argc, char *argv[]) signal(SIGINT, handle_interrupt); wl_list_init(&outputs); + // TODO use argv[1] if present char *path = get_script_path(); if ( path == NULL ) { @@ -449,6 +368,7 @@ int main(int argc, char *argv[]) { fputs((char *)res, stderr); ret = EXIT_FAILURE; + loop = false; goto early_exit; } diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..d3880d2 --- /dev/null +++ b/src/types.h @@ -0,0 +1,25 @@ +#ifndef RIVERGUILE_TYPES_H_INCLUDED +#define RIVERGUILE_TYPES_H_INCLUDED + +#include +#include + +#include "river-layout-v3.h" + +struct Output +{ + struct wl_list link; + 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 + * in a separate event before the actual user command. This event is + * guaranteed to be received. + */ + uint32_t user_command_tags; +}; + +#endif