diff --git a/.gitignore b/.gitignore index 881a400..e25da60 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ protocol/*.h riverguile /result /.direnv +/.cache +protocol/*.html +/compile_commands.json diff --git a/Makefile b/Makefile index ff2b3c2..21f28eb 100644 --- a/Makefile +++ b/Makefile @@ -24,10 +24,12 @@ OBJ=src/riverguile.o $\ src/load-script.o $\ protocol/river-layout-v3.o $\ protocol/ext-idle-notify-v1.o $\ - protocol/river-control-unstable-v1.o + protocol/river-control-unstable-v1.o $\ + protocol/river-status-unstable-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 $\ - protocol/river-control-unstable-v1.c protocol/river-control-unstable-v1.h + protocol/river-control-unstable-v1.c protocol/river-control-unstable-v1.h $\ + protocol/river-status-unstable-v1.c protocol/river-status-unstable-v1.h all: riverguile diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml new file mode 100644 index 0000000..e9629dd --- /dev/null +++ b/protocol/river-status-unstable-v1.xml @@ -0,0 +1,148 @@ + + + + Copyright 2020 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + + + A global factory for objects that receive status information specific + to river. It could be used to implement, for example, a status bar. + + + + + This request indicates that the client will not use the + river_status_manager object any more. Objects that have been created + through this instance are not affected. + + + + + + This creates a new river_output_status object for the given wl_output. + + + + + + + + This creates a new river_seat_status object for the given wl_seat. + + + + + + + + + This interface allows clients to receive information about the current + windowing state of an output. + + + + + This request indicates that the client will not use the + river_output_status object any more. + + + + + + Sent once binding the interface and again whenever the tag focus of + the output changes. + + + + + + + Sent once on binding the interface and again whenever the tag state + of the output changes. + + + + + + + Sent once on binding the interface and again whenever the set of + tags with at least one urgent view changes. + + + + + + + Sent once on binding the interface should a layout name exist and again + whenever the name changes. + + + + + + + Sent when the current layout name has been removed without a new one + being set, for example when the active layout generator disconnects. + + + + + + + This interface allows clients to receive information about the current + focus of a seat. Note that (un)focused_output events will only be sent + if the client has bound the relevant wl_output globals. + + + + + This request indicates that the client will not use the + river_seat_status object any more. + + + + + + Sent on binding the interface and again whenever an output gains focus. + + + + + + + Sent whenever an output loses focus. + + + + + + + Sent once on binding the interface and again whenever the focused + view or a property thereof changes. The title may be an empty string + if no view is focused or the focused view did not set a title. + + + + + + + Sent once on binding the interface and again whenever a new mode + is entered (e.g. with riverctl enter-mode foobar). + + + + + diff --git a/src/load-script.c b/src/load-script.c index 7357efc..eb01020 100644 --- a/src/load-script.c +++ b/src/load-script.c @@ -4,12 +4,22 @@ #include #include +#include "libguile/bitvectors.h" +#include "libguile/boolean.h" +#include "libguile/foreign-object.h" +#include "libguile/list.h" +#include "libguile/numbers.h" +#include "libguile/pairs.h" +#include "libguile/scm.h" +#include "libguile/strings.h" +#include "libguile/symbols.h" #include "riverguile.h" #include "output.h" #include "seat.h" #include "river-control-unstable-v1.h" #include "call-new-output-handler.h" +#include "wayland-util.h" /** * ISO C forbids casting a function pointer to a void pointer because on some @@ -246,6 +256,83 @@ error: return SCM_UNSPECIFIED; } +static SCM output_t; + +static SCM scm_str(const char *chars) { + if (chars != NULL) + return scm_from_utf8_string(chars); + return scm_from_utf8_string(""); +} + +static SCM bitvector_tags(uint32_t tags) { + SCM bitvector = scm_c_make_bitvector(32, SCM_BOOL_F); + for (int i = 0; i < 32; i++) { + if ((tags & (1 << i)) != 0) + scm_c_bitvector_set_bit_x(bitvector, i); + } + return bitvector; +} + +static SCM output_for(struct Output *output) { + SCM output_scm = scm_make_foreign_object_0(output_t); + scm_foreign_object_set_x(output_scm, 0, scm_from_int(output->name)); + scm_foreign_object_set_x(output_scm, 1, + bitvector_tags(output->tags.focused_tags)); + SCM view_tags = SCM_EOL; + for (ssize_t i = output->tags.view_tags_len - 1; i >= 0; i--) + view_tags = scm_cons(bitvector_tags(*(output->tags.view_tags + i)), view_tags); + scm_foreign_object_set_x(output_scm, 2, view_tags); + scm_foreign_object_set_x(output_scm, 3, + bitvector_tags(output->tags.urgent_tags)); + scm_foreign_object_set_x(output_scm, 4, scm_str(output->tags.layout_name)); + return output_scm; +} + +static SCM outputs(void) { + SCM lst = SCM_EOL; + struct Output *output, *tmp_o; + wl_list_for_each_safe(output, tmp_o, &context.outputs, link) + lst = scm_cons(output_for(output), lst); + + return scm_reverse_x(lst, SCM_EOL); +} + +static SCM seat_t; + +// Assumes there's only one seat available +static SCM seat(void) { + struct Seat *seat; + // Take the first seat + wl_list_for_each(seat, &context.seats, link) + break; + struct Output *output = NULL; + if ( seat->status.focused_output != NULL ) + wl_list_for_each(output, &context.outputs, link) + if ( output->wl_output == seat->status.focused_output ) + break; + return scm_make_foreign_object_3( + seat_t, + output != NULL ? output_for(output) : SCM_BOOL_F, + scm_str(seat->status.focused_view_title), + scm_str(seat->status.mode)); +} + +#define getters(X) \ + X(output_t, output_name, "output-name", 0) \ + X(output_t, output_focused_tags, "output-focused-tags", 1) \ + X(output_t, output_view_tags, "output-view-tags", 2) \ + X(output_t, output_urgent_tags, "output-urgent-tags", 3) \ + X(output_t, output_layout_name, "output-layout-name", 4) \ + X(seat_t, seat_focused_output, "seat-focused-output", 0) \ + X(seat_t, seat_focused_view_title, "seat-focused-view-title", 1) \ + X(seat_t, seat_mode, "seat-mode", 2) + +#define X(t, c, scm, i) static SCM c (SCM output) { scm_assert_foreign_object_type(t, output); return scm_foreign_object_ref(output, i); } +getters(X) +#undef X + + + static void *load_script_inner (void *data) { const char *path = (char *)data; @@ -265,6 +352,30 @@ void *load_script (void *data) scm_c_define_gsubr_fix("install-handler", 2, 0, 0, install_handler); scm_c_define_gsubr_fix("riverctl", 1, 0, 1, riverctl); + output_t = scm_make_foreign_object_type( + scm_from_utf8_symbol("output"), + scm_list_5(scm_from_utf8_symbol("name"), + scm_from_utf8_symbol("focused-tags"), + scm_from_utf8_symbol("view-tags"), + scm_from_utf8_symbol("urgent-tags"), + scm_from_utf8_symbol("layout")), + NULL); + + seat_t = scm_make_foreign_object_type( + scm_from_utf8_symbol("seat"), + scm_list_3(scm_from_utf8_symbol("focused-output"), + scm_from_utf8_symbol("focused-view-title"), + scm_from_utf8_symbol("mode")), + NULL); + + scm_c_define_gsubr_fix("outputs", 0, 0, 0, outputs); + scm_c_define_gsubr_fix("seat", 0, 0, 0, seat); + + #define X(t, c, scm, i) scm_c_define_gsubr_fix(scm, 1, 0, 0, c); + getters(X) + #undef X + + /* 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. diff --git a/src/output.c b/src/output.c index 89ae499..722118c 100644 --- a/src/output.c +++ b/src/output.c @@ -7,11 +7,13 @@ #include "river-layout-v3.h" +#include "river-status-unstable-v1.h" #include "riverguile.h" #include "output.h" #include "call-layout-demand-handler.h" #include "call-user-command-handler.h" #include "call-new-output-handler.h" +#include "wayland-util.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) @@ -95,6 +97,50 @@ void output_configure_layout (struct Output *output) river_layout_v3_add_listener(output->layout, &layout_listener, output); } + +void output_set_focused_tags(void *data, struct zriver_output_status_v1 *output, uint32_t tags) { + ((struct Output *) data)->tags.focused_tags = tags; +} + +void output_set_urgent_tags(void *data, struct zriver_output_status_v1 *output, uint32_t tags) { + ((struct Output *) data)->tags.urgent_tags = tags; +} + +void output_set_view_tags(void *data, struct zriver_output_status_v1 *output, struct wl_array *tags) { + struct Output *output_ = data; + if (output_->tags.view_tags != NULL) + free(output_->tags.view_tags); + output_->tags.view_tags_len = tags->size; + output_->tags.view_tags = calloc(tags->size, 1); + assert(output_->tags.view_tags != NULL); + + memcpy(output_->tags.view_tags, tags->data, tags->size); +} + +void output_set_layout_name(void *data, struct zriver_output_status_v1 *output, const char *name) { + struct Output *output_ = data; + if (output_->tags.layout_name != NULL) + free(output_->tags.layout_name); + output_->tags.layout_name = calloc(1, strlen(name) + 1); + assert(output_->tags.layout_name != NULL); + strcpy(output_->tags.layout_name, name); +} + +void output_clear_layout_name(void *data, struct zriver_output_status_v1 *output) { + struct Output *output_ = data; + if (output_->tags.layout_name != NULL) + free(output_->tags.layout_name); + output_->tags.layout_name = NULL; +} + +static const struct zriver_output_status_v1_listener status_listener = { + .focused_tags = output_set_focused_tags, + .urgent_tags = output_set_urgent_tags, + .view_tags = output_set_view_tags, + .layout_name = output_set_layout_name, + .layout_name_clear = output_clear_layout_name, +}; + struct Output *output_create (struct wl_output *wl_output, uint32_t name) { struct Output *output = calloc(1, sizeof(struct Output)); @@ -131,11 +177,23 @@ struct Output *output_create (struct wl_output *wl_output, uint32_t name) return output; } +void output_status_init(struct Output* output) { + output->status = zriver_status_manager_v1_get_river_output_status(context.status_manager, output->wl_output); + zriver_output_status_v1_add_listener(output->status, &status_listener, output); +} void output_destroy (struct Output *output) { if ( output->layout != NULL ) river_layout_v3_destroy(output->layout); + if ( output->tags.view_tags != NULL ) + free(output->tags.view_tags); + if ( output->tags.layout_name != NULL ) + free(output->tags.layout_name); + + if ( output->status != NULL ) + zriver_output_status_v1_destroy(output->status); + assert(output->wl_output != NULL); wl_output_destroy(output->wl_output); free(output); diff --git a/src/output.h b/src/output.h index f0445e7..96922c2 100644 --- a/src/output.h +++ b/src/output.h @@ -2,15 +2,28 @@ #define RIVERGUILE_OUTPUT_H #include +#include #include #include "river-layout-v3.h" +#include "river-status-unstable-v1.h" + +struct OutputTags { + uint32_t focused_tags; + + uint32_t *view_tags; + size_t view_tags_len; + + uint32_t urgent_tags; + char *layout_name; +}; struct Output { struct wl_list link; struct wl_output *wl_output; struct river_layout_v3 *layout; + struct zriver_output_status_v1 *status; uint32_t name; /* Tags for the next user command. Due to backwards compatability, the @@ -19,10 +32,14 @@ struct Output * guaranteed to be received. */ uint32_t user_command_tags; + + struct OutputTags tags; }; + struct Output *output_create (struct wl_output *wl_output, uint32_t name); void output_destroy (struct Output *output); +void output_status_init(struct Output* output); void output_configure_layout (struct Output *output); #endif diff --git a/src/riverguile.c b/src/riverguile.c index 16cef85..84cbae3 100644 --- a/src/riverguile.c +++ b/src/riverguile.c @@ -1,3 +1,4 @@ +#include "wayland-client-core.h" #include #include #include @@ -21,6 +22,7 @@ #include "river-layout-v3.h" #include "ext-idle-notify-v1.h" #include "river-control-unstable-v1.h" +#include "river-status-unstable-v1.h" #include "load-script.h" #include "call-exit-handler.h" @@ -66,6 +68,12 @@ static void registry_handle_global (void *data, struct wl_registry *registry, registry, name, &ext_idle_notifier_v1_interface, 1 ); } + else if ( strcmp(interface, zriver_status_manager_v1_interface.name) == 0 ) + { + context.status_manager = wl_registry_bind( + registry, name, &zriver_status_manager_v1_interface, 4 + ); + } else if ( strcmp(interface, wl_output_interface.name) == 0 ) { struct wl_output *wl_output = wl_registry_bind( @@ -135,7 +143,8 @@ static const struct wl_callback_listener sync_callback_listener = { static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data) { static int i = 0; - if ( i == 1 ) + i++; + if ( i > 2 ) { assert(context.mode == ONESHOT); context.loop = false; @@ -145,6 +154,22 @@ static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint3 wl_callback_destroy(wl_callback); context.sync_callback = NULL; + if (i == 1) { + struct Output *output; + wl_list_for_each(output, &context.outputs, link) { + output_status_init(output); + } + + struct Seat *seat; + wl_list_for_each(seat, &context.seats, link) { + seat_status_init(seat); + } + + context.sync_callback = wl_display_sync(context.wl_display); + wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL); + return; + } + /* Load the script after connecting to the server and binding interfaces * to allow calling Wayland requests from it. */ @@ -171,8 +196,6 @@ static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint3 /* 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); @@ -381,6 +404,8 @@ int main (int argc, char *argv[]) river_layout_manager_v3_destroy(context.layout_manager); if ( context.river_control != NULL ) zriver_control_v1_destroy(context.river_control); + if ( context.status_manager != NULL ) + zriver_status_manager_v1_destroy(context.status_manager); if ( context.sync_callback != NULL ) wl_callback_destroy(context.sync_callback); if ( context.wl_registry != NULL ) diff --git a/src/riverguile.h b/src/riverguile.h index 6fd163a..953810b 100644 --- a/src/riverguile.h +++ b/src/riverguile.h @@ -5,6 +5,7 @@ #include #include +#include "river-status-unstable-v1.h" #include "seat.h" enum Riverguile_mode @@ -37,6 +38,7 @@ struct Context struct river_layout_manager_v3 *layout_manager; struct ext_idle_notifier_v1 *idle_notifier; struct zriver_control_v1 *river_control; + struct zriver_status_manager_v1 *status_manager; struct wl_list seats; struct wl_list outputs; diff --git a/src/seat.c b/src/seat.c index 6e5c604..4cfcc23 100644 --- a/src/seat.c +++ b/src/seat.c @@ -7,9 +7,11 @@ #include "ext-idle-notify-v1.h" +#include "river-status-unstable-v1.h" #include "riverguile.h" #include "seat.h" #include "call-idle-handler.h" +#include "wayland-client-protocol.h" static void idle_notification_handle_idled (void *data, struct ext_idle_notification_v1 *ext_idle_notification_v1) @@ -90,6 +92,43 @@ static void idle_destroy (struct Idle *idle) free(idle); } + +void seat_focused_output(void *seat, struct zriver_seat_status_v1 *status_, struct wl_output *output) { + struct SeatStatus *status = &((struct Seat *)seat)->status; + status->focused_output = output; +} + + +void seat_unfocused_output(void *seat, struct zriver_seat_status_v1 *status_, struct wl_output *output) { + struct SeatStatus *status = &((struct Seat *)seat)->status; + status->focused_output = NULL; +} + +void seat_focused_view(void *seat, struct zriver_seat_status_v1 *status_, const char *title) { + struct SeatStatus *status = &((struct Seat *)seat)->status; + if (status->focused_view_title != NULL) + free(status->focused_view_title); + status->focused_view_title = calloc(1, strlen(title) + 1); + assert(status->focused_view_title != NULL); + strcpy(status->focused_view_title, title); +} + +void seat_mode(void *seat, struct zriver_seat_status_v1 *status_, const char *mode) { + struct SeatStatus *status = &((struct Seat *)seat)->status; + if (status->mode != NULL) + free(status->mode); + status->mode = calloc(1, strlen(mode) + 1); + assert(status->mode!= NULL); + strcpy(status->mode, mode); +} + +static const struct zriver_seat_status_v1_listener seat_status_listener = { + .focused_output = seat_focused_output, + .unfocused_output = seat_unfocused_output, + .focused_view = seat_focused_view, + .mode = seat_mode +}; + struct Seat *seat_create (struct wl_seat *wl_seat, uint32_t name) { struct Seat *seat = calloc(1, sizeof(struct Seat)); @@ -107,6 +146,11 @@ struct Seat *seat_create (struct wl_seat *wl_seat, uint32_t name) return seat; } +void seat_status_init(struct Seat *seat) { + seat->status_monitor = zriver_status_manager_v1_get_river_seat_status(context.status_manager, seat->wl_seat); + zriver_seat_status_v1_add_listener(seat->status_monitor, &seat_status_listener, seat); +} + void seat_destroy (struct Seat *seat) { struct Idle *idle, *tmp_i; @@ -116,6 +160,14 @@ void seat_destroy (struct Seat *seat) idle_destroy(idle); } + if (seat->status.focused_output != NULL) + free(seat->status.focused_view_title); + if (seat->status.mode != NULL) + free(seat->status.mode); + + if ( seat->status_monitor != NULL ) + zriver_seat_status_v1_destroy(seat->status_monitor); + assert(seat->wl_seat != NULL); wl_seat_destroy(seat->wl_seat); free(seat); diff --git a/src/seat.h b/src/seat.h index 4d83abe..2205da4 100644 --- a/src/seat.h +++ b/src/seat.h @@ -4,14 +4,23 @@ #include #include #include +#include "river-status-unstable-v1.h" + +struct SeatStatus { + struct wl_output *focused_output; + char *focused_view_title; + char *mode; +}; struct Seat { struct wl_list link; struct wl_seat *wl_seat; + struct zriver_seat_status_v1 *status_monitor; uint32_t name; struct wl_list idles; + struct SeatStatus status; }; struct Idle @@ -24,6 +33,7 @@ struct Idle }; struct Seat *seat_create (struct wl_seat *wl_seat, uint32_t name); +void seat_status_init(struct Seat *seat); void seat_destroy (struct Seat *seat); bool seat_add_idle (struct Seat *seat, SCM proc, uint32_t ms);