From 16b7dd00529d7fb6e607e9346c496534de3282ae Mon Sep 17 00:00:00 2001 From: sewn Date: Fri, 28 Jun 2024 09:06:38 +0300 Subject: [PATCH] initial commit --- .gitignore | 3 + LICENSE | 21 + Makefile | 61 +++ README.md | 32 ++ config.h | 10 + dam-demo.png | Bin 0 -> 4868 bytes dam.c | 701 ++++++++++++++++++++++++++++++++ drwl.h | 293 +++++++++++++ poolbuf.h | 112 +++++ river-control-unstable-v1.xml | 85 ++++ river-status-unstable-v1.xml | 148 +++++++ wlr-layer-shell-unstable-v1.xml | 390 ++++++++++++++++++ 12 files changed, 1856 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.h create mode 100644 dam-demo.png create mode 100644 dam.c create mode 100644 drwl.h create mode 100644 poolbuf.h create mode 100644 river-control-unstable-v1.xml create mode 100644 river-status-unstable-v1.xml create mode 100644 wlr-layer-shell-unstable-v1.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d577a31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dam +*.o +*-protocol.* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4754b85 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2023-2024 sewn +Copyright (c) 2015 Eric Pruitt + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5484260 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +.POSIX: + +PREFIX = /usr/local + +PKG_CONFIG = pkg-config + +PKGS = wayland-client fcft pixman-1 +INCS = `$(PKG_CONFIG) --cflags $(PKGS)` +LIBS = `$(PKG_CONFIG) --libs $(PKGS)` + +FPCFLAGS = -pedantic -Wall $(INCS) $(CPPFLAGS) $(CFLAGS) +LDLIBS = $(LIBS) + +SRC = dam.o xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o \ + river-control-unstable-v1-protocol.o river-status-unstable-v1-protocol.o +OBJ = $(SRC:.c=.o) + +all: dam + +.c.o: + $(CC) -o $@ -c $(FPCFLAGS) -c $< + +dam.o: wlr-layer-shell-unstable-v1-protocol.h \ + river-control-unstable-v1-protocol.h river-status-unstable-v1-protocol.h + +dam: $(OBJ) + $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) + +WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` +WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` + +xdg-shell-protocol.c: + $(WAYLAND_SCANNER) private-code $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +xdg-shell-protocol.h: + $(WAYLAND_SCANNER) client-header $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +wlr-layer-shell-unstable-v1-protocol.c: + $(WAYLAND_SCANNER) private-code wlr-layer-shell-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) client-header wlr-layer-shell-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.o: xdg-shell-protocol.o +river-control-unstable-v1-protocol.c: + $(WAYLAND_SCANNER) private-code river-control-unstable-v1.xml $@ +river-control-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) client-header river-control-unstable-v1.xml $@ +river-status-unstable-v1-protocol.c: + $(WAYLAND_SCANNER) private-code river-status-unstable-v1.xml $@ +river-status-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) client-header river-status-unstable-v1.xml $@ + +clean: + rm -f dam *.o *-protocol.* + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dam $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dam + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dam + +.PHONY: all clean install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..34a61fb --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# dam + +dam is a itsy-bitsy dwm-esque bar for [river]. + +![](dam-demo.png) + +To use a status-bar, you can pass in status text via stdin: +``` +slstatus -s | dam +``` + +## Building + +To build dam first ensure that you have the following dependencies: + +* wayland +* wayland-protocols +* fcft +* pixman +* pkg-config + +Afterwards, run: +``` +make +make install +``` + +## Usage + +Run `dam`. + +[river]: https://codeberg.org/river diff --git a/config.h b/config.h new file mode 100644 index 0000000..45aac10 --- /dev/null +++ b/config.h @@ -0,0 +1,10 @@ +/* appearance */ +static const char *fonts[] = { "monospace:size=10" }; +static uint32_t colors[][3] = { + /* fg bg */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff }, +}; + +/* tagging */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; diff --git a/dam-demo.png b/dam-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..3b47e20c3baec4f653ad9bfe6d0721e4f3475243 GIT binary patch literal 4868 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drwl.h" +#include "poolbuf.h" +#include "river-status-unstable-v1-protocol.h" +#include "river-control-unstable-v1-protocol.h" +#include "wlr-layer-shell-unstable-v1-protocol.h" + +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TEXTW(bar, text) (drwl_font_getwidth(bar->drw, text) + bar->lrpad) + +enum { ClkTagBar, ClkLayout, ClkMode, ClkTitle, ClkStatus }; /* clicks */ + +enum { SchemeNorm, SchemeSel }; + +typedef struct { + uint32_t wl_name; + struct wl_output *wl_output; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + struct zriver_output_status_v1 *river_output_status; + Drwl *drw; + uint32_t width, height, scale; + int lrpad; + + uint32_t mtags, ctags, urg; + char *layout, *title; + + bool selected; + + struct wl_list link; +} Bar; + +typedef struct { + uint32_t wl_name; + struct wl_seat *wl_seat; + struct wl_pointer *pointer; + struct zriver_seat_status_v1 *river_seat_status; + Bar *bar; + char *mode; + int pointer_x, pointer_y; + uint32_t button; + + struct wl_list link; +} Seat; + +typedef struct { + unsigned int click; + unsigned int button; + void (*func)(uint32_t); + uint32_t arg; +} Button; + +#include "config.h" + +static struct wl_display *display; +static struct wl_registry *registry; +static struct wl_shm *shm; +static struct wl_compositor *compositor; +static struct zwlr_layer_shell_v1 *layer_shell; +static struct zriver_status_manager_v1 *river_status_manager; +static struct zriver_control_v1 *river_control; + +static struct wl_list seats, bars; +static char stext[256]; + +static void +noop() +{ + /* + * :3c + */ +} + +static void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +static void +bar_load_fonts(Bar *bar) +{ + char fontattrs[12]; + + drwl_destroy_font(bar->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%d", 96 * bar->scale); + if (!(drwl_load_font(bar->drw, LENGTH(fonts), fonts, fontattrs))) + die("failed to load fonts"); + bar->lrpad = bar->drw->font->height; + bar->height = bar->drw->font->height + 2; +} + +static void +bar_draw(Bar *bar) +{ + int i; + int tw = 0; + int x = 0, w; + int boxs = bar->drw->font->height / 9; + int boxw = bar->drw->font->height / 6 + 2; + PoolBuf *buf; + Seat *seat; + + if (bar->width < 1 || bar->height < 1) + return; + + if (!(buf = poolbuf_create(shm, bar->width, bar->height))) + die("poolbuf_create:"); + drwl_prepare_drawing(bar->drw, bar->width, bar->height, buf->data, buf->stride); + drwl_setscheme(bar->drw, colors[SchemeNorm]); + + /* draw status first so it can be overdrawn by tags later */ + if (bar->selected) { /* status is only drawn on selected monitor */ + drwl_setscheme(bar->drw, colors[SchemeNorm]); + tw = TEXTW(bar, stext) - bar->lrpad + 2; /* 2px right padding */ + drwl_text(bar->drw, bar->width - tw, 0, tw, bar->height, 0, stext, 0); + } + + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(bar, tags[i]); + drwl_setscheme(bar->drw, colors[bar->mtags & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(bar->drw, x, 0, w, bar->height, bar->lrpad / 2, tags[i], bar->urg & 1 << i); + if (bar->ctags & 1 << i) + drwl_rect(bar->drw, x + boxs, boxs, boxw, boxw, 0, + bar->urg & 1 << i); + x += w; + } + + if (bar->layout) { + w = TEXTW(bar, bar->layout); + drwl_setscheme(bar->drw, colors[SchemeNorm]); + x = drwl_text(bar->drw, x, 0, w, bar->height, bar->lrpad / 2, bar->layout, 0); + } + + wl_list_for_each(seat, &seats, link) { + w = TEXTW(bar, seat->mode); + drwl_setscheme(bar->drw, colors[SchemeNorm]); + x = drwl_text(bar->drw, x, 0, w, bar->height, bar->lrpad / 2, seat->mode, 1); + } + + if ((w = bar->width - tw - x) > bar->height) { + if (bar->title) { + drwl_setscheme(bar->drw, colors[bar->selected ? SchemeSel : SchemeNorm]); + drwl_text(bar->drw, x, 0, w, bar->height, bar->lrpad / 2, bar->title, 0); + } else { + drwl_setscheme(bar->drw, colors[SchemeNorm]); + drwl_rect(bar->drw, x, 0, w, bar->height, 1, 1); + } + } + + drwl_finish_drawing(bar->drw); + wl_surface_set_buffer_scale(bar->surface, bar->scale); + wl_surface_attach(bar->surface, buf->wl_buf, 0, 0); + wl_surface_damage_buffer(bar->surface, 0, 0, bar->width, bar->height); + poolbuf_destroy(buf); + wl_surface_commit(bar->surface); +} + +static void +bars_draw() +{ + Bar *bar; + + wl_list_for_each(bar, &bars, link) + bar_draw(bar); +} + +static void +bar_destroy(Bar *bar) +{ + wl_list_remove(&bar->link); + free(bar->layout); + free(bar->title); + drwl_destroy(bar->drw); + zriver_output_status_v1_destroy(bar->river_output_status); + zwlr_layer_surface_v1_destroy(bar->layer_surface); + wl_surface_destroy(bar->surface); + wl_output_destroy(bar->wl_output); +} + +static void +layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) +{ + Bar *bar = data; + + bar->width = width * bar->scale; + bar->height = height * bar->scale; + zwlr_layer_surface_v1_ack_configure(bar->layer_surface, serial); + bar_draw(bar); +} + +static void +layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *layer_surface) +{ + Bar *bar = data; + bar_destroy(bar); +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void +surface_handle_preferred_scale(void *data, + struct wl_surface *wl_surface, int32_t scale) +{ + Bar *bar = data; + bar->scale = scale; + bar_load_fonts(bar); + zwlr_layer_surface_v1_set_size(bar->layer_surface, 0, bar->height / bar->scale); +} + +static const struct wl_surface_listener surface_listener = { + .enter = noop, + .leave = noop, + .preferred_buffer_scale = surface_handle_preferred_scale, + .preferred_buffer_transform = noop, +}; + +static void +river_output_status_handle_focused_tags(void *data, + struct zriver_output_status_v1 *output_status, uint32_t tags) +{ + Bar *bar = data; + + bar->mtags = tags; + + bar_draw(bar); +} + +static void +river_output_status_handle_urgent_tags(void *data, + struct zriver_output_status_v1 *output_status, uint32_t tags) +{ + Bar *bar = data; + + bar->urg = tags; + + bar_draw(bar); +} + +static void +river_output_status_handle_view_tags(void *data, + struct zriver_output_status_v1 *output_status, struct wl_array *wl_array) +{ + uint32_t *vt; + Bar *bar = data; + + bar->ctags = 0; + wl_array_for_each(vt, wl_array) + bar->ctags |= *vt; + + bar_draw(bar); +} + +static void +river_output_status_handle_layout_name(void *data, + struct zriver_output_status_v1 *output_status, const char *name) +{ + Bar *bar = data; + + if (bar->layout) + free(bar->layout); + bar->layout = strdup(name); + + bar_draw(bar); +} + +static void +river_output_status_handle_layout_name_clear(void *data, + struct zriver_output_status_v1 *output_status) +{ + Bar *bar = data; + + if (bar->layout) + free(bar->layout); + + bar_draw(bar); +} + +static const struct zriver_output_status_v1_listener river_output_status_listener = { + .focused_tags = river_output_status_handle_focused_tags, + .urgent_tags = river_output_status_handle_urgent_tags, + .view_tags = river_output_status_handle_view_tags, + .layout_name = river_output_status_handle_layout_name, + .layout_name_clear = river_output_status_handle_layout_name_clear +}; + + +static void +river_seat_status_handle_focused_output(void *data, + struct zriver_seat_status_v1 *seat_status, struct wl_output *wl_output) +{ + Bar *bar; + Seat *seat = data; + + wl_list_for_each(bar, &bars, link) { + if (bar->wl_output != wl_output) + continue; + + seat->bar = bar; + seat->bar->selected = true; + bar_draw(bar); + break; + } +} + +static void +river_seat_status_handle_unfocused_output(void *data, struct zriver_seat_status_v1 *seat_status, + struct wl_output *wl_output) +{ + Seat *seat = data; + + if (!seat->bar) + return; + + seat->bar->selected = false; + bar_draw(seat->bar); + seat->bar = NULL; +} + +static void +river_seat_status_handle_focused_view(void *data, + struct zriver_seat_status_v1 *seat_status, const char *title) +{ + Seat *seat = data; + + if (!seat->bar) + return; + + if (seat->bar->title) + free(seat->bar->title); + + seat->bar->title = strdup(title); + bar_draw(seat->bar); +} + +static void +river_seat_status_handle_mode(void *data, + struct zriver_seat_status_v1 *seat_status, const char *name) +{ + Seat *seat = data; + + if (seat->mode) + free(seat->mode); + + seat->mode = strdup(name); + bars_draw(); +} + +static const struct zriver_seat_status_v1_listener river_seat_status_listener = { + .focused_output = river_seat_status_handle_focused_output, + .unfocused_output = river_seat_status_handle_unfocused_output, + .focused_view = river_seat_status_handle_focused_view, + .mode = river_seat_status_handle_mode, +}; + +static void +pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + Seat *seat = data; + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); +} + +static void +pointer_handle_frame(void *data, struct wl_pointer *pointer) +{ + int lw, mw = 0; + unsigned int i = 0, /* j = 0, */ x = 0; + unsigned int click; + /* Seat *iter; */ + Seat *seat = data; + char tagbuf[4]; + + if (!seat->button || !seat->bar) + return; + + lw = TEXTW(seat->bar, seat->bar->layout); + + do + x += TEXTW(seat->bar, tags[i]); + while (seat->pointer_x >= x && ++i < LENGTH(tags)); + +/* + wl_list_for_each(iter, &seats, link) { + mw += TEXTW(seat->bar, iter->mode); + if (seat->pointer_x < x + mw) + j++; + } +*/ + + if (i < LENGTH(tags)) + click = ClkTagBar; + else if (seat->pointer_x > x + lw && seat->pointer_x < x + lw + mw) + click = ClkMode; + else if (seat->pointer_x < x + lw) + click = ClkLayout; + else if (seat->pointer_x > seat->bar->width - (int)TEXTW(seat->bar, stext)) + click = ClkStatus; + else + click = ClkTitle; + + switch (click) { + case ClkTagBar: + zriver_control_v1_add_argument(river_control, + seat->button == BTN_LEFT ? "set-focused-tags" : + seat->button == BTN_MIDDLE ? "toggle-focused-tags" : + "set-view-tags"); + snprintf(tagbuf, sizeof(tagbuf), "%d", 1 << i); + zriver_control_v1_add_argument(river_control, tagbuf); + zriver_control_v1_run_command(river_control, seat->wl_seat); + break; + case ClkLayout: + /* TODO: cannot disable rivertile; change layout to 'floating' */ + break; + case ClkMode: + /* TODO: river has no method of getting a list of modes */ + break; + case ClkTitle: + zriver_control_v1_add_argument(river_control, "zoom"); + zriver_control_v1_run_command(river_control, seat->wl_seat); + break; + case ClkStatus: + /* dwm spawns termcmd */ + break; + } +} + +static void +pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + Seat *seat = data; + seat->button = state == WL_POINTER_BUTTON_STATE_PRESSED ? button : 0; +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = noop, + .leave = noop, + .motion = pointer_motion, + .axis = noop, + .frame = pointer_handle_frame, + .axis_discrete = noop, + .axis_source = noop, + .axis_stop = noop, + .button = pointer_handle_button, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + Seat *seat = data; + + if (!(caps & WL_SEAT_CAPABILITY_POINTER)) + return; + + seat->pointer = wl_seat_get_pointer(seat->wl_seat); + wl_pointer_add_listener(seat->pointer, &pointer_listener, seat); +} + + +static void +seat_destroy(Seat *seat) +{ + wl_list_remove(&seat->link); + zriver_seat_status_v1_destroy(seat->river_seat_status); + wl_pointer_destroy(seat->pointer); + wl_seat_destroy(seat->wl_seat); +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = noop, +}; + +static void +registry_handle_global(void *data, struct wl_registry *wl_registry, + uint32_t name, const char *interface, uint32_t version) +{ + if (!strcmp(interface, wl_compositor_interface.name)) + compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + else if (!strcmp(interface, wl_shm_interface.name)) + shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) + layer_shell = wl_registry_bind(wl_registry, name, &zwlr_layer_shell_v1_interface, 2); + else if (!strcmp(interface, wl_seat_interface.name)) { + Seat *seat = calloc(1, sizeof(Seat)); + seat->wl_name = name; + seat->wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 5); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + wl_list_insert(&seats, &seat->link); + } else if (!strcmp(interface, zriver_control_v1_interface.name)) + river_control = wl_registry_bind(registry, name, &zriver_control_v1_interface, 1); + else if (!strcmp(interface, zriver_status_manager_v1_interface.name)) + river_status_manager = wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, 4); + else if (!strcmp(interface, wl_output_interface.name)) { + Bar *bar = calloc(1, sizeof(Bar)); + bar->scale = 1; + bar->wl_name = name; + bar->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 2); + wl_list_insert(&bars, &bar->link); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + Seat *seat; + Bar *bar; + + wl_list_for_each(seat, &seats, link) { + if (seat->wl_name == name) { + seat_destroy(seat); + return; + } + } + wl_list_for_each(bar, &bars, link) { + if (bar->wl_name == name) { + bar_destroy(bar); + return; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +static void +readstdin(void) +{ + ssize_t n; + + n = read(STDIN_FILENO, stext, sizeof(stext) - 1); + if (n < 0) + die("read:"); + + stext[n] = '\0'; + stext[strcspn(stext, "\n")] = '\0'; + bars_draw(); +} + +static void +setup(void) +{ + Seat *seat; + Bar *bar; + + if (!(display = wl_display_connect(NULL))) + die("failed to connect to wayland"); + + wl_list_init(&bars); + wl_list_init(&seats); + + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_roundtrip(display); + + if (!compositor || !shm || !layer_shell || !river_status_manager || !river_control) + die("unsupported compositor"); + + drwl_init(); + + wl_list_for_each(seat, &seats, link) { + seat->river_seat_status = zriver_status_manager_v1_get_river_seat_status( + river_status_manager, seat->wl_seat); + zriver_seat_status_v1_add_listener(seat->river_seat_status, + &river_seat_status_listener, seat); + } + + wl_list_for_each(bar, &bars, link) { + if (!(bar->drw = drwl_create())) + die("failed to create drwl context"); + bar_load_fonts(bar); + + bar->river_output_status = zriver_status_manager_v1_get_river_output_status( + river_status_manager, bar->wl_output); + zriver_output_status_v1_add_listener(bar->river_output_status, + &river_output_status_listener, bar); + + bar->surface = wl_compositor_create_surface(compositor); + wl_surface_add_listener(bar->surface, &surface_listener, NULL); + + bar->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + layer_shell, bar->surface, bar->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "bar"); + zwlr_layer_surface_v1_add_listener( + bar->layer_surface, &layer_surface_listener, bar); + zwlr_layer_surface_v1_set_size(bar->layer_surface, 0, bar->height); + zwlr_layer_surface_v1_set_exclusive_zone(bar->layer_surface, bar->height); + zwlr_layer_surface_v1_set_anchor(bar->layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + wl_surface_commit(bar->surface); + } +} + +static void +run(void) +{ + struct pollfd pfds[] = { + { .fd = wl_display_get_fd(display), .events = POLLIN }, + { .fd = STDIN_FILENO, .events = POLLIN }, + }; + + for (;;) { + if (wl_display_prepare_read(display) < 0) + if (wl_display_dispatch_pending(display) < 0) + die("wl_display_dispatch_pending:"); + + if (wl_display_flush(display) < 0) + die("wl_display_flush:"); + + if (poll(pfds, 2, -1) < 0) { + wl_display_cancel_read(display); + die("poll:"); + } + + if (pfds[1].revents & POLLHUP) { + pfds[1].fd = -1; + memset(stext, 0, LENGTH(stext)); + bars_draw(); + } + if (pfds[1].revents & POLLIN) + readstdin(); + + if (!(pfds[0].revents & POLLIN)) { + wl_display_cancel_read(display); + continue; + } + + if (wl_display_read_events(display) < 0) + die("wl_display_read_events:"); + if (wl_display_dispatch_pending(display) < 0) + die("wl_display_dispatch_pending"); + } +} + +static void +cleanup(void) +{ + Seat *seat; + Bar *bar; + + wl_list_for_each(seat, &seats, link) + seat_destroy(seat); + wl_list_for_each(bar, &bars, link) + bar_destroy(bar); + + drwl_fini(); + zriver_control_v1_destroy(river_control); + zriver_status_manager_v1_destroy(river_status_manager); + zwlr_layer_shell_v1_destroy(layer_shell); + wl_compositor_destroy(compositor); + wl_shm_destroy(shm); + wl_registry_destroy(registry); + wl_display_disconnect(display); +} + +int +main(int argc, char *argv[]) +{ + setup(); + run(); + cleanup(); + return EXIT_SUCCESS; +} diff --git a/drwl.h b/drwl.h new file mode 100644 index 0000000..d30631b --- /dev/null +++ b/drwl.h @@ -0,0 +1,293 @@ +/* + * drwl - https://codeberg.org/sewn/drwl + * See LICENSE file for copyright and license details. + */ +#pragma once + +#include +#include +#include + +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +enum { ColFg, ColBg }; /* colorscheme index */ + +typedef struct { + pixman_image_t *pix; + struct fcft_font *font; + uint32_t *scheme; +} Drwl; + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const uint32_t utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const uint32_t utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static inline uint32_t +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static inline size_t +utf8decode(const char *c, uint32_t *u) +{ + size_t i, j, len, type; + uint32_t udecoded; + + *u = UTF_INVALID; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < UTF_SIZ && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + if (!BETWEEN(*u, utfmin[len], utfmax[len]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return len; +} + +static int +drwl_init(void) +{ + fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); + return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); +} + +/* + * Caller must call drwl_init before drwl_create, and + * drwl_destroy on returned context after finalizing usage. + */ +static Drwl * +drwl_create(void) +{ + Drwl *drwl; + + if (!(drwl = calloc(1, sizeof(Drwl)))) + return NULL; + + return drwl; +} + +static void +drwl_setfont(Drwl *drwl, struct fcft_font *font) +{ + if (drwl) + drwl->font = font; +} + +/* + * Returned font is set within the drawing context if given. + * Caller must call drwl_destroy_font on returned font when done using it, + * otherwise use drwl_destroy when instead given a drwl context. + */ +static struct fcft_font * +drwl_load_font(Drwl *drwl, size_t fontcount, + const char *fonts[static fontcount], const char *attributes) +{ + struct fcft_font *font = fcft_from_name(fontcount, fonts, attributes); + if (drwl) + drwl_setfont(drwl, font); + return font; +} + +static void +drwl_destroy_font(struct fcft_font *font) +{ + fcft_destroy(font); +} + +static inline pixman_color_t +convert_color(uint32_t clr) +{ + return (pixman_color_t){ + ((clr >> 24) & 0xFF) * 0x101, + ((clr >> 16) & 0xFF) * 0x101, + ((clr >> 8) & 0xFF) * 0x101, + (clr & 0xFF) * 0x101 + }; +} + +static void +drwl_setscheme(Drwl *drwl, uint32_t *scm) +{ + if (drwl) + drwl->scheme = scm; +} + +static inline int +drwl_stride(unsigned int width) +{ + return (((PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) * width + 7) / 8 + 4 - 1) & -4); +} + +/* + * Caller must call drwl_finish_drawing when finished drawing. + * Parameter stride can be calculated using drwl_stride. + */ +static void +drwl_prepare_drawing(Drwl *drwl, unsigned int w, unsigned int h, + uint32_t *bits, int stride) +{ + pixman_region32_t clip; + + if (!drwl) + return; + + drwl->pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, w, h, bits, stride); + pixman_region32_init_rect(&clip, 0, 0, w, h); + pixman_image_set_clip_region32(drwl->pix, &clip); + pixman_region32_fini(&clip); +} + +static void +drwl_rect(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + int filled, int invert) +{ + pixman_color_t clr; + if (!drwl || !drwl->scheme || !drwl->pix) + return; + + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + if (filled) + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->pix, &clr, 1, + &(pixman_rectangle16_t){x, y, w, h}); + else + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->pix, &clr, 4, + (pixman_rectangle16_t[4]){ + { x, y, w, 1 }, + { x, y + h - 1, w, 1 }, + { x, y, 1, h }, + { x + w - 1, y, 1, h }}); +} + +static int +drwl_text(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert) +{ + int ty; + int utf8charlen, render = x || y || w || h; + long x_kern; + uint32_t cp = 0, last_cp = 0; + pixman_color_t clr; + pixman_image_t *fg_pix = NULL; + int noellipsis = 0; + const struct fcft_glyph *glyph, *eg; + + if (!drwl || (render && (!drwl->scheme || !w || !drwl->pix)) || !text || !drwl->font) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + fg_pix = pixman_image_create_solid_fill(&clr); + + drwl_rect(drwl, x, y, w, h, 1, !invert); + + x += lpad; + w -= lpad; + } + + // U+2026 == … + eg = fcft_rasterize_char_utf32(drwl->font, 0x2026, FCFT_SUBPIXEL_DEFAULT); + + while (*text) { + utf8charlen = utf8decode(text, &cp); + + glyph = fcft_rasterize_char_utf32(drwl->font, cp, FCFT_SUBPIXEL_DEFAULT); + if (!glyph) + continue; + + x_kern = 0; + if (last_cp) + fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); + last_cp = cp; + + ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; + + /* draw ellipsis if remaining text doesn't fit */ + if (!noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && *(text + 1) != '\0') { + if (drwl_text(drwl, 0, 0, 0, 0, 0, text, 0) + - glyph->advance.x < eg->advance.x) { + noellipsis = 1; + } else { + w -= eg->advance.x; + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->pix, 0, 0, 0, 0, + x + eg->x, ty - eg->y, eg->width, eg->height); + } + } + + if ((x_kern + glyph->advance.x) > w) + break; + + x += x_kern; + + if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) + // pre-rendered glyphs (eg. emoji) + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, drwl->pix, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + else if (render) + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->pix, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + + text += utf8charlen; + x += glyph->advance.x; + w -= glyph->advance.x; + } + + if (render) + pixman_image_unref(fg_pix); + + return x + (render ? w : 0); +} + +static unsigned int +drwl_font_getwidth(Drwl *drwl, const char *text) +{ + if (!drwl || !drwl->font || !text) + return 0; + return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); +} + +static void +drwl_finish_drawing(Drwl *drwl) +{ + if (drwl && drwl->pix) + pixman_image_unref(drwl->pix); +} + +static void +drwl_destroy(Drwl *drwl) +{ + if (drwl->pix) + pixman_image_unref(drwl->pix); + if (drwl->font) + drwl_destroy_font(drwl->font); + free(drwl); +} + +static void +drwl_fini(void) +{ + fcft_fini(); +} diff --git a/poolbuf.h b/poolbuf.h new file mode 100644 index 0000000..3cf9e7e --- /dev/null +++ b/poolbuf.h @@ -0,0 +1,112 @@ +/* + * poolbuf - https://codeberg.org/sewn/drwl + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include + +#include "drwl.h" + +typedef struct { + struct wl_buffer *wl_buf; + int32_t stride; + int32_t size; + void *data; +} PoolBuf; + +#if defined(__linux__) || defined(___FreeBSD__) +#ifdef __linux__ +#include +#endif +#define __POOLBUF_HAS_MEMFD_CREATE +int memfd_create(const char *, unsigned); +#else +static void +randname(char *buf) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int +create_shm(void) +{ + char name[] = "/drwl-XXXXXX"; + int retries = 100; + + do { + randname(name + sizeof(name) - 7); + --retries; + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + return -1; +} +#endif + +/* Caller must call poolbuf_destroy after finalizing usage */ +static PoolBuf * +poolbuf_create(struct wl_shm *shm, int32_t width, int32_t height) +{ + int fd; + void *data; + struct wl_shm_pool *shm_pool; + struct wl_buffer *wl_buf; + int32_t stride = drwl_stride(width); + int32_t size = stride * height; + PoolBuf *buf; + +#ifdef __POOLBUF_HAS_MEMFD_CREATE + fd = memfd_create("drwl-shm-buffer-pool", + MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL); +#else + fd = create_shm(); +#endif + if (fd < 0) + return NULL; + + if ((ftruncate(fd, size)) < 0) { + close(fd); + return NULL; + } + + data = mmap(NULL, size, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + close(fd); + return NULL; + } + + shm_pool = wl_shm_create_pool(shm, fd, size); + wl_buf = wl_shm_pool_create_buffer(shm_pool, 0, + width, height, stride, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(shm_pool); + close(fd); + + buf = calloc(1, sizeof(PoolBuf)); + buf->wl_buf = wl_buf; + buf->stride = stride; + buf->size = size; + buf->data = data; + return buf; +} + +static void +poolbuf_destroy(PoolBuf *buf) +{ + wl_buffer_destroy(buf->wl_buf); + munmap(buf->data, buf->size); + free(buf); +} diff --git a/river-control-unstable-v1.xml b/river-control-unstable-v1.xml new file mode 100644 index 0000000..aa5fc4d --- /dev/null +++ b/river-control-unstable-v1.xml @@ -0,0 +1,85 @@ + + + + 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. + + + + + This interface allows clients to run compositor commands and receive a + success/failure response with output or a failure message respectively. + + Each command is built up in a series of add_argument requests and + executed with a run_command request. The first argument is the command + to be run. + + A complete list of commands should be made available in the man page of + the compositor. + + + + + This request indicates that the client will not use the + river_control object any more. Objects that have been created + through this instance are not affected. + + + + + + Arguments are stored by the server in the order they were sent until + the run_command request is made. + + + + + + + Execute the command built up using the add_argument request for the + given seat. + + + + + + + + + This object is created by the run_command request. Exactly one of the + success or failure events will be sent. This object will be destroyed + by the compositor after one of the events is sent. + + + + + Sent when the command has been successfully received and executed by + the compositor. Some commands may produce output, in which case the + output argument will be a non-empty string. + + + + + + + Sent when the command could not be carried out. This could be due to + sending a non-existent command, no command, not enough arguments, too + many arguments, invalid arguments, etc. + + + + + diff --git a/river-status-unstable-v1.xml b/river-status-unstable-v1.xml new file mode 100644 index 0000000..e9629dd --- /dev/null +++ b/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/wlr-layer-shell-unstable-v1.xml b/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, 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. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + +