summaryrefslogtreecommitdiff
path: root/lib/renderers
diff options
context:
space:
mode:
authorJari Vetoniemi <mailroxas@gmail.com>2014-10-25 01:38:30 +0300
committerJari Vetoniemi <mailroxas@gmail.com>2014-10-25 01:38:30 +0300
commit3f5e21a83f525586d1ee5b1bcd04cc2e50350403 (patch)
tree226cb57348ae89bc62df34cfd7ee2afe43f03c45 /lib/renderers
parentf8d97efb8f1bc5a7e764e1bb14f0e97ac36de68d (diff)
downloadbemenu-3f5e21a83f525586d1ee5b1bcd04cc2e50350403.tar.gz
bemenu-3f5e21a83f525586d1ee5b1bcd04cc2e50350403.tar.bz2
bemenu-3f5e21a83f525586d1ee5b1bcd04cc2e50350403.zip
Cleanup wayland renderer and plugin support.
Diffstat (limited to 'lib/renderers')
-rw-r--r--lib/renderers/cairo.h101
-rw-r--r--lib/renderers/curses/curses.c72
-rw-r--r--lib/renderers/wayland/CMakeLists.txt6
-rw-r--r--lib/renderers/wayland/registry.c285
-rw-r--r--lib/renderers/wayland/wayland.c623
-rw-r--r--lib/renderers/wayland/wayland.h100
-rw-r--r--lib/renderers/wayland/window.c311
-rw-r--r--lib/renderers/wayland/xdg-shell.xml422
8 files changed, 1279 insertions, 641 deletions
diff --git a/lib/renderers/cairo.h b/lib/renderers/cairo.h
new file mode 100644
index 0000000..9c1ff5a
--- /dev/null
+++ b/lib/renderers/cairo.h
@@ -0,0 +1,101 @@
+#ifndef _BM_CAIRO_H_
+#define _BM_CAIRO_H_
+
+#include "internal.h"
+#include <string.h>
+#include <assert.h>
+#include <cairo/cairo.h>
+
+struct cairo {
+ cairo_t *cr;
+ cairo_surface_t *surface;
+};
+
+struct cairo_color {
+ float r, g, b, a;
+};
+
+struct cairo_font {
+ const char *name;
+ uint32_t size;
+};
+
+struct cairo_paint {
+ struct cairo_color color;
+ struct cairo_font font;
+};
+
+struct cairo_result {
+ cairo_text_extents_t te;
+};
+
+__attribute__((unused)) BM_LOG_ATTR(6, 7) static bool
+bm_cairo_draw_line(struct cairo *cairo, struct cairo_paint *paint, struct cairo_result *result, int32_t x, int32_t y, const char *fmt, ...)
+{
+ static size_t blen = 0;
+ static char *buffer = NULL;
+ assert(cairo && paint && result && fmt);
+ memset(result, 0, sizeof(struct cairo_result));
+
+ va_list args;
+ va_start(args, fmt);
+ bool ret = bm_vrprintf(&buffer, &blen, fmt, args);
+ va_end(args);
+
+ if (!ret)
+ return false;
+
+ cairo_set_source_rgba(cairo->cr, paint->color.r, paint->color.b, paint->color.g, paint->color.a);
+
+ cairo_text_extents_t te;
+ cairo_select_font_face(cairo->cr, paint->font.name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(cairo->cr, paint->font.size);
+ cairo_text_extents(cairo->cr, buffer, &te);
+ cairo_move_to(cairo->cr, x, y * te.height + te.height);
+ cairo_show_text(cairo->cr, buffer);
+
+ memcpy(&result->te, &te, sizeof(te));
+ return true;
+}
+
+__attribute__((unused)) static void
+bm_cairo_paint(struct cairo *cairo, uint32_t width, uint32_t height, const struct bm_menu *menu)
+{
+ cairo_set_source_rgb(cairo->cr, 18.0f / 255.0f, 18 / 255.0f, 18.0f / 255.0f);
+ cairo_rectangle(cairo->cr, 0, 0, width, height);
+ cairo_fill(cairo->cr);
+
+ struct cairo_result result;
+ memset(&result, 0, sizeof(result));
+
+ struct cairo_paint paint;
+ memset(&paint, 0, sizeof(paint));
+ paint.font.name = "Terminus";
+ paint.font.size = 12;
+ paint.color = (struct cairo_color){ 1.0, 0.0, 0.0, 1.0 };
+
+ if (menu->title)
+ bm_cairo_draw_line(cairo, &paint, &result, result.te.x_advance, 0, "%s ", menu->title);
+ if (menu->filter)
+ bm_cairo_draw_line(cairo, &paint, &result, result.te.x_advance, 0, "%s", menu->filter);
+
+ uint32_t count, cl = 1;
+ uint32_t lines = height / paint.font.size;
+ struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
+ for (uint32_t i = (menu->index / (lines - 1)) * (lines - 1); i < count && cl < lines; ++i) {
+ bool highlighted = (items[i] == bm_menu_get_highlighted_item(menu));
+
+ if (highlighted)
+ paint.color = (struct cairo_color){ 1.0, 0.0, 0.0, 1.0 };
+ else if (bm_menu_item_is_selected(menu, items[i]))
+ paint.color = (struct cairo_color){ 1.0, 0.0, 0.0, 1.0 };
+ else
+ paint.color = (struct cairo_color){ 1.0, 1.0, 1.0, 1.0 };
+
+ bm_cairo_draw_line(cairo, &paint, &result, 0, cl++, "%s%s", (highlighted ? ">> " : " "), (items[i]->text ? items[i]->text : ""));
+ }
+}
+
+#endif /* _BM_CAIRO_H */
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/renderers/curses/curses.c b/lib/renderers/curses/curses.c
index c372704..42212b6 100644
--- a/lib/renderers/curses/curses.c
+++ b/lib/renderers/curses/curses.c
@@ -1,25 +1,20 @@
-#if __APPLE__
-# define _C99_SOURCE
-# include <stdio.h> /* vsnprintf */
-# undef _C99_SOURCE
-#endif
-
-#define _XOPEN_SOURCE 500
-#include <signal.h> /* sigaction */
-#include <stdarg.h> /* vsnprintf */
-#undef _XOPEN_SOURCE
+#define _DEFAULT_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
#include "internal.h"
+#include "version.h"
#include <wchar.h>
+#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
-#include <ncurses.h>
#include <dlfcn.h>
#include <assert.h>
+#include <ncurses.h>
+
#if _WIN32
static const char *TTY = "CON";
#else
@@ -61,7 +56,8 @@ crash_handler(int sig)
terminate();
}
-static void resize_handler(int sig)
+static void
+resize_handler(int sig)
{
(void)sig;
if (!curses.stdscr)
@@ -71,56 +67,26 @@ static void resize_handler(int sig)
refresh();
}
-static bool
-resize_buffer(char **buffer, size_t *osize, size_t nsize)
-{
- assert(buffer);
- assert(osize);
-
- if (nsize == 0 || nsize <= *osize)
- return false;
-
- void *tmp;
- if (!*buffer || !(tmp = realloc(*buffer, nsize))) {
- if (!(tmp = malloc(nsize)))
- return 0;
-
- if (*buffer) {
- memcpy(tmp, *buffer, *osize);
- free(*buffer);
- }
- }
-
- *buffer = tmp;
- *osize = nsize;
- return true;
-}
-
-#if __GNUC__
-__attribute__((format(printf, 3, 4)))
-#endif
-static void
-draw_line(int32_t pair, int32_t y, const char *format, ...)
+BM_LOG_ATTR(3, 4) static void
+draw_line(int32_t pair, int32_t y, const char *fmt, ...)
{
static size_t blen = 0;
static char *buffer = NULL;
+ assert(fmt);
size_t ncols;
if ((ncols = getmaxx(curses.stdscr)) <= 0)
return;
va_list args;
- va_start(args, format);
- size_t nlen = vsnprintf(NULL, 0, format, args);
+ va_start(args, fmt);
+ bool ret = bm_vrprintf(&buffer, &blen, fmt, args);
va_end(args);
- if ((!buffer || nlen > blen) && !resize_buffer(&buffer, &blen, nlen + 1))
+ if (!ret)
return;
- va_start(args, format);
- vsnprintf(buffer, blen, format, args);
- va_end(args);
-
+ size_t nlen = strlen(buffer);
size_t dw = 0, i = 0;
while (dw < ncols && i < nlen) {
if (buffer[i] == '\t') buffer[i] = ' ';
@@ -132,7 +98,7 @@ draw_line(int32_t pair, int32_t y, const char *format, ...)
if (dw < ncols) {
/* line is too short, widen it */
size_t offset = i + (ncols - dw);
- if (blen <= offset && !resize_buffer(&buffer, &blen, offset + 1))
+ if (blen <= offset && !bm_resize_buffer(&buffer, &blen, offset + 1))
return;
memset(buffer + nlen, ' ', offset - nlen);
@@ -145,7 +111,7 @@ draw_line(int32_t pair, int32_t y, const char *format, ...)
size_t offset = i - (dw - ncols) + (ncols - cc) + 1;
if (blen <= offset) {
int32_t diff = offset - blen + 1;
- if (!resize_buffer(&buffer, &blen, blen + diff))
+ if (!bm_resize_buffer(&buffer, &blen, blen + diff))
return;
}
@@ -353,6 +319,8 @@ static bool
constructor(struct bm_menu *menu)
{
(void)menu;
+ assert(!curses.stdscr && "bemenu supports only one curses instance");
+
memset(&curses, 0, sizeof(curses));
struct sigaction action;
@@ -374,6 +342,8 @@ register_renderer(struct render_api *api)
api->get_displayed_count = get_displayed_count;
api->poll_key = poll_key;
api->render = render;
+ api->prioritory = BM_PRIO_TERMINAL;
+ api->version = BM_VERSION;
return "curses";
}
diff --git a/lib/renderers/wayland/CMakeLists.txt b/lib/renderers/wayland/CMakeLists.txt
index 01c3f09..4f93d18 100644
--- a/lib/renderers/wayland/CMakeLists.txt
+++ b/lib/renderers/wayland/CMakeLists.txt
@@ -3,8 +3,10 @@ FIND_PACKAGE(Wayland)
if (WAYLAND_FOUND)
FIND_PACKAGE(Cairo REQUIRED)
FIND_PACKAGE(XKBCommon REQUIRED)
- INCLUDE_DIRECTORIES(${WAYLAND_CLIENT_INCLUDE_DIR} ${XKBCOMMON_INCLUDE_DIR} ${CAIRO_INCLUDE_DIRECTORIES})
- ADD_LIBRARY(bemenu-renderer-wayland SHARED wayland.c)
+ INCLUDE(Wayland)
+ WAYLAND_ADD_PROTOCOL_CLIENT(proto-xdg-shell "xdg-shell.xml" xdg-shell)
+ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${WAYLAND_CLIENT_INCLUDE_DIR} ${XKBCOMMON_INCLUDE_DIR} ${CAIRO_INCLUDE_DIRECTORIES})
+ ADD_LIBRARY(bemenu-renderer-wayland SHARED wayland.c registry.c window.c ${proto-xdg-shell})
SET_TARGET_PROPERTIES(bemenu-renderer-wayland PROPERTIES PREFIX "")
TARGET_LINK_LIBRARIES(bemenu-renderer-wayland ${WAYLAND_CLIENT_LIBRARIES} ${XKBCOMMON_LIBRARIES} ${CAIRO_LIBRARIES})
INSTALL(TARGETS bemenu-renderer-wayland DESTINATION lib/bemenu)
diff --git a/lib/renderers/wayland/registry.c b/lib/renderers/wayland/registry.c
new file mode 100644
index 0000000..8a00659
--- /dev/null
+++ b/lib/renderers/wayland/registry.c
@@ -0,0 +1,285 @@
+#include "wayland.h"
+
+#include <unistd.h>
+#include <sys/mman.h>
+
+const char *BM_XKB_MASK_NAMES[MASK_LAST] = {
+ XKB_MOD_NAME_SHIFT,
+ XKB_MOD_NAME_CAPS,
+ XKB_MOD_NAME_CTRL,
+ XKB_MOD_NAME_ALT,
+ "Mod2",
+ "Mod3",
+ XKB_MOD_NAME_LOGO,
+ "Mod5",
+};
+
+const enum mod_bit BM_XKB_MODS[MASK_LAST] = {
+ MOD_SHIFT,
+ MOD_CAPS,
+ MOD_CTRL,
+ MOD_ALT,
+ MOD_MOD2,
+ MOD_MOD3,
+ MOD_LOGO,
+ MOD_MOD5
+};
+
+static void
+xdg_shell_ping(void *data, struct xdg_shell *shell, uint32_t serial)
+{
+ (void)data;
+ xdg_shell_pong(shell, serial);
+}
+
+static const struct xdg_shell_listener xdg_shell_listener = {
+ .ping = xdg_shell_ping,
+};
+
+static void
+shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
+{
+ (void)wl_shm;
+ struct wayland *wayland = data;
+ wayland->formats |= (1 << format);
+}
+
+struct wl_shm_listener shm_listener = {
+ .format = shm_format
+};
+
+static void
+keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
+{
+ (void)keyboard;
+ struct input *input = data;
+
+ if (!data) {
+ close(fd);
+ return;
+ }
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ close(fd);
+ return;
+ }
+
+ char *map_str;
+ if ((map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ close(fd);
+ return;
+ }
+
+ struct xkb_keymap *keymap = xkb_keymap_new_from_string(input->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
+ munmap(map_str, size);
+ close(fd);
+
+ if (!keymap) {
+ fprintf(stderr, "failed to compile keymap\n");
+ return;
+ }
+
+ struct xkb_state *state;
+ if (!(state = xkb_state_new(keymap))) {
+ fprintf(stderr, "failed to create XKB state\n");
+ xkb_keymap_unref(keymap);
+ return;
+ }
+
+ xkb_keymap_unref(input->xkb.keymap);
+ xkb_state_unref(input->xkb.state);
+ input->xkb.keymap = keymap;
+ input->xkb.state = state;
+
+ for (uint32_t i = 0; i < MASK_LAST; ++i)
+ input->xkb.masks[i] = xkb_keymap_mod_get_index(input->xkb.keymap, BM_XKB_MASK_NAMES[i]);
+}
+
+static void
+keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
+{
+ (void)data, (void)keyboard, (void)serial, (void)surface, (void)keys;
+}
+
+static void
+keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
+{
+ (void)data, (void)keyboard, (void)serial, (void)surface;
+}
+
+static void
+keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w)
+{
+ (void)keyboard, (void)serial, (void)time;
+ struct input *input = data;
+ enum wl_keyboard_key_state state = state_w;
+
+ if (!input->xkb.state)
+ return;
+
+ uint32_t code = key + 8;
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(input->xkb.state, code);
+
+ input->sym = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? sym : XKB_KEY_NoSymbol);
+ input->code = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? code : 0);
+
+ if (input->notify.key)
+ input->notify.key(state, sym, code);
+
+#if 0
+ if (state == WL_KEYBOARD_KEY_STATE_RELEASED &&
+ key == input->repeat_key) {
+ its.it_interval.tv_sec = 0;
+ its.it_interval.tv_nsec = 0;
+ its.it_value.tv_sec = 0;
+ its.it_value.tv_nsec = 0;
+ timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
+ } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED &&
+ xkb_keymap_key_repeats(input->xkb.keymap, code)) {
+ input->repeat_sym = sym;
+ input->repeat_key = key;
+ input->repeat_time = time;
+ its.it_interval.tv_sec = input->repeat_rate_sec;
+ its.it_interval.tv_nsec = input->repeat_rate_nsec;
+ its.it_value.tv_sec = input->repeat_delay_sec;
+ its.it_value.tv_nsec = input->repeat_delay_nsec;
+ timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
+ }
+#endif
+}
+
+static void
+keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
+{
+ (void)keyboard, (void)serial;
+ struct input *input = data;
+
+ if (!input->xkb.keymap)
+ return;
+
+ xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
+ xkb_mod_mask_t mask = xkb_state_serialize_mods(input->xkb.state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
+
+ input->modifiers = 0;
+ for (uint32_t i = 0; i < MASK_LAST; ++i) {
+ if (mask & input->xkb.masks[i])
+ input->modifiers |= BM_XKB_MODS[i];
+ }
+}
+
+static void
+keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay)
+{
+ (void)data, (void)keyboard, (void)rate, (void)delay;
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ .keymap = keyboard_handle_keymap,
+ .enter = keyboard_handle_enter,
+ .leave = keyboard_handle_leave,
+ .key = keyboard_handle_key,
+ .modifiers = keyboard_handle_modifiers,
+ .repeat_info = keyboard_handle_repeat_info
+};
+
+static void
+seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps)
+{
+ struct input *input = data;
+
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) {
+ input->keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(input->keyboard, &keyboard_listener, data);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) {
+ wl_keyboard_destroy(input->keyboard);
+ input->keyboard = NULL;
+ }
+}
+
+static void
+seat_handle_name(void *data, struct wl_seat *seat, const char *name)
+{
+ (void)data, (void)seat, (void)name;
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_handle_capabilities,
+ seat_handle_name
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
+{
+ (void)version;
+ struct wayland *wayland = data;
+
+ if (strcmp(interface, "wl_compositor") == 0) {
+ wayland->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, "xdg_shell") == 0) {
+ wayland->xdg_shell = wl_registry_bind(registry, id, &xdg_shell_interface, 1);
+ xdg_shell_use_unstable_version(wayland->xdg_shell, 4);
+ xdg_shell_add_listener(wayland->xdg_shell, &xdg_shell_listener, data);
+ } else if (strcmp(interface, "wl_shell") == 0) {
+ wayland->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
+ } else if (strcmp(interface, "wl_seat") == 0) {
+ wayland->seat = wl_registry_bind(registry, id, &wl_seat_interface, XDG_SHELL_VERSION_CURRENT);
+ wl_seat_add_listener(wayland->seat, &seat_listener, &wayland->input);
+ } else if (strcmp(interface, "wl_shm") == 0) {
+ wayland->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
+ wl_shm_add_listener(wayland->shm, &shm_listener, data);
+ }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
+{
+ (void)data, (void)registry, (void)name;
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+void
+bm_wl_registry_destroy(struct wayland *wayland)
+{
+ assert(wayland);
+
+ if (wayland->shm)
+ wl_shm_destroy(wayland->shm);
+
+ if (wayland->shell)
+ wl_shell_destroy(wayland->shell);
+
+ if (wayland->xdg_shell)
+ xdg_shell_destroy(wayland->xdg_shell);
+
+ if (wayland->compositor)
+ wl_compositor_destroy(wayland->compositor);
+
+ if (wayland->registry)
+ wl_registry_destroy(wayland->registry);
+}
+
+bool
+bm_wl_registry_register(struct wayland *wayland)
+{
+ assert(wayland);
+
+ if (!(wayland->registry = wl_display_get_registry(wayland->display)))
+ return false;
+
+ wl_registry_add_listener(wayland->registry, &registry_listener, wayland);
+ wl_display_roundtrip(wayland->display); // trip 1, registry globals
+ if (!wayland->compositor || !wayland->seat || !wayland->shm || !(wayland->shell || wayland->xdg_shell))
+ return false;
+
+ wl_display_roundtrip(wayland->display); // trip 2, global listeners
+ if (!wayland->input.keyboard || !(wayland->formats & (1 << WL_SHM_FORMAT_ARGB8888)))
+ return false;
+
+ return true;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/renderers/wayland/wayland.c b/lib/renderers/wayland/wayland.c
index abc0d2e..289fa5a 100644
--- a/lib/renderers/wayland/wayland.c
+++ b/lib/renderers/wayland/wayland.c
@@ -1,575 +1,33 @@
-#define _DEFAULT_SOURCE
#include "internal.h"
+#include "version.h"
+#include "wayland.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
-#include <errno.h>
-#include <unistd.h>
-#include <assert.h>
-#include <fcntl.h>
-#include <sys/mman.h>
-
-#include <wayland-client.h>
-#include <xkbcommon/xkbcommon.h>
-#include <cairo/cairo.h>
-
-#define MOD_SHIFT_MASK 0x01
-#define MOD_ALT_MASK 0x02
-#define MOD_CONTROL_MASK 0x04
-
-struct xkb {
- struct xkb_context *context;
- struct xkb_keymap *keymap;
- struct xkb_state *state;
- xkb_mod_mask_t control_mask;
- xkb_mod_mask_t alt_mask;
- xkb_mod_mask_t shift_mask;
- xkb_keysym_t sym;
- uint32_t key;
- uint32_t modifiers;
-};
-
-struct cairo {
- cairo_t *cr;
- cairo_surface_t *surface;
-};
-
-struct buffer {
- struct cairo cairo;
- struct wl_buffer *buffer;
- int busy;
-};
-
-static struct wayland {
- struct bm_menu *menu;
- struct wl_display *display;
- struct wl_registry *registry;
- struct wl_compositor *compositor;
- struct wl_surface *surface;
- struct wl_shell_surface *shell_surface;
- struct wl_seat *seat;
- struct wl_keyboard *keyboard;
- struct wl_shm *shm;
-#if 0
- struct xdg_shell *shell;
-#else
- struct wl_shell *shell;
-#endif
- struct xkb xkb;
- struct buffer buffers[2];
- uint32_t width, height;
- uint32_t formats;
-} wayland;
-
-// XXX: move to utils.c if reused
-static char*
-csprintf(const char *fmt, ...)
-{
- assert(fmt);
-
- va_list args;
- va_start(args, fmt);
- size_t len = vsnprintf(NULL, 0, fmt, args) + 1;
- va_end(args);
-
- char *buffer;
- if (!(buffer = calloc(1, len)))
- return NULL;
-
- va_start(args, fmt);
- vsnprintf(buffer, len, fmt, args);
- va_end(args);
- return buffer;
-}
-
-static int
-set_cloexec_or_close(int fd)
-{
- if (fd == -1)
- return -1;
-
- long flags = fcntl(fd, F_GETFD);
- if (flags == -1)
- goto err;
-
- if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
- goto err;
-
- return fd;
-
-err:
- close(fd);
- return -1;
-}
-
-static int
-create_tmpfile_cloexec(char *tmpname)
-{
- int fd;
-
-#ifdef HAVE_MKOSTEMP
- if ((fd = mkostemp(tmpname, O_CLOEXEC)) >= 0)
- unlink(tmpname);
-#else
- if ((fd = mkstemp(tmpname)) >= 0) {
- fd = set_cloexec_or_close(fd);
- unlink(tmpname);
- }
-#endif
-
- return fd;
-}
-
-static int
-os_create_anonymous_file(off_t size)
-{
- static const char template[] = "/bemenu-shared-XXXXXX";
- int fd;
- int ret;
-
- const char *path;
- if (!(path = getenv("XDG_RUNTIME_DIR")) || strlen(path) <= 0) {
- errno = ENOENT;
- return -1;
- }
-
- char *name;
- int ts = (path[strlen(path) - 1] == '/');
- if (!(name = csprintf("%s%s%s", path, (ts ? "" : "/"), template)))
- return -1;
-
- fd = create_tmpfile_cloexec(name);
- free(name);
-
- if (fd < 0)
- return -1;
-
-#ifdef HAVE_POSIX_FALLOCATE
- if ((ret = posix_fallocate(fd, 0, size)) != 0) {
- close(fd);
- errno = ret;
- return -1;
- }
-#else
- if ((ret = ftruncate(fd, size)) < 0) {
- close(fd);
- return -1;
- }
-#endif
-
- return fd;
-}
-
-static void
-buffer_release(void *data, struct wl_buffer *buffer)
-{
- struct buffer *mybuf = data;
- mybuf->busy = 0;
-}
-
-static const struct wl_buffer_listener buffer_listener = {
- buffer_release
-};
-
-static bool
-create_shm_buffer(struct buffer *buffer, int32_t width, int32_t height, uint32_t format)
-{
- uint32_t stride = width * 4;
- uint32_t size = stride * height;
-
- int fd;
- if ((fd = os_create_anonymous_file(size)) < 0) {
- fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
- return false;
- }
-
- void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (data == MAP_FAILED) {
- fprintf(stderr, "mmap failed: %m\n");
- close(fd);
- return false;
- }
-
- // FIXME: error handling
- struct wl_shm_pool *pool = wl_shm_create_pool(wayland.shm, fd, size);
- buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format);
- wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer);
- wl_shm_pool_destroy(pool);
- close(fd);
-
- buffer->cairo.surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride);
- buffer->cairo.cr = cairo_create(buffer->cairo.surface);
- return true;
-}
-
-static struct buffer*
-next_buffer(void)
-{
- struct buffer *buffer;
-
- if (!wayland.buffers[0].busy)
- buffer = &wayland.buffers[0];
- else if (!wayland.buffers[1].busy)
- buffer = &wayland.buffers[1];
- else
- return NULL;
-
- if (!buffer->buffer) {
- if (!create_shm_buffer(buffer, wayland.width, wayland.height, WL_SHM_FORMAT_ARGB8888))
- return NULL;
- }
-
- return buffer;
-}
-
-static bool
-resize_buffer(char **buffer, size_t *osize, size_t nsize)
-{
- assert(buffer);
- assert(osize);
-
- if (nsize == 0 || nsize <= *osize)
- return false;
-
- void *tmp;
- if (!*buffer || !(tmp = realloc(*buffer, nsize))) {
- if (!(tmp = malloc(nsize)))
- return 0;
-
- if (*buffer) {
- memcpy(tmp, *buffer, *osize);
- free(*buffer);
- }
- }
-
- *buffer = tmp;
- *osize = nsize;
- return true;
-}
-
-#if __GNUC__
-__attribute__((format(printf, 5, 6)))
-#endif
-static int32_t
-draw_line(struct cairo *c, int32_t pair, int32_t x, int32_t y, const char *format, ...)
-{
- static size_t blen = 0;
- static char *buffer = NULL;
-
- va_list args;
- va_start(args, format);
- size_t nlen = vsnprintf(NULL, 0, format, args);
- va_end(args);
-
- if ((!buffer || nlen > blen) && !resize_buffer(&buffer, &blen, nlen + 1))
- return;
-
- va_start(args, format);
- vsnprintf(buffer, blen, format, args);
- va_end(args);
-
- cairo_text_extents_t te;
- switch (pair) {
- case 2:
- cairo_set_source_rgb(c->cr, 1.0, 0.0, 0.0);
- break;
- case 1:
- cairo_set_source_rgb(c->cr, 1.0, 0.0, 0.0);
- break;
- case 0:
- cairo_set_source_rgb(c->cr, 1.0, 1.0, 1.0);
- break;
- };
-
- cairo_select_font_face(c->cr, "Terminus", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
- cairo_set_font_size(c->cr, 12);
- cairo_text_extents(c->cr, buffer, &te);
- cairo_move_to(c->cr, x, y * te.height + te.height);
- cairo_show_text(c->cr, buffer);
- return x + te.x_advance;
-}
-
-static void
-paint(struct cairo *c, int width, int height, uint32_t time)
-{
- cairo_set_source_rgb(c->cr, 18.0f / 255.0f, 18 / 255.0f, 18.0f / 255.0f);
- cairo_rectangle(c->cr, 0, 0, width, height);
- cairo_fill(c->cr);
-
- struct bm_menu *menu = wayland.menu;
-
- int32_t x = 0;
- if (menu->title)
- x = draw_line(c, 1, x, 0, "%s ", menu->title);
- if (menu->filter)
- x = draw_line(c, 1, x, 0, "%s", menu->filter);
-
- uint32_t lines = height / 8;
- uint32_t count, cl = 1;
- struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
- for (uint32_t i = (menu->index / (lines - 1)) * (lines - 1); i < count && cl < lines; ++i) {
- bool highlighted = (items[i] == bm_menu_get_highlighted_item(menu));
- int32_t color = (highlighted ? 2 : (bm_menu_item_is_selected(menu, items[i]) ? 1 : 0));
- draw_line(c, color, 0, cl++, "%s%s", (highlighted ? ">> " : " "), (items[i]->text ? items[i]->text : ""));
- }
-}
-
-static void
-redraw(void *data, uint32_t time)
-{
- struct wayland *d = data;
- struct buffer *buffer;
-
- if (!(buffer = next_buffer()))
- return;
-
- paint(&buffer->cairo, d->width, d->height, time);
- wl_surface_attach(d->surface, buffer->buffer, 0, 0);
- wl_surface_commit(d->surface);
- buffer->busy = 1;
-}
-
-static void
-shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
-{
- (void)wl_shm;
- struct wayland *d = data;
- d->formats |= (1 << format);
-}
-
-struct wl_shm_listener shm_listener = {
- .format = shm_format
-};
-
-#if 0
-static void
-xdg_shell_ping(void *data, struct xdg_shell *shell, uint32_t serial)
-{
- (void)data;
- xdg_shell_pong(shell, serial);
-}
-
-static const struct xdg_shell_listener xdg_shell_listener = {
- xdg_shell_ping,
-};
-#endif
-
-static void
-keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
-{
- struct wayland *d = data;
-
- if (!data) {
- close(fd);
- return;
- }
-
- if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
- close(fd);
- return;
- }
-
- char *map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
- if (map_str == MAP_FAILED) {
- close(fd);
- return;
- }
-
- struct xkb_keymap *keymap = xkb_keymap_new_from_string(d->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
- munmap(map_str, size);
- close(fd);
-
- if (!keymap) {
- fprintf(stderr, "failed to compile keymap\n");
- return;
- }
-
- struct xkb_state *state = xkb_state_new(keymap);
- if (!state) {
- fprintf(stderr, "failed to create XKB state\n");
- xkb_keymap_unref(keymap);
- return;
- }
-
- xkb_keymap_unref(d->xkb.keymap);
- xkb_state_unref(d->xkb.state);
- d->xkb.keymap = keymap;
- d->xkb.state = state;
- d->xkb.control_mask = 1 << xkb_keymap_mod_get_index(d->xkb.keymap, "Control");
- d->xkb.alt_mask = 1 << xkb_keymap_mod_get_index(d->xkb.keymap, "Mod1");
- d->xkb.shift_mask = 1 << xkb_keymap_mod_get_index(d->xkb.keymap, "Shift");
-}
-
-static void
-keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
-{
- (void)data, (void)keyboard, (void)serial, (void)surface, (void)keys;
-}
-
-static void
-keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
-{
- (void)data, (void)keyboard, (void)serial, (void)surface;
-}
-
-static void
-keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w)
-{
- struct wayland *d = data;
- enum wl_keyboard_key_state state = state_w;
-
- if (!d->xkb.state)
- return;
-
- uint32_t code = key + 8;
- xkb_keysym_t sym = xkb_state_key_get_one_sym(d->xkb.state, code);
-
- d->xkb.key = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? code : 0);
- d->xkb.sym = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? sym : XKB_KEY_NoSymbol);
-
-#if 0
- if (state == WL_KEYBOARD_KEY_STATE_RELEASED &&
- key == input->repeat_key) {
- its.it_interval.tv_sec = 0;
- its.it_interval.tv_nsec = 0;
- its.it_value.tv_sec = 0;
- its.it_value.tv_nsec = 0;
- timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
- } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED &&
- xkb_keymap_key_repeats(input->xkb.keymap, code)) {
- input->repeat_sym = sym;
- input->repeat_key = key;
- input->repeat_time = time;
- its.it_interval.tv_sec = input->repeat_rate_sec;
- its.it_interval.tv_nsec = input->repeat_rate_nsec;
- its.it_value.tv_sec = input->repeat_delay_sec;
- its.it_value.tv_nsec = input->repeat_delay_nsec;
- timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
- }
-#endif
-}
-
-static void
-keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
-{
- (void)keyboard, (void)serial;
- struct wayland *d = data;
-
- if (!d->xkb.keymap)
- return;
-
- xkb_state_update_mask(d->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
- xkb_mod_mask_t mask = xkb_state_serialize_mods(d->xkb.state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
-
- d->xkb.modifiers = 0;
- if (mask & d->xkb.control_mask)
- d->xkb.modifiers |= MOD_CONTROL_MASK;
- if (mask & d->xkb.alt_mask)
- d->xkb.modifiers |= MOD_ALT_MASK;
- if (mask & d->xkb.shift_mask)
- d->xkb.modifiers |= MOD_SHIFT_MASK;
-}
-
-static void
-keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay)
-{
- (void)data, (void)keyboard, (void)rate, (void)delay;
-}
-
-static const struct wl_keyboard_listener keyboard_listener = {
- .keymap = keyboard_handle_keymap,
- .enter = keyboard_handle_enter,
- .leave = keyboard_handle_leave,
- .key = keyboard_handle_key,
- .modifiers = keyboard_handle_modifiers,
- .repeat_info = keyboard_handle_repeat_info
-};
-
-static void
-seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps)
-{
- struct wayland *d = data;
-
- if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) {
- d->keyboard = wl_seat_get_keyboard(seat);
- wl_keyboard_add_listener(d->keyboard, &keyboard_listener, data);
- } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) {
- wl_keyboard_destroy(d->keyboard);
- d->keyboard = NULL;
- }
-}
-
-static void
-seat_handle_name(void *data, struct wl_seat *seat, const char *name)
-{
- (void)data, (void)seat, (void)name;
-}
-
-static const struct wl_seat_listener seat_listener = {
- seat_handle_capabilities,
- seat_handle_name
-};
-
-static void
-registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
-{
- (void)version;
- struct wayland *d = data;
-
- if (strcmp(interface, "wl_compositor") == 0) {
- d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
-#if 0
- } else if (strcmp(interface, "xdg_shell") == 0) {
- d->shell = wl_registry_bind(registry, id, &xdg_shell_interface, 1);
- xdg_shell_use_unstable_version(d->shell, XDG_VERSION);
- xdg_shell_add_listener(d->shell, &xdg_shell_listener, d);
-#else
- } else if (strcmp(interface, "wl_shell") == 0) {
- d->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
-#endif
- } else if (strcmp(interface, "wl_seat") == 0) {
- d->seat = wl_registry_bind(registry, id, &wl_seat_interface, 4);
- wl_seat_add_listener(d->seat, &seat_listener, d);
- } else if (strcmp(interface, "wl_shm") == 0) {
- d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
- wl_shm_add_listener(d->shm, &shm_listener, d);
- }
-}
-
-static void
-registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
-{
- (void)data, (void)registry, (void)name;
-}
-
-static const struct wl_registry_listener registry_listener = {
- registry_handle_global,
- registry_handle_global_remove
-};
static void
render(const struct bm_menu *menu)
{
- (void)menu;
- redraw(&wayland, 0);
- wl_display_dispatch(wayland.display);
+ struct wayland *wayland = menu->renderer->internal;
+ bm_wl_window_render(&wayland->window, menu);
+ wl_display_dispatch(wayland->display);
}
static enum bm_key
poll_key(const struct bm_menu *menu, unsigned int *unicode)
{
- (void)menu;
- assert(unicode);
+ struct wayland *wayland = menu->renderer->internal;
+ assert(wayland && unicode);
*unicode = 0;
- if (wayland.xkb.sym == XKB_KEY_NoSymbol)
+ if (wayland->input.sym == XKB_KEY_NoSymbol)
return BM_KEY_UNICODE;
- *unicode = xkb_state_key_get_utf32(wayland.xkb.state, wayland.xkb.key);
+ *unicode = xkb_state_key_get_utf32(wayland->input.xkb.state, wayland->input.code);
- switch (wayland.xkb.sym) {
+ switch (wayland->input.sym) {
case XKB_KEY_Up:
return BM_KEY_UP;
@@ -621,68 +79,55 @@ poll_key(const struct bm_menu *menu, unsigned int *unicode)
static uint32_t
get_displayed_count(const struct bm_menu *menu)
{
- (void)menu;
- return wayland.height / 8;
+ struct wayland *wayland = menu->renderer->internal;
+ assert(wayland);
+ return wayland->window.height / 12;
}
static void
destructor(struct bm_menu *menu)
{
- (void)menu;
-
- if (wayland.shm)
- wl_shm_destroy(wayland.shm);
+ struct wayland *wayland = menu->renderer->internal;
-#if 0
- if (wayland.shell)
- xdg_shell_destroy(wayland.shell);
-#endif
-
- if (wayland.compositor)
- wl_compositor_destroy(wayland.compositor);
+ if (!wayland)
+ return;
- if (wayland.registry)
- wl_registry_destroy(wayland.registry);
+ bm_wl_window_destroy(&wayland->window);
+ bm_wl_registry_destroy(wayland);
- if (wayland.display) {
- wl_display_flush(wayland.display);
- wl_display_disconnect(wayland.display);
+ if (wayland->display) {
+ wl_display_flush(wayland->display);
+ wl_display_disconnect(wayland->display);
}
+
+ free(wayland);
+ menu->renderer->internal = NULL;
}
static bool
constructor(struct bm_menu *menu)
{
- (void)menu;
- memset(&wayland, 0, sizeof(wayland));
-
- if (!(wayland.display = wl_display_connect(NULL)))
+ struct wayland *wayland;
+ if (!(menu->renderer->internal = wayland = calloc(1, sizeof(struct wayland))))
goto fail;
- if (!(wayland.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
+ if (!(wayland->display = wl_display_connect(NULL)))
goto fail;
- wayland.width = 800;
- wayland.height = 480;
-
- wayland.registry = wl_display_get_registry(wayland.display);
- wl_registry_add_listener(wayland.registry, &registry_listener, &wayland);
- wl_display_roundtrip(wayland.display); // shm bind
- if (!wayland.shm)
+ if (!(wayland->input.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
goto fail;
- wl_display_roundtrip(wayland.display); // shm formats
- if (!(wayland.formats & (1 << WL_SHM_FORMAT_ARGB8888)))
+ if (!bm_wl_registry_register(wayland))
goto fail;
- if (!(wayland.surface = wl_compositor_create_surface(wayland.compositor)))
+ struct wl_surface *surface;
+ if (!(surface = wl_compositor_create_surface(wayland->compositor)))
goto fail;
- if (!(wayland.shell_surface = wl_shell_get_shell_surface(wayland.shell, wayland.surface)))
+ if (!bm_wl_window_create(&wayland->window, wayland->shm, wayland->shell, wayland->xdg_shell, surface))
goto fail;
- wayland.menu = menu;
- wl_surface_damage(wayland.surface, 0, 0, wayland.width, wayland.height);
+ wayland->window.notify.render = bm_cairo_paint;
return true;
fail:
@@ -698,6 +143,8 @@ register_renderer(struct render_api *api)
api->get_displayed_count = get_displayed_count;
api->poll_key = poll_key;
api->render = render;
+ api->prioritory = BM_PRIO_GUI;
+ api->version = BM_VERSION;
return "wayland";
}
diff --git a/lib/renderers/wayland/wayland.h b/lib/renderers/wayland/wayland.h
new file mode 100644
index 0000000..fd29405
--- /dev/null
+++ b/lib/renderers/wayland/wayland.h
@@ -0,0 +1,100 @@
+#ifndef _BM_WAYLAND_H_
+#define _BM_WAYLAND_H_
+
+#include <wayland-client.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "wayland-xdg-shell-client-protocol.h"
+
+#include "renderers/cairo.h"
+
+struct bm_menu;
+
+enum mod_bit {
+ MOD_SHIFT = 1<<0,
+ MOD_CAPS = 1<<1,
+ MOD_CTRL = 1<<2,
+ MOD_ALT = 1<<3,
+ MOD_MOD2 = 1<<4,
+ MOD_MOD3 = 1<<5,
+ MOD_LOGO = 1<<6,
+ MOD_MOD5 = 1<<7,
+};
+
+enum mask {
+ MASK_SHIFT,
+ MASK_CAPS,
+ MASK_CTRL,
+ MASK_ALT,
+ MASK_MOD2,
+ MASK_MOD3,
+ MASK_LOGO,
+ MASK_MOD5,
+ MASK_LAST
+};
+
+const char *BM_XKB_MASK_NAMES[MASK_LAST];
+const enum mod_bit BM_XKB_MODS[MASK_LAST];
+
+struct xkb {
+ struct xkb_state *state;
+ struct xkb_context *context;
+ struct xkb_keymap *keymap;
+ xkb_mod_mask_t masks[MASK_LAST];
+};
+
+struct input {
+ struct wl_keyboard *keyboard;
+ struct xkb xkb;
+
+ xkb_keysym_t sym;
+ uint32_t code;
+ uint32_t modifiers;
+
+ struct {
+ void (*key)(enum wl_keyboard_key_state state, xkb_keysym_t sym, uint32_t code);
+ } notify;
+};
+
+struct buffer {
+ struct cairo cairo;
+ struct wl_buffer *buffer;
+ uint32_t width, height;
+ bool busy;
+};
+
+struct window {
+ struct wl_surface *surface;
+ struct wl_shell_surface *shell_surface;
+ struct xdg_surface *xdg_surface;
+ struct wl_shm *shm;
+ struct buffer buffers[2];
+ uint32_t width, height;
+
+ struct {
+ void (*render)(struct cairo *cairo, uint32_t width, uint32_t height, const struct bm_menu *menu);
+ } notify;
+};
+
+struct wayland {
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct wl_compositor *compositor;
+ struct wl_seat *seat;
+ struct xdg_shell *xdg_shell;
+ struct wl_shell *shell;
+ struct wl_shm *shm;
+ struct input input;
+ struct window window;
+ uint32_t formats;
+};
+
+bool bm_wl_registry_register(struct wayland *wayland);
+void bm_wl_registry_destroy(struct wayland *wayland);
+void bm_wl_window_render(struct window *window, const struct bm_menu *menu);
+bool bm_wl_window_create(struct window *window, struct wl_shm *shm, struct wl_shell *shell, struct xdg_shell *xdg_shell, struct wl_surface *surface);
+void bm_wl_window_destroy(struct window *window);
+
+#endif /* _BM_WAYLAND_H_ */
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/renderers/wayland/window.c b/lib/renderers/wayland/window.c
new file mode 100644
index 0000000..9234ef2
--- /dev/null
+++ b/lib/renderers/wayland/window.c
@@ -0,0 +1,311 @@
+#define _DEFAULT_SOURCE
+#include "internal.h"
+#include "wayland.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#define USE_XDG_SHELL false
+
+static int
+set_cloexec_or_close(int fd)
+{
+ if (fd == -1)
+ return -1;
+
+ long flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ goto err;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ goto err;
+
+ return fd;
+
+err:
+ close(fd);
+ return -1;
+}
+
+static int
+create_tmpfile_cloexec(char *tmpname)
+{
+ int fd;
+
+#ifdef HAVE_MKOSTEMP
+ if ((fd = mkostemp(tmpname, O_CLOEXEC)) >= 0)
+ unlink(tmpname);
+#else
+ if ((fd = mkstemp(tmpname)) >= 0) {
+ fd = set_cloexec_or_close(fd);
+ unlink(tmpname);
+ }
+#endif
+
+ return fd;
+}
+
+static int
+os_create_anonymous_file(off_t size)
+{
+ static const char template[] = "/bemenu-shared-XXXXXX";
+ int fd;
+ int ret;
+
+ const char *path;
+ if (!(path = getenv("XDG_RUNTIME_DIR")) || strlen(path) <= 0) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ char *name;
+ int ts = (path[strlen(path) - 1] == '/');
+ if (!(name = bm_dprintf("%s%s%s", path, (ts ? "" : "/"), template)))
+ return -1;
+
+ fd = create_tmpfile_cloexec(name);
+ free(name);
+
+ if (fd < 0)
+ return -1;
+
+#ifdef HAVE_POSIX_FALLOCATE
+ if ((ret = posix_fallocate(fd, 0, size)) != 0) {
+ close(fd);
+ errno = ret;
+ return -1;
+ }
+#else
+ if ((ret = ftruncate(fd, size)) < 0) {
+ close(fd);
+ return -1;
+ }
+#endif
+
+ return fd;
+}
+
+static void
+buffer_release(void *data, struct wl_buffer *wl_buffer)
+{
+ (void)wl_buffer;
+ struct buffer *buffer = data;
+ buffer->busy = false;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ .release = buffer_release
+};
+
+static void
+destroy_buffer(struct buffer *buffer)
+{
+ if (buffer->buffer)
+ wl_buffer_destroy(buffer->buffer);
+ if (buffer->cairo.cr)
+ cairo_destroy(buffer->cairo.cr);
+ if (buffer->cairo.surface)
+ cairo_surface_destroy(buffer->cairo.surface);
+
+ memset(buffer, 0, sizeof(struct buffer));
+}
+
+static bool
+create_buffer(struct wl_shm *shm, struct buffer *buffer, int32_t width, int32_t height, uint32_t format)
+{
+ int fd = -1;
+ struct wl_shm_pool *pool = NULL;
+ uint32_t stride = width * 4;
+ uint32_t size = stride * height;
+
+ if ((fd = os_create_anonymous_file(size)) < 0) {
+ fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
+ return false;
+ }
+
+ void *data;
+ if ((data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ fprintf(stderr, "mmap failed: %m\n");
+ close(fd);
+ return false;
+ }
+
+ if (!(pool = wl_shm_create_pool(shm, fd, size))) {
+ close(fd);
+ return false;
+ }
+
+ if (!(buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format)))
+ goto fail;
+
+ wl_shm_pool_destroy(pool);
+ pool = NULL;
+
+ close(fd);
+ fd = -1;
+
+ wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer);
+
+ if (!(buffer->cairo.surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride)))
+ goto fail;
+
+ if (!(buffer->cairo.cr = cairo_create(buffer->cairo.surface)))
+ goto fail;
+
+ buffer->width = width;
+ buffer->height = height;
+ return true;
+
+fail:
+ if (fd > -1)
+ close(fd);
+ if (pool)
+ wl_shm_pool_destroy(pool);
+ destroy_buffer(buffer);
+ return false;
+}
+
+static struct buffer*
+next_buffer(struct window *window)
+{
+ assert(window);
+
+ struct buffer *buffer = NULL;
+ for (int32_t i = 0; i < 2; ++i) {
+ if (window->buffers[i].busy)
+ continue;
+
+ buffer = &window->buffers[i];
+ break;
+ }
+
+ if (!buffer)
+ return NULL;
+
+ if (window->width != buffer->width || window->height != buffer->height)
+ destroy_buffer(buffer);
+
+ if (!buffer->buffer && !create_buffer(window->shm, buffer, window->width, window->height, WL_SHM_FORMAT_ARGB8888))
+ return NULL;
+
+ return buffer;
+}
+
+static void
+resize(struct window *window, uint32_t width, uint32_t height)
+{
+ window->width = width;
+ window->height = height;
+}
+
+static void
+shell_surface_ping(void *data, struct wl_shell_surface *surface, uint32_t serial)
+{
+ (void)data;
+ wl_shell_surface_pong(surface, serial);
+}
+
+static void
+shell_surface_configure(void *data, struct wl_shell_surface *surface, uint32_t edges, int32_t width, int32_t height)
+{
+ (void)surface, (void)edges;
+ resize(data, width, height);
+}
+
+static void
+shell_surface_popup_done(void *data, struct wl_shell_surface *surface)
+{
+ (void)data, (void)surface;
+}
+
+static const struct wl_shell_surface_listener shell_surface_listener = {
+ .ping = shell_surface_ping,
+ .configure = shell_surface_configure,
+ .popup_done = shell_surface_popup_done,
+};
+
+static void
+xdg_surface_configure(void *data, struct xdg_surface *surface, int32_t width, int32_t height, struct wl_array *states, uint32_t serial)
+{
+ (void)states;
+ resize(data, width, height);
+ xdg_surface_ack_configure(surface, serial);
+}
+
+static void
+xdg_surface_close(void *data, struct xdg_surface *surface)
+{
+ (void)data, (void)surface;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_configure,
+ .close = xdg_surface_close,
+};
+
+void
+bm_wl_window_render(struct window *window, const struct bm_menu *menu)
+{
+ assert(window && menu);
+
+ struct buffer *buffer;
+ if (!(buffer = next_buffer(window)))
+ return;
+
+ if (window->notify.render)
+ window->notify.render(&buffer->cairo, buffer->width, buffer->height, menu);
+
+ wl_surface_damage(window->surface, 0, 0, buffer->width, buffer->height);
+ wl_surface_attach(window->surface, buffer->buffer, 0, 0);
+ wl_surface_commit(window->surface);
+ buffer->busy = true;
+}
+
+void
+bm_wl_window_destroy(struct window *window)
+{
+ assert(window);
+
+ for (int32_t i = 0; i < 2; ++i)
+ destroy_buffer(&window->buffers[i]);
+
+ if (window->xdg_surface)
+ xdg_surface_destroy(window->xdg_surface);
+
+ if (window->shell_surface)
+ wl_shell_surface_destroy(window->shell_surface);
+
+ if (window->surface)
+ wl_surface_destroy(window->surface);
+}
+
+bool
+bm_wl_window_create(struct window *window, struct wl_shm *shm, struct wl_shell *shell, struct xdg_shell *xdg_shell, struct wl_surface *surface)
+{
+ assert(window);
+
+ if (USE_XDG_SHELL && xdg_shell && (window->xdg_surface = xdg_shell_get_xdg_surface(xdg_shell, surface))) {
+ xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window);
+ xdg_surface_set_title(window->xdg_surface, "bemenu");
+ } else if (shell && (window->shell_surface = wl_shell_get_shell_surface(shell, surface))) {
+ wl_shell_surface_add_listener(window->shell_surface, &shell_surface_listener, window);
+ wl_shell_surface_set_title(window->shell_surface, "bemenu");
+ wl_shell_surface_set_class(window->shell_surface, "bemenu");
+ wl_shell_surface_set_toplevel(window->shell_surface);
+ } else {
+ return false;
+ }
+
+ window->width = 800;
+ window->height = 240;
+
+ window->shm = shm;
+ window->surface = surface;
+ wl_surface_damage(surface, 0, 0, window->width, window->height);
+ return true;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/renderers/wayland/xdg-shell.xml b/lib/renderers/wayland/xdg-shell.xml
new file mode 100644
index 0000000..776438b
--- /dev/null
+++ b/lib/renderers/wayland/xdg-shell.xml
@@ -0,0 +1,422 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell">
+
+ <copyright>
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+
+ 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.
+ </copyright>
+
+ <interface name="xdg_shell" version="1">
+ <description summary="create desktop-style surfaces">
+ This interface is implemented by servers that provide
+ desktop-style user interfaces.
+
+ It allows clients to associate a xdg_surface with
+ a basic surface.
+ </description>
+
+ <enum name="version">
+ <description summary="latest protocol version">
+ The 'current' member of this enum gives the version of the
+ protocol. Implementations can compare this to the version
+ they implement using static_assert to ensure the protocol and
+ implementation versions match.
+ </description>
+ <entry name="current" value="4" summary="Always the latest version"/>
+ </enum>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ </enum>
+
+ <request name="use_unstable_version">
+ <description summary="enable use of this unstable version">
+ Negotiate the unstable version of the interface. This
+ mechanism is in place to ensure client and server agree on the
+ unstable versions of the protocol that they speak or exit
+ cleanly if they don't agree. This request will go away once
+ the xdg-shell protocol is stable.
+ </description>
+ <arg name="version" type="int"/>
+ </request>
+
+ <request name="get_xdg_surface">
+ <description summary="create a shell surface from a surface">
+ Create a shell surface for an existing surface.
+
+ This request gives the surface the role of xdg_surface. If the
+ surface already has another role, it raises a protocol error.
+
+ Only one shell or popup surface can be associated with a given
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_surface"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="get_xdg_popup">
+ <description summary="create a shell surface from a surface">
+ Create a popup surface for an existing surface.
+
+ This request gives the surface the role of xdg_popup. If the
+ surface already has another role, it raises a protocol error.
+
+ Only one shell or popup surface can be associated with a given
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_popup"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="parent" type="object" interface="wl_surface"/>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial of the implicit grab on the pointer"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="flags" type="uint"/>
+ </request>
+
+ <event name="ping">
+ <description summary="check if the client is alive">
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time.
+ </description>
+ <arg name="serial" type="uint" summary="pass this to the callback"/>
+ </event>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the ping event"/>
+ </request>
+ </interface>
+
+ <interface name="xdg_surface" version="1">
+
+ <description summary="desktop-style metadata interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides requests to treat surfaces like windows, allowing to set
+ properties like maximized, fullscreen, minimized, and to move and resize
+ them, and associate metadata like title and app id.
+
+ On the server side the object is automatically destroyed when
+ the related wl_surface is destroyed. On client side,
+ xdg_surface.destroy() must be called before destroying
+ the wl_surface object.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_surface interface">
+ The xdg_surface interface is removed from the wl_surface object
+ that was turned into a xdg_surface with
+ xdg_shell.get_xdg_surface request. The xdg_surface properties,
+ like maximized and fullscreen, are lost. The wl_surface loses
+ its role as a xdg_surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <request name="set_parent">
+ <description summary="surface is a child of another surface">
+ Child surfaces are stacked above their parents, and will be
+ unmapped if the parent is unmapped too. They should not appear
+ on task bars and alt+tab.
+ </description>
+ <arg name="parent" type="object" interface="wl_surface" allow-null="true"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string"/>
+ </request>
+
+ <request name="set_app_id">
+ <description summary="set surface class">
+ Set an id for the surface.
+
+ The app id identifies the general class of applications to which
+ the surface belongs.
+
+ It should be the ID that appears in the new desktop entry
+ specification, the interface name.
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="show_window_menu">
+ <description summary="show the window menu">
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the parent surface. There are
+ no guarantees as to what the window menu contains.
+
+ Your surface must have focus on the seat passed in to pop up the
+ window menu.
+ </description>
+
+ <arg name="seat" type="object" interface="wl_seat" summary="the seat to pop the window up on"/>
+ <arg name="serial" type="uint" summary="serial of the event to pop up the window for"/>
+ <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+ <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start a pointer-driven move of the surface.
+
+ This request must be used in response to a button press event.
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial of the implicit grab on the pointer"/>
+ </request>
+
+ <enum name="resize_edge">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation. The server may
+ use this information to adapt its behavior, e.g. choose
+ an appropriate cursor image.
+ </description>
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="right" value="8"/>
+ <entry name="top_right" value="9"/>
+ <entry name="bottom_right" value="10"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a pointer-driven resizing of the surface.
+
+ This request must be used in response to a button press event.
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial of the implicit grab on the pointer"/>
+ <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of state on the surface">
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered. They will get applied on
+ the next commit.
+
+ Desktop environments may extend this enum by taking up a range of
+ values and documenting the range they chose in this description.
+ They are not required to document the values for the range that they
+ chose. Ideally, any good extensions from a desktop environment should
+ make its way into standardization into this enum.
+
+ The current reserved ranges are:
+
+ 0x0000 - 0x0FFF: xdg-shell core values, documented below.
+ 0x1000 - 0x1FFF: GNOME
+ </description>
+ <entry name="maximized" value="1" summary="the surface is maximized">
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client.
+ </entry>
+ <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ The surface is fullscreen. The window geometry specified in the configure
+ event must be obeyed by the client.
+ </entry>
+ <entry name="resizing" value="3">
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+ </entry>
+ <entry name="activated" value="4">
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+ </entry>
+ </enum>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to resize its surface.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. The states listed in the event specify how the
+ width/height arguments should be interpreted.
+
+ A client should arrange a new surface, and then send a
+ ack_configure request with the serial sent in this configure
+ event before attaching a new surface.
+
+ If the client receives multiple configure events before it
+ can respond to one, it is free to discard all but the last
+ event it received.
+ </description>
+
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ <arg name="states" type="array"/>
+ <arg name="serial" type="uint"/>
+ </event>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, a client should then ack it
+ using the ack_configure request to ensure that the compositor
+ knows the client has seen the event.
+
+ By this point, the state is confirmed, and the next attach should
+ contain the buffer drawn for the configure event you are acking.
+ </description>
+ <arg name="serial" type="uint" summary="a serial to configure for"/>
+ </request>
+
+ <request name="set_window_geometry">
+ <description summary="set the new window geometry">
+ The window geometry of a window is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows.
+
+ The default value is the full bounds of the surface, including any
+ subsurfaces. Once the window geometry of the surface is set once,
+ it is not possible to unset it, and it will remain the same until
+ set_window_geometry is called again, even if a new subsurface or
+ buffer is attached.
+
+ If responding to a configure event, the window geometry in here
+ must respect the sizing negotiations specified by the states in
+ the configure event.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_maximized" />
+ <request name="unset_maximized" />
+
+ <request name="set_fullscreen">
+ <description summary="set the window as fullscreen on a monitor">
+ Make the surface fullscreen.
+
+ You can specify an output that you would prefer to be fullscreen.
+ If this value is NULL, it's up to the compositor to choose which
+ display will be used to map this surface.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+ <request name="unset_fullscreen" />
+
+ <request name="set_minimized" />
+
+ <event name="close">
+ <description summary="surface wants to be closed">
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any...
+
+ This is only a request that the user intends to close your
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data...
+ </description>
+ </event>
+ </interface>
+
+ <interface name="xdg_popup" version="1">
+ <description summary="desktop-style metadata interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style popups/menus. A popup
+ surface is a transient surface with an added pointer grab.
+
+ An existing implicit grab will be changed to owner-events mode,
+ and the popup grab will continue after the implicit grab ends
+ (i.e. releasing the mouse button does not cause the popup to be
+ unmapped).
+
+ The popup grab continues until the window is destroyed or a mouse
+ button is pressed in any other clients window. A click in any of
+ the clients surfaces is reported as normal, however, clicks in
+ other clients surfaces will be discarded and trigger the callback.
+
+ The x and y arguments specify the locations of the upper left
+ corner of the surface relative to the upper left corner of the
+ parent surface, in surface local coordinates.
+
+ xdg_popup surfaces are always transient for another surface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_surface interface">
+ The xdg_surface interface is removed from the wl_surface object
+ that was turned into a xdg_surface with
+ xdg_shell.get_xdg_surface request. The xdg_surface properties,
+ like maximized and fullscreen, are lost. The wl_surface loses
+ its role as a xdg_surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup grab is broken,
+ that is, when the users clicks a surface that doesn't belong
+ to the client owning the popup surface.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the implicit grab on the pointer"/>
+ </event>
+
+ </interface>
+</protocol>