diff --git a/Makefile b/Makefile index e80e0f5..3655e6b 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ DATADIR=$(PREFIX)/share MANDIR=$(DATADIR)/man CFLAGS=-g -Wall -Werror -Wextra -Wpedantic -Wno-unused-parameter $\ - -Wno-overlength-strings -Wconversion -Wformat-security -Wformat $\ - -Wsign-conversion -Wfloat-conversion -Wunused-result $\ + -Wno-overlength-strings -Wformat-security -Wformat -Wunused-result $\ $(shell pkg-config --cflags wayland-client) $\ $(shell guile-config compile) LIBS=-lrt $\ @@ -45,3 +44,4 @@ clean: $(RM) $(IMG) .PHONY: clean install uninstall all + diff --git a/riverguile.c b/riverguile.c index 36296fd..9b9d69d 100644 --- a/riverguile.c +++ b/riverguile.c @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __linux__ #include @@ -42,18 +43,88 @@ struct Output 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(s)); + return true; +} + +static void *call_layout_demand_handler (void *data) +{ + struct Layout_demand_parameters *params = (struct Layout_demand_parameters *)data; + + SCM call_result = scm_call( + scm_variable_ref(scm_c_lookup("layout-demand-handler")), + scm_from_uint32(params->view_count), + scm_from_uint32(params->width), + scm_from_uint32(params->height), + SCM_UNDEFINED + ); + + 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; +} + 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; - for (size_t i = 0; i < view_count; i++) + + struct Layout_demand_parameters params = { + .view_count = view_count, + .width = width, + .height = height, + .serial = serial, + .output = output, + }; + + void *res = scm_with_guile(call_layout_demand_handler, (void *)¶ms); + + if ( res != NULL ) { - river_layout_v3_push_view_dimensions( - output->layout, 0, 0, - width, height, serial - ); + fputs(res, stderr); + loop = false; + ret = EXIT_FAILURE; + return; } - river_layout_v3_commit(output->layout, "()", serial); + + // TODO if 'layout-name' variable exists, use it. + river_layout_v3_commit(output->layout, "(🐦)", serial); } static void layout_handle_namespace_in_use (void *data, struct river_layout_v3 *river_layout_v3) @@ -63,10 +134,16 @@ 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 = { @@ -231,6 +308,106 @@ static void handle_error (int signum) kill(getpid(), signum); } +static void *load_script (void *data) +{ + /* Note: All guile objects are garbage collected. */ + + char *path = (char *)data; + (void)scm_c_primitive_load(path); + + /* Check if the handler has been defined. */ + SCM variable = scm_module_variable( + scm_current_module(), + scm_string_to_symbol( + scm_from_utf8_string("layout-demand-handler") + ) + ); + + if ( scm_is_false(variable) == 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, ...) +{ + /* Determine length of formatted text. */ + va_list args; + va_start(args, fmt); + /* +1 for NULL. */ + unsigned long len = (unsigned long)vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + char *buffer = calloc(1, len); + if ( buffer == NULL ) + { + fprintf(stderr, "ERROR: calloc: %s\n", strerror(errno)); + return NULL; + } + + /* Finally write formatted text into buffer. */ + va_start(args, fmt); + vsnprintf(buffer, len, fmt, args); + va_end(args); + + return buffer; +} + +static char *get_script_path (void) +{ + struct { + const char *fmt; + bool needs_env; + const char *env; + } paths[] = { + { .fmt = "layout.scm", .needs_env = false, .env = NULL }, + { .fmt = "%s/riverguile/layout.scm", .needs_env = true, .env = getenv("XDG_CONFIG_HOME") }, + { .fmt = "%s/river/layout.scm", .needs_env = true, .env = getenv("XDG_CONFIG_HOME") }, + { .fmt = "%s/.config/riverguile/layout.scm", .needs_env = true, .env = getenv("HOME") }, + { .fmt = "%s/.config/river/layout.scm", .needs_env = true, .env = getenv("HOME") }, + { .fmt = "/etc/riverguile/layout.scm", .needs_env = false, .env = NULL } + }; + + char *path = NULL; + for (size_t i = 0; i < (sizeof(paths) / sizeof(paths[0])); i++) + { + if ( path != NULL ) + free(path); + + /* Path needs env var, but it is not set. */ + if ( paths[i].needs_env && paths[i].env == NULL ) + continue; + + path = formatted_buffer(paths[i].fmt, paths[i].env); + if ( path == NULL ) + return NULL; + + /* Does the path exist? */ + if ( access(path, F_OK) != 0 ) + continue; + + /* Is it readable? */ + if ( access(path, R_OK) != 0 ) + { + fprintf(stderr, "ERROR: Script '%s' exists, but is not readable.\n", path); + free(path); + return NULL; + } + + return path; + } + + if ( path != NULL ) + free(path); + + fputs("ERROR: No layout script found.\n", stderr); + return NULL; +} + int main(int argc, char *argv[]) { signal(SIGSEGV, handle_error); @@ -238,6 +415,21 @@ int main(int argc, char *argv[]) signal(SIGINT, handle_interrupt); wl_list_init(&outputs); + char *path = get_script_path(); + if ( path == NULL ) + { + ret = EXIT_FAILURE; + goto early_exit; + } + + void *res = scm_with_guile(load_script, (void *)path); + if ( res != NULL ) + { + fputs((char *)res, stderr); + ret = EXIT_FAILURE; + goto early_exit; + } + /* We query the display name here instead of letting wl_display_connect() * figure it out itself, because libwayland (for legacy reasons) falls * back to using "wayland-0" when $WAYLAND_DISPLAY is not set, which is @@ -248,7 +440,7 @@ int main(int argc, char *argv[]) { fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr); ret = EXIT_FAILURE; - goto cleanup; + goto early_exit; } wl_display = wl_display_connect(display_name); @@ -256,7 +448,7 @@ int main(int argc, char *argv[]) { fputs("ERROR: Can not connect to wayland display.\n", stderr); ret = EXIT_FAILURE; - goto cleanup; + goto early_exit; } wl_registry = wl_display_get_registry(wl_display); @@ -269,7 +461,6 @@ int main(int argc, char *argv[]) while ( loop && wl_display_dispatch(wl_display) > 0 ); struct Output *output, *tmp; -cleanup: wl_list_for_each_safe(output, tmp, &outputs, link) { wl_list_remove(&output->link); @@ -282,5 +473,10 @@ cleanup: wl_registry_destroy(wl_registry); wl_display_disconnect(wl_display); +early_exit: + if ( path != NULL ) + free(path); + return ret; } +