moduel/river: wip: new module; 'tags' status on Wayland compositor 'river'

This commit is contained in:
Daniel Eklöf 2020-07-23 19:00:42 +02:00
parent e04b678518
commit 7b2d524598
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 787 additions and 0 deletions

View file

@ -18,6 +18,7 @@ if backend_wayland
generated_wayland_protocols = []
foreach prot : [
'../external/wlr-layer-shell-unstable-v1.xml',
'../external/river-status-unstable-v1.xml',
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml']

116
external/river-status-unstable-v1.xml vendored Normal file
View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_status_unstable_v1">
<copyright>
Copyright 2020 Isaac Freund
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.
</copyright>
<interface name="zriver_status_manager_v1" version="1">
<description summary="manage river status objects">
A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_status_manager object">
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.
</description>
</request>
<request name="get_river_output_status">
<description summary="create an output status object">
This creates a new river_output_status object for the given wl_output.
</description>
<arg name="id" type="new_id" interface="zriver_output_status_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="get_river_seat_status">
<description summary="create a seat status object">
This creates a new river_seat_status object for the given wl_seat.
</description>
<arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
</interface>
<interface name="zriver_output_status_v1" version="1">
<description summary="track output tags and focus">
This interface allows clients to receive information about the current
windowing state of an output.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_output_status object">
This request indicates that the client will not use the
river_output_status object any more.
</description>
</request>
<event name="focused_tags">
<description summary="focused tags of the output">
Sent once binding the interface and again whenever the tag focus of
the output changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="view_tags">
<description summary="tag state of an output's views">
Sent once on binding the interface and again whenever the tag state
of the output changes.
</description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event>
</interface>
<interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_seat_status object">
This request indicates that the client will not use the
river_seat_status object any more.
</description>
</request>
<event name="focused_output">
<description summary="the seat focused an output">
Sent on binding the interface and again whenever an output gains focus.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="unfocused_output">
<description summary="the seat unfocused an output">
Sent whenever an output loses focus.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="focused_view">
<description summary="information on the focused view">
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.
</description>
<arg name="title" type="string" summary="title of the focused view"/>
</event>
</interface>
</protocol>

View file

@ -29,6 +29,12 @@ if backend_x11
}
endif
if backend_wayland
deps += {
'river': [[], []],
}
endif
foreach mod, data : deps
sources = data[0]
dep = data[1]

660
modules/river.c Normal file
View file

@ -0,0 +1,660 @@
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <poll.h>
#include <wayland-client.h>
#include <tllist.h>
#define LOG_MODULE "river"
#define LOG_ENABLE_DBG 1
#include "../log.h"
#include "../plugin.h"
#include "../particles/dynlist.h"
#include "river-status-unstable-v1.h"
#include "xdg-output-unstable-v1.h"
struct private;
struct output {
struct private *m;
struct wl_output *wl_output;
struct zxdg_output_v1 *xdg_output;
struct zriver_output_status_v1 *status;
uint32_t wl_name;
char *name;
/* Tags */
uint32_t occupied;
uint32_t focused;
};
struct seat {
struct private *m;
struct wl_seat *wl_seat;
struct zriver_seat_status_v1 *status;
uint32_t wl_name;
char *name;
char *title;
struct output *output;
};
struct private {
struct module *mod;
struct zxdg_output_manager_v1 *xdg_output_manager;
struct zriver_status_manager_v1 *status_manager;
struct particle *template;
struct particle *title;
tll(struct output) outputs;
tll(struct seat) seats;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->template->destroy(m->template);
if (m->title != NULL)
m->title->destroy(m->title);
free(m);
module_default_destroy(mod);
}
static struct exposable *
content(struct module *mod)
{
const struct private *m = mod->private;
mtx_lock(&m->mod->lock);
uint32_t output_focused = 0;
uint32_t seat_focused = 0;
uint32_t occupied = 0;
tll_foreach(m->outputs, it) {
const struct output *output = &it->item;
output_focused |= output->focused;
occupied |= output->occupied;
#if 0
/* TODO: river bug */
tll_foreach(m->seats, it2) {
const struct seat *seat = &it2->item;
if (seat->output == output) {
seat_focused |= output->focused;
}
}
#else
seat_focused |= output->focused;
#endif
}
const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0;
struct exposable *tag_parts[32 + seat_count];
for (unsigned i = 0; i < 32; i++) {
/* It's visible if any output has it focused */
bool visible = output_focused & (1u << i);
/* It's focused if any output that has seat focus has it focused */
bool focused = seat_focused & (1u << i);
const char *state = visible ? focused ? "focused" : "unfocused" : "invisible";
#if 0
LOG_DBG("tag: #%u, visible=%d, focused=%d, occupied=%d, state=%s",
i, visible, focused, occupied & (1u << i), state);
#endif
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_int(mod, "id", i + 1),
tag_new_bool(mod, "visible", visible),
tag_new_bool(mod, "focused", focused),
tag_new_bool(mod, "occupied", occupied & (1u << i)),
tag_new_string(mod, "state", state),
},
.count = 5,
};
tag_parts[i] = m->template->instantiate(m->template, &tags);
tag_set_destroy(&tags);
}
if (m->title != NULL) {
size_t i = 32;
tll_foreach(m->seats, it) {
const struct seat *seat = &it->item;
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "seat", seat->name),
tag_new_string(mod, "title", seat->title),
},
.count = 2,
};
tag_parts[i++] = m->title->instantiate(m->title, &tags);
tag_set_destroy(&tags);
}
}
mtx_unlock(&m->mod->lock);
return dynlist_exposable_new(tag_parts, 32 + seat_count, 0, 0);
}
static bool
verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
{
if (version >= wanted)
return true;
LOG_ERR("%s: need interface version %u, but compositor only implements %u",
iface, wanted, version);
return false;
}
static void
output_destroy(struct output *output)
{
free(output->name);
if (output->status != NULL)
zriver_output_status_v1_destroy(output->status);
if (output->xdg_output != NULL)
zxdg_output_v1_destroy(output->xdg_output);
if (output->wl_output != NULL)
wl_output_destroy(output->wl_output);
}
static void
seat_destroy(struct seat *seat)
{
free(seat->title);
free(seat->name);
if (seat->status != NULL)
zriver_seat_status_v1_destroy(seat->status);
if (seat->wl_seat != NULL)
wl_seat_destroy(seat->wl_seat);
}
static void
focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags)
{
struct output *output = data;
struct module *mod = output->m->mod;
LOG_DBG("output: %s: focused tags: 0x%08x", output->name, tags);
mtx_lock(&mod->lock);
{
output->focused = tags;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static void
view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
struct wl_array *tags)
{
struct output *output = data;
struct module *mod = output->m->mod;
mtx_lock(&mod->lock);
{
output->occupied = 0;
/* Each entry in the list is a view, and the value is the tags
* associated with that view */
uint32_t *set;
wl_array_for_each(set, tags) {
output->occupied |= *set;
}
LOG_DBG("output: %s: occupied tags: 0x%0x", output->name, output->occupied);
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static const struct zriver_output_status_v1_listener river_status_output_listener = {
.focused_tags = &focused_tags,
.view_tags = &view_tags,
};
static void
xdg_output_handle_logical_position(void *data,
struct zxdg_output_v1 *xdg_output,
int32_t x, int32_t y)
{
}
static void
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
int32_t width, int32_t height)
{
}
static void
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
{
}
static void
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
const char *name)
{
struct output *output = data;
struct module *mod = output->m->mod;
mtx_lock(&mod->lock);
{
free(output->name);
output->name = name != NULL ? strdup(name) : NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static void
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
const char *description)
{
}
static struct zxdg_output_v1_listener xdg_output_listener = {
.logical_position = xdg_output_handle_logical_position,
.logical_size = xdg_output_handle_logical_size,
.done = xdg_output_handle_done,
.name = xdg_output_handle_name,
.description = xdg_output_handle_description,
};
static void
instantiate_output(struct output *output)
{
assert(output->wl_output != NULL);
if (output->m->status_manager != NULL && output->status == NULL) {
output->status = zriver_status_manager_v1_get_river_output_status(
output->m->status_manager, output->wl_output);
if (output->status != NULL) {
zriver_output_status_v1_add_listener(
output->status, &river_status_output_listener, output);
}
}
if (output->m->xdg_output_manager != NULL && output->xdg_output == NULL) {
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
output->m->xdg_output_manager, output->wl_output);
if (output->xdg_output != NULL) {
zxdg_output_v1_add_listener(
output->xdg_output, &xdg_output_listener, output);
}
}
}
static void
focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *wl_output)
{
/* TODO: never called by river */
abort();
struct seat *seat = data;
struct private *m = seat->m;
struct module *mod = m->mod;
mtx_lock(&mod->lock);
{
struct output *output = NULL;
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
}
}
LOG_DBG("seat: %s: focused output: %s", seat->name, output != NULL ? output->name : "<unknown>");
if (output == NULL)
LOG_WARN("seat: %s: couldn't find output we are mapped on", seat->name);
seat->output = output;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static void
unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *wl_output)
{
struct seat *seat = data;
struct private *m = seat->m;
struct module *mod = m->mod;
mtx_lock(&mod->lock);
{
struct output *output = NULL;
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
}
}
LOG_DBG("seat: %s: unfocused output: %s", seat->name, output != NULL ? output->name : "<unknown>");
if (output == NULL)
LOG_WARN("seat: %s: couldn't find output we were unmapped from", seat->name);
seat->output = NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static void
focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title)
{
struct seat *seat = data;
struct module *mod = seat->m->mod;
LOG_DBG("seat: %s: focused view: %s", seat->name, title);
mtx_lock(&mod->lock);
{
free(seat->title);
seat->title = title != NULL ? strdup(title) : NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static const struct zriver_seat_status_v1_listener river_seat_status_listener = {
.focused_output = &focused_output,
.unfocused_output = &unfocused_output,
.focused_view = &focused_view,
};
static void
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
enum wl_seat_capability caps)
{
}
static void
seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
{
struct seat *seat = data;
struct module *mod = seat->m->mod;
mtx_lock(&mod->lock);
{
free(seat->name);
seat->name = name != NULL ? strdup(name) : NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};
static void
instantiate_seat(struct seat *seat)
{
assert(seat->wl_seat != NULL);
if (seat->m->status_manager == NULL)
return;
seat->status = zriver_status_manager_v1_get_river_seat_status(
seat->m->status_manager, seat->wl_seat);
if (seat->status == NULL)
return;
zriver_seat_status_v1_add_listener(
seat->status, &river_seat_status_listener, seat);
}
static void
handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
struct private *m = data;
if (strcmp(interface, wl_output_interface.name) == 0) {
const uint32_t required = 1;
if (!verify_iface_version(interface, version, required))
return;
struct wl_output *wl_output = wl_registry_bind(
registry, name, &wl_output_interface, required);
if (wl_output == NULL)
return;
mtx_lock(&m->mod->lock);
tll_push_back(m->outputs, ((struct output){.m = m, .wl_output = wl_output, .wl_name = name}));
instantiate_output(&tll_back(m->outputs));
mtx_unlock(&m->mod->lock);
}
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
const uint32_t required = 2;
if (!verify_iface_version(interface, version, required))
return;
m->xdg_output_manager = wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, required);
mtx_lock(&m->mod->lock);
tll_foreach(m->outputs, it)
instantiate_output(&it->item);
mtx_unlock(&m->mod->lock);
}
else if (strcmp(interface, wl_seat_interface.name) == 0) {
const uint32_t required = 2;
if (!verify_iface_version(interface, version, required))
return;
struct wl_seat *wl_seat = wl_registry_bind(
registry, name, &wl_seat_interface, required);
if (wl_seat == NULL)
return;
mtx_lock(&m->mod->lock);
tll_push_back(m->seats, ((struct seat){.m = m, .wl_seat = wl_seat, .wl_name = name}));
struct seat *seat = &tll_back(m->seats);
wl_seat_add_listener(wl_seat, &seat_listener, seat);
instantiate_seat(seat);
mtx_unlock(&m->mod->lock);
}
else if (strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
const uint32_t required = 1;
if (!verify_iface_version(interface, version, required))
return;
m->status_manager = wl_registry_bind(
registry, name, &zriver_status_manager_v1_interface, required);
mtx_lock(&m->mod->lock);
tll_foreach(m->outputs, it)
instantiate_output(&it->item);
tll_foreach(m->seats, it)
instantiate_seat(&it->item);
mtx_unlock(&m->mod->lock);
}
}
static void
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
struct private *m = data;
mtx_lock(&m->mod->lock);
tll_foreach(m->outputs, it) {
if (it->item.wl_name == name) {
output_destroy(&it->item);
tll_remove(m->outputs, it);
mtx_unlock(&m->mod->lock);
return;
}
}
tll_foreach(m->seats, it) {
if (it->item.wl_name == name) {
seat_destroy(&it->item);
tll_remove(m->seats, it);
mtx_unlock(&m->mod->lock);
return;
}
}
mtx_unlock(&m->mod->lock);
}
static const struct wl_registry_listener registry_listener = {
.global = &handle_global,
.global_remove = &handle_global_remove,
};
static int
run(struct module *mod)
{
struct private *m = mod->private;
int ret = 1;
struct wl_display *display = NULL;
struct wl_registry *registry = NULL;
if ((display = wl_display_connect(NULL)) == NULL) {
LOG_ERR("no Wayland compositor running?");
goto out;
}
if ((registry = wl_display_get_registry(display)) == NULL ||
wl_registry_add_listener(registry, &registry_listener, m) != 0)
{
LOG_ERR("failed to get Wayland registry");
goto out;
}
wl_display_roundtrip(display);
if (m->status_manager == NULL) {
LOG_ERR("river does not appear to be running");
goto out;
}
while (true) {
wl_display_flush(display);
struct pollfd fds[] = {
{.fd = mod->abort_fd, .events = POLLIN},
{.fd = wl_display_get_fd(display), .events = POLLIN},
};
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
if (r == -1) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if ((fds[0].revents & POLLIN) || (fds[0].revents & POLLHUP))
break;
if (fds[1].revents & POLLHUP) {
LOG_ERRNO("disconnected from Wayland compositor");
break;
}
assert(fds[1].revents & POLLIN);
wl_display_dispatch(display);
}
ret = 0;
out:
tll_foreach(m->seats, it)
seat_destroy(&it->item);
tll_free(m->seats);
tll_foreach(m->outputs, it)
output_destroy(&it->item);
tll_free(m->outputs);
if (m->xdg_output_manager != NULL)
zxdg_output_manager_v1_destroy(m->xdg_output_manager);
if (m->status_manager != NULL)
zriver_status_manager_v1_destroy(m->status_manager);
if (registry != NULL)
wl_registry_destroy(registry);
if (display != NULL)
wl_display_disconnect(display);
return ret;
}
static struct module *
river_new(struct particle *template, struct particle *title)
{
struct private *m = calloc(1, sizeof(*m));
m->template = template;
m->title = title;
struct module *mod = module_common_new();
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
m->mod = mod;
return mod;
}
static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited)
{
const struct yml_node *c = yml_get_value(node, "content");
const struct yml_node *title = yml_get_value(node, "title");
return river_new(
conf_to_particle(c, inherited),
title != NULL ? conf_to_particle(title, inherited) : NULL);
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"title", false, &conf_verify_particle},
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_river_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_river_iface")));
#endif

View file

@ -41,6 +41,7 @@ EXTERN_MODULE(label);
EXTERN_MODULE(mpd);
EXTERN_MODULE(network);
EXTERN_MODULE(removables);
EXTERN_MODULE(river);
EXTERN_MODULE(sway_xkb);
EXTERN_MODULE(xkb);
EXTERN_MODULE(xwindow);
@ -114,6 +115,9 @@ init(void)
REGISTER_CORE_MODULE(mpd, mpd);
REGISTER_CORE_MODULE(network, network);
REGISTER_CORE_MODULE(removables, removables);
#if defined(HAVE_PLUGIN_river)
REGISTER_CORE_MODULE(river, river);
#endif
REGISTER_CORE_MODULE(sway-xkb, sway_xkb);
#if defined(HAVE_PLUGIN_xkb)
REGISTER_CORE_MODULE(xkb, xkb);