1
0
Fork 0
mirror of https://github.com/Cloudef/bemenu synced 2024-06-01 04:16:21 +02:00

Cleanup wayland renderer and plugin support.

This commit is contained in:
Jari Vetoniemi 2014-10-25 01:38:30 +03:00
parent f8d97efb8f
commit 3f5e21a83f
19 changed files with 1606 additions and 685 deletions

77
CMake/Wayland.cmake Normal file
View File

@ -0,0 +1,77 @@
#=============================================================================
# Copyright (C) 2012-2013 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# 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()

View File

@ -1,4 +1,4 @@
#define _XOPEN_SOURCE 500
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -9,19 +9,23 @@
#include <bemenu.h>
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);

View File

@ -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}

View File

@ -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 :*/

View File

@ -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 <stddef.h> /* for size_t */
#endif
#include <stdarg.h>
/**
* 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 :*/

View File

@ -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 :*/

View File

@ -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 :*/

View File

@ -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;
}

101
lib/renderers/cairo.h Normal file
View File

@ -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 :*/

View File

@ -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";
}

View File

@ -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)

View File

@ -0,0 +1,285 @@
#include "wayland.h"
#include <unistd.h>
#include <sys/mman.h>
const char *BM_XKB_MASK_NAMES[MASK_LAST] = {
XKB_MOD_NAME_SHIFT,
XKB_MOD_NAME_CAPS,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_ALT,
"Mod2",
"Mod3",
XKB_MOD_NAME_LOGO,
"Mod5",
};
const enum mod_bit BM_XKB_MODS[MASK_LAST] = {
MOD_SHIFT,
MOD_CAPS,
MOD_CTRL,
MOD_ALT,
MOD_MOD2,
MOD_MOD3,
MOD_LOGO,
MOD_MOD5
};
static void
xdg_shell_ping(void *data, struct xdg_shell *shell, uint32_t serial)
{
(void)data;
xdg_shell_pong(shell, serial);
}
static const struct xdg_shell_listener xdg_shell_listener = {
.ping = xdg_shell_ping,
};
static void
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
(void)wl_shm;
struct wayland *wayland = data;
wayland->formats |= (1 << format);
}
struct wl_shm_listener shm_listener = {
.format = shm_format
};
static void
keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
{
(void)keyboard;
struct input *input = data;
if (!data) {
close(fd);
return;
}
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
char *map_str;
if ((map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
close(fd);
return;
}
struct xkb_keymap *keymap = xkb_keymap_new_from_string(input->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
munmap(map_str, size);
close(fd);
if (!keymap) {
fprintf(stderr, "failed to compile keymap\n");
return;
}
struct xkb_state *state;
if (!(state = xkb_state_new(keymap))) {
fprintf(stderr, "failed to create XKB state\n");
xkb_keymap_unref(keymap);
return;
}
xkb_keymap_unref(input->xkb.keymap);
xkb_state_unref(input->xkb.state);
input->xkb.keymap = keymap;
input->xkb.state = state;
for (uint32_t i = 0; i < MASK_LAST; ++i)
input->xkb.masks[i] = xkb_keymap_mod_get_index(input->xkb.keymap, BM_XKB_MASK_NAMES[i]);
}
static void
keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
{
(void)data, (void)keyboard, (void)serial, (void)surface, (void)keys;
}
static void
keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
{
(void)data, (void)keyboard, (void)serial, (void)surface;
}
static void
keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w)
{
(void)keyboard, (void)serial, (void)time;
struct input *input = data;
enum wl_keyboard_key_state state = state_w;
if (!input->xkb.state)
return;
uint32_t code = key + 8;
xkb_keysym_t sym = xkb_state_key_get_one_sym(input->xkb.state, code);
input->sym = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? sym : XKB_KEY_NoSymbol);
input->code = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? code : 0);
if (input->notify.key)
input->notify.key(state, sym, code);
#if 0
if (state == WL_KEYBOARD_KEY_STATE_RELEASED &&
key == input->repeat_key) {
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
} else if (state == WL_KEYBOARD_KEY_STATE_PRESSED &&
xkb_keymap_key_repeats(input->xkb.keymap, code)) {
input->repeat_sym = sym;
input->repeat_key = key;
input->repeat_time = time;
its.it_interval.tv_sec = input->repeat_rate_sec;
its.it_interval.tv_nsec = input->repeat_rate_nsec;
its.it_value.tv_sec = input->repeat_delay_sec;
its.it_value.tv_nsec = input->repeat_delay_nsec;
timerfd_settime(input->repeat_timer_fd, 0, &its, NULL);
}
#endif
}
static void
keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
{
(void)keyboard, (void)serial;
struct input *input = data;
if (!input->xkb.keymap)
return;
xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
xkb_mod_mask_t mask = xkb_state_serialize_mods(input->xkb.state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
input->modifiers = 0;
for (uint32_t i = 0; i < MASK_LAST; ++i) {
if (mask & input->xkb.masks[i])
input->modifiers |= BM_XKB_MODS[i];
}
}
static void
keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay)
{
(void)data, (void)keyboard, (void)rate, (void)delay;
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
.repeat_info = keyboard_handle_repeat_info
};
static void
seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps)
{
struct input *input = data;
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) {
input->keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(input->keyboard, &keyboard_listener, data);
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) {
wl_keyboard_destroy(input->keyboard);
input->keyboard = NULL;
}
}
static void
seat_handle_name(void *data, struct wl_seat *seat, const char *name)
{
(void)data, (void)seat, (void)name;
}
static const struct wl_seat_listener seat_listener = {
seat_handle_capabilities,
seat_handle_name
};
static void
registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
(void)version;
struct wayland *wayland = data;
if (strcmp(interface, "wl_compositor") == 0) {
wayland->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
} else if (strcmp(interface, "xdg_shell") == 0) {
wayland->xdg_shell = wl_registry_bind(registry, id, &xdg_shell_interface, 1);
xdg_shell_use_unstable_version(wayland->xdg_shell, 4);
xdg_shell_add_listener(wayland->xdg_shell, &xdg_shell_listener, data);
} else if (strcmp(interface, "wl_shell") == 0) {
wayland->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
} else if (strcmp(interface, "wl_seat") == 0) {
wayland->seat = wl_registry_bind(registry, id, &wl_seat_interface, XDG_SHELL_VERSION_CURRENT);
wl_seat_add_listener(wayland->seat, &seat_listener, &wayland->input);
} else if (strcmp(interface, "wl_shm") == 0) {
wayland->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
wl_shm_add_listener(wayland->shm, &shm_listener, data);
}
}
static void
registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
(void)data, (void)registry, (void)name;
}
static const struct wl_registry_listener registry_listener = {
registry_handle_global,
registry_handle_global_remove
};
void
bm_wl_registry_destroy(struct wayland *wayland)
{
assert(wayland);
if (wayland->shm)
wl_shm_destroy(wayland->shm);
if (wayland->shell)
wl_shell_destroy(wayland->shell);
if (wayland->xdg_shell)
xdg_shell_destroy(wayland->xdg_shell);
if (wayland->compositor)
wl_compositor_destroy(wayland->compositor);
if (wayland->registry)
wl_registry_destroy(wayland->registry);
}
bool
bm_wl_registry_register(struct wayland *wayland)
{
assert(wayland);
if (!(wayland->registry = wl_display_get_registry(wayland->display)))
return false;
wl_registry_add_listener(wayland->registry, &registry_listener, wayland);
wl_display_roundtrip(wayland->display); // trip 1, registry globals
if (!wayland->compositor || !wayland->seat || !wayland->shm || !(wayland->shell || wayland->xdg_shell))
return false;
wl_display_roundtrip(wayland->display); // trip 2, global listeners
if (!wayland->input.keyboard || !(wayland->formats & (1 << WL_SHM_FORMAT_ARGB8888)))
return false;
return true;
}
/* vim: set ts=8 sw=4 tw=0 :*/

View File

@ -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;
struct wayland *wayland = menu->renderer->internal;
if (wayland.shm)
wl_shm_destroy(wayland.shm);
if (!wayland)
return;
#if 0
if (wayland.shell)
xdg_shell_destroy(wayland.shell);
#endif
bm_wl_window_destroy(&wayland->window);
bm_wl_registry_destroy(wayland);
if (wayland.compositor)
wl_compositor_destroy(wayland.compositor);
if (wayland.registry)
wl_registry_destroy(wayland.registry);
if (wayland.display) {
wl_display_flush(wayland.display);
wl_display_disconnect(wayland.display);
if (wayland->display) {
wl_display_flush(wayland->display);
wl_display_disconnect(wayland->display);
}
free(wayland);
menu->renderer->internal = NULL;
}
static bool
constructor(struct bm_menu *menu)
{
(void)menu;
memset(&wayland, 0, sizeof(wayland));
if (!(wayland.display = wl_display_connect(NULL)))
struct wayland *wayland;
if (!(menu->renderer->internal = wayland = calloc(1, sizeof(struct wayland))))
goto fail;
if (!(wayland.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
if (!(wayland->display = wl_display_connect(NULL)))
goto fail;
wayland.width = 800;
wayland.height = 480;
wayland.registry = wl_display_get_registry(wayland.display);
wl_registry_add_listener(wayland.registry, &registry_listener, &wayland);
wl_display_roundtrip(wayland.display); // shm bind
if (!wayland.shm)
if (!(wayland->input.xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
goto fail;
wl_display_roundtrip(wayland.display); // shm formats
if (!(wayland.formats & (1 << WL_SHM_FORMAT_ARGB8888)))
if (!bm_wl_registry_register(wayland))
goto fail;
if (!(wayland.surface = wl_compositor_create_surface(wayland.compositor)))
struct wl_surface *surface;
if (!(surface = wl_compositor_create_surface(wayland->compositor)))
goto fail;
if (!(wayland.shell_surface = wl_shell_get_shell_surface(wayland.shell, wayland.surface)))
if (!bm_wl_window_create(&wayland->window, wayland->shm, wayland->shell, wayland->xdg_shell, surface))
goto fail;
wayland.menu = menu;
wl_surface_damage(wayland.surface, 0, 0, wayland.width, wayland.height);
wayland->window.notify.render = bm_cairo_paint;
return true;
fail:
@ -698,6 +143,8 @@ register_renderer(struct render_api *api)
api->get_displayed_count = get_displayed_count;
api->poll_key = poll_key;
api->render = render;
api->prioritory = BM_PRIO_GUI;
api->version = BM_VERSION;
return "wayland";
}

View File

@ -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 :*/

View File

@ -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 :*/

View File

@ -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>

View File

@ -5,6 +5,7 @@
#include "internal.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
@ -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.
*

View File

@ -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 :*/

View File

@ -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;