From 3f5e21a83f525586d1ee5b1bcd04cc2e50350403 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Sat, 25 Oct 2014 01:38:30 +0300 Subject: Cleanup wayland renderer and plugin support. --- CMake/Wayland.cmake | 77 +++++ client/client.c | 57 ++-- lib/CMakeLists.txt | 4 +- lib/bemenu.h | 37 ++- lib/internal.h | 37 ++- lib/library.c | 36 +- lib/list.c | 20 +- lib/menu.c | 19 +- lib/renderers/cairo.h | 101 ++++++ lib/renderers/curses/curses.c | 72 ++-- lib/renderers/wayland/CMakeLists.txt | 6 +- lib/renderers/wayland/registry.c | 285 ++++++++++++++++ lib/renderers/wayland/wayland.c | 623 ++--------------------------------- lib/renderers/wayland/wayland.h | 100 ++++++ lib/renderers/wayland/window.c | 311 +++++++++++++++++ lib/renderers/wayland/xdg-shell.xml | 422 ++++++++++++++++++++++++ lib/util.c | 80 +++++ lib/version.h.in | 2 +- test/bm_menu_new.c | 2 +- 19 files changed, 1606 insertions(+), 685 deletions(-) create mode 100644 CMake/Wayland.cmake create mode 100644 lib/renderers/cairo.h create mode 100644 lib/renderers/wayland/registry.c create mode 100644 lib/renderers/wayland/wayland.h create mode 100644 lib/renderers/wayland/window.c create mode 100644 lib/renderers/wayland/xdg-shell.xml diff --git a/CMake/Wayland.cmake b/CMake/Wayland.cmake new file mode 100644 index 0000000..42f97b3 --- /dev/null +++ b/CMake/Wayland.cmake @@ -0,0 +1,77 @@ +#============================================================================= +# Copyright (C) 2012-2013 Pier Luigi Fiorini +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Pier Luigi Fiorini nor the names of his +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) + +# wayland_add_protocol_client(outfiles inputfile basename) +function(WAYLAND_ADD_PROTOCOL_CLIENT _sources _protocol _basename) + if(NOT WAYLAND_SCANNER_EXECUTABLE) + message(FATAL "The wayland-scanner executable has nto been found on your system. You must install it.") + endif() + + get_filename_component(_infile ${_protocol} ABSOLUTE) + set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${_basename}-client-protocol.h") + set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${_basename}-protocol.c") + + add_custom_command(OUTPUT "${_client_header}" + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header < ${_infile} > ${_client_header} + DEPENDS ${_infile} VERBATIM) + + add_custom_command(OUTPUT "${_code}" + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code < ${_infile} > ${_code} + DEPENDS ${_infile} VERBATIM) + + list(APPEND ${_sources} "${_client_header}" "${_code}") + set(${_sources} ${${_sources}} PARENT_SCOPE) +endfunction() + +# wayland_add_protocol_server(outfiles inputfile basename) +function(WAYLAND_ADD_PROTOCOL_SERVER _sources _protocol _basename) + if(NOT WAYLAND_SCANNER_EXECUTABLE) + message(FATAL "The wayland-scanner executable has nto been found on your system. You must install it.") + endif() + + get_filename_component(_infile ${_protocol} ABSOLUTE) + set(_server_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${_basename}-server-protocol.h") + set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${_basename}-protocol.c") + + add_custom_command(OUTPUT "${_server_header}" + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} server-header < ${_infile} > ${_server_header} + DEPENDS ${_infile} VERBATIM) + + add_custom_command(OUTPUT "${_code}" + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code < ${_infile} > ${_code} + DEPENDS ${_infile} VERBATIM) + + list(APPEND ${_sources} "${_server_header}" "${_code}") + set(${_sources} ${${_sources}} PARENT_SCOPE) +endfunction() diff --git a/client/client.c b/client/client.c index def8978..2d12559 100644 --- a/client/client.c +++ b/client/client.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _DEFAULT_SOURCE #include #include #include @@ -9,19 +9,23 @@ #include static struct { + enum bm_prioritory prioritory; enum bm_filter_mode filter_mode; - int wrap; - unsigned int lines; + int32_t wrap; + uint32_t lines; const char *title; - int selected; - int bottom; - int grab; - int monitor; + const char *renderer; + int32_t selected; + int32_t bottom; + int32_t grab; + int32_t monitor; } client = { + .prioritory = BM_PRIO_ANY, .filter_mode = BM_FILTER_MODE_DMENU, .wrap = 0, .lines = 0, .title = "bemenu", + .renderer = NULL, .selected = 0, .bottom = 0, .grab = 0, @@ -84,10 +88,12 @@ usage(FILE *out, const char *name) " -w, --wrap wraps cursor selection.\n" " -l, --list list items vertically with the given number of lines.\n" " -p, --prompt defines the prompt text to be displayed.\n" - " -I, --index select item at index automatically.\n\n" + " -I, --index select item at index automatically.\n" + " --backend options: curses, wayland\n" + " --prioritory options: terminal, gui\n\n" "Backend specific options\n" - " c = ncurses\n" // x == x11 + " c = ncurses, w == wayland\n" " (...) At end of help indicates the backend support for option.\n\n" " -b, --bottom appears at the bottom of the screen. ()\n" @@ -113,17 +119,19 @@ parse_args(int *argc, char **argv[]) { "list", required_argument, 0, 'l' }, { "prompt", required_argument, 0, 'p' }, { "index", required_argument, 0, 'I' }, + { "backend", required_argument, 0, 0x100 }, + { "prioritory", required_argument, 0, 0x101 }, { "bottom", no_argument, 0, 'b' }, { "grab", no_argument, 0, 'f' }, { "monitor", required_argument, 0, 'm' }, - { "fn", required_argument, 0, 0x100 }, - { "nb", required_argument, 0, 0x101 }, - { "nf", required_argument, 0, 0x102 }, - { "sb", required_argument, 0, 0x103 }, - { "sf", required_argument, 0, 0x104 }, + { "fn", required_argument, 0, 0x102 }, + { "nb", required_argument, 0, 0x103 }, + { "nf", required_argument, 0, 0x104 }, + { "sb", required_argument, 0, 0x105 }, + { "sf", required_argument, 0, 0x106 }, - { "disco", no_argument, 0, 0x105 }, + { "disco", no_argument, 0, 0x107 }, { 0, 0, 0, 0 } }; @@ -160,6 +168,17 @@ parse_args(int *argc, char **argv[]) client.selected = strtol(optarg, NULL, 10); break; + case 0x100: + client.renderer = optarg; + break; + + case 0x101: + if (!strcmp(optarg, "terminal")) + client.prioritory = BM_PRIO_TERMINAL; + else if (!strcmp(optarg, "gui")) + client.prioritory = BM_PRIO_GUI; + break; + case 'b': client.bottom = 1; break; @@ -170,14 +189,14 @@ parse_args(int *argc, char **argv[]) client.monitor = strtol(optarg, NULL, 10); break; - case 0x100: - case 0x101: case 0x102: case 0x103: case 0x104: + case 0x105: + case 0x106: break; - case 0x105: + case 0x107: disco(); break; @@ -241,7 +260,7 @@ main(int argc, char **argv) parse_args(&argc, &argv); struct bm_menu *menu; - if (!(menu = bm_menu_new(NULL))) + if (!(menu = bm_menu_new(client.renderer, client.prioritory))) return EXIT_FAILURE; bm_menu_set_title(menu, client.title); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 416f8d1..0c87bf8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -29,11 +29,13 @@ ENDIF () STRING(REGEX MATCHALL "[0-9]+" VERSION_COMPONENTS ${BEMENU_VERSION}) LIST(GET VERSION_COMPONENTS 0 SOVERSION) +# Add include directories +INCLUDE_DIRECTORIES(${BEMENU_INCLUDE} ${CMAKE_CURRENT_BINARY_DIR}) + # Compile renderer plugins ADD_SUBDIRECTORY(renderers) # Compile -INCLUDE_DIRECTORIES(${BEMENU_INCLUDE} ${CMAKE_CURRENT_BINARY_DIR}) ADD_LIBRARY(bemenu SHARED ${BEMENU_SOURCE}) SET_TARGET_PROPERTIES(bemenu PROPERTIES VERSION ${BEMENU_VERSION} diff --git a/lib/bemenu.h b/lib/bemenu.h index a58dcb3..20574f6 100644 --- a/lib/bemenu.h +++ b/lib/bemenu.h @@ -1,3 +1,6 @@ +#ifndef _BEMENU_H_ +#define _BEMENU_H_ + /** * @file bemenu.h * @@ -79,6 +82,27 @@ const char* bm_version(void); * @addtogroup Renderer * @{ */ +/** + * Prioritories for renderer plugins. + */ +enum bm_prioritory { + /** + * Do not use this in renderers. + * This flag is for bm_menu_new, if any renderer is fine. + */ + BM_PRIO_ANY, + + /** + * Renderer runs in terminal. + */ + BM_PRIO_TERMINAL, + + /** + * Renderer runs in GUI. + */ + BM_PRIO_GUI, +}; + /** * Get name of the renderer. * @@ -87,6 +111,14 @@ const char* bm_version(void); */ const char* bm_renderer_get_name(const struct bm_renderer *renderer); +/** + * Get prioritory of the renderer. + * + * @param renderer bm_renderer instance. + * @return bm_prioritory enum value. + */ +enum bm_prioritory bm_renderer_get_prioritory(const struct bm_renderer *renderer); + /** * @} Renderer */ @@ -158,9 +190,10 @@ enum bm_key { * Create new bm_menu instance. * * @param renderer Name of renderer to be used for this instance, pass **NULL** for auto-detection. + * @param prioritory @link ::bm_prioritory @endlink enum for which kind of renderer is wanted. Pass BM_PRIO_ANY, for anything. * @return bm_menu for new menu instance, **NULL** if creation failed. */ -struct bm_menu* bm_menu_new(const char *renderer); +struct bm_menu* bm_menu_new(const char *renderer, enum bm_prioritory prioritory); /** * Release bm_menu instance. @@ -512,4 +545,6 @@ const char* bm_item_get_text(const struct bm_item *item); /** @} Item */ +#endif /* _BEMENU_H_ */ + /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/internal.h b/lib/internal.h index aa35ed8..fe392a1 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -1,9 +1,20 @@ +#ifndef _BEMENU_INTERNAL_H_ +#define _BEMENU_INTERNAL_H_ + #include "bemenu.h" +#if __GNUC__ +# define BM_LOG_ATTR(x, y) __attribute__((format(printf, x, y))) +#else +# define BM_LOG_ATTR(x, y) +#endif + #ifndef size_t # include /* for size_t */ #endif +#include + /** * Destructor function pointer for some list calls. */ @@ -59,6 +70,18 @@ struct render_api { * Tells underlying renderer to draw the menu. */ void (*render)(const struct bm_menu *menu); + + /** + * Version of the plugin. + * Should match BM_VERSION or failure. + */ + const char *version; + + /** + * Prioritory of the plugin. + * Terminal renderers should be first, then graphicals. + */ + enum bm_prioritory prioritory; }; /** @@ -80,6 +103,12 @@ struct bm_renderer { */ void *handle; + /** + * Data used by the renderer internally. + * Nobody else should touch this. + */ + void *internal; + /** * API */ @@ -117,7 +146,7 @@ struct bm_menu { /** * Underlying renderer access. */ - const struct bm_renderer *renderer; + struct bm_renderer *renderer; /** * Items contained in menu instance. @@ -202,9 +231,13 @@ bool list_add_item_at(struct list *list, void *item, uint32_t index); bool list_add_item(struct list *list, void *item); bool list_remove_item_at(struct list *list, uint32_t index); bool list_remove_item(struct list *list, const void *item); +void list_sort(struct list *list, int (*compar)(const void *a, const void *b)); /* util.c */ char* bm_strdup(const char *s); +bool bm_resize_buffer(char **in_out_buffer, size_t *in_out_size, size_t nsize); +BM_LOG_ATTR(1, 2) char* bm_dprintf(const char *fmt, ...); +bool bm_vrprintf(char **in_out_buffer, size_t *in_out_len, const char *fmt, va_list args); size_t bm_strip_token(char *string, const char *token, size_t *out_next); int bm_strupcmp(const char *hay, const char *needle); int bm_strnupcmp(const char *hay, const char *needle, size_t len); @@ -217,4 +250,6 @@ size_t bm_utf8_rune_remove(char *string, size_t start, size_t *out_rune_width); size_t bm_utf8_rune_insert(char **string, size_t *bufSize, size_t start, const char *rune, uint32_t u8len, size_t *out_rune_width); size_t bm_unicode_insert(char **string, size_t *bufSize, size_t start, uint32_t unicode, size_t *out_rune_width); +#endif /* _BEMENU_INTERNAL_H_ */ + /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/library.c b/lib/library.c index 443721c..c7f5e60 100644 --- a/lib/library.c +++ b/lib/library.c @@ -36,10 +36,16 @@ load(const char *file, struct bm_renderer *renderer) if (!(regfun = chckDlLoadSymbol(handle, "register_renderer", &error))) goto fail; - renderer->file = bm_strdup(file); + const char *name; + if (!(name = regfun(&renderer->api))) + goto fail; + + if (strcmp(renderer->api.version, BM_VERSION)) + goto fail; + renderer->handle = handle; - const char *name = regfun(&renderer->api); - renderer->name = (name ? bm_strdup(name) : NULL); + renderer->name = bm_strdup(name); + renderer->file = bm_strdup(file); return true; fail: @@ -49,6 +55,13 @@ fail: return false; } +static int +compare(const void *a, const void *b) +{ + const struct bm_renderer *ra = *(struct bm_renderer**)a, *rb = *(struct bm_renderer**)b; + return (ra->api.prioritory > rb->api.prioritory); +} + static bool load_to_list(const char *file) { @@ -60,7 +73,12 @@ load_to_list(const char *file) goto fail; chckDlUnload(renderer->handle); - return list_add_item(&renderers, renderer); + + if (!list_add_item(&renderers, renderer)) + goto fail; + + list_sort(&renderers, compare); + return true; fail: if (renderer) @@ -76,6 +94,8 @@ bm_renderer_activate(struct bm_renderer *renderer, struct bm_menu *menu) if (!load(renderer->file, renderer)) return false; + menu->renderer = renderer; + if (!renderer->api.constructor(menu)) goto fail; @@ -83,6 +103,7 @@ bm_renderer_activate(struct bm_renderer *renderer, struct bm_menu *menu) fail: chckDlUnload(renderer->handle); + menu->renderer = NULL; return false; } @@ -155,4 +176,11 @@ bm_renderer_get_name(const struct bm_renderer *renderer) return renderer->name; } +enum bm_prioritory +bm_renderer_get_prioritory(const struct bm_renderer *renderer) +{ + assert(renderer); + return renderer->api.prioritory; +} + /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/list.c b/lib/list.c index 918e830..993998f 100644 --- a/lib/list.c +++ b/lib/list.c @@ -79,15 +79,8 @@ list_grow(struct list *list, uint32_t step) void *tmp; uint32_t nsize = sizeof(struct bm_item*) * (list->allocated + step); - if (!list->items || !(tmp = realloc(list->items, nsize))) { - if (!(tmp = malloc(nsize))) - return false; - - if (list->items) { - memcpy(tmp, list->items, sizeof(struct bm_item*) * list->allocated); - free(list->items); - } - } + if (!(tmp = realloc(list->items, nsize))) + return false; list->items = tmp; list->allocated += step; @@ -136,9 +129,18 @@ list_remove_item_at(struct list *list, uint32_t index) bool list_remove_item(struct list *list, const void *item) { + assert(list && item); + uint32_t i; for (i = 0; i < list->count && list->items[i] != item; ++i); return list_remove_item_at(list, i); } +void +list_sort(struct list *list, int (*compar)(const void *a, const void *b)) +{ + assert(list && compar); + qsort(list->items, list->count, sizeof(void*), compar); +} + /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/menu.c b/lib/menu.c index 6a5001c..594344c 100644 --- a/lib/menu.c +++ b/lib/menu.c @@ -25,7 +25,7 @@ bm_menu_item_is_selected(const struct bm_menu *menu, const struct bm_item *item) } struct bm_menu* -bm_menu_new(const char *renderer) +bm_menu_new(const char *renderer, enum bm_prioritory prioritory) { struct bm_menu *menu; if (!(menu = calloc(1, sizeof(struct bm_menu)))) @@ -34,19 +34,24 @@ bm_menu_new(const char *renderer) uint32_t count; const struct bm_renderer **renderers = bm_get_renderers(&count); - bool status = false; for (uint32_t i = 0; i < count; ++i) { + if (prioritory != BM_PRIO_ANY && renderers[i]->api.prioritory != prioritory) + continue; + if (renderer && strcmp(renderer, renderers[i]->name)) continue; - if (bm_renderer_activate((struct bm_renderer*)renderers[i], menu)) { - status = true; - menu->renderer = renderers[i]; - break; + if (renderers[i]->api.prioritory == BM_PRIO_TERMINAL) { + const char *term = getenv("TERM"); + if (!term || !strlen(term)) + continue; } + + if (bm_renderer_activate((struct bm_renderer*)renderers[i], menu)) + break; } - if (!status) { + if (!menu->renderer) { bm_menu_free(menu); return NULL; } 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 +#include +#include + +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 /* vsnprintf */ -# undef _C99_SOURCE -#endif - -#define _XOPEN_SOURCE 500 -#include /* sigaction */ -#include /* vsnprintf */ -#undef _XOPEN_SOURCE +#define _DEFAULT_SOURCE +#define _XOPEN_SOURCE_EXTENDED #include "internal.h" +#include "version.h" #include +#include #include #include #include #include -#include #include #include +#include + #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 +#include + +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 #include #include #include -#include -#include -#include -#include -#include - -#include -#include -#include - -#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 +#include + +#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 +#include +#include +#include +#include + +#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 @@ + + + + + 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. + + + + + This interface is implemented by servers that provide + desktop-style user interfaces. + + It allows clients to associate a xdg_surface with + a basic surface. + + + + + 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. + + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. + + + + + + + + + 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. + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + 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). + + + + + + + + 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. + + + + + + + + + + + + + + + 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). + + + + + + + + + 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 + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + The surface is fullscreen. The window geometry specified in the configure + event must be obeyed by the client. + + + 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. + + + 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. + + + + + + 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. + + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + + + 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. + + + + + + + + + + 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... + + + + + + + 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. + + + + + 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. + + + + + + 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. + + + + + + diff --git a/lib/util.c b/lib/util.c index 246e102..0bacc4e 100644 --- a/lib/util.c +++ b/lib/util.c @@ -5,6 +5,7 @@ #include "internal.h" #include +#include #include #include #include @@ -31,6 +32,85 @@ bm_strdup(const char *string) return (char *)memcpy(copy, string, len); } +/** + * Small wrapper around realloc. + * Resizes the buffer. + * + * @param in_out_buffer Reference to the input buffer that will be modified on succesful resize. + * @param in_out_size Current buffer size, will be modified with new size on succesful resize. + * @param nsize New size to resize the buffer to. + * @return true for succesful resize, false for failure. + */ +bool +bm_resize_buffer(char **in_out_buffer, size_t *in_out_size, size_t nsize) +{ + assert(in_out_buffer && in_out_size); + + if (nsize == 0 || nsize <= *in_out_size) + return false; + + void *tmp; + if (!(tmp = realloc(*in_out_buffer, nsize))) + return false; + + *in_out_buffer = tmp; + *in_out_size = nsize; + return true; +} + +/** + * Formatted printf that returns allocated char array. + * + * @param fmt Format as C "string". + * @return Copy of the formatted C "string". + */ +char* +bm_dprintf(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; +} + +/** + * Formatted printf that reuses and grows buffer when neccessary. + * + * @param in_out_buffer Reference to buffer that holds the new formatted text. + * @param in_out_len Reference to the length of current buffer and outs as resized length. + * @param fmt Format as C "string". + * @param va_list Argument list. + * @return true if successful, false if failure. + */ +bool +bm_vrprintf(char **in_out_buffer, size_t *in_out_len, const char *fmt, va_list args) +{ + assert(in_out_buffer && in_out_len && fmt); + + va_list copy; + va_copy(copy, args); + + size_t len = vsnprintf(NULL, 0, fmt, args) + 1; + + if ((!*in_out_buffer || *in_out_len < len) && !bm_resize_buffer(in_out_buffer, in_out_len, len)) + return false; + + vsnprintf(*in_out_buffer, len, fmt, copy); + va_end(copy); + return true; +} + /** * Replaces next token in string with '\0' and returns position for the replaced token. * diff --git a/lib/version.h.in b/lib/version.h.in index 9d9b851..ad82a1a 100644 --- a/lib/version.h.in +++ b/lib/version.h.in @@ -1,4 +1,4 @@ #define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" -static const char *BM_VERSION = "@BEMENU_VERSION@"; +#define BM_VERSION "@BEMENU_VERSION@" /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/test/bm_menu_new.c b/test/bm_menu_new.c index 20ecbd5..9d9e791 100644 --- a/test/bm_menu_new.c +++ b/test/bm_menu_new.c @@ -22,7 +22,7 @@ main(int argc, char **argv) uint32_t count; const struct bm_renderer **renderers = bm_get_renderers(&count); for (int32_t i = 0; i < count; ++i) { - struct bm_menu *menu = bm_menu_new(bm_renderer_get_name(renderers[i])); + struct bm_menu *menu = bm_menu_new(bm_renderer_get_name(renderers[i]), BM_PRIO_ANY); if (!strcmp(bm_renderer_get_name(renderers[i]), "curses") && !isatty(STDIN_FILENO)) { assert(!menu); continue; -- cgit v1.2.3-70-g09d2