diff options
Diffstat (limited to 'lib/renderers')
| -rw-r--r-- | lib/renderers/cairo.h | 101 | ||||
| -rw-r--r-- | lib/renderers/curses/curses.c | 72 | ||||
| -rw-r--r-- | lib/renderers/wayland/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | lib/renderers/wayland/registry.c | 285 | ||||
| -rw-r--r-- | lib/renderers/wayland/wayland.c | 623 | ||||
| -rw-r--r-- | lib/renderers/wayland/wayland.h | 100 | ||||
| -rw-r--r-- | lib/renderers/wayland/window.c | 311 | ||||
| -rw-r--r-- | lib/renderers/wayland/xdg-shell.xml | 422 | 
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, ®istry_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, ®istry_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> | 
