Initial commit
This commit is contained in:
commit
2f1c189d53
13 changed files with 1825 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build
|
32
LICENSE
Normal file
32
LICENSE
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
MIT/X Consortium License
|
||||||
|
|
||||||
|
© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
|
||||||
|
© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
|
||||||
|
© 2006-2007 Michał Janeczek <janeczek@gmail.com>
|
||||||
|
© 2007 Kris Maglione <jg@suckless.org>
|
||||||
|
© 2009 Gottox <gottox@s01.de>
|
||||||
|
© 2009 Markus Schnalke <meillo@marmaro.de>
|
||||||
|
© 2009 Evan Gates <evan.gates@gmail.com>
|
||||||
|
© 2010-2012 Connor Lane Smith <cls@lubutu.com>
|
||||||
|
© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org>
|
||||||
|
© 2015-2019 Quentin Rameau <quinq@fifth.space>
|
||||||
|
© 2018-2019 Henrik Nyman <h@nyymanni.com>
|
||||||
|
© 2022 adnano <me@adnano.co>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
29
README.md
Normal file
29
README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# wmenu - dmenu for Wayland
|
||||||
|
|
||||||
|
An efficient dynamic menu for supported Wayland compositors (requires
|
||||||
|
`wlr_layer_shell_v1` support).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
|
||||||
|
- cairo
|
||||||
|
- pango
|
||||||
|
- wayland
|
||||||
|
- xkbcommon
|
||||||
|
- scdoc (optional)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ meson build
|
||||||
|
$ ninja -C build
|
||||||
|
# ninja -C build install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See wmenu(1)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This project started as a fork of [dmenu-wl](https://github.com/nyyManni/dmenu-wayland).
|
||||||
|
However, most of the code was rewritten from scratch.
|
26
docs/meson.build
Normal file
26
docs/meson.build
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
scdoc_dep = dependency('scdoc', version: '>=1.9.2', native: true)
|
||||||
|
|
||||||
|
if scdoc_dep.found()
|
||||||
|
scdoc = find_program(
|
||||||
|
scdoc_dep.get_pkgconfig_variable('scdoc'),
|
||||||
|
native: true,
|
||||||
|
)
|
||||||
|
mandir = get_option('mandir')
|
||||||
|
|
||||||
|
docs = [
|
||||||
|
'wmenu.1',
|
||||||
|
]
|
||||||
|
|
||||||
|
foreach path : docs
|
||||||
|
custom_target(
|
||||||
|
path,
|
||||||
|
output: path,
|
||||||
|
input: '@0@.scd'.format(path),
|
||||||
|
capture: true,
|
||||||
|
feed: true,
|
||||||
|
command: [scdoc],
|
||||||
|
install: true,
|
||||||
|
install_dir: '@0@/man1'.format(mandir)
|
||||||
|
)
|
||||||
|
endforeach
|
||||||
|
endif
|
86
docs/wmenu.1.scd
Normal file
86
docs/wmenu.1.scd
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
wmenu(1)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
wmenu - dynamic menu for Wayland
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
*wmenu* [-biv] \
|
||||||
|
[-f _font_] \
|
||||||
|
[-l _lines_] \
|
||||||
|
[-o _output_] \
|
||||||
|
[-p _prompt_] \
|
||||||
|
[-N _color_] [-n _color_] \
|
||||||
|
[-M _color_] [-m _color_] \
|
||||||
|
[-S _color_] [-s _color_]
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
wmenu is a dynamic menu for Wayland, which reads a list of newline-separated
|
||||||
|
items from stdin. When the user selects an item and presses Return, their choice
|
||||||
|
is printed to stdout and wmenu terminates. Entering text will narrow the items
|
||||||
|
to those matching the tokens in the input.
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
*-b*
|
||||||
|
wmenu appears at the bottom of the screen.
|
||||||
|
|
||||||
|
*-i*
|
||||||
|
wmenu matches menu items case insensitively.
|
||||||
|
|
||||||
|
*-v*
|
||||||
|
prints version information to stdout, then exits.
|
||||||
|
|
||||||
|
*-f* _font_
|
||||||
|
defines the font used. For more information, see
|
||||||
|
https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
|
||||||
|
|
||||||
|
*-l* _lines_
|
||||||
|
wmenu lists items vertically, with the given number of lines.
|
||||||
|
|
||||||
|
*-o* _output_
|
||||||
|
wmenu is displayed on the output with the given name.
|
||||||
|
|
||||||
|
*-p* _prompt_
|
||||||
|
defines the prompt to be displayed to the left of the input field.
|
||||||
|
|
||||||
|
*-N* _RRGGBB[AA]_
|
||||||
|
defines the normal background color.
|
||||||
|
|
||||||
|
*-n* _RRGGBB[AA]_
|
||||||
|
defines the normal foreground color.
|
||||||
|
|
||||||
|
*-M* _RRGGBB[AA]_
|
||||||
|
defines the prompt background color.
|
||||||
|
|
||||||
|
*-m* _RRGGBB[AA]_
|
||||||
|
defines the prompt foreground color.
|
||||||
|
|
||||||
|
*-S* _RRGGBB[AA]_
|
||||||
|
defines the selection background color.
|
||||||
|
|
||||||
|
*-s* _RRGGBB[AA]_
|
||||||
|
defines the selection foreground color.
|
||||||
|
|
||||||
|
# USAGE
|
||||||
|
|
||||||
|
wmenu is completely controlled by the keyboard. Items are selected using the
|
||||||
|
arrow keys, page up, page down, home, and end.
|
||||||
|
|
||||||
|
*Tab*
|
||||||
|
Copy the selected item to the input field.
|
||||||
|
|
||||||
|
*Return*
|
||||||
|
Confirm selection. Prints the selected item to stdout and exits, returning
|
||||||
|
success.
|
||||||
|
|
||||||
|
*Ctrl-Return*
|
||||||
|
Confirm selection. Prints the selected item to stdout and continues.
|
||||||
|
|
||||||
|
*Shift-Return*
|
||||||
|
Confirm input. Prints the input text to stdout and exits, returning success.
|
||||||
|
|
||||||
|
*Escape*
|
||||||
|
Exit without selecting an item, returning failure.
|
983
main.c
Normal file
983
main.c
Normal file
|
@ -0,0 +1,983 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/timerfd.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <wayland-client-protocol.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
#include "pango.h"
|
||||||
|
#include "pool-buffer.h"
|
||||||
|
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||||
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
struct menu_item {
|
||||||
|
char *text;
|
||||||
|
int width;
|
||||||
|
struct menu_item *next; // traverses all items
|
||||||
|
struct menu_item *left, *right; // traverses matching items
|
||||||
|
};
|
||||||
|
|
||||||
|
struct output {
|
||||||
|
struct menu_state *menu;
|
||||||
|
struct wl_output *output;
|
||||||
|
struct zxdg_output_v1 *xdg_output;
|
||||||
|
int32_t scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct menu_state {
|
||||||
|
struct output *output;
|
||||||
|
char *output_name;
|
||||||
|
|
||||||
|
struct wl_display *display;
|
||||||
|
struct wl_compositor *compositor;
|
||||||
|
struct wl_shm *shm;
|
||||||
|
struct wl_seat *seat;
|
||||||
|
struct wl_keyboard *keyboard;
|
||||||
|
struct zwlr_layer_shell_v1 *layer_shell;
|
||||||
|
struct zxdg_output_manager_v1 *output_manager;
|
||||||
|
|
||||||
|
struct wl_surface *surface;
|
||||||
|
struct zwlr_layer_surface_v1 *layer_surface;
|
||||||
|
|
||||||
|
struct xkb_context *xkb_context;
|
||||||
|
struct xkb_keymap *xkb_keymap;
|
||||||
|
struct xkb_state *xkb_state;
|
||||||
|
|
||||||
|
struct pool_buffer buffers[2];
|
||||||
|
struct pool_buffer *current;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int padding;
|
||||||
|
int inputw;
|
||||||
|
int promptw;
|
||||||
|
int left_arrow, right_arrow;
|
||||||
|
|
||||||
|
bool bottom;
|
||||||
|
int (*fstrncmp)(const char *, const char *, size_t);
|
||||||
|
char *font;
|
||||||
|
int lines;
|
||||||
|
char *prompt;
|
||||||
|
uint32_t background, foreground;
|
||||||
|
uint32_t promptbg, promptfg;
|
||||||
|
uint32_t selectionbg, selectionfg;
|
||||||
|
|
||||||
|
char text[BUFSIZ];
|
||||||
|
size_t cursor;
|
||||||
|
|
||||||
|
int repeat_timer;
|
||||||
|
int repeat_delay;
|
||||||
|
int repeat_period;
|
||||||
|
enum wl_keyboard_key_state repeat_key_state;
|
||||||
|
xkb_keysym_t repeat_sym;
|
||||||
|
|
||||||
|
bool run;
|
||||||
|
bool failure;
|
||||||
|
|
||||||
|
struct menu_item *items;
|
||||||
|
struct menu_item *matches;
|
||||||
|
struct menu_item *selection;
|
||||||
|
struct menu_item *leftmost, *rightmost;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
|
||||||
|
cairo_set_source_rgba(cairo,
|
||||||
|
(color >> (3*8) & 0xFF) / 255.0,
|
||||||
|
(color >> (2*8) & 0xFF) / 255.0,
|
||||||
|
(color >> (1*8) & 0xFF) / 255.0,
|
||||||
|
(color >> (0*8) & 0xFF) / 255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insert(struct menu_state *state, const char *s, ssize_t n);
|
||||||
|
static void match(struct menu_state *state);
|
||||||
|
static size_t nextrune(struct menu_state *state, int incr);
|
||||||
|
|
||||||
|
int render_text(struct menu_state *state, cairo_t *cairo, const char *str,
|
||||||
|
int x, int y, int width, int height,
|
||||||
|
uint32_t foreground, uint32_t background,
|
||||||
|
int left_padding, int right_padding) {
|
||||||
|
|
||||||
|
int text_width, text_height;
|
||||||
|
get_text_size(cairo, state->font, &text_width, &text_height, NULL, 1, str);
|
||||||
|
int text_y = (state->height / 2.0) - (text_height / 2.0);
|
||||||
|
|
||||||
|
if (x + left_padding + text_width > width) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (background) {
|
||||||
|
int bg_width = text_width + left_padding + right_padding;
|
||||||
|
cairo_set_source_u32(cairo, background);
|
||||||
|
cairo_rectangle(cairo, x, 0, bg_width, height);
|
||||||
|
cairo_fill(cairo);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_move_to(cairo, x + left_padding, text_y);
|
||||||
|
cairo_set_source_u32(cairo, foreground);
|
||||||
|
pango_printf(cairo, state->font, 1, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return x + text_width + left_padding + right_padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scroll_matches(struct menu_state *state) {
|
||||||
|
if (!state->matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate available space
|
||||||
|
int padding = state->padding;
|
||||||
|
int width = state->width - state->inputw - state->promptw
|
||||||
|
- state->left_arrow - state->right_arrow;
|
||||||
|
if (state->leftmost == NULL) {
|
||||||
|
state->leftmost = state->matches;
|
||||||
|
if (state->rightmost == NULL) {
|
||||||
|
int offs = 0;
|
||||||
|
struct menu_item *item;
|
||||||
|
for (item = state->matches; item->left != state->selection; item = item->right) {
|
||||||
|
offs += item->width + 2 * padding;
|
||||||
|
if (offs >= width) {
|
||||||
|
state->leftmost = item->left;
|
||||||
|
offs = width - offs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int offs = 0;
|
||||||
|
struct menu_item *item;
|
||||||
|
for (item = state->rightmost; item; item = item->left) {
|
||||||
|
offs += item->width + 2 * padding;
|
||||||
|
if (offs >= width) {
|
||||||
|
state->leftmost = item->right;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state->rightmost == NULL) {
|
||||||
|
state->rightmost = state->matches;
|
||||||
|
int offs = 0;
|
||||||
|
struct menu_item *item;
|
||||||
|
for (item = state->leftmost; item; item = item->right) {
|
||||||
|
offs += item->width + 2 * padding;
|
||||||
|
if (offs >= width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state->rightmost = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_to_cairo(struct menu_state *state, cairo_t *cairo) {
|
||||||
|
int width = state->width;
|
||||||
|
int height = state->height;
|
||||||
|
int padding = state->padding;
|
||||||
|
|
||||||
|
int text_height;
|
||||||
|
get_text_size(cairo, state->font, NULL, &text_height, NULL, 1, "");
|
||||||
|
int y = (height / 2.0) - (text_height / 2.0);
|
||||||
|
|
||||||
|
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
|
||||||
|
cairo_set_source_u32(cairo, state->background);
|
||||||
|
cairo_paint(cairo);
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
|
||||||
|
// Draw prompt
|
||||||
|
if (state->prompt) {
|
||||||
|
state->promptw = render_text(state, cairo, state->prompt,
|
||||||
|
0, y, state->width, state->height,
|
||||||
|
state->promptfg, state->promptbg,
|
||||||
|
padding, padding/2);
|
||||||
|
x += state->promptw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
cairo_set_source_u32(cairo, state->background);
|
||||||
|
cairo_rectangle(cairo, x, 0, 300, height);
|
||||||
|
cairo_fill(cairo);
|
||||||
|
|
||||||
|
// Draw input
|
||||||
|
render_text(state, cairo, state->text,
|
||||||
|
x, y, state->width, state->height,
|
||||||
|
state->foreground, 0, padding, padding);
|
||||||
|
|
||||||
|
// Draw cursor
|
||||||
|
{
|
||||||
|
int cursor_width = 2;
|
||||||
|
int cursor_margin = 2;
|
||||||
|
int cursor_pos = x + padding
|
||||||
|
+ text_width(cairo, state->font, state->text)
|
||||||
|
- text_width(cairo, state->font, &state->text[state->cursor])
|
||||||
|
- cursor_width / 2;
|
||||||
|
cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width,
|
||||||
|
state->height - 2 * cursor_margin);
|
||||||
|
cairo_fill(cairo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state->matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave room for input
|
||||||
|
x += state->inputw;
|
||||||
|
|
||||||
|
// Calculate scroll indicator widths
|
||||||
|
state->left_arrow = text_width(cairo, state->font, "<") + 2 * padding;
|
||||||
|
state->right_arrow = text_width(cairo, state->font, ">") + 2 * padding;
|
||||||
|
|
||||||
|
// Remember scroll indicator position
|
||||||
|
int left_arrow_pos = x + padding;
|
||||||
|
x += state->left_arrow;
|
||||||
|
|
||||||
|
// Draw matches
|
||||||
|
bool scroll_right = false;
|
||||||
|
struct menu_item *item;
|
||||||
|
for (item = state->leftmost; item; item = item->right) {
|
||||||
|
uint32_t bg_color = state->selection == item ? state->selectionbg : state->background;
|
||||||
|
uint32_t fg_color = state->selection == item ? state->selectionfg : state->foreground;
|
||||||
|
x = render_text(state, cairo, item->text,
|
||||||
|
x, y, width - state->right_arrow, height,
|
||||||
|
fg_color, bg_color, padding, padding);
|
||||||
|
if (x == -1) {
|
||||||
|
scroll_right = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw left scroll indicator if necessary
|
||||||
|
if (state->leftmost != state->matches) {
|
||||||
|
cairo_move_to(cairo, left_arrow_pos, y);
|
||||||
|
pango_printf(cairo, state->font, 1, "<");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw right scroll indicator if necessary
|
||||||
|
if (scroll_right) {
|
||||||
|
cairo_move_to(cairo, width - state->right_arrow + padding, y);
|
||||||
|
pango_printf(cairo, state->font, 1, ">");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_frame(struct menu_state *state) {
|
||||||
|
cairo_surface_t *recorder = cairo_recording_surface_create(
|
||||||
|
CAIRO_CONTENT_COLOR_ALPHA, NULL);
|
||||||
|
cairo_t *cairo = cairo_create(recorder);
|
||||||
|
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
|
||||||
|
cairo_font_options_t *fo = cairo_font_options_create();
|
||||||
|
cairo_set_font_options(cairo, fo);
|
||||||
|
cairo_font_options_destroy(fo);
|
||||||
|
cairo_save(cairo);
|
||||||
|
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
||||||
|
cairo_paint(cairo);
|
||||||
|
cairo_restore(cairo);
|
||||||
|
|
||||||
|
render_to_cairo(state, cairo);
|
||||||
|
|
||||||
|
int scale = state->output ? state->output->scale : 1;
|
||||||
|
state->current = get_next_buffer(state->shm,
|
||||||
|
state->buffers, state->width, state->height, scale);
|
||||||
|
if (!state->current) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_t *shm = state->current->cairo;
|
||||||
|
cairo_save(shm);
|
||||||
|
cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR);
|
||||||
|
cairo_paint(shm);
|
||||||
|
cairo_restore(shm);
|
||||||
|
cairo_set_source_surface(shm, recorder, 0, 0);
|
||||||
|
cairo_paint(shm);
|
||||||
|
|
||||||
|
wl_surface_attach(state->surface, state->current->buffer, 0, 0);
|
||||||
|
wl_surface_damage(state->surface, 0, 0, state->width, state->height);
|
||||||
|
wl_surface_commit(state->surface);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
cairo_destroy(cairo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void noop() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
static void surface_enter(void *data, struct wl_surface *surface,
|
||||||
|
struct wl_output *wl_output) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
state->output = wl_output_get_user_data(wl_output);
|
||||||
|
wl_surface_set_buffer_scale(state->surface, state->output->scale);
|
||||||
|
wl_surface_commit(state->surface);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_surface_listener surface_listener = {
|
||||||
|
.enter = surface_enter,
|
||||||
|
.leave = noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void layer_surface_configure(void *data,
|
||||||
|
struct zwlr_layer_surface_v1 *surface,
|
||||||
|
uint32_t serial, uint32_t width, uint32_t height) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
state->width = width;
|
||||||
|
state->height = height;
|
||||||
|
zwlr_layer_surface_v1_ack_configure(surface, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void layer_surface_closed(void *data,
|
||||||
|
struct zwlr_layer_surface_v1 *surface) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
state->run = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct zwlr_layer_surface_v1_listener layer_surface_listener = {
|
||||||
|
.configure = layer_surface_configure,
|
||||||
|
.closed = layer_surface_closed,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) {
|
||||||
|
struct output *output = data;
|
||||||
|
output->scale = factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_name(void *data, struct zxdg_output_v1 *xdg_output,
|
||||||
|
const char *name) {
|
||||||
|
struct output *output = data;
|
||||||
|
struct menu_state *state = output->menu;
|
||||||
|
char *outname = state->output_name;
|
||||||
|
if (!state->output && outname && strcmp(outname, name) == 0) {
|
||||||
|
state->output = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_output_listener output_listener = {
|
||||||
|
.geometry = noop,
|
||||||
|
.mode = noop,
|
||||||
|
.done = noop,
|
||||||
|
.scale = output_scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
||||||
|
uint32_t format, int32_t fd, uint32_t size) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
||||||
|
close(fd);
|
||||||
|
state->run = false;
|
||||||
|
state->failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if (map_shm == MAP_FAILED) {
|
||||||
|
close(fd);
|
||||||
|
state->run = false;
|
||||||
|
state->failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->xkb_keymap = xkb_keymap_new_from_string(state->xkb_context,
|
||||||
|
map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
|
||||||
|
munmap(map_shm, size);
|
||||||
|
close(fd);
|
||||||
|
state->xkb_state = xkb_state_new(state->xkb_keymap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void keypress(struct menu_state *state, enum wl_keyboard_key_state key_state,
|
||||||
|
xkb_keysym_t sym) {
|
||||||
|
if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ctrl = xkb_state_mod_name_is_active(state->xkb_state,
|
||||||
|
XKB_MOD_NAME_CTRL,
|
||||||
|
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
|
||||||
|
bool shift = xkb_state_mod_name_is_active(state->xkb_state,
|
||||||
|
XKB_MOD_NAME_SHIFT,
|
||||||
|
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
|
||||||
|
|
||||||
|
char buf[8];
|
||||||
|
size_t len = strlen(state->text);
|
||||||
|
switch (sym) {
|
||||||
|
case XKB_KEY_KP_Enter:
|
||||||
|
case XKB_KEY_Return:
|
||||||
|
if (shift) {
|
||||||
|
puts(state->text);
|
||||||
|
fflush(stdout);
|
||||||
|
state->run = false;
|
||||||
|
} else {
|
||||||
|
char *text = state->selection ? state->selection->text
|
||||||
|
: state->text;
|
||||||
|
puts(text);
|
||||||
|
fflush(stdout);
|
||||||
|
if (!ctrl) {
|
||||||
|
state->run = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Left:
|
||||||
|
if (state->cursor && (!state->selection || !state->selection->left)) {
|
||||||
|
state->cursor = nextrune(state, -1);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
if (state->selection && state->selection->left) {
|
||||||
|
if (state->selection == state->leftmost) {
|
||||||
|
state->rightmost = state->selection->left;
|
||||||
|
state->leftmost = NULL;
|
||||||
|
}
|
||||||
|
state->selection = state->selection->left;
|
||||||
|
scroll_matches(state);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Right:
|
||||||
|
if (state->cursor < len) {
|
||||||
|
state->cursor = nextrune(state, +1);
|
||||||
|
render_frame(state);
|
||||||
|
} else if (state->cursor == len) {
|
||||||
|
if (state->selection && state->selection->right) {
|
||||||
|
if (state->selection == state->rightmost) {
|
||||||
|
state->leftmost = state->selection->right;
|
||||||
|
state->rightmost = NULL;
|
||||||
|
}
|
||||||
|
state->selection = state->selection->right;
|
||||||
|
scroll_matches(state);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Page_Up:
|
||||||
|
if (state->leftmost && state->leftmost->left) {
|
||||||
|
state->rightmost = state->leftmost->left;
|
||||||
|
state->leftmost = NULL;
|
||||||
|
scroll_matches(state);
|
||||||
|
state->selection = state->leftmost;
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Page_Down:
|
||||||
|
if (state->rightmost && state->rightmost->right) {
|
||||||
|
state->leftmost = state->rightmost->right;
|
||||||
|
state->rightmost = NULL;
|
||||||
|
state->selection = state->leftmost;
|
||||||
|
scroll_matches(state);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Home:
|
||||||
|
if (state->selection == state->matches) {
|
||||||
|
if (state->cursor != 0) {
|
||||||
|
state->cursor = 0;
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state->selection = state->matches;
|
||||||
|
state->leftmost = state->matches;
|
||||||
|
state->rightmost = NULL;
|
||||||
|
scroll_matches(state);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_End:
|
||||||
|
if (state->cursor < len) {
|
||||||
|
state->cursor = len;
|
||||||
|
render_frame(state);
|
||||||
|
} else {
|
||||||
|
if (!state->selection || !state->selection->right) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (state->selection && state->selection->right) {
|
||||||
|
state->selection = state->selection->right;
|
||||||
|
}
|
||||||
|
state->leftmost = NULL;
|
||||||
|
state->rightmost = state->selection;
|
||||||
|
scroll_matches(state);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_BackSpace:
|
||||||
|
if (state->cursor > 0) {
|
||||||
|
insert(state, NULL, nextrune(state, -1) - state->cursor);
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Delete:
|
||||||
|
if (state->cursor == len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->cursor = nextrune(state, +1);
|
||||||
|
insert(state, NULL, nextrune(state, -1) - state->cursor);
|
||||||
|
render_frame(state);
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Tab:
|
||||||
|
if (!state->selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strncpy(state->text, state->selection->text, sizeof state->text);
|
||||||
|
state->cursor = strlen(state->text);
|
||||||
|
match(state);
|
||||||
|
render_frame(state);
|
||||||
|
break;
|
||||||
|
case XKB_KEY_Escape:
|
||||||
|
state->failure = true;
|
||||||
|
state->run = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (xkb_keysym_to_utf8(sym, buf, 8)) {
|
||||||
|
insert(state, buf, strnlen(buf, 8));
|
||||||
|
render_frame(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyboard_repeat(struct menu_state *state) {
|
||||||
|
keypress(state, state->repeat_key_state, state->repeat_sym);
|
||||||
|
struct itimerspec spec = { 0 };
|
||||||
|
spec.it_value.tv_sec = state->repeat_period / 1000;
|
||||||
|
spec.it_value.tv_nsec = (state->repeat_period % 1000) * 1000000l;
|
||||||
|
timerfd_settime(state->repeat_timer, 0, &spec, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
|
||||||
|
uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
|
||||||
|
enum wl_keyboard_key_state key_state = _key_state;
|
||||||
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb_state, key + 8);
|
||||||
|
keypress(state, key_state, sym);
|
||||||
|
|
||||||
|
if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && state->repeat_period >= 0) {
|
||||||
|
state->repeat_key_state = key_state;
|
||||||
|
state->repeat_sym = sym;
|
||||||
|
|
||||||
|
struct itimerspec spec = { 0 };
|
||||||
|
spec.it_value.tv_sec = state->repeat_delay / 1000;
|
||||||
|
spec.it_value.tv_nsec = (state->repeat_delay % 1000) * 1000000l;
|
||||||
|
timerfd_settime(state->repeat_timer, 0, &spec, NULL);
|
||||||
|
} else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) {
|
||||||
|
struct itimerspec spec = { 0 };
|
||||||
|
timerfd_settime(state->repeat_timer, 0, &spec, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
|
||||||
|
int32_t rate, int32_t delay) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
state->repeat_delay = delay;
|
||||||
|
if (rate > 0) {
|
||||||
|
state->repeat_period = 1000 / rate;
|
||||||
|
} else {
|
||||||
|
state->repeat_period = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keyboard_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) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched,
|
||||||
|
mods_locked, 0, 0, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_keyboard_listener keyboard_listener = {
|
||||||
|
.keymap = keyboard_keymap,
|
||||||
|
.enter = noop,
|
||||||
|
.leave = noop,
|
||||||
|
.key = keyboard_key,
|
||||||
|
.modifiers = keyboard_modifiers,
|
||||||
|
.repeat_info = keyboard_repeat_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void seat_capabilities(void *data, struct wl_seat *seat,
|
||||||
|
enum wl_seat_capability caps) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
||||||
|
state->keyboard = wl_seat_get_keyboard(seat);
|
||||||
|
wl_keyboard_add_listener(state->keyboard, &keyboard_listener, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct wl_seat_listener seat_listener = {
|
||||||
|
.capabilities = seat_capabilities,
|
||||||
|
.name = noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct zxdg_output_v1_listener xdg_output_listener = {
|
||||||
|
.logical_position = noop,
|
||||||
|
.logical_size = noop,
|
||||||
|
.done = noop,
|
||||||
|
.name = output_name,
|
||||||
|
.description = noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void handle_global(void *data, struct wl_registry *registry,
|
||||||
|
uint32_t name, const char *interface, uint32_t version) {
|
||||||
|
struct menu_state *state = data;
|
||||||
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||||
|
state->compositor = wl_registry_bind(registry, name,
|
||||||
|
&wl_compositor_interface, 4);
|
||||||
|
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||||
|
state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||||
|
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
|
struct wl_seat *seat = wl_registry_bind(registry, name,
|
||||||
|
&wl_seat_interface, 4);
|
||||||
|
wl_seat_add_listener(seat, &seat_listener, state);
|
||||||
|
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
||||||
|
state->layer_shell = wl_registry_bind(registry, name,
|
||||||
|
&zwlr_layer_shell_v1_interface, 1);
|
||||||
|
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||||
|
state->output_manager = wl_registry_bind(registry, name,
|
||||||
|
&zxdg_output_manager_v1_interface, 3);
|
||||||
|
} else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||||
|
struct output *output = calloc(1, sizeof(struct output));
|
||||||
|
output->output = wl_registry_bind(registry, name,
|
||||||
|
&wl_output_interface, 3);
|
||||||
|
output->menu = state;
|
||||||
|
output->scale = 1;
|
||||||
|
wl_output_set_user_data(output->output, output);
|
||||||
|
wl_output_add_listener(output->output, &output_listener, output);
|
||||||
|
if (state->output_manager != NULL) {
|
||||||
|
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
|
||||||
|
state->output_manager, output->output);
|
||||||
|
zxdg_output_v1_add_listener(output->xdg_output,
|
||||||
|
&xdg_output_listener, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener registry_listener = {
|
||||||
|
.global = handle_global,
|
||||||
|
.global_remove = noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
void insert(struct menu_state *state, const char *s, ssize_t n) {
|
||||||
|
if (strlen(state->text) + n > sizeof state->text - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memmove(state->text + state->cursor + n, state->text + state->cursor,
|
||||||
|
sizeof state->text - state->cursor - MAX(n, 0));
|
||||||
|
if (n > 0) {
|
||||||
|
memcpy(state->text + state->cursor, s, n);
|
||||||
|
}
|
||||||
|
state->cursor += n;
|
||||||
|
match(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
char * fstrstr(struct menu_state *state, const char *s, const char *sub) {
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
for(len = strlen(sub); *s; s++)
|
||||||
|
if(!state->fstrncmp(s, sub, len))
|
||||||
|
return (char *)s;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append_item(struct menu_item *item, struct menu_item **list, struct menu_item **last) {
|
||||||
|
if(!*last)
|
||||||
|
*list = item;
|
||||||
|
else
|
||||||
|
(*last)->right = item;
|
||||||
|
item->left = *last;
|
||||||
|
item->right = NULL;
|
||||||
|
*last = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void match(struct menu_state *state) {
|
||||||
|
struct menu_item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
|
||||||
|
|
||||||
|
state->matches = NULL;
|
||||||
|
state->leftmost = NULL;
|
||||||
|
size_t len = strlen(state->text);
|
||||||
|
state->matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
|
||||||
|
for (item = state->items; item; item = item->next) {
|
||||||
|
if (!state->fstrncmp(state->text, item->text, len + 1)) {
|
||||||
|
append_item(item, &lexact, &exactend);
|
||||||
|
} else if (!state->fstrncmp(state->text, item->text, len)) {
|
||||||
|
append_item(item, &lprefix, &prefixend);
|
||||||
|
} else if (fstrstr(state, item->text, state->text)) {
|
||||||
|
append_item(item, &lsubstr, &substrend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lexact) {
|
||||||
|
state->matches = lexact;
|
||||||
|
itemend = exactend;
|
||||||
|
}
|
||||||
|
if (lprefix) {
|
||||||
|
if (itemend) {
|
||||||
|
itemend->right = lprefix;
|
||||||
|
lprefix->left = itemend;
|
||||||
|
} else {
|
||||||
|
state->matches = lprefix;
|
||||||
|
}
|
||||||
|
itemend = prefixend;
|
||||||
|
}
|
||||||
|
if (lsubstr) {
|
||||||
|
if (itemend) {
|
||||||
|
itemend->right = lsubstr;
|
||||||
|
lsubstr->left = itemend;
|
||||||
|
itemend = substrend;
|
||||||
|
} else {
|
||||||
|
state->matches = lsubstr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state->selection = state->matches;
|
||||||
|
state->leftmost = state->matches;
|
||||||
|
state->rightmost = NULL;
|
||||||
|
scroll_matches(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nextrune(struct menu_state *state, int incr) {
|
||||||
|
size_t n, len;
|
||||||
|
|
||||||
|
len = strlen(state->text);
|
||||||
|
for(n = state->cursor + incr; n < len && (state->text[n] & 0xc0) == 0x80; n += incr);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_stdin(struct menu_state *state) {
|
||||||
|
char buf[sizeof state->text], *p;
|
||||||
|
struct menu_item *item, **end;
|
||||||
|
|
||||||
|
for(end = &state->items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) {
|
||||||
|
if((p = strchr(buf, '\n'))) {
|
||||||
|
*p = '\0';
|
||||||
|
}
|
||||||
|
item = malloc(sizeof *item);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item->text = strdup(buf);
|
||||||
|
item->next = item->left = item->right = NULL;
|
||||||
|
|
||||||
|
cairo_t *cairo = state->current->cairo;
|
||||||
|
item->width = text_width(cairo, state->font, item->text);
|
||||||
|
if (item->width > state->inputw) {
|
||||||
|
state->inputw = item->width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void menu_init(struct menu_state *state) {
|
||||||
|
int height = get_font_height(state->font);
|
||||||
|
state->height = height + 2;
|
||||||
|
state->padding = height / 2;
|
||||||
|
|
||||||
|
state->display = wl_display_connect(NULL);
|
||||||
|
if (!state->display) {
|
||||||
|
fprintf(stderr, "wl_display_connect: %s\n", strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if (!state->xkb_context) {
|
||||||
|
fprintf(stderr, "xkb_context_new: %s\n", strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0);
|
||||||
|
assert(state->repeat_timer >= 0);
|
||||||
|
|
||||||
|
struct wl_registry *registry = wl_display_get_registry(state->display);
|
||||||
|
wl_registry_add_listener(registry, ®istry_listener, state);
|
||||||
|
wl_display_roundtrip(state->display);
|
||||||
|
assert(state->compositor != NULL);
|
||||||
|
assert(state->layer_shell != NULL);
|
||||||
|
assert(state->shm != NULL);
|
||||||
|
assert(state->output_manager != NULL);
|
||||||
|
|
||||||
|
// Second roundtrip for xdg-output
|
||||||
|
wl_display_roundtrip(state->display);
|
||||||
|
|
||||||
|
if (state->output_name && !state->output) {
|
||||||
|
fprintf(stderr, "Output %s not found\n", state->output_name);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void menu_create_surface(struct menu_state *state) {
|
||||||
|
state->surface = wl_compositor_create_surface(state->compositor);
|
||||||
|
wl_surface_add_listener(state->surface, &surface_listener, state);
|
||||||
|
state->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
|
||||||
|
state->layer_shell,
|
||||||
|
state->surface,
|
||||||
|
NULL,
|
||||||
|
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||||
|
"menu"
|
||||||
|
);
|
||||||
|
assert(state->layer_surface != NULL);
|
||||||
|
|
||||||
|
uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
|
||||||
|
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
|
||||||
|
if (state->bottom) {
|
||||||
|
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
||||||
|
} else {
|
||||||
|
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
zwlr_layer_surface_v1_set_anchor(state->layer_surface, anchor);
|
||||||
|
zwlr_layer_surface_v1_set_size(state->layer_surface, 0, state->height);
|
||||||
|
zwlr_layer_surface_v1_set_exclusive_zone(state->layer_surface, -1);
|
||||||
|
zwlr_layer_surface_v1_set_keyboard_interactivity(state->layer_surface, true);
|
||||||
|
zwlr_layer_surface_v1_add_listener(state->layer_surface,
|
||||||
|
&layer_surface_listener, state);
|
||||||
|
|
||||||
|
wl_surface_commit(state->surface);
|
||||||
|
wl_display_roundtrip(state->display);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_color(const char *color, uint32_t *result) {
|
||||||
|
if (color[0] == '#') {
|
||||||
|
++color;
|
||||||
|
}
|
||||||
|
int len = strlen(color);
|
||||||
|
if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *ptr;
|
||||||
|
uint32_t parsed = strtoul(color, &ptr, 16);
|
||||||
|
if (*ptr != '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*result = len == 6 ? ((parsed << 8) | 0xFF) : parsed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
struct menu_state state = {
|
||||||
|
.fstrncmp = strncmp,
|
||||||
|
.font = "monospace 10",
|
||||||
|
.background = 0x222222ff,
|
||||||
|
.foreground = 0xbbbbbbff,
|
||||||
|
.promptbg = 0x005577ff,
|
||||||
|
.promptfg = 0xeeeeeeff,
|
||||||
|
.selectionbg = 0x005577ff,
|
||||||
|
.selectionfg = 0xeeeeeeff,
|
||||||
|
.run = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *usage =
|
||||||
|
"Usage: wmenu [-biv] [-f font] [-l lines] [-o output] [-p prompt]\n"
|
||||||
|
"\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n";
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt(argc, argv, "bhivf:l:o:p:N:n:M:m:S:s:")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'b':
|
||||||
|
state.bottom = true;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
state.fstrncmp = strncasecmp;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
puts("wmenu " VERSION);
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
case 'f':
|
||||||
|
state.font = optarg;
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
// TODO
|
||||||
|
fputs("warning: -l unimplemented\n", stderr);
|
||||||
|
state.lines = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
state.output_name = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
state.prompt = optarg;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
if (!parse_color(optarg, &state.background)) {
|
||||||
|
fprintf(stderr, "Invalid background color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
if (!parse_color(optarg, &state.foreground)) {
|
||||||
|
fprintf(stderr, "Invalid foreground color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
if (!parse_color(optarg, &state.promptbg)) {
|
||||||
|
fprintf(stderr, "Invalid prompt background color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if (!parse_color(optarg, &state.promptfg)) {
|
||||||
|
fprintf(stderr, "Invalid prompt foreground color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
if (!parse_color(optarg, &state.selectionbg)) {
|
||||||
|
fprintf(stderr, "Invalid selection background color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (!parse_color(optarg, &state.selectionfg)) {
|
||||||
|
fprintf(stderr, "Invalid selection foreground color: %s", optarg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%s", usage);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind < argc) {
|
||||||
|
fprintf(stderr, "%s", usage);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_init(&state);
|
||||||
|
menu_create_surface(&state);
|
||||||
|
render_frame(&state);
|
||||||
|
|
||||||
|
read_stdin(&state);
|
||||||
|
match(&state);
|
||||||
|
|
||||||
|
struct pollfd fds[] = {
|
||||||
|
{ wl_display_get_fd(state.display), POLLIN },
|
||||||
|
{ state.repeat_timer, POLLIN },
|
||||||
|
};
|
||||||
|
const int nfds = sizeof(fds) / sizeof(*fds);
|
||||||
|
|
||||||
|
while (state.run) {
|
||||||
|
errno = 0;
|
||||||
|
do {
|
||||||
|
if (wl_display_flush(state.display) == -1 && errno != EAGAIN) {
|
||||||
|
fprintf(stderr, "wl_display_flush: %s\n", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (errno == EAGAIN);
|
||||||
|
|
||||||
|
if (poll(fds, nfds, -1) < 0) {
|
||||||
|
fprintf(stderr, "poll: %s\n", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
if (wl_display_dispatch(state.display) < 0) {
|
||||||
|
state.run = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLIN) {
|
||||||
|
keyboard_repeat(&state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_display_disconnect(state.display);
|
||||||
|
|
||||||
|
if (state.failure) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
54
meson.build
Normal file
54
meson.build
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
project(
|
||||||
|
'wmenu',
|
||||||
|
'c',
|
||||||
|
version: '0.1.0',
|
||||||
|
license: 'MIT',
|
||||||
|
default_options: [
|
||||||
|
'c_std=c11',
|
||||||
|
'warning_level=2',
|
||||||
|
'werror=true',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
|
add_project_arguments(cc.get_supported_arguments([
|
||||||
|
'-DVERSION="@0@"'.format(meson.project_version()),
|
||||||
|
|
||||||
|
'-Wno-missing-field-initializers',
|
||||||
|
'-Wno-unused-parameter',
|
||||||
|
'-Wundef',
|
||||||
|
'-Wvla',
|
||||||
|
]), language : 'c')
|
||||||
|
|
||||||
|
cairo = dependency('cairo')
|
||||||
|
pango = dependency('pango')
|
||||||
|
pangocairo = dependency('pangocairo')
|
||||||
|
wayland_client = dependency('wayland-client')
|
||||||
|
wayland_protos = dependency('wayland-protocols')
|
||||||
|
xkbcommon = dependency('xkbcommon')
|
||||||
|
|
||||||
|
rt = cc.find_library('rt')
|
||||||
|
|
||||||
|
subdir('protocols')
|
||||||
|
subdir('docs')
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'wmenu',
|
||||||
|
files(
|
||||||
|
'main.c',
|
||||||
|
'pango.c',
|
||||||
|
'pool-buffer.c',
|
||||||
|
),
|
||||||
|
dependencies: [
|
||||||
|
cairo,
|
||||||
|
client_protos,
|
||||||
|
pango,
|
||||||
|
pangocairo,
|
||||||
|
rt,
|
||||||
|
wayland_client,
|
||||||
|
wayland_protos,
|
||||||
|
xkbcommon,
|
||||||
|
],
|
||||||
|
install: true,
|
||||||
|
)
|
98
pango.c
Normal file
98
pango.c
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
#include <pango/pangocairo.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int get_font_height(char *fontstr) {
|
||||||
|
PangoFontMap *fontmap = pango_cairo_font_map_get_default();
|
||||||
|
PangoContext *context = pango_font_map_create_context(fontmap);
|
||||||
|
PangoFontDescription *desc = pango_font_description_from_string(fontstr);
|
||||||
|
PangoFont *font = pango_font_map_load_font(fontmap, context, desc);
|
||||||
|
if (font == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
PangoFontMetrics *metrics = pango_font_get_metrics(font, NULL);
|
||||||
|
int height = pango_font_metrics_get_height(metrics) / PANGO_SCALE;
|
||||||
|
pango_font_description_free(desc);
|
||||||
|
pango_font_metrics_unref(metrics);
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
|
||||||
|
const char *text, double scale) {
|
||||||
|
PangoLayout *layout = pango_cairo_create_layout(cairo);
|
||||||
|
PangoAttrList *attrs = pango_attr_list_new();
|
||||||
|
pango_layout_set_text(layout, text, -1);
|
||||||
|
pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
|
||||||
|
PangoFontDescription *desc = pango_font_description_from_string(font);
|
||||||
|
pango_layout_set_font_description(layout, desc);
|
||||||
|
pango_layout_set_single_paragraph_mode(layout, 1);
|
||||||
|
pango_layout_set_attributes(layout, attrs);
|
||||||
|
pango_attr_list_unref(attrs);
|
||||||
|
pango_font_description_free(desc);
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
|
||||||
|
int *baseline, double scale, const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
// Add one since vsnprintf excludes null terminator.
|
||||||
|
int length = vsnprintf(NULL, 0, fmt, args) + 1;
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char *buf = malloc(length);
|
||||||
|
if (buf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(buf, length, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale);
|
||||||
|
pango_cairo_update_layout(cairo, layout);
|
||||||
|
pango_layout_get_pixel_size(layout, width, height);
|
||||||
|
if (baseline) {
|
||||||
|
*baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(layout);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int text_width(cairo_t *cairo, const char *font, const char *text) {
|
||||||
|
int text_width;
|
||||||
|
get_text_size(cairo, font, &text_width, NULL, NULL, 1, text);
|
||||||
|
return text_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pango_printf(cairo_t *cairo, const char *font, double scale,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
// Add one since vsnprintf excludes null terminator.
|
||||||
|
int length = vsnprintf(NULL, 0, fmt, args) + 1;
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char *buf = malloc(length);
|
||||||
|
if (buf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(buf, length, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale);
|
||||||
|
cairo_font_options_t *fo = cairo_font_options_create();
|
||||||
|
cairo_get_font_options(cairo, fo);
|
||||||
|
pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo);
|
||||||
|
cairo_font_options_destroy(fo);
|
||||||
|
pango_cairo_update_layout(cairo, layout);
|
||||||
|
pango_cairo_show_layout(cairo, layout);
|
||||||
|
g_object_unref(layout);
|
||||||
|
free(buf);
|
||||||
|
}
|
18
pango.h
Normal file
18
pango.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef DMENU_PANGO_H
|
||||||
|
#define DMENU_PANGO_H
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
#include <pango/pangocairo.h>
|
||||||
|
|
||||||
|
int get_font_height(const char *font);
|
||||||
|
PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
|
||||||
|
const char *text, double scale);
|
||||||
|
void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
|
||||||
|
int *baseline, double scale, const char *fmt, ...);
|
||||||
|
int text_width(cairo_t *cairo, const char *font, const char *text);
|
||||||
|
void pango_printf(cairo_t *cairo, const char *font, double scale,
|
||||||
|
const char *fmt, ...);
|
||||||
|
|
||||||
|
#endif
|
145
pool-buffer.c
Normal file
145
pool-buffer.c
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <assert.h>
|
||||||
|
#include <cairo.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pango/pangocairo.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include "pool-buffer.h"
|
||||||
|
|
||||||
|
static void randname(char *buf) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
long r = ts.tv_nsec;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
buf[i] = 'A'+(r&15)+(r&16)*2;
|
||||||
|
r >>= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int anonymous_shm_open(void) {
|
||||||
|
char name[] = "/wmenu-XXXXXX";
|
||||||
|
int retries = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
randname(name + strlen(name) - 6);
|
||||||
|
|
||||||
|
--retries;
|
||||||
|
// shm_open guarantees that O_CLOEXEC is set
|
||||||
|
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||||
|
if (fd >= 0) {
|
||||||
|
shm_unlink(name);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
} while (retries > 0 && errno == EEXIST);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_shm_file(off_t size) {
|
||||||
|
int fd = anonymous_shm_open();
|
||||||
|
if (fd < 0) {
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftruncate(fd, size) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
|
||||||
|
struct pool_buffer *buffer = data;
|
||||||
|
buffer->busy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_buffer_listener buffer_listener = {
|
||||||
|
.release = buffer_release
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct pool_buffer *create_buffer(struct wl_shm *shm,
|
||||||
|
struct pool_buffer *buf, int32_t width, int32_t height,
|
||||||
|
int32_t scale, uint32_t format) {
|
||||||
|
uint32_t stride = width * scale * 4;
|
||||||
|
size_t size = stride * height * scale;
|
||||||
|
|
||||||
|
int fd = create_shm_file(size);
|
||||||
|
assert(fd != -1);
|
||||||
|
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
|
||||||
|
buf->buffer = wl_shm_pool_create_buffer(pool, 0,
|
||||||
|
width * scale, height * scale, stride, format);
|
||||||
|
wl_shm_pool_destroy(pool);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
buf->size = size;
|
||||||
|
buf->width = width;
|
||||||
|
buf->height = height;
|
||||||
|
buf->scale = scale;
|
||||||
|
buf->data = data;
|
||||||
|
buf->surface = cairo_image_surface_create_for_data(data,
|
||||||
|
CAIRO_FORMAT_ARGB32, width * scale, height * scale, stride);
|
||||||
|
cairo_surface_set_device_scale(buf->surface, scale, scale);
|
||||||
|
buf->cairo = cairo_create(buf->surface);
|
||||||
|
buf->pango = pango_cairo_create_context(buf->cairo);
|
||||||
|
|
||||||
|
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_buffer(struct pool_buffer *buffer) {
|
||||||
|
if (buffer->buffer) {
|
||||||
|
wl_buffer_destroy(buffer->buffer);
|
||||||
|
}
|
||||||
|
if (buffer->cairo) {
|
||||||
|
cairo_destroy(buffer->cairo);
|
||||||
|
}
|
||||||
|
if (buffer->surface) {
|
||||||
|
cairo_surface_destroy(buffer->surface);
|
||||||
|
}
|
||||||
|
if (buffer->pango) {
|
||||||
|
g_object_unref(buffer->pango);
|
||||||
|
}
|
||||||
|
if (buffer->data) {
|
||||||
|
munmap(buffer->data, buffer->size);
|
||||||
|
}
|
||||||
|
memset(buffer, 0, sizeof(struct pool_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
|
||||||
|
struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale) {
|
||||||
|
struct pool_buffer *buffer = NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 2; ++i) {
|
||||||
|
if (pool[i].busy) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buffer = &pool[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer->width != width || buffer->height != height
|
||||||
|
|| buffer->scale != scale) {
|
||||||
|
destroy_buffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffer->buffer) {
|
||||||
|
if (!create_buffer(shm, buffer, width, height, scale,
|
||||||
|
WL_SHM_FORMAT_ARGB8888)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer->busy = true;
|
||||||
|
return buffer;
|
||||||
|
}
|
21
pool-buffer.h
Normal file
21
pool-buffer.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* Taken from sway. MIT licensed */
|
||||||
|
#include <cairo.h>
|
||||||
|
#include <pango/pangocairo.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
struct pool_buffer {
|
||||||
|
struct wl_buffer *buffer;
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
cairo_t *cairo;
|
||||||
|
PangoContext *pango;
|
||||||
|
size_t size;
|
||||||
|
int32_t width, height, scale;
|
||||||
|
void *data;
|
||||||
|
bool busy;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
|
||||||
|
struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale);
|
||||||
|
void destroy_buffer(struct pool_buffer *buffer);
|
47
protocols/meson.build
Normal file
47
protocols/meson.build
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
|
||||||
|
|
||||||
|
wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true)
|
||||||
|
if wayland_scanner_dep.found()
|
||||||
|
wayland_scanner = find_program(
|
||||||
|
wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'),
|
||||||
|
native: true,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
wayland_scanner = find_program('wayland-scanner', native: true)
|
||||||
|
endif
|
||||||
|
|
||||||
|
protocols = [
|
||||||
|
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||||
|
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
|
||||||
|
['wlr-layer-shell-unstable-v1.xml'],
|
||||||
|
]
|
||||||
|
|
||||||
|
wl_protos_src = []
|
||||||
|
wl_protos_headers = []
|
||||||
|
|
||||||
|
foreach p : protocols
|
||||||
|
xml = join_paths(p)
|
||||||
|
wl_protos_src += custom_target(
|
||||||
|
xml.underscorify() + '_protocol_c',
|
||||||
|
input: xml,
|
||||||
|
output: '@BASENAME@-protocol.c',
|
||||||
|
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
|
||||||
|
)
|
||||||
|
wl_protos_headers += custom_target(
|
||||||
|
xml.underscorify() + '_client_h',
|
||||||
|
input: xml,
|
||||||
|
output: '@BASENAME@-client-protocol.h',
|
||||||
|
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
|
||||||
|
)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
lib_client_protos = static_library(
|
||||||
|
'client_protos',
|
||||||
|
wl_protos_src + wl_protos_headers,
|
||||||
|
dependencies: wayland_client.partial_dependency(compile_args: true),
|
||||||
|
)
|
||||||
|
|
||||||
|
client_protos = declare_dependency(
|
||||||
|
link_with: lib_client_protos,
|
||||||
|
sources: wl_protos_headers,
|
||||||
|
)
|
285
protocols/wlr-layer-shell-unstable-v1.xml
Normal file
285
protocols/wlr-layer-shell-unstable-v1.xml
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_layer_shell_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2017 Drew DeVault
|
||||||
|
|
||||||
|
Permission to use, copy, modify, distribute, and sell this
|
||||||
|
software and its documentation for any purpose is hereby granted
|
||||||
|
without fee, provided that the above copyright notice appear in
|
||||||
|
all copies and that both that copyright notice and this permission
|
||||||
|
notice appear in supporting documentation, and that the name of
|
||||||
|
the copyright holders not be used in advertising or publicity
|
||||||
|
pertaining to distribution of the software without specific,
|
||||||
|
written prior permission. The copyright holders make no
|
||||||
|
representations about the suitability of this software for any
|
||||||
|
purpose. It is provided "as is" without express or implied
|
||||||
|
warranty.
|
||||||
|
|
||||||
|
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwlr_layer_shell_v1" version="1">
|
||||||
|
<description summary="create surfaces that are layers of the desktop">
|
||||||
|
Clients can use this interface to assign the surface_layer role to
|
||||||
|
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||||
|
rendered with a defined z-depth respective to each other. They may also be
|
||||||
|
anchored to the edges and corners of a screen and specify input handling
|
||||||
|
semantics. This interface should be suitable for the implementation of
|
||||||
|
many desktop shell components, and a broad number of other applications
|
||||||
|
that interact with the desktop.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="get_layer_surface">
|
||||||
|
<description summary="create a layer_surface from a surface">
|
||||||
|
Create a layer surface for an existing surface. This assigns the role of
|
||||||
|
layer_surface, or raises a protocol error if another role is already
|
||||||
|
assigned.
|
||||||
|
|
||||||
|
Creating a layer surface from a wl_surface which has a buffer attached
|
||||||
|
or committed is a client error, and any attempts by a client to attach
|
||||||
|
or manipulate a buffer prior to the first layer_surface.configure call
|
||||||
|
must also be treated as errors.
|
||||||
|
|
||||||
|
You may pass NULL for output to allow the compositor to decide which
|
||||||
|
output to use. Generally this will be the one that the user most
|
||||||
|
recently interacted with.
|
||||||
|
|
||||||
|
Clients can specify a namespace that defines the purpose of the layer
|
||||||
|
surface.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||||
|
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||||
|
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||||
|
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||||
|
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="layer">
|
||||||
|
<description summary="available layers for surfaces">
|
||||||
|
These values indicate which layers a surface can be rendered in. They
|
||||||
|
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||||
|
will typically be rendered between the bottom and top layers.
|
||||||
|
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||||
|
Multiple surfaces can share a single layer, and ordering within a
|
||||||
|
single layer is undefined.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<entry name="background" value="0"/>
|
||||||
|
<entry name="bottom" value="1"/>
|
||||||
|
<entry name="top" value="2"/>
|
||||||
|
<entry name="overlay" value="3"/>
|
||||||
|
</enum>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_layer_surface_v1" version="1">
|
||||||
|
<description summary="layer metadata interface">
|
||||||
|
An interface that may be implemented by a wl_surface, for surfaces that
|
||||||
|
are designed to be rendered as a layer of a stacked desktop-like
|
||||||
|
environment.
|
||||||
|
|
||||||
|
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
|
||||||
|
is double-buffered, and will be applied at the time wl_surface.commit of
|
||||||
|
the corresponding wl_surface is called.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="set_size">
|
||||||
|
<description summary="sets the size of the surface">
|
||||||
|
Sets the size of the surface in surface-local coordinates. The
|
||||||
|
compositor will display the surface centered with respect to its
|
||||||
|
anchors.
|
||||||
|
|
||||||
|
If you pass 0 for either value, the compositor will assign it and
|
||||||
|
inform you of the assignment in the configure event. You must set your
|
||||||
|
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||||
|
protocol error. Both values are 0 by default.
|
||||||
|
|
||||||
|
Size is double-buffered, see wl_surface.commit.
|
||||||
|
</description>
|
||||||
|
<arg name="width" type="uint"/>
|
||||||
|
<arg name="height" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_anchor">
|
||||||
|
<description summary="configures the anchor point of the surface">
|
||||||
|
Requests that the compositor anchor the surface to the specified edges
|
||||||
|
and corners. If two orthoginal edges are specified (e.g. 'top' and
|
||||||
|
'left'), then the anchor point will be the intersection of the edges
|
||||||
|
(e.g. the top left corner of the output); otherwise the anchor point
|
||||||
|
will be centered on that edge, or in the center if none is specified.
|
||||||
|
|
||||||
|
Anchor is double-buffered, see wl_surface.commit.
|
||||||
|
</description>
|
||||||
|
<arg name="anchor" type="uint" enum="anchor"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_exclusive_zone">
|
||||||
|
<description summary="configures the exclusive geometry of this surface">
|
||||||
|
Requests that the compositor avoids occluding an area of the surface
|
||||||
|
with other surfaces. The compositor's use of this information is
|
||||||
|
implementation-dependent - do not assume that this region will not
|
||||||
|
actually be occluded.
|
||||||
|
|
||||||
|
A positive value is only meaningful if the surface is anchored to an
|
||||||
|
edge, rather than a corner. The zone is the number of surface-local
|
||||||
|
coordinates from the edge that are considered exclusive.
|
||||||
|
|
||||||
|
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||||
|
how they should interact with surfaces that do. If set to zero, the
|
||||||
|
surface indicates that it would like to be moved to avoid occluding
|
||||||
|
surfaces with a positive excluzive zone. If set to -1, the surface
|
||||||
|
indicates that it would not like to be moved to accommodate for other
|
||||||
|
surfaces, and the compositor should extend it all the way to the edges
|
||||||
|
it is anchored to.
|
||||||
|
|
||||||
|
For example, a panel might set its exclusive zone to 10, so that
|
||||||
|
maximized shell surfaces are not shown on top of it. A notification
|
||||||
|
might set its exclusive zone to 0, so that it is moved to avoid
|
||||||
|
occluding the panel, but shell surfaces are shown underneath it. A
|
||||||
|
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||||
|
they stretch below or over the panel.
|
||||||
|
|
||||||
|
The default value is 0.
|
||||||
|
|
||||||
|
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||||
|
</description>
|
||||||
|
<arg name="zone" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_margin">
|
||||||
|
<description summary="sets a margin from the anchor point">
|
||||||
|
Requests that the surface be placed some distance away from the anchor
|
||||||
|
point on the output, in surface-local coordinates. Setting this value
|
||||||
|
for edges you are not anchored to has no effect.
|
||||||
|
|
||||||
|
The exclusive zone includes the margin.
|
||||||
|
|
||||||
|
Margin is double-buffered, see wl_surface.commit.
|
||||||
|
</description>
|
||||||
|
<arg name="top" type="int"/>
|
||||||
|
<arg name="right" type="int"/>
|
||||||
|
<arg name="bottom" type="int"/>
|
||||||
|
<arg name="left" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_keyboard_interactivity">
|
||||||
|
<description summary="requests keyboard events">
|
||||||
|
Set to 1 to request that the seat send keyboard events to this layer
|
||||||
|
surface. For layers below the shell surface layer, the seat will use
|
||||||
|
normal focus semantics. For layers above the shell surface layers, the
|
||||||
|
seat will always give exclusive keyboard focus to the top-most layer
|
||||||
|
which has keyboard interactivity set to true.
|
||||||
|
|
||||||
|
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||||
|
you do not want to receive them, set the input region on your surface
|
||||||
|
to an empty region.
|
||||||
|
|
||||||
|
Events is double-buffered, see wl_surface.commit.
|
||||||
|
</description>
|
||||||
|
<arg name="keyboard_interactivity" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_popup">
|
||||||
|
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||||
|
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||||
|
should have been created via xdg_surface::get_popup with the parent set
|
||||||
|
to NULL, and this request must be invoked before committing the popup's
|
||||||
|
initial state.
|
||||||
|
|
||||||
|
See the documentation of xdg_popup for more details about what an
|
||||||
|
xdg_popup is and how it is used.
|
||||||
|
</description>
|
||||||
|
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="ack_configure">
|
||||||
|
<description summary="ack a configure event">
|
||||||
|
When a configure event is received, if a client commits the
|
||||||
|
surface in response to the configure event, then the client
|
||||||
|
must make an ack_configure request sometime before the commit
|
||||||
|
request, passing along the serial of the configure event.
|
||||||
|
|
||||||
|
If the client receives multiple configure events before it
|
||||||
|
can respond to one, it only has to ack the last configure event.
|
||||||
|
|
||||||
|
A client is not required to commit immediately after sending
|
||||||
|
an ack_configure request - it may even ack_configure several times
|
||||||
|
before its next surface commit.
|
||||||
|
|
||||||
|
A client may send multiple ack_configure requests before committing, but
|
||||||
|
only the last request sent before a commit indicates which configure
|
||||||
|
event the client really is responding to.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the layer_surface">
|
||||||
|
This request destroys the layer surface.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="configure">
|
||||||
|
<description summary="suggest a surface change">
|
||||||
|
The configure event asks the client to resize its surface.
|
||||||
|
|
||||||
|
Clients should arrange their surface for the new states, and then send
|
||||||
|
an ack_configure request with the serial sent in this configure event at
|
||||||
|
some point before committing the new surface.
|
||||||
|
|
||||||
|
The client is free to dismiss all but the last configure event it
|
||||||
|
received.
|
||||||
|
|
||||||
|
The width and height arguments specify the size of the window in
|
||||||
|
surface-local coordinates.
|
||||||
|
|
||||||
|
The size is a hint, in the sense that the client is free to ignore it if
|
||||||
|
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||||
|
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||||
|
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||||
|
surface will be centered on this axis.
|
||||||
|
|
||||||
|
If the width or height arguments are zero, it means the client should
|
||||||
|
decide its own window dimension.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint"/>
|
||||||
|
<arg name="width" type="uint"/>
|
||||||
|
<arg name="height" type="uint"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="closed">
|
||||||
|
<description summary="surface should be closed">
|
||||||
|
The closed event is sent by the compositor when the surface will no
|
||||||
|
longer be shown. The output may have been destroyed or the user may
|
||||||
|
have asked for it to be removed. Further changes to the surface will be
|
||||||
|
ignored. The client should destroy the resource after receiving this
|
||||||
|
event, and create a new surface if they so choose.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||||
|
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||||
|
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="anchor" bitfield="true">
|
||||||
|
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||||
|
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||||
|
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||||
|
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||||
|
</enum>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
Loading…
Add table
Reference in a new issue