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);