├── .dir-locals.el ├── LICENSE ├── README.md ├── dmenu-wl.1 ├── dmenu-wl_run ├── dmenu.c ├── dmenu_path.c ├── draw.c ├── draw.h ├── meson.build └── wlr-layer-shell-unstable-v1.xml /.dir-locals.el: -------------------------------------------------------------------------------- 1 | 2 | ((nil . ((tab-width . 4) 3 | (c-basic-offset . 4) 4 | (sentence-end-double-space . t) 5 | (indent-tabs-mode . t) 6 | (fill-column . 80))) 7 | (c-mode . ((c-file-style . "GNU") 8 | (c-noise-macro-names . ("UNINIT" "CALLBACK" "ALIGN_STACK"))))) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2018-2019 Henrik Nyman 4 | © 2012 Steve Engledow 5 | © 2010 Connor Lane Smith 6 | © 2006-2010 Anselm R Garbe 7 | © 2009 Gottox 8 | © 2009 Markus Schnalke 9 | © 2009 Evan Gates 10 | © 2006-2008 Sander van Dijk 11 | © 2006-2007 Michał Janeczek 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this software and associated documentation files (the "Software"), 15 | to deal in the Software without restriction, including without limitation 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | and/or sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 26 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 | DEALINGS IN THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dmenu-wl - dynamic menu 2 | dmenu-wl is an efficient dynamic menu for wayland (wlroots). 3 | 4 | ## TODO 5 | Missing dmenu (for X) features: 6 | - Echo (non-interactive use) 7 | - Handling pagination 8 | - Vertical layout 9 | - Return-early 10 | - Control-Enter handling 11 | - Keyrepeat 12 | - Choice (-c) -mode 13 | 14 | Other TODO items: 15 | - Cleaner exiting 16 | - Generate protocol files with `wayland-scanner` 17 | 18 | ## Requirements 19 | Requires a compositor which implements wlr-layer-shell and xdg-output 20 | protocols. Basically this means a wlroots-based compositor is needed. 21 | Gnome and KDE are therefore [not supported](https://github.com/nyyManni/dmenu-wayland/issues/16). 22 | Tested with sway 1.0. 23 | 24 | Required libraries (and headers): 25 | - wayland-client 26 | - cairo 27 | - pango-1.0 28 | - pangocairo-1.0 29 | - xkbcommon 30 | - glib-2.0 31 | - gobject-2.0 32 | 33 | 34 | ## Installation 35 | ``` 36 | mkdir build 37 | meson build 38 | ninja -C build 39 | sudo ninja -C build install 40 | ``` 41 | 42 | ## Running dmenu-wl ... 43 | 44 | ### ... as the application launcher in Sway 45 | 46 | Add to sway configuration (`~/.config/sway/config`) to run the launcher on Win+D. 47 | 48 | bindsym $mod+d exec dmenu-wl_run -i 49 | 50 | ### ... from the command-line 51 | 52 | See the man page for details. 53 | ``` 54 | Usage: dmenu-wl [OPTION]... 55 | 56 | Display newline-separated input stdin as a menubar 57 | 58 | -e, --echo display text from stdin with no user 59 | interaction 60 | -ec, --echo-centre same as -e but align text centrally 61 | -er, --echo-right same as -e but align text right 62 | -et, --echo-timeout SECS close the message after SEC seconds 63 | when using -e, -ec, or -er 64 | -b, --bottom dmenu appears at the bottom of the screen 65 | -h, --height N set dmenu to be N pixels high 66 | -i, --insensitive dmenu matches menu items case insensitively 67 | -l, --lines LINES dmenu lists items vertically, within the 68 | given number of lines 69 | -m, --monitor MONITOR dmenu appears on the given monitor 70 | (0-based index or monitor name) 71 | -p, --prompt PROMPT prompt to be displayed to the left of the 72 | input field 73 | -po, --prompt-only PROMPT same as -p but don't wait for stdin 74 | useful for a prompt with no menu 75 | -r, --return-early return as soon as a single match is found 76 | -fn, --font-name FONT font or font set to be used 77 | -nb, --normal-background COLOR normal background color 78 | #RRGGBB and #RRGGBBAA supported 79 | -nf, --normal-foreground COLOR normal foreground color 80 | -sb, --selected-background COLOR selected background color 81 | -sf, --selected-foreground COLOR selected foreground color 82 | -v, --version display version information 83 | ``` 84 | -------------------------------------------------------------------------------- /dmenu-wl.1: -------------------------------------------------------------------------------- 1 | .TH DMENU 1 dmenu-wl\-VERSION 2 | .SH NAME 3 | dmenu-wl \- dynamic menu 4 | .SH SYNOPSIS 5 | .B dmenu-wl 6 | .RB [ \-b ] 7 | .RB [ \-i ] 8 | .RB [ \-l 9 | .IR lines ] 10 | .RB [ \-m 11 | .IR monitor ] 12 | .RB [ \-p 13 | .IR prompt ] 14 | .RB [ \-fn 15 | .IR font ] 16 | .RB [ \-nb 17 | .IR color ] 18 | .RB [ \-nf 19 | .IR color ] 20 | .RB [ \-sb 21 | .IR color ] 22 | .RB [ \-sf 23 | .IR color ] 24 | .RB [ \-v ] 25 | .P 26 | .BR dmenu-wl_run " ..." 27 | .P 28 | .B dmenu-wl_path 29 | .SH DESCRIPTION 30 | .B dmenu-wl 31 | is a dynamic menu for Wayland, originally designed for X and 32 | .BR dwm (1). 33 | It manages huge numbers of user-defined menu items efficiently. 34 | .P 35 | dmenu-wl reads a list of newline-separated items from standard input and creates a 36 | menu (or displays a notification if using -e). When the user selects an item or 37 | enters any text and presses Return, their choice is printed to standard output 38 | and dmenu-wl terminates. 39 | .P 40 | .B dmenu-wl_run 41 | is a dmenu-wl script used by dwm which lists programs in the user's PATH and 42 | executes the selected item. 43 | .P 44 | .B dmenu-wl_path 45 | is a program used by dmenu-wl_run to find and cache a list of executables. 46 | .SH OPTIONS 47 | .B \-e 48 | dmenu displays text from stdin with no user interaction. 49 | .TP 50 | .B \-ec 51 | same as -e but text is aligned centrally. 52 | .TP 53 | .B \-er 54 | same as -e but text is aligned to the right. 55 | .TP 56 | .BI \-et " secs " 57 | when using -e, close the message after the given number of seconds 58 | .TP 59 | .B \-b 60 | dmenu appears at the bottom of the screen. 61 | .TP 62 | .BI \-h " height" 63 | dmenu is drawn at least the given number of pixels high. 64 | .TP 65 | .B \-i 66 | dmenu matches menu items case insensitively. 67 | .TP 68 | .BI \-l " lines" 69 | dmenu lists items vertically, within the given number of lines. 70 | .TP 71 | .BI \-m " monitor" 72 | dmenu appears on the given monitor (0-based index or monitor name) 73 | .TP 74 | .BI \-p " prompt" 75 | defines the prompt to be displayed to the left of the input field. 76 | .TP 77 | .BI \-po " prompt" 78 | same as -p but don't wait for stdin - useful for a prompt with no selection options. 79 | .TP 80 | .B \-r 81 | Return as soon as a single match is found. i.e. don't wait for the user to press return. 82 | .TP 83 | .BI \-fn " font" 84 | defines the font or font set used. 85 | .TP 86 | .BI \-nb " color" 87 | defines the normal background color. 88 | .IR #RRGGBB and 89 | .IR #RRGGBBAA 90 | are supported. 91 | .TP 92 | .BI \-nf " color" 93 | defines the normal foreground color. 94 | .TP 95 | .BI \-sb " color" 96 | defines the selected background color. 97 | .TP 98 | .BI \-sf " color" 99 | defines the selected foreground color. 100 | .TP 101 | .B \-v 102 | prints version information to standard output, then exits. 103 | .SH USAGE 104 | dmenu-wl is completely controlled by the keyboard. Besides standard Unix line 105 | editing and item selection (Up/Down/Left/Right, PageUp/PageDown, Home/End), the 106 | following keys are recognized: 107 | .TP 108 | .B Tab (Control\-i) 109 | Copy the selected item to the input field. 110 | .TP 111 | .B Return (Control\-j) 112 | Confirm selection. Prints the selected item to standard output and exits, 113 | returning success. 114 | .TP 115 | .B Shift\-Return (Control\-Shift\-j) 116 | Confirm input. Prints the input text to standard output and exits, returning 117 | success. 118 | .TP 119 | .B Escape (Control\-c) 120 | Exit without selecting an item, returning failure. 121 | .TP 122 | .B Control\-y 123 | Paste the current selection into the input field. 124 | .SH SEE ALSO 125 | .BR dwm (1) 126 | -------------------------------------------------------------------------------- /dmenu-wl_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | dmenu-wl_path | dmenu-wl "$@" | ${SHELL:-"/bin/sh"} & 3 | -------------------------------------------------------------------------------- /dmenu.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "draw.h" 13 | 14 | #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) 15 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 16 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 17 | 18 | typedef struct Item Item; 19 | struct Item { 20 | char *text; 21 | Item *next; /* traverses all items */ 22 | Item *left, *right; /* traverses matching items */ 23 | int32_t width; 24 | }; 25 | 26 | typedef enum { 27 | LEFT, 28 | RIGHT, 29 | CENTRE 30 | } TextPosition; 31 | 32 | struct { 33 | int32_t width; 34 | int32_t height; 35 | int32_t text_height; 36 | int32_t text_y; 37 | int32_t input_field; 38 | int32_t scroll_left; 39 | int32_t matches; 40 | int32_t scroll_right; 41 | } window_config; 42 | 43 | const char *progname; 44 | 45 | static uint32_t color_bg = 0x222222ff; 46 | static uint32_t color_fg = 0xbbbbbbff; 47 | static uint32_t color_input_bg = 0x222222ff; 48 | static uint32_t color_input_fg = 0xbbbbbbff; 49 | static uint32_t color_prompt_bg = 0x005577ff; 50 | static uint32_t color_prompt_fg = 0xeeeeeeff; 51 | static uint32_t color_selected_bg = 0x005577ff; 52 | static uint32_t color_selected_fg = 0xeeeeeeff; 53 | 54 | static int32_t panel_height = 20; 55 | 56 | static void appenditem(Item *item, Item **list, Item **last); 57 | static char *fstrstr(const char *s, const char *sub); 58 | static void insert(const char *s, ssize_t n); 59 | static void match(void); 60 | static size_t nextrune(int incr); 61 | static void readstdin(void); 62 | static void alarmhandler(int signum); 63 | /* static void handle_return(char* value); */ 64 | static void usage(void); 65 | static int retcode = EXIT_SUCCESS; 66 | static int selected_monitor = 0; 67 | static char *selected_monitor_name = 0; 68 | 69 | static char text[BUFSIZ]; 70 | static char text_[BUFSIZ]; 71 | static int itemcount = 0; 72 | static int lines = 0; 73 | static int timeout = 3; 74 | static size_t cursor = 0; 75 | static const char *prompt = NULL; 76 | static bool message = false; 77 | static bool nostdin = false; 78 | static bool returnearly = false; 79 | static bool show_in_bottom = false; 80 | static TextPosition messageposition = LEFT; 81 | static Item *items = NULL; 82 | static Item *matches, *sel; 83 | static Item *prev, *curr, *next; 84 | static Item *leftmost, *rightmost; 85 | static char *font = "Mono"; 86 | 87 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 88 | 89 | void 90 | insert(const char *s, ssize_t n) { 91 | if(strlen(text) + n > sizeof text - 1) 92 | return; 93 | memmove(text + cursor + n, text + cursor, sizeof text - cursor - MAX(n, 0)); 94 | if(n > 0) 95 | memcpy(text + cursor, s, n); 96 | cursor += n; 97 | match(); 98 | } 99 | 100 | void keyrepeat(struct dmenu_panel *panel) { 101 | if (panel->on_keyevent) { 102 | panel->on_keyevent(panel, panel->repeat_key_state, panel->repeat_sym, 103 | panel->keyboard.control, panel->keyboard.shift); 104 | } 105 | } 106 | 107 | void keypress(struct dmenu_panel *panel, enum wl_keyboard_key_state state, 108 | xkb_keysym_t sym, bool ctrl, bool shft) { 109 | char buf[8]; 110 | size_t len = strlen(text); 111 | 112 | if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return; 113 | 114 | if (ctrl) { 115 | switch (xkb_keysym_to_lower(sym)) { 116 | case XKB_KEY_a: 117 | sym = XKB_KEY_Home; 118 | break; 119 | case XKB_KEY_e: 120 | sym = XKB_KEY_End; 121 | break; 122 | case XKB_KEY_f: 123 | case XKB_KEY_n: 124 | sym = XKB_KEY_Right; 125 | break; 126 | case XKB_KEY_b: 127 | case XKB_KEY_p: 128 | sym = XKB_KEY_Left; 129 | break; 130 | case XKB_KEY_h: 131 | sym = XKB_KEY_BackSpace; 132 | break; 133 | case XKB_KEY_j: 134 | sym = XKB_KEY_Return; 135 | break; 136 | case XKB_KEY_g: 137 | case XKB_KEY_c: 138 | retcode = EXIT_FAILURE; 139 | dmenu_close(panel); 140 | return; 141 | } 142 | } 143 | switch (sym) { 144 | case XKB_KEY_KP_Enter: /* fallthrough */ 145 | case XKB_KEY_Return: 146 | dmenu_close(panel); 147 | fputs((sel && !shft) ? sel->text : text, stdout); 148 | fflush(stdout); 149 | break; 150 | case XKB_KEY_Escape: 151 | retcode = EXIT_FAILURE; 152 | dmenu_close(panel); 153 | break; 154 | case XKB_KEY_Left: 155 | if(cursor && (!sel || !sel->left)) { 156 | cursor = nextrune(-1); 157 | } if (sel && sel->left) { 158 | sel = sel->left; 159 | } 160 | break; 161 | case XKB_KEY_Right: 162 | if (cursor < len) { 163 | cursor = nextrune(+1); 164 | } else if (cursor == len) { 165 | if (sel && sel->right) sel = sel->right; 166 | } 167 | break; 168 | 169 | case XKB_KEY_End: 170 | if(cursor < len) { 171 | cursor = len; 172 | break; 173 | } 174 | while(sel && sel->right) 175 | sel = sel->right; 176 | break; 177 | case XKB_KEY_Home: 178 | if(sel == matches) { 179 | cursor = 0; 180 | break; 181 | } 182 | sel = curr = matches; 183 | /* calcoffsets(); */ 184 | break; 185 | 186 | case XKB_KEY_BackSpace: 187 | if (cursor > 0) 188 | insert(NULL, nextrune(-1) - cursor); 189 | break; 190 | case XKB_KEY_Delete: 191 | if (cursor == len) 192 | return; 193 | cursor = nextrune(+1); 194 | break; 195 | case XKB_KEY_Tab: 196 | if(!sel) return; 197 | strncpy(text, sel->text, sizeof text); 198 | cursor = strlen(text); 199 | match(); 200 | break; 201 | default: 202 | if (xkb_keysym_to_utf8(sym, buf, 8)) { 203 | insert(buf, strnlen(buf, 8)); 204 | } 205 | } 206 | dmenu_draw(panel); 207 | } 208 | 209 | void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { 210 | cairo_set_source_rgba(cairo, 211 | (color >> (3*8) & 0xFF) / 255.0, 212 | (color >> (2*8) & 0xFF) / 255.0, 213 | (color >> (1*8) & 0xFF) / 255.0, 214 | (color >> (0*8) & 0xFF) / 255.0); 215 | } 216 | 217 | int32_t draw_text(cairo_t *cairo, int32_t width, int32_t height, const char *str, 218 | int32_t x, int32_t scale, uint32_t 219 | foreground_color, uint32_t background_color, int32_t padding) { 220 | 221 | int32_t text_width, text_height; 222 | get_text_size(cairo, font, &text_width, &text_height, 223 | NULL, scale, false, str); 224 | int32_t text_y = (height / 2.0) - (text_height / 2.0); 225 | 226 | if (x + padding * scale + text_width + 30 * scale > width) { 227 | /* if (x + padding * scale + text_width > width) { */ 228 | 229 | cairo_move_to(cairo, width, text_y); 230 | pango_printf(cairo, font, scale, false, ">"); 231 | } else { 232 | if (background_color) { 233 | cairo_set_source_u32(cairo, background_color); 234 | cairo_rectangle(cairo, x, 0, text_width + 2 * padding * scale, height); 235 | cairo_fill(cairo); 236 | } 237 | 238 | cairo_move_to(cairo, x + padding * scale, text_y); 239 | cairo_set_source_u32(cairo, foreground_color); 240 | 241 | pango_printf(cairo, font, scale, false, str); 242 | } 243 | 244 | return x + text_width + 2 * padding * scale; 245 | } 246 | 247 | void draw(cairo_t *cairo, int32_t width, int32_t height, int32_t scale) { 248 | 249 | int32_t x = window_config.input_field; 250 | 251 | int32_t item_padding = 10; 252 | 253 | int32_t text_width, text_height; 254 | get_text_size(cairo, font, &text_width, &text_height, NULL, scale, 255 | false, "Aj"); 256 | int32_t text_y = (height / 2.0) - (text_height / 2.0); 257 | 258 | cairo_set_source_u32(cairo, color_bg); 259 | cairo_paint(cairo); 260 | 261 | if (prompt) { 262 | x = draw_text(cairo, width, height, prompt, 0, scale, color_prompt_fg, 263 | color_prompt_bg, 6); 264 | window_config.input_field = x; 265 | } else { 266 | window_config.input_field = 0; 267 | } 268 | 269 | cairo_set_source_u32(cairo, color_input_bg); 270 | cairo_rectangle(cairo, window_config.input_field, 0, 300 * scale, height); 271 | cairo_fill(cairo); 272 | 273 | draw_text(cairo, width, height, text, x, scale, color_input_fg, 0, 6); 274 | 275 | { 276 | /* draw cursor */ 277 | memset(text_, 0, BUFSIZ); 278 | strncpy(text_, text, cursor); 279 | int32_t text_width, text_height; 280 | get_text_size(cairo, font, &text_width, &text_height, NULL, scale, 281 | false, text_); 282 | /* int32_t text_y = (height / 2.0) - (text_height / 2.0); */ 283 | int32_t padding = 6 * scale; 284 | cairo_rectangle(cairo, x + padding + text_width, text_y, 285 | scale, text_height); 286 | cairo_fill(cairo); 287 | } 288 | 289 | x += 300 * scale; 290 | 291 | /* Scroll indicator will be drawn later if required. */ 292 | int32_t scroll_indicator_pos = x; 293 | x += 20 * scale; 294 | 295 | if (matches) { 296 | /* draw matches */ 297 | Item *item; 298 | /* for (item = matches; item; item = item->right) { */ 299 | /* if (item->width == -1) { */ 300 | /* get_text_size(cairo, font, &item->width, NULL, NULL, scale, */ 301 | /* false, item->text); */ 302 | /* item->width += item_padding; */ 303 | /* /\* printf("%d ", item->width); *\/ */ 304 | /* } */ 305 | /* } */ 306 | 307 | /* /\* Figure out if we need to scroll. *\/ */ 308 | /* int32_t item_pos = x; */ 309 | /* bool found = false; */ 310 | /* rightmost = NULL; */ 311 | /* for (item = leftmost; item; item = item->right) { */ 312 | /* item_pos += item->width; */ 313 | /* if (item_pos >= (width - x - 80 * scale)) { */ 314 | /* rightmost = item->left; */ 315 | /* printf("rightmost: %s\n", item->left->text); */ 316 | /* found = true; */ 317 | /* break; */ 318 | /* } */ 319 | /* } */ 320 | 321 | for (item = matches; item; item = item->right) { 322 | uint32_t bg_color = sel == item ? color_selected_bg : color_bg; 323 | uint32_t fg_color = sel == item ? color_selected_fg : color_fg; 324 | if (x < width) { 325 | /* x = draw_text(cairo, width - 20 * scale, height, item->text, */ 326 | /* x, scale, fg_color, bg_color, item_padding); */ 327 | x = draw_text(cairo, width - 20 * scale, height, item->text, 328 | x, scale, fg_color, bg_color, item_padding); 329 | } else { 330 | break; 331 | } 332 | } 333 | 334 | if (leftmost != matches) { 335 | cairo_move_to(cairo, scroll_indicator_pos, text_y); 336 | pango_printf(cairo, font, scale, false, "<"); 337 | } 338 | } 339 | } 340 | 341 | uint32_t parse_color(char *str) { 342 | if (!str) eprintf("NULL as color value\n"); 343 | 344 | size_t len = strnlen(str, BUFSIZ); 345 | 346 | if ((len != 7 && len != 9) || str[0] != '#') 347 | eprintf("Color format must be '#rrggbb[aa]'\n"); 348 | 349 | uint32_t _val = strtol(&str[1], NULL, 16); 350 | 351 | uint32_t color = 0x000000ff; 352 | if (len == 9) /* Alpha specified */ 353 | color = _val; 354 | else /* No alpha specified, assume full opacity */ 355 | color = (_val << 8) + 0xff; 356 | 357 | return color; 358 | } 359 | 360 | int 361 | main(int argc, char **argv) { 362 | int i; 363 | 364 | progname = "dmenu"; 365 | for (i = 1; i < argc; i++) { 366 | if (!strcmp(argv[i], "-v") || !strcmp(argv[1], "--version")) { 367 | fputs("dmenu-wl-" VERSION 368 | ", © 2006-2018 dmenu engineers, see LICENSE for details\n", 369 | stdout); 370 | exit(EXIT_SUCCESS); 371 | } else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--bottom")) 372 | show_in_bottom = true; 373 | else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--echo")) 374 | message = true; 375 | else if (!strcmp(argv[i], "-ec") || !strcmp(argv[i], "--echo-centre")) 376 | message = true, messageposition = CENTRE; 377 | else if (!strcmp(argv[i], "-er") || !strcmp(argv[i], "--echo-right")) 378 | message = true, messageposition = RIGHT; 379 | else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--insensitive")) 380 | fstrncmp = strncasecmp; 381 | else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--return-early")) 382 | returnearly = true; 383 | else if (i == argc - 1) { 384 | printf("2\n"); 385 | usage(); 386 | 387 | } 388 | /* opts that need 1 arg */ 389 | else if (!strcmp(argv[i], "-et") || !strcmp(argv[i], "--echo-timeout")) 390 | timeout = atoi(argv[++i]); 391 | else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height")) 392 | panel_height = atoi(argv[++i]); 393 | else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--lines")) 394 | lines = atoi(argv[++i]); 395 | else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--monitor")) { 396 | ++i; 397 | bool is_num = true; 398 | for (int j = 0; j < strlen(argv[i]); ++j) { 399 | if (!isdigit(argv[i][j])) { 400 | is_num = false; 401 | break; 402 | } 403 | } 404 | if (is_num) { 405 | selected_monitor = atoi(argv[i]); 406 | } else { 407 | selected_monitor = -1; 408 | selected_monitor_name = argv[i]; 409 | } 410 | } 411 | else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--prompt")) 412 | prompt = argv[++i]; 413 | else if (!strcmp(argv[i], "-po") || !strcmp(argv[i], "--prompt-only")) 414 | prompt = argv[++i], nostdin = true; 415 | else if (!strcmp(argv[i], "-fn") || !strcmp(argv[i], "--font-name")) 416 | font = argv[++i]; 417 | else if (!strcmp(argv[i], "-nb") || !strcmp(argv[i], "--normal-background")) 418 | color_bg = color_input_bg = parse_color(argv[++i]); 419 | else if (!strcmp(argv[i], "-nf") || !strcmp(argv[i], "--normal-foreground")) 420 | color_fg = color_input_fg = parse_color(argv[++i]); 421 | else if (!strcmp(argv[i], "-sb") || 422 | !strcmp(argv[i], "--selected-background")) 423 | color_prompt_bg = color_selected_bg = parse_color(argv[++i]); 424 | else if (!strcmp(argv[i], "-sf") || 425 | !strcmp(argv[i], "--selected-foreground")) 426 | color_prompt_fg = color_selected_fg = parse_color(argv[++i]); 427 | else { 428 | usage(); 429 | } 430 | } 431 | 432 | if (message) { 433 | signal(SIGALRM, alarmhandler); 434 | alarm(timeout); 435 | } 436 | if(!nostdin) { 437 | readstdin(); 438 | } 439 | 440 | struct dmenu_panel dmenu; 441 | dmenu.selected_monitor = selected_monitor; 442 | dmenu.selected_monitor_name = selected_monitor_name; 443 | dmenu_init_panel(&dmenu, panel_height, show_in_bottom); 444 | 445 | 446 | dmenu.on_keyevent = keypress; 447 | dmenu.on_keyrepeat = keyrepeat; 448 | dmenu.draw = draw; 449 | match(); 450 | 451 | struct monitor_info *monitor = dmenu.monitor; 452 | 453 | double factor = monitor->scale / ((double)monitor->physical_width / monitor->logical_width); 454 | 455 | window_config.height =round_to_int(dmenu.height / ((double)monitor->physical_width 456 | / monitor->logical_width)); 457 | window_config.height *= monitor->scale; 458 | 459 | window_config.width = round_to_int(monitor->physical_width * factor); 460 | get_text_size(dmenu.surface.cairo, font, NULL, &window_config.text_height, 461 | NULL, monitor->scale, false, "Aj"); 462 | window_config.text_y = (window_config.height / 2.0) - (window_config.text_height / 2.0); 463 | 464 | 465 | dmenu_show(&dmenu); 466 | 467 | return retcode; 468 | } 469 | 470 | void 471 | appenditem(Item *item, Item **list, Item **last) { 472 | if(!*last) 473 | *list = item; 474 | else 475 | (*last)->right = item; 476 | item->left = *last; 477 | item->right = NULL; 478 | *last = item; 479 | } 480 | 481 | char * 482 | fstrstr(const char *s, const char *sub) { 483 | size_t len; 484 | 485 | for(len = strlen(sub); *s; s++) 486 | if(!fstrncmp(s, sub, len)) 487 | return (char *)s; 488 | return NULL; 489 | } 490 | 491 | void 492 | match(void) { 493 | size_t len; 494 | Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; 495 | 496 | rightmost = leftmost = NULL; 497 | len = strlen(text); 498 | matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; 499 | for(item = items; item; item = item->next) 500 | if(!fstrncmp(text, item->text, len + 1)) { 501 | appenditem(item, &lexact, &exactend); 502 | } 503 | else if(!fstrncmp(text, item->text, len)) { 504 | appenditem(item, &lprefix, &prefixend); 505 | } 506 | else if(fstrstr(item->text, text)) { 507 | appenditem(item, &lsubstr, &substrend); 508 | } 509 | 510 | if(lexact) { 511 | matches = lexact; 512 | itemend = exactend; 513 | } 514 | if(lprefix) { 515 | if(itemend) { 516 | itemend->right = lprefix; 517 | lprefix->left = itemend; 518 | } 519 | else 520 | matches = lprefix; 521 | itemend = prefixend; 522 | } 523 | if(lsubstr) { 524 | if(itemend) { 525 | itemend->right = lsubstr; 526 | lsubstr->left = itemend; 527 | } 528 | else 529 | matches = lsubstr; 530 | } 531 | curr = prev = next = sel = matches; 532 | /* calcoffsets(); */ 533 | 534 | leftmost = matches; 535 | 536 | if(returnearly && !curr->right) { 537 | /* handle_return(curr->text); */ 538 | } 539 | } 540 | 541 | size_t 542 | nextrune(int incr) { 543 | size_t n, len; 544 | 545 | len = strlen(text); 546 | for(n = cursor + incr; n >= 0 && n < len && (text[n] & 0xc0) == 0x80; n += incr); 547 | return n; 548 | } 549 | 550 | void 551 | readstdin(void) { 552 | char buf[sizeof text], *p; 553 | Item *item, **end; 554 | 555 | for(end = &items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { 556 | itemcount++; 557 | 558 | if((p = strchr(buf, '\n'))) { 559 | *p = '\0'; 560 | } 561 | if(!(item = malloc(sizeof *item))) { 562 | eprintf("cannot malloc %u bytes\n", sizeof *item); 563 | } 564 | item->width = -1; 565 | if(!(item->text = strdup(buf))) { 566 | eprintf("cannot strdup %u bytes\n", strlen(buf)+1); 567 | } 568 | item->next = item->left = item->right = NULL; 569 | /* inputw = MAX(inputw, textw(dc, item->text)); */ 570 | } 571 | } 572 | 573 | 574 | void 575 | alarmhandler(int signum) { 576 | exit(EXIT_SUCCESS); 577 | } 578 | 579 | void 580 | usage(void) { 581 | printf("Usage: dmenu [OPTION]...\n"); 582 | printf("Display newline-separated input stdin as a menubar\n"); 583 | printf("\n"); 584 | printf(" -e, --echo display text from stdin with no user\n"); 585 | printf(" interaction\n"); 586 | printf(" -ec, --echo-centre same as -e but align text centrally\n"); 587 | printf(" -er, --echo-right same as -e but align text right\n"); 588 | printf(" -et, --echo-timeout SECS close the message after SEC seconds\n"); 589 | printf(" when using -e, -ec, or -er\n"); 590 | printf(" -b, --bottom dmenu appears at the bottom of the screen\n"); 591 | printf(" -h, --height N set dmenu to be N pixels high\n"); 592 | printf(" -i, --insensitive dmenu matches menu items case insensitively\n"); 593 | printf(" -l, --lines LINES dmenu lists items vertically, within the\n"); 594 | printf(" given number of lines\n"); 595 | printf(" -m, --monitor MONITOR dmenu appears on the given Xinerama screen\n"); 596 | printf(" (does nothing on wayland, supported for)\n"); 597 | printf(" compatibility with dmenu.\n"); 598 | printf(" -p, --prompt PROMPT prompt to be displayed to the left of the\n"); 599 | printf(" input field\n"); 600 | printf(" -po, --prompt-only PROMPT same as -p but don't wait for stdin\n"); 601 | printf(" useful for a prompt with no menu\n"); 602 | printf(" -r, --return-early return as soon as a single match is found\n"); 603 | printf(" -fn, --font-name FONT font or font set to be used\n"); 604 | printf(" -nb, --normal-background COLOR normal background color\n"); 605 | printf(" #RRGGBB and #RRGGBBAA supported\n"); 606 | printf(" -nf, --normal-foreground COLOR normal foreground color\n"); 607 | printf(" -sb, --selected-background COLOR selected background color\n"); 608 | printf(" -sf, --selected-foreground COLOR selected foreground color\n"); 609 | printf(" -v, --version display version information\n"); 610 | 611 | exit(EXIT_FAILURE); 612 | } 613 | -------------------------------------------------------------------------------- /dmenu_path.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define CACHE ".dmenu_cache" 11 | 12 | static void die(const char *s); 13 | static int qstrcmp(const void *a, const void *b); 14 | static void scan(void); 15 | static int uptodate(void); 16 | 17 | static char **items = NULL; 18 | static const char *home, *path; 19 | 20 | int 21 | main(void) { 22 | if(!(home = getenv("HOME"))) 23 | die("no $HOME"); 24 | if(!(path = getenv("PATH"))) 25 | die("no $PATH"); 26 | if(chdir(home) < 0) 27 | die("chdir failed"); 28 | if(uptodate()) { 29 | execlp("cat", "cat", CACHE, NULL); 30 | die("exec failed"); 31 | } 32 | scan(); 33 | return EXIT_SUCCESS; 34 | } 35 | 36 | void 37 | die(const char *s) { 38 | fprintf(stderr, "dmenu_path: %s\n", s); 39 | exit(EXIT_FAILURE); 40 | } 41 | 42 | int 43 | qstrcmp(const void *a, const void *b) { 44 | return strcmp(*(const char **)a, *(const char **)b); 45 | } 46 | 47 | void 48 | scan(void) { 49 | char buf[PATH_MAX]; 50 | char *dir, *p; 51 | size_t i, count; 52 | struct dirent *ent; 53 | DIR *dp; 54 | FILE *cache; 55 | 56 | count = 0; 57 | if(!(p = strdup(path))) 58 | die("strdup failed"); 59 | for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":")) { 60 | if(!(dp = opendir(dir))) 61 | continue; 62 | while((ent = readdir(dp))) { 63 | snprintf(buf, sizeof buf, "%s/%s", dir, ent->d_name); 64 | if(ent->d_name[0] == '.' || access(buf, X_OK) < 0) 65 | continue; 66 | if(!(items = realloc(items, ++count * sizeof *items))) 67 | die("malloc failed"); 68 | if(!(items[count-1] = strdup(ent->d_name))) 69 | die("strdup failed"); 70 | } 71 | closedir(dp); 72 | } 73 | qsort(items, count, sizeof *items, qstrcmp); 74 | if(!(cache = fopen(CACHE, "w"))) 75 | die("open failed"); 76 | for(i = 0; i < count; i++) { 77 | if(i > 0 && !strcmp(items[i], items[i-1])) 78 | continue; 79 | fprintf(cache, "%s\n", items[i]); 80 | fprintf(stdout, "%s\n", items[i]); 81 | } 82 | fclose(cache); 83 | free(p); 84 | } 85 | 86 | int 87 | uptodate(void) { 88 | char *dir, *p; 89 | time_t mtime; 90 | struct stat st; 91 | 92 | if(stat(CACHE, &st) < 0) 93 | return 0; 94 | mtime = st.st_mtime; 95 | if(!(p = strdup(path))) 96 | die("strdup failed"); 97 | for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":")) 98 | if(!stat(dir, &st) && st.st_mtime > mtime) 99 | return 0; 100 | free(p); 101 | return 1; 102 | } 103 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "draw.h" 20 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" 21 | #include "xdg-shell-client-protocol.h" 22 | #include "xdg-output-unstable-v1-client-protocol.h" 23 | 24 | 25 | static const char overflow[] = "[buffer overflow]"; 26 | static const int max_chars = 16384; 27 | 28 | struct monitor_info *monitors[16] = {0}; 29 | static int n_monitors = 0; 30 | 31 | int32_t round_to_int(double val) { 32 | return (int32_t)(val + 0.5); 33 | } 34 | 35 | static void randname(char *buf) { 36 | struct timespec ts; 37 | clock_gettime(CLOCK_REALTIME, &ts); 38 | long r = ts.tv_nsec; 39 | for (int i = 0; i < 6; ++i) { 40 | buf[i] = 'A'+(r&15)+(r&16)*2; 41 | r >>= 5; 42 | } 43 | } 44 | 45 | static int anonymous_shm_open(void) { 46 | char name[] = "/dmenu-XXXXXX"; 47 | int retries = 100; 48 | 49 | do { 50 | randname(name + strlen(name) - 6); 51 | 52 | --retries; 53 | // shm_open guarantees that O_CLOEXEC is set 54 | int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); 55 | if (fd >= 0) { 56 | shm_unlink(name); 57 | return fd; 58 | } 59 | } while (retries > 0 && errno == EEXIST); 60 | 61 | return -1; 62 | } 63 | 64 | int create_shm_file(off_t size) { 65 | int fd = anonymous_shm_open(); 66 | if (fd < 0) { 67 | return fd; 68 | } 69 | 70 | if (ftruncate(fd, size) < 0) { 71 | close(fd); 72 | return -1; 73 | } 74 | 75 | return fd; 76 | } 77 | 78 | PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, 79 | const char *text, double scale, bool markup) { 80 | PangoLayout *layout = pango_cairo_create_layout(cairo); 81 | PangoAttrList *attrs; 82 | if (markup) { 83 | char *buf; 84 | GError *error = NULL; 85 | if (pango_parse_markup(text, -1, 0, &attrs, &buf, NULL, &error)) { 86 | pango_layout_set_text(layout, buf, -1); 87 | free(buf); 88 | } else { 89 | /* wlr_log(WLR_ERROR, "pango_parse_markup '%s' -> error %s", text, */ 90 | /* error->message); */ 91 | g_error_free(error); 92 | markup = false; // fallback to plain text 93 | } 94 | } 95 | if (!markup) { 96 | attrs = pango_attr_list_new(); 97 | pango_layout_set_text(layout, text, -1); 98 | } 99 | 100 | pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); 101 | PangoFontDescription *desc = pango_font_description_from_string(font); 102 | pango_layout_set_font_description(layout, desc); 103 | pango_layout_set_single_paragraph_mode(layout, 1); 104 | pango_layout_set_attributes(layout, attrs); 105 | pango_attr_list_unref(attrs); 106 | pango_font_description_free(desc); 107 | return layout; 108 | } 109 | 110 | void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, 111 | int *baseline, double scale, bool markup, const char *fmt, ...) { 112 | char buf[max_chars]; 113 | 114 | va_list args; 115 | va_start(args, fmt); 116 | if (vsnprintf(buf, sizeof(buf), fmt, args) >= max_chars) { 117 | strcpy(&buf[sizeof(buf) - sizeof(overflow)], overflow); 118 | } 119 | va_end(args); 120 | 121 | PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 122 | pango_cairo_update_layout(cairo, layout); 123 | pango_layout_get_pixel_size(layout, width, height); 124 | if (baseline) { 125 | *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; 126 | } 127 | g_object_unref(layout); 128 | } 129 | 130 | void pango_printf(cairo_t *cairo, const char *font, 131 | double scale, bool markup, const char *fmt, ...) { 132 | char buf[max_chars]; 133 | 134 | va_list args; 135 | va_start(args, fmt); 136 | if (vsnprintf(buf, sizeof(buf), fmt, args) >= max_chars) { 137 | strcpy(&buf[sizeof(buf) - sizeof(overflow)], overflow); 138 | } 139 | va_end(args); 140 | 141 | PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 142 | cairo_font_options_t *fo = cairo_font_options_create(); 143 | cairo_get_font_options(cairo, fo); 144 | pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); 145 | cairo_font_options_destroy(fo); 146 | pango_cairo_update_layout(cairo, layout); 147 | pango_cairo_show_layout(cairo, layout); 148 | g_object_unref(layout); 149 | } 150 | 151 | 152 | void dmenu_draw(struct dmenu_panel *panel) { 153 | 154 | cairo_t *cairo = panel->surface.cairo; 155 | cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 156 | cairo_paint(cairo); 157 | cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); 158 | struct monitor_info *m = panel->monitor; 159 | double factor = m->scale / ((double)m->physical_width 160 | / m->logical_width); 161 | 162 | int32_t width = round_to_int(m->physical_width * factor); 163 | int32_t height = panel->height * m->scale; 164 | 165 | if (panel->draw) { 166 | panel->draw(cairo, width, height, m->scale); 167 | } 168 | wl_surface_attach(panel->surface.surface, panel->surface.buffer, 0, 0); 169 | zwlr_layer_surface_v1_set_keyboard_interactivity(panel->surface.layer_surface, true); 170 | wl_surface_damage(panel->surface.surface, 0, 0, m->logical_width, panel->height); 171 | wl_surface_commit(panel->surface.surface); 172 | 173 | } 174 | 175 | cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { 176 | switch (subpixel) { 177 | case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: 178 | return CAIRO_SUBPIXEL_ORDER_RGB; 179 | case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: 180 | return CAIRO_SUBPIXEL_ORDER_BGR; 181 | case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: 182 | return CAIRO_SUBPIXEL_ORDER_VRGB; 183 | case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: 184 | return CAIRO_SUBPIXEL_ORDER_VBGR; 185 | default: 186 | return CAIRO_SUBPIXEL_ORDER_DEFAULT; 187 | } 188 | return CAIRO_SUBPIXEL_ORDER_DEFAULT; 189 | } 190 | 191 | void 192 | eprintf(const char *fmt, ...) { 193 | va_list ap; 194 | 195 | fprintf(stderr, "%s: ", progname); 196 | va_start(ap, fmt); 197 | vfprintf(stderr, fmt, ap); 198 | va_end(ap); 199 | exit(EXIT_FAILURE); 200 | } 201 | 202 | static void layer_surface_configure(void *data, 203 | struct zwlr_layer_surface_v1 *surface, 204 | uint32_t serial, uint32_t width, uint32_t height) { 205 | zwlr_layer_surface_v1_ack_configure(surface, serial); 206 | } 207 | 208 | static void layer_surface_closed(void *_data, 209 | struct zwlr_layer_surface_v1 *surface) { 210 | } 211 | 212 | struct zwlr_layer_surface_v1_listener layer_surface_listener = { 213 | .configure = layer_surface_configure, 214 | .closed = layer_surface_closed, 215 | }; 216 | 217 | 218 | int32_t subpixel; 219 | int32_t physical_height; 220 | 221 | 222 | static void output_geometry(void *data, struct wl_output *wl_output, int32_t x, 223 | int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, 224 | const char *make, const char *model, int32_t transform) { 225 | struct monitor_info *monitor = data; 226 | monitor->subpixel = subpixel; 227 | } 228 | 229 | static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, 230 | int32_t width, int32_t height, int32_t refresh) { 231 | struct monitor_info *monitor = data; 232 | monitor->physical_width = width; 233 | monitor->physical_height = height; 234 | } 235 | 236 | static void output_done(void *data, struct wl_output *wl_output) { 237 | } 238 | 239 | static void output_scale(void *data, struct wl_output *wl_output, 240 | int32_t factor) { 241 | struct monitor_info *monitor = data; 242 | monitor->scale = factor; 243 | } 244 | 245 | struct wl_output_listener output_listener = { 246 | .geometry = output_geometry, 247 | .mode = output_mode, 248 | .done = output_done, 249 | .scale = output_scale, 250 | }; 251 | static void xdg_output_handle_logical_position(void *data, 252 | struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { 253 | // Who cares 254 | } 255 | 256 | static void xdg_output_handle_logical_size(void *data, 257 | struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { 258 | struct monitor_info *monitor = data; 259 | 260 | monitor->logical_width = width; 261 | monitor->logical_height = height; 262 | } 263 | 264 | static void xdg_output_handle_done(void *data, 265 | struct zxdg_output_v1 *xdg_output) { 266 | // Who cares 267 | } 268 | 269 | static void xdg_output_handle_name(void *data, 270 | struct zxdg_output_v1 *xdg_output, const char *name) { 271 | struct monitor_info *monitor = data; 272 | strncpy(monitor->name, name, MAX_MONITOR_NAME_LEN); 273 | } 274 | 275 | static void xdg_output_handle_description(void *data, 276 | struct zxdg_output_v1 *xdg_output, const char *description) { 277 | } 278 | 279 | struct zxdg_output_v1_listener xdg_output_listener = { 280 | .logical_position = xdg_output_handle_logical_position, 281 | .logical_size = xdg_output_handle_logical_size, 282 | .done = xdg_output_handle_done, 283 | .name = xdg_output_handle_name, 284 | .description = xdg_output_handle_description, 285 | }; 286 | 287 | static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, 288 | uint32_t format, int32_t fd, uint32_t size) { 289 | 290 | struct dmenu_panel *panel = data; 291 | 292 | panel->keyboard.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 293 | 294 | if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { 295 | close(fd); 296 | exit(1); 297 | } 298 | char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); 299 | if (map_shm == MAP_FAILED) { 300 | close(fd); 301 | exit(1); 302 | } 303 | panel->keyboard.xkb_keymap = xkb_keymap_new_from_string( 304 | panel->keyboard.xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); 305 | munmap(map_shm, size); 306 | close(fd); 307 | 308 | panel->keyboard.xkb_state = xkb_state_new(panel->keyboard.xkb_keymap); 309 | } 310 | 311 | static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, 312 | uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { 313 | // Who cares 314 | } 315 | 316 | static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, 317 | uint32_t serial, struct wl_surface *surface) { 318 | // Who cares 319 | } 320 | 321 | static void keyboard_repeat(struct dmenu_panel *panel) { 322 | if (panel->on_keyrepeat) { 323 | panel->on_keyrepeat(panel); 324 | } 325 | 326 | struct itimerspec spec = { 0 }; 327 | spec.it_value.tv_sec = panel->repeat_period / 1000; 328 | spec.it_value.tv_nsec = (panel->repeat_period % 1000) * 1000000l; 329 | timerfd_settime(panel->repeat_timer, 0, &spec, NULL); 330 | } 331 | 332 | static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, 333 | uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { 334 | struct dmenu_panel *panel = data; 335 | 336 | enum wl_keyboard_key_state key_state = _key_state; 337 | xkb_keysym_t sym = xkb_state_key_get_one_sym(panel->keyboard.xkb_state, key + 8); 338 | if (panel->on_keyevent) { 339 | panel->on_keyevent(panel, key_state, sym, panel->keyboard.control, 340 | panel->keyboard.shift); 341 | 342 | if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && panel->repeat_period >= 0) { 343 | panel->repeat_key_state = key_state; 344 | panel->repeat_sym = sym; 345 | 346 | struct itimerspec spec = { 0 }; 347 | spec.it_value.tv_sec = panel->repeat_delay / 1000; 348 | spec.it_value.tv_nsec = (panel->repeat_delay % 1000) * 1000000l; 349 | timerfd_settime(panel->repeat_timer, 0, &spec, NULL); 350 | } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { 351 | struct itimerspec spec = { 0 }; 352 | timerfd_settime(panel->repeat_timer, 0, &spec, NULL); 353 | } 354 | } 355 | } 356 | 357 | static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, 358 | int32_t rate, int32_t delay) { 359 | struct dmenu_panel *panel = data; 360 | panel->repeat_delay = delay; 361 | if (rate > 0) { 362 | panel->repeat_period = 1000 / rate; 363 | } else { 364 | panel->repeat_period = -1; 365 | } 366 | } 367 | 368 | static void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, 369 | uint32_t serial, uint32_t mods_depressed, 370 | uint32_t mods_latched, uint32_t mods_locked, 371 | uint32_t group) { 372 | struct dmenu_panel *panel = data; 373 | xkb_state_update_mask(panel->keyboard.xkb_state, 374 | mods_depressed, mods_latched, mods_locked, 0, 0, group); 375 | panel->keyboard.control = xkb_state_mod_name_is_active(panel->keyboard.xkb_state, 376 | XKB_MOD_NAME_CTRL, 377 | XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 378 | panel->keyboard.shift = xkb_state_mod_name_is_active(panel->keyboard.xkb_state, 379 | XKB_MOD_NAME_SHIFT, 380 | XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 381 | } 382 | static const struct wl_keyboard_listener keyboard_listener = { 383 | .keymap = keyboard_keymap, 384 | .enter = keyboard_enter, 385 | .leave = keyboard_leave, 386 | .key = keyboard_key, 387 | .modifiers = keyboard_modifiers, 388 | .repeat_info = keyboard_repeat_info, 389 | }; 390 | 391 | static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, 392 | enum wl_seat_capability caps) { 393 | struct dmenu_panel *panel = data; 394 | if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { 395 | panel->keyboard.kbd = wl_seat_get_keyboard (panel->display_info.seat); 396 | wl_keyboard_add_listener (panel->keyboard.kbd, &keyboard_listener, panel); 397 | } 398 | } 399 | static void seat_handle_name(void *data, struct wl_seat *wl_seat, 400 | const char *name) { 401 | // Who cares 402 | } 403 | 404 | const struct wl_seat_listener seat_listener = { 405 | .capabilities = seat_handle_capabilities, 406 | .name = seat_handle_name, 407 | }; 408 | 409 | void set_monitor_xdg_output(struct dmenu_panel *panel, struct monitor_info *monitor){ 410 | monitor->xdg_output = 411 | zxdg_output_manager_v1_get_xdg_output(panel->display_info.xdg_output_manager, 412 | monitor->output); 413 | zxdg_output_v1_add_listener(monitor->xdg_output, &xdg_output_listener, 414 | monitor); 415 | } 416 | 417 | static void handle_global(void *data, struct wl_registry *registry, 418 | uint32_t name, const char *interface, uint32_t version) { 419 | struct dmenu_panel *panel = data; 420 | 421 | if (strcmp(interface, wl_compositor_interface.name) == 0) { 422 | panel->display_info.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); 423 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { 424 | panel->display_info.seat = wl_registry_bind (registry, name, &wl_seat_interface, 4); 425 | wl_seat_add_listener (panel->display_info.seat, &seat_listener, panel); 426 | } else if (strcmp(interface, wl_shm_interface.name) == 0) { 427 | panel->surface.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 428 | } else if (strcmp(interface, wl_output_interface.name) == 0) { 429 | 430 | if(n_monitors >= 16) return; 431 | 432 | monitors[n_monitors] = malloc(sizeof(struct monitor_info)); 433 | monitors[n_monitors]->panel = panel; 434 | memset(monitors[n_monitors]->name, 0, MAX_MONITOR_NAME_LEN); 435 | monitors[n_monitors]->output = wl_registry_bind(registry, name, &wl_output_interface, 2); 436 | 437 | wl_output_add_listener(monitors[n_monitors]->output, &output_listener, 438 | monitors[n_monitors]); 439 | 440 | if (panel->display_info.xdg_output_manager != NULL) { 441 | set_monitor_xdg_output(panel, monitors[n_monitors]); 442 | } 443 | n_monitors++; 444 | 445 | } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { 446 | panel->surface.layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); 447 | 448 | } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { 449 | panel->display_info.xdg_output_manager = wl_registry_bind(registry, name, 450 | &zxdg_output_manager_v1_interface, 2); 451 | 452 | for(int m = 0; m < n_monitors; m++){ 453 | set_monitor_xdg_output(panel, monitors[m]); 454 | } 455 | } 456 | 457 | } 458 | 459 | static void handle_global_remove(void *data, struct wl_registry *registry, 460 | uint32_t name) { 461 | } 462 | 463 | static void buffer_release(void *data, struct wl_buffer *wl_buffer) { 464 | } 465 | 466 | static const struct wl_buffer_listener buffer_listener = { 467 | .release = buffer_release 468 | }; 469 | 470 | struct wl_buffer *dmenu_create_buffer(struct dmenu_panel *panel) { 471 | struct monitor_info *m = panel->monitor; 472 | double factor = m->scale / ((double)m->physical_width 473 | / m->logical_width); 474 | 475 | int32_t width = round_to_int(m->physical_width * factor); 476 | int32_t height = round_to_int(panel->height * m->scale); 477 | 478 | int stride = width * 4; 479 | int size = stride * height; 480 | 481 | int fd = create_shm_file(size); 482 | if (fd < 0) { 483 | fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size); 484 | return NULL; 485 | } 486 | 487 | panel->surface.shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 488 | if (panel->surface.shm_data == MAP_FAILED) { 489 | fprintf(stderr, "mmap failed: %m\n"); 490 | close(fd); 491 | return NULL; 492 | } 493 | 494 | struct wl_shm_pool *pool = wl_shm_create_pool(panel->surface.shm, fd, size); 495 | struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, 496 | stride, WL_SHM_FORMAT_ARGB8888); 497 | wl_shm_pool_destroy(pool); 498 | 499 | 500 | wl_buffer_add_listener(buffer, &buffer_listener, panel); 501 | 502 | cairo_surface_t *s = cairo_image_surface_create_for_data(panel->surface.shm_data, 503 | CAIRO_FORMAT_ARGB32, 504 | width, height, width * 4); 505 | panel->surface.cairo = cairo_create(s); 506 | cairo_set_antialias(panel->surface.cairo, CAIRO_ANTIALIAS_BEST); 507 | cairo_font_options_t *fo = cairo_font_options_create(); 508 | cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); 509 | cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); 510 | cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(m->subpixel)); 511 | cairo_set_font_options(panel->surface.cairo, fo); 512 | cairo_font_options_destroy(fo); 513 | cairo_save(panel->surface.cairo); 514 | 515 | return buffer; 516 | } 517 | 518 | static const struct wl_registry_listener registry_listener = { 519 | .global = handle_global, 520 | .global_remove = handle_global_remove, 521 | }; 522 | 523 | 524 | void dmenu_init_panel(struct dmenu_panel *panel, int32_t height, bool bottom) { 525 | if(!setlocale(LC_CTYPE, "")) 526 | weprintf("no locale support\n"); 527 | 528 | if(!(panel->display_info.display = wl_display_connect(NULL))) 529 | eprintf("cannot open display\n"); 530 | 531 | if ((panel->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0)) < 0) 532 | eprintf("cannot create timer fd\n"); 533 | 534 | panel->height = height; 535 | panel->keyboard.control = false; 536 | panel->on_keyevent = NULL; 537 | 538 | struct wl_registry *registry = wl_display_get_registry(panel->display_info.display); 539 | wl_registry_add_listener(registry, ®istry_listener, panel); 540 | 541 | wl_display_roundtrip(panel->display_info.display); 542 | 543 | /* Second roundtrip for xdg-output. Will populate display dimensions. */ 544 | wl_display_roundtrip(panel->display_info.display); 545 | 546 | 547 | panel->surface.surface = wl_compositor_create_surface(panel->display_info.compositor); 548 | 549 | panel->monitor = NULL; 550 | if (!panel->selected_monitor_name) { 551 | panel->monitor = monitors[panel->selected_monitor]; 552 | } else { 553 | for (int i = 0; i < n_monitors; ++i) { 554 | if (monitors[i] && !strncmp(panel->selected_monitor_name, 555 | monitors[i]->name, 556 | MAX_MONITOR_NAME_LEN)) { 557 | panel->monitor = monitors[i]; 558 | break; 559 | } 560 | } 561 | } 562 | if (!panel->monitor) { 563 | if (!panel->selected_monitor_name) 564 | eprintf("No monitor with index %i available.\n", panel->selected_monitor); 565 | else 566 | eprintf("No monitor with name %s available.\n", panel->selected_monitor_name); 567 | } 568 | 569 | panel->surface.buffer = dmenu_create_buffer(panel); 570 | 571 | if (!panel->surface.layer_shell) 572 | eprintf("Compositor does not implement wlr-layer-shell protocol.\n"); 573 | panel->surface.layer_surface = 574 | zwlr_layer_shell_v1_get_layer_surface(panel->surface.layer_shell, 575 | panel->surface.surface, 576 | panel->monitor->output, 577 | ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 578 | "panel"); 579 | 580 | zwlr_layer_surface_v1_set_size(panel->surface.layer_surface, 581 | panel->monitor->logical_width, panel->height); 582 | zwlr_layer_surface_v1_set_anchor(panel->surface.layer_surface, 583 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | 584 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | 585 | (bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM 586 | : ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)); 587 | 588 | 589 | zwlr_layer_surface_v1_add_listener(panel->surface.layer_surface, 590 | &layer_surface_listener, panel); 591 | 592 | wl_surface_set_buffer_scale(panel->surface.surface, 593 | panel->monitor->scale); 594 | wl_surface_commit(panel->surface.surface); 595 | wl_display_roundtrip(panel->display_info.display); 596 | 597 | zwlr_layer_surface_v1_set_keyboard_interactivity(panel->surface.layer_surface, true); 598 | 599 | wl_surface_attach(panel->surface.surface, panel->surface.buffer, 0, 0); 600 | wl_surface_commit(panel->surface.surface); 601 | } 602 | 603 | void dmenu_show(struct dmenu_panel *dmenu) { 604 | dmenu_draw(dmenu); 605 | 606 | zwlr_layer_surface_v1_set_keyboard_interactivity(dmenu->surface.layer_surface, true); 607 | wl_surface_commit(dmenu->surface.surface); 608 | 609 | struct pollfd fds[] = { 610 | { wl_display_get_fd(dmenu->display_info.display), POLLIN }, 611 | { dmenu->repeat_timer, POLLIN }, 612 | }; 613 | const int nfds = sizeof(fds) / sizeof(*fds); 614 | 615 | wl_display_flush(dmenu->display_info.display); 616 | 617 | dmenu->running = true; 618 | while (dmenu->running) { 619 | if (wl_display_flush(dmenu->display_info.display) < 0) { 620 | if (errno == EAGAIN) 621 | continue; 622 | break; 623 | } 624 | 625 | if (poll(fds, nfds, -1) < 0) { 626 | if (errno == EAGAIN) 627 | continue; 628 | break; 629 | } 630 | 631 | if (fds[0].revents & POLLIN) { 632 | if (wl_display_dispatch(dmenu->display_info.display) < 0) { 633 | dmenu->running = false; 634 | } 635 | } 636 | 637 | if (fds[1].revents & POLLIN) { 638 | keyboard_repeat(dmenu); 639 | } 640 | } 641 | 642 | /* dmenu_close called */ 643 | wl_display_disconnect(dmenu->display_info.display); 644 | 645 | } 646 | void dmenu_close(struct dmenu_panel *dmenu) { 647 | dmenu->running = false; 648 | } 649 | 650 | 651 | void 652 | weprintf(const char *fmt, ...) { 653 | va_list ap; 654 | 655 | fprintf(stderr, "%s: warning: ", progname); 656 | va_start(ap, fmt); 657 | vfprintf(stderr, fmt, ap); 658 | va_end(ap); 659 | } 660 | -------------------------------------------------------------------------------- /draw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" 6 | #include 7 | 8 | #define MAX_MONITOR_NAME_LEN 255 9 | 10 | #define FG(dc, col) ((col)[(dc)->invert ? ColBG : ColFG]) 11 | #define BG(dc, col) ((col)[(dc)->invert ? ColFG : ColBG]) 12 | 13 | enum { ColBG, ColFG, ColBorder, ColLast }; 14 | 15 | struct dmenu_panel; 16 | 17 | struct monitor_info { 18 | int32_t physical_width; 19 | int32_t physical_height; 20 | int32_t logical_width; 21 | int32_t logical_height; 22 | double scale; 23 | 24 | char name[MAX_MONITOR_NAME_LEN]; 25 | 26 | enum wl_output_subpixel subpixel; 27 | 28 | struct zxdg_output_v1 *xdg_output; 29 | 30 | struct wl_output *output; 31 | struct dmenu_panel *panel; 32 | }; 33 | 34 | extern struct monitor_info *monitors[]; 35 | 36 | struct display_info { 37 | struct zxdg_output_manager_v1 *xdg_output_manager; 38 | struct wl_display * display; 39 | struct wl_compositor *compositor; 40 | struct wl_seat *seat; 41 | 42 | }; 43 | 44 | struct keyboard_info { 45 | struct wl_keyboard *kbd; 46 | struct xkb_context *xkb_context; 47 | struct xkb_keymap *xkb_keymap; 48 | struct xkb_state *xkb_state; 49 | bool control; 50 | bool shift; 51 | }; 52 | 53 | struct surface { 54 | cairo_t *cairo; 55 | struct wl_buffer *buffer; 56 | struct wl_surface *surface; 57 | struct wl_shm *shm; 58 | void *shm_data; 59 | struct zwlr_layer_shell_v1 *layer_shell; 60 | struct zwlr_layer_surface_v1 *layer_surface; 61 | }; 62 | 63 | struct dmenu_panel { 64 | struct keyboard_info keyboard; 65 | /* struct monitor_info monitor; */ 66 | int selected_monitor; 67 | char *selected_monitor_name; 68 | 69 | struct monitor_info *monitor; 70 | struct display_info display_info; 71 | 72 | struct surface surface; 73 | 74 | void (*on_keyevent)(struct dmenu_panel *,enum wl_keyboard_key_state, 75 | xkb_keysym_t, bool, bool); 76 | void (*on_keyrepeat)(struct dmenu_panel *); 77 | 78 | void (*draw)(cairo_t *, int32_t, int32_t, int32_t); 79 | 80 | int32_t width; 81 | int32_t height; 82 | 83 | int repeat_timer; 84 | int repeat_delay; 85 | int repeat_period; 86 | enum wl_keyboard_key_state repeat_key_state; 87 | xkb_keysym_t repeat_sym; 88 | 89 | bool running; 90 | }; 91 | 92 | void dmenu_init_panel(struct dmenu_panel *, int32_t, bool); 93 | void dmenu_draw(struct dmenu_panel *); 94 | void dmenu_show(struct dmenu_panel *); 95 | void dmenu_close(struct dmenu_panel *); 96 | 97 | 98 | void pango_printf(cairo_t *cairo, const char *font, 99 | double scale, bool markup, const char *fmt, ...); 100 | void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, 101 | int *baseline, double scale, bool markup, const char *fmt, ...); 102 | void eprintf(const char *fmt, ...); 103 | void weprintf(const char *fmt, ...); 104 | int32_t round_to_int(double val); 105 | 106 | extern const char *progname; 107 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'dmenu-wl', 3 | 'c', 4 | version: '4.2.1', 5 | license: 'MIT', 6 | default_options: ['c_std=c99'] 7 | ) 8 | 9 | add_project_arguments( 10 | [ 11 | '-pedantic', 12 | '-Wall', 13 | '-D_DEFAULT_SOURCE', 14 | '-DVERSION="@0@"'.format(meson.project_version()), 15 | ], 16 | language : 'c' 17 | ) 18 | 19 | cc = meson.get_compiler('c') 20 | 21 | cairo = dependency('cairo') 22 | glib = dependency('glib-2.0') 23 | gobject = dependency('gobject-2.0') 24 | pango = dependency('pango') 25 | pangocairo = dependency('pangocairo') 26 | rt = cc.find_library('rt') 27 | wayland_client = dependency('wayland-client') 28 | wayland_protos = dependency('wayland-protocols') 29 | wayland_scanner_dep = dependency('wayland-scanner') 30 | wayland_scanner = find_program( 31 | wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner') 32 | ) 33 | xkbcommon = dependency('xkbcommon') 34 | 35 | wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 36 | 37 | protocols = [ 38 | [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], 39 | [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], 40 | 'wlr-layer-shell-unstable-v1.xml' 41 | ] 42 | 43 | protos_src = [] 44 | protos_headers = [] 45 | 46 | foreach p : protocols 47 | xml = join_paths(p) 48 | protos_src += custom_target( 49 | xml.underscorify() + '_client_c', 50 | input: xml, 51 | output: '@BASENAME@-protocol.c', 52 | command: [wayland_scanner, 'public-code', '@INPUT@', '@OUTPUT@'], 53 | ) 54 | protos_headers += custom_target( 55 | xml.underscorify() + '_client_h', 56 | input: xml, 57 | output: '@BASENAME@-client-protocol.h', 58 | command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], 59 | ) 60 | endforeach 61 | 62 | dmenu_deps = [ 63 | cairo, 64 | glib, 65 | gobject, 66 | pango, 67 | pangocairo, 68 | rt, 69 | wayland_client, 70 | xkbcommon 71 | ] 72 | 73 | dmenu_src = ['dmenu.c', 'draw.c'] + protos_src + protos_headers 74 | 75 | executable('dmenu-wl', dmenu_src, dependencies: dmenu_deps, install: true) 76 | 77 | executable('dmenu-wl_path', 'dmenu_path.c', install: true) 78 | 79 | install_data('dmenu-wl_run', install_dir: get_option('bindir')) 80 | 81 | install_man('dmenu-wl.1') 82 | 83 | -------------------------------------------------------------------------------- /wlr-layer-shell-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2017 Drew DeVault 5 | 6 | Permission to use, copy, modify, distribute, and sell this 7 | software and its documentation for any purpose is hereby granted 8 | without fee, provided that the above copyright notice appear in 9 | all copies and that both that copyright notice and this permission 10 | notice appear in supporting documentation, and that the name of 11 | the copyright holders not be used in advertising or publicity 12 | pertaining to distribution of the software without specific, 13 | written prior permission. The copyright holders make no 14 | representations about the suitability of this software for any 15 | purpose. It is provided "as is" without express or implied 16 | warranty. 17 | 18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | THIS SOFTWARE. 26 | 27 | 28 | 29 | 30 | Clients can use this interface to assign the surface_layer role to 31 | wl_surfaces. Such surfaces are assigned to a "layer" of the output and 32 | rendered with a defined z-depth respective to each other. They may also be 33 | anchored to the edges and corners of a screen and specify input handling 34 | semantics. This interface should be suitable for the implementation of 35 | many desktop shell components, and a broad number of other applications 36 | that interact with the desktop. 37 | 38 | 39 | 40 | 41 | Create a layer surface for an existing surface. This assigns the role of 42 | layer_surface, or raises a protocol error if another role is already 43 | assigned. 44 | 45 | Creating a layer surface from a wl_surface which has a buffer attached 46 | or committed is a client error, and any attempts by a client to attach 47 | or manipulate a buffer prior to the first layer_surface.configure call 48 | must also be treated as errors. 49 | 50 | You may pass NULL for output to allow the compositor to decide which 51 | output to use. Generally this will be the one that the user most 52 | recently interacted with. 53 | 54 | Clients can specify a namespace that defines the purpose of the layer 55 | surface. 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | These values indicate which layers a surface can be rendered in. They 73 | are ordered by z depth, bottom-most first. Traditional shell surfaces 74 | will typically be rendered between the bottom and top layers. 75 | Fullscreen shell surfaces are typically rendered at the top layer. 76 | Multiple surfaces can share a single layer, and ordering within a 77 | single layer is undefined. 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | An interface that may be implemented by a wl_surface, for surfaces that 90 | are designed to be rendered as a layer of a stacked desktop-like 91 | environment. 92 | 93 | Layer surface state (size, anchor, exclusive zone, margin, interactivity) 94 | is double-buffered, and will be applied at the time wl_surface.commit of 95 | the corresponding wl_surface is called. 96 | 97 | 98 | 99 | 100 | Sets the size of the surface in surface-local coordinates. The 101 | compositor will display the surface centered with respect to its 102 | anchors. 103 | 104 | If you pass 0 for either value, the compositor will assign it and 105 | inform you of the assignment in the configure event. You must set your 106 | anchor to opposite edges in the dimensions you omit; not doing so is a 107 | protocol error. Both values are 0 by default. 108 | 109 | Size is double-buffered, see wl_surface.commit. 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Requests that the compositor anchor the surface to the specified edges 118 | and corners. If two orthoginal edges are specified (e.g. 'top' and 119 | 'left'), then the anchor point will be the intersection of the edges 120 | (e.g. the top left corner of the output); otherwise the anchor point 121 | will be centered on that edge, or in the center if none is specified. 122 | 123 | Anchor is double-buffered, see wl_surface.commit. 124 | 125 | 126 | 127 | 128 | 129 | 130 | Requests that the compositor avoids occluding an area of the surface 131 | with other surfaces. The compositor's use of this information is 132 | implementation-dependent - do not assume that this region will not 133 | actually be occluded. 134 | 135 | A positive value is only meaningful if the surface is anchored to an 136 | edge, rather than a corner. The zone is the number of surface-local 137 | coordinates from the edge that are considered exclusive. 138 | 139 | Surfaces that do not wish to have an exclusive zone may instead specify 140 | how they should interact with surfaces that do. If set to zero, the 141 | surface indicates that it would like to be moved to avoid occluding 142 | surfaces with a positive excluzive zone. If set to -1, the surface 143 | indicates that it would not like to be moved to accommodate for other 144 | surfaces, and the compositor should extend it all the way to the edges 145 | it is anchored to. 146 | 147 | For example, a panel might set its exclusive zone to 10, so that 148 | maximized shell surfaces are not shown on top of it. A notification 149 | might set its exclusive zone to 0, so that it is moved to avoid 150 | occluding the panel, but shell surfaces are shown underneath it. A 151 | wallpaper or lock screen might set their exclusive zone to -1, so that 152 | they stretch below or over the panel. 153 | 154 | The default value is 0. 155 | 156 | Exclusive zone is double-buffered, see wl_surface.commit. 157 | 158 | 159 | 160 | 161 | 162 | 163 | Requests that the surface be placed some distance away from the anchor 164 | point on the output, in surface-local coordinates. Setting this value 165 | for edges you are not anchored to has no effect. 166 | 167 | The exclusive zone includes the margin. 168 | 169 | Margin is double-buffered, see wl_surface.commit. 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | Set to 1 to request that the seat send keyboard events to this layer 180 | surface. For layers below the shell surface layer, the seat will use 181 | normal focus semantics. For layers above the shell surface layers, the 182 | seat will always give exclusive keyboard focus to the top-most layer 183 | which has keyboard interactivity set to true. 184 | 185 | Layer surfaces receive pointer, touch, and tablet events normally. If 186 | you do not want to receive them, set the input region on your surface 187 | to an empty region. 188 | 189 | Events is double-buffered, see wl_surface.commit. 190 | 191 | 192 | 193 | 194 | 195 | 196 | This assigns an xdg_popup's parent to this layer_surface. This popup 197 | should have been created via xdg_surface::get_popup with the parent set 198 | to NULL, and this request must be invoked before committing the popup's 199 | initial state. 200 | 201 | See the documentation of xdg_popup for more details about what an 202 | xdg_popup is and how it is used. 203 | 204 | 205 | 206 | 207 | 208 | 209 | When a configure event is received, if a client commits the 210 | surface in response to the configure event, then the client 211 | must make an ack_configure request sometime before the commit 212 | request, passing along the serial of the configure event. 213 | 214 | If the client receives multiple configure events before it 215 | can respond to one, it only has to ack the last configure event. 216 | 217 | A client is not required to commit immediately after sending 218 | an ack_configure request - it may even ack_configure several times 219 | before its next surface commit. 220 | 221 | A client may send multiple ack_configure requests before committing, but 222 | only the last request sent before a commit indicates which configure 223 | event the client really is responding to. 224 | 225 | 226 | 227 | 228 | 229 | 230 | This request destroys the layer surface. 231 | 232 | 233 | 234 | 235 | 236 | The configure event asks the client to resize its surface. 237 | 238 | Clients should arrange their surface for the new states, and then send 239 | an ack_configure request with the serial sent in this configure event at 240 | some point before committing the new surface. 241 | 242 | The client is free to dismiss all but the last configure event it 243 | received. 244 | 245 | The width and height arguments specify the size of the window in 246 | surface-local coordinates. 247 | 248 | The size is a hint, in the sense that the client is free to ignore it if 249 | it doesn't resize, pick a smaller size (to satisfy aspect ratio or 250 | resize in steps of NxM pixels). If the client picks a smaller size and 251 | is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the 252 | surface will be centered on this axis. 253 | 254 | If the width or height arguments are zero, it means the client should 255 | decide its own window dimension. 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | The closed event is sent by the compositor when the surface will no 265 | longer be shown. The output may have been destroyed or the user may 266 | have asked for it to be removed. Further changes to the surface will be 267 | ignored. The client should destroy the resource after receiving this 268 | event, and create a new surface if they so choose. 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | --------------------------------------------------------------------------------