Copy files over
This commit is contained in:
commit
9499f8bf03
11 changed files with 2093 additions and 0 deletions
8
.dir-locals.el
Normal file
8
.dir-locals.el
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
((nil . ((tab-width . 4)
|
||||
(c-basic-offset . 4)
|
||||
(sentence-end-double-space . t)
|
||||
(indent-tabs-mode . t)
|
||||
(fill-column . 80)))
|
||||
(c-mode . ((c-file-style . "GNU")
|
||||
(c-noise-macro-names . ("UNINIT" "CALLBACK" "ALIGN_STACK")))))
|
29
LICENSE
Normal file
29
LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
|||
MIT/X Consortium License
|
||||
|
||||
© 2018-2019 Henrik Nyman <h@nyymanni.com>
|
||||
© 2012 Steve Engledow <steve@offend.me.uk>
|
||||
© 2010 Connor Lane Smith <cls@lubutu.com>
|
||||
© 2006-2010 Anselm R Garbe <anselm@garbe.us>
|
||||
© 2009 Gottox <gottox@s01.de>
|
||||
© 2009 Markus Schnalke <meillo@marmaro.de>
|
||||
© 2009 Evan Gates <evan.gates@gmail.com>
|
||||
© 2006-2008 Sander van Dijk <a dot h dot vandijk at gmail dot com>
|
||||
© 2006-2007 Michał Janeczek <janeczek at gmail dot com>
|
||||
|
||||
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.
|
83
README.md
Normal file
83
README.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
# dmenu-wl - dynamic menu
|
||||
dmenu-wl is an efficient dynamic menu for wayland (wlroots).
|
||||
|
||||
## TODO
|
||||
Missing dmenu (for X) features:
|
||||
- Echo (non-interactive use)
|
||||
- Handling pagination
|
||||
- Vertical layout
|
||||
- Return-early
|
||||
- Control-Enter handling
|
||||
- Keyrepeat
|
||||
- Choice (-c) -mode
|
||||
|
||||
Other TODO items:
|
||||
- Cleaner exiting
|
||||
- Generate protocol files with `wayland-scanner`
|
||||
|
||||
## Requirements
|
||||
Requires a compositor which implements wlr-layer-shell and xdg-output
|
||||
protocols. Basically this means a wlroots-based compositor is needed.
|
||||
Gnome and KDE are therefore [not supported](https://github.com/nyyManni/dmenu-wayland/issues/16).
|
||||
Tested with sway 1.0.
|
||||
|
||||
Required libraries (and headers):
|
||||
- wayland-client
|
||||
- cairo
|
||||
- pango-1.0
|
||||
- pangocairo-1.0
|
||||
- xkbcommon
|
||||
- glib-2.0
|
||||
- gobject-2.0
|
||||
|
||||
|
||||
## Installation
|
||||
```
|
||||
mkdir build
|
||||
meson build
|
||||
ninja -C build
|
||||
sudo ninja -C build install
|
||||
```
|
||||
|
||||
## Running dmenu-wl ...
|
||||
|
||||
### ... as the application launcher in Sway
|
||||
|
||||
Add to sway configuration (`~/.config/sway/config`) to run the launcher on Win+D.
|
||||
|
||||
bindsym $mod+d exec dmenu-wl_run -i
|
||||
|
||||
### ... from the command-line
|
||||
|
||||
See the man page for details.
|
||||
```
|
||||
Usage: dmenu-wl [OPTION]...
|
||||
|
||||
Display newline-separated input stdin as a menubar
|
||||
|
||||
-e, --echo display text from stdin with no user
|
||||
interaction
|
||||
-ec, --echo-centre same as -e but align text centrally
|
||||
-er, --echo-right same as -e but align text right
|
||||
-et, --echo-timeout SECS close the message after SEC seconds
|
||||
when using -e, -ec, or -er
|
||||
-b, --bottom dmenu appears at the bottom of the screen
|
||||
-h, --height N set dmenu to be N pixels high
|
||||
-i, --insensitive dmenu matches menu items case insensitively
|
||||
-l, --lines LINES dmenu lists items vertically, within the
|
||||
given number of lines
|
||||
-m, --monitor MONITOR dmenu appears on the given monitor
|
||||
(0-based index or monitor name)
|
||||
-p, --prompt PROMPT prompt to be displayed to the left of the
|
||||
input field
|
||||
-po, --prompt-only PROMPT same as -p but don't wait for stdin
|
||||
useful for a prompt with no menu
|
||||
-r, --return-early return as soon as a single match is found
|
||||
-fn, --font-name FONT font or font set to be used
|
||||
-nb, --normal-background COLOR normal background color
|
||||
#RRGGBB and #RRGGBBAA supported
|
||||
-nf, --normal-foreground COLOR normal foreground color
|
||||
-sb, --selected-background COLOR selected background color
|
||||
-sf, --selected-foreground COLOR selected foreground color
|
||||
-v, --version display version information
|
||||
```
|
125
dmenu-wl.1
Normal file
125
dmenu-wl.1
Normal file
|
@ -0,0 +1,125 @@
|
|||
.TH DMENU 1 dmenu-wl\-VERSION
|
||||
.SH NAME
|
||||
dmenu-wl \- dynamic menu
|
||||
.SH SYNOPSIS
|
||||
.B dmenu-wl
|
||||
.RB [ \-b ]
|
||||
.RB [ \-i ]
|
||||
.RB [ \-l
|
||||
.IR lines ]
|
||||
.RB [ \-m
|
||||
.IR monitor ]
|
||||
.RB [ \-p
|
||||
.IR prompt ]
|
||||
.RB [ \-fn
|
||||
.IR font ]
|
||||
.RB [ \-nb
|
||||
.IR color ]
|
||||
.RB [ \-nf
|
||||
.IR color ]
|
||||
.RB [ \-sb
|
||||
.IR color ]
|
||||
.RB [ \-sf
|
||||
.IR color ]
|
||||
.RB [ \-v ]
|
||||
.P
|
||||
.BR dmenu-wl_run " ..."
|
||||
.P
|
||||
.B dmenu-wl_path
|
||||
.SH DESCRIPTION
|
||||
.B dmenu-wl
|
||||
is a dynamic menu for Wayland, originally designed for X and
|
||||
.BR dwm (1).
|
||||
It manages huge numbers of user-defined menu items efficiently.
|
||||
.P
|
||||
dmenu-wl reads a list of newline-separated items from standard input and creates a
|
||||
menu (or displays a notification if using -e). When the user selects an item or
|
||||
enters any text and presses Return, their choice is printed to standard output
|
||||
and dmenu-wl terminates.
|
||||
.P
|
||||
.B dmenu-wl_run
|
||||
is a dmenu-wl script used by dwm which lists programs in the user's PATH and
|
||||
executes the selected item.
|
||||
.P
|
||||
.B dmenu-wl_path
|
||||
is a program used by dmenu-wl_run to find and cache a list of executables.
|
||||
.SH OPTIONS
|
||||
.B \-e
|
||||
dmenu displays text from stdin with no user interaction.
|
||||
.TP
|
||||
.B \-ec
|
||||
same as -e but text is aligned centrally.
|
||||
.TP
|
||||
.B \-er
|
||||
same as -e but text is aligned to the right.
|
||||
.TP
|
||||
.BI \-et " secs "
|
||||
when using -e, close the message after the given number of seconds
|
||||
.TP
|
||||
.B \-b
|
||||
dmenu appears at the bottom of the screen.
|
||||
.TP
|
||||
.BI \-h " height"
|
||||
dmenu is drawn at least the given number of pixels high.
|
||||
.TP
|
||||
.B \-i
|
||||
dmenu matches menu items case insensitively.
|
||||
.TP
|
||||
.BI \-l " lines"
|
||||
dmenu lists items vertically, within the given number of lines.
|
||||
.TP
|
||||
.BI \-m " monitor"
|
||||
dmenu appears on the given monitor (0-based index or monitor name)
|
||||
.TP
|
||||
.BI \-p " prompt"
|
||||
defines the prompt to be displayed to the left of the input field.
|
||||
.TP
|
||||
.BI \-po " prompt"
|
||||
same as -p but don't wait for stdin - useful for a prompt with no selection options.
|
||||
.TP
|
||||
.B \-r
|
||||
Return as soon as a single match is found. i.e. don't wait for the user to press return.
|
||||
.TP
|
||||
.BI \-fn " font"
|
||||
defines the font or font set used.
|
||||
.TP
|
||||
.BI \-nb " color"
|
||||
defines the normal background color.
|
||||
.IR #RRGGBB and
|
||||
.IR #RRGGBBAA
|
||||
are supported.
|
||||
.TP
|
||||
.BI \-nf " color"
|
||||
defines the normal foreground color.
|
||||
.TP
|
||||
.BI \-sb " color"
|
||||
defines the selected background color.
|
||||
.TP
|
||||
.BI \-sf " color"
|
||||
defines the selected foreground color.
|
||||
.TP
|
||||
.B \-v
|
||||
prints version information to standard output, then exits.
|
||||
.SH USAGE
|
||||
dmenu-wl is completely controlled by the keyboard. Besides standard Unix line
|
||||
editing and item selection (Up/Down/Left/Right, PageUp/PageDown, Home/End), the
|
||||
following keys are recognized:
|
||||
.TP
|
||||
.B Tab (Control\-i)
|
||||
Copy the selected item to the input field.
|
||||
.TP
|
||||
.B Return (Control\-j)
|
||||
Confirm selection. Prints the selected item to standard output and exits,
|
||||
returning success.
|
||||
.TP
|
||||
.B Shift\-Return (Control\-Shift\-j)
|
||||
Confirm input. Prints the input text to standard output and exits, returning
|
||||
success.
|
||||
.TP
|
||||
.B Escape (Control\-c)
|
||||
Exit without selecting an item, returning failure.
|
||||
.TP
|
||||
.B Control\-y
|
||||
Paste the current selection into the input field.
|
||||
.SH SEE ALSO
|
||||
.BR dwm (1)
|
2
dmenu-wl_run
Executable file
2
dmenu-wl_run
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
dmenu-wl_path | dmenu-wl "$@" | ${SHELL:-"/bin/sh"} &
|
612
dmenu.c
Normal file
612
dmenu.c
Normal file
|
@ -0,0 +1,612 @@
|
|||
/* See LICENSE file for copyright and license details. */
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include "draw.h"
|
||||
|
||||
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
typedef struct Item Item;
|
||||
struct Item {
|
||||
char *text;
|
||||
Item *next; /* traverses all items */
|
||||
Item *left, *right; /* traverses matching items */
|
||||
int32_t width;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
CENTRE
|
||||
} TextPosition;
|
||||
|
||||
struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t text_height;
|
||||
int32_t text_y;
|
||||
int32_t input_field;
|
||||
int32_t scroll_left;
|
||||
int32_t matches;
|
||||
int32_t scroll_right;
|
||||
} window_config;
|
||||
|
||||
const char *progname;
|
||||
|
||||
static uint32_t color_bg = 0x222222ff;
|
||||
static uint32_t color_fg = 0xbbbbbbff;
|
||||
static uint32_t color_input_bg = 0x222222ff;
|
||||
static uint32_t color_input_fg = 0xbbbbbbff;
|
||||
static uint32_t color_prompt_bg = 0x005577ff;
|
||||
static uint32_t color_prompt_fg = 0xeeeeeeff;
|
||||
static uint32_t color_selected_bg = 0x005577ff;
|
||||
static uint32_t color_selected_fg = 0xeeeeeeff;
|
||||
|
||||
static int32_t panel_height = 20;
|
||||
|
||||
static void appenditem(Item *item, Item **list, Item **last);
|
||||
static char *fstrstr(const char *s, const char *sub);
|
||||
static void insert(const char *s, ssize_t n);
|
||||
static void match(void);
|
||||
static size_t nextrune(int incr);
|
||||
static void readstdin(void);
|
||||
static void alarmhandler(int signum);
|
||||
/* static void handle_return(char* value); */
|
||||
static void usage(void);
|
||||
static int retcode = EXIT_SUCCESS;
|
||||
static int selected_monitor = 0;
|
||||
static char *selected_monitor_name = 0;
|
||||
|
||||
static char text[BUFSIZ];
|
||||
static char text_[BUFSIZ];
|
||||
static int itemcount = 0;
|
||||
static int lines = 0;
|
||||
static int timeout = 3;
|
||||
static size_t cursor = 0;
|
||||
static const char *prompt = NULL;
|
||||
static bool message = false;
|
||||
static bool nostdin = false;
|
||||
static bool returnearly = false;
|
||||
static bool show_in_bottom = false;
|
||||
static TextPosition messageposition = LEFT;
|
||||
static Item *items = NULL;
|
||||
static Item *matches, *sel;
|
||||
static Item *prev, *curr, *next;
|
||||
static Item *leftmost, *rightmost;
|
||||
static char *font = "Mono";
|
||||
|
||||
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
|
||||
|
||||
void
|
||||
insert(const char *s, ssize_t n) {
|
||||
if(strlen(text) + n > sizeof text - 1)
|
||||
return;
|
||||
memmove(text + cursor + n, text + cursor, sizeof text - cursor - MAX(n, 0));
|
||||
if(n > 0)
|
||||
memcpy(text + cursor, s, n);
|
||||
cursor += n;
|
||||
match();
|
||||
}
|
||||
|
||||
void keyrepeat(struct dmenu_panel *panel) {
|
||||
if (panel->on_keyevent) {
|
||||
panel->on_keyevent(panel, panel->repeat_key_state, panel->repeat_sym,
|
||||
panel->keyboard.control, panel->keyboard.shift);
|
||||
}
|
||||
}
|
||||
|
||||
void keypress(struct dmenu_panel *panel, enum wl_keyboard_key_state state,
|
||||
xkb_keysym_t sym, bool ctrl, bool shft) {
|
||||
char buf[8];
|
||||
size_t len = strlen(text);
|
||||
|
||||
if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return;
|
||||
|
||||
if (ctrl) {
|
||||
switch (xkb_keysym_to_lower(sym)) {
|
||||
case XKB_KEY_a:
|
||||
sym = XKB_KEY_Home;
|
||||
break;
|
||||
case XKB_KEY_e:
|
||||
sym = XKB_KEY_End;
|
||||
break;
|
||||
case XKB_KEY_f:
|
||||
case XKB_KEY_n:
|
||||
sym = XKB_KEY_Right;
|
||||
break;
|
||||
case XKB_KEY_b:
|
||||
case XKB_KEY_p:
|
||||
sym = XKB_KEY_Left;
|
||||
break;
|
||||
case XKB_KEY_h:
|
||||
sym = XKB_KEY_BackSpace;
|
||||
break;
|
||||
case XKB_KEY_j:
|
||||
sym = XKB_KEY_Return;
|
||||
break;
|
||||
case XKB_KEY_g:
|
||||
case XKB_KEY_c:
|
||||
retcode = EXIT_FAILURE;
|
||||
dmenu_close(panel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
switch (sym) {
|
||||
case XKB_KEY_KP_Enter: /* fallthrough */
|
||||
case XKB_KEY_Return:
|
||||
dmenu_close(panel);
|
||||
fputs((sel && !shft) ? sel->text : text, stdout);
|
||||
fflush(stdout);
|
||||
break;
|
||||
case XKB_KEY_Escape:
|
||||
retcode = EXIT_FAILURE;
|
||||
dmenu_close(panel);
|
||||
break;
|
||||
case XKB_KEY_Left:
|
||||
if(cursor && (!sel || !sel->left)) {
|
||||
cursor = nextrune(-1);
|
||||
} if (sel && sel->left) {
|
||||
sel = sel->left;
|
||||
}
|
||||
break;
|
||||
case XKB_KEY_Right:
|
||||
if (cursor < len) {
|
||||
cursor = nextrune(+1);
|
||||
} else if (cursor == len) {
|
||||
if (sel && sel->right) sel = sel->right;
|
||||
}
|
||||
break;
|
||||
|
||||
case XKB_KEY_End:
|
||||
if(cursor < len) {
|
||||
cursor = len;
|
||||
break;
|
||||
}
|
||||
while(sel && sel->right)
|
||||
sel = sel->right;
|
||||
break;
|
||||
case XKB_KEY_Home:
|
||||
if(sel == matches) {
|
||||
cursor = 0;
|
||||
break;
|
||||
}
|
||||
sel = curr = matches;
|
||||
/* calcoffsets(); */
|
||||
break;
|
||||
|
||||
case XKB_KEY_BackSpace:
|
||||
if (cursor > 0)
|
||||
insert(NULL, nextrune(-1) - cursor);
|
||||
break;
|
||||
case XKB_KEY_Delete:
|
||||
if (cursor == len)
|
||||
return;
|
||||
cursor = nextrune(+1);
|
||||
break;
|
||||
case XKB_KEY_Tab:
|
||||
if(!sel) return;
|
||||
strncpy(text, sel->text, sizeof text);
|
||||
cursor = strlen(text);
|
||||
match();
|
||||
break;
|
||||
default:
|
||||
if (xkb_keysym_to_utf8(sym, buf, 8)) {
|
||||
insert(buf, strnlen(buf, 8));
|
||||
}
|
||||
}
|
||||
dmenu_draw(panel);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int32_t draw_text(cairo_t *cairo, int32_t width, int32_t height, const char *str,
|
||||
int32_t x, int32_t scale, uint32_t
|
||||
foreground_color, uint32_t background_color, int32_t padding) {
|
||||
|
||||
int32_t text_width, text_height;
|
||||
get_text_size(cairo, font, &text_width, &text_height,
|
||||
NULL, scale, false, str);
|
||||
int32_t text_y = (height / 2.0) - (text_height / 2.0);
|
||||
|
||||
if (x + padding * scale + text_width + 30 * scale > width) {
|
||||
/* if (x + padding * scale + text_width > width) { */
|
||||
|
||||
cairo_move_to(cairo, width, text_y);
|
||||
pango_printf(cairo, font, scale, false, ">");
|
||||
} else {
|
||||
if (background_color) {
|
||||
cairo_set_source_u32(cairo, background_color);
|
||||
cairo_rectangle(cairo, x, 0, text_width + 2 * padding * scale, height);
|
||||
cairo_fill(cairo);
|
||||
}
|
||||
|
||||
cairo_move_to(cairo, x + padding * scale, text_y);
|
||||
cairo_set_source_u32(cairo, foreground_color);
|
||||
|
||||
pango_printf(cairo, font, scale, false, str);
|
||||
}
|
||||
|
||||
return x + text_width + 2 * padding * scale;
|
||||
}
|
||||
|
||||
void draw(cairo_t *cairo, int32_t width, int32_t height, int32_t scale) {
|
||||
|
||||
int32_t x = window_config.input_field;
|
||||
|
||||
int32_t item_padding = 10;
|
||||
|
||||
int32_t text_width, text_height;
|
||||
get_text_size(cairo, font, &text_width, &text_height, NULL, scale,
|
||||
false, "Aj");
|
||||
int32_t text_y = (height / 2.0) - (text_height / 2.0);
|
||||
|
||||
cairo_set_source_u32(cairo, color_bg);
|
||||
cairo_paint(cairo);
|
||||
|
||||
if (prompt) {
|
||||
x = draw_text(cairo, width, height, prompt, 0, scale, color_prompt_fg,
|
||||
color_prompt_bg, 6);
|
||||
window_config.input_field = x;
|
||||
} else {
|
||||
window_config.input_field = 0;
|
||||
}
|
||||
|
||||
cairo_set_source_u32(cairo, color_input_bg);
|
||||
cairo_rectangle(cairo, window_config.input_field, 0, 300 * scale, height);
|
||||
cairo_fill(cairo);
|
||||
|
||||
draw_text(cairo, width, height, text, x, scale, color_input_fg, 0, 6);
|
||||
|
||||
{
|
||||
/* draw cursor */
|
||||
memset(text_, 0, BUFSIZ);
|
||||
strncpy(text_, text, cursor);
|
||||
int32_t text_width, text_height;
|
||||
get_text_size(cairo, font, &text_width, &text_height, NULL, scale,
|
||||
false, text_);
|
||||
/* int32_t text_y = (height / 2.0) - (text_height / 2.0); */
|
||||
int32_t padding = 6 * scale;
|
||||
cairo_rectangle(cairo, x + padding + text_width, text_y,
|
||||
scale, text_height);
|
||||
cairo_fill(cairo);
|
||||
}
|
||||
|
||||
x += 300 * scale;
|
||||
|
||||
/* Scroll indicator will be drawn later if required. */
|
||||
int32_t scroll_indicator_pos = x;
|
||||
x += 20 * scale;
|
||||
|
||||
if (matches) {
|
||||
/* draw matches */
|
||||
Item *item;
|
||||
/* for (item = matches; item; item = item->right) { */
|
||||
/* if (item->width == -1) { */
|
||||
/* get_text_size(cairo, font, &item->width, NULL, NULL, scale, */
|
||||
/* false, item->text); */
|
||||
/* item->width += item_padding; */
|
||||
/* /\* printf("%d ", item->width); *\/ */
|
||||
/* } */
|
||||
/* } */
|
||||
|
||||
/* /\* Figure out if we need to scroll. *\/ */
|
||||
/* int32_t item_pos = x; */
|
||||
/* bool found = false; */
|
||||
/* rightmost = NULL; */
|
||||
/* for (item = leftmost; item; item = item->right) { */
|
||||
/* item_pos += item->width; */
|
||||
/* if (item_pos >= (width - x - 80 * scale)) { */
|
||||
/* rightmost = item->left; */
|
||||
/* printf("rightmost: %s\n", item->left->text); */
|
||||
/* found = true; */
|
||||
/* break; */
|
||||
/* } */
|
||||
/* } */
|
||||
|
||||
for (item = matches; item; item = item->right) {
|
||||
uint32_t bg_color = sel == item ? color_selected_bg : color_bg;
|
||||
uint32_t fg_color = sel == item ? color_selected_fg : color_fg;
|
||||
if (x < width) {
|
||||
/* x = draw_text(cairo, width - 20 * scale, height, item->text, */
|
||||
/* x, scale, fg_color, bg_color, item_padding); */
|
||||
x = draw_text(cairo, width - 20 * scale, height, item->text,
|
||||
x, scale, fg_color, bg_color, item_padding);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (leftmost != matches) {
|
||||
cairo_move_to(cairo, scroll_indicator_pos, text_y);
|
||||
pango_printf(cairo, font, scale, false, "<");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t parse_color(char *str) {
|
||||
if (!str) eprintf("NULL as color value\n");
|
||||
|
||||
size_t len = strnlen(str, BUFSIZ);
|
||||
|
||||
if ((len != 7 && len != 9) || str[0] != '#')
|
||||
eprintf("Color format must be '#rrggbb[aa]'\n");
|
||||
|
||||
uint32_t _val = strtol(&str[1], NULL, 16);
|
||||
|
||||
uint32_t color = 0x000000ff;
|
||||
if (len == 9) /* Alpha specified */
|
||||
color = _val;
|
||||
else /* No alpha specified, assume full opacity */
|
||||
color = (_val << 8) + 0xff;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv) {
|
||||
int i;
|
||||
|
||||
progname = "dmenu";
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "-v") || !strcmp(argv[1], "--version")) {
|
||||
fputs("dmenu-wl-" VERSION
|
||||
", © 2006-2018 dmenu engineers, see LICENSE for details\n",
|
||||
stdout);
|
||||
exit(EXIT_SUCCESS);
|
||||
} else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--bottom"))
|
||||
show_in_bottom = true;
|
||||
else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--echo"))
|
||||
message = true;
|
||||
else if (!strcmp(argv[i], "-ec") || !strcmp(argv[i], "--echo-centre"))
|
||||
message = true, messageposition = CENTRE;
|
||||
else if (!strcmp(argv[i], "-er") || !strcmp(argv[i], "--echo-right"))
|
||||
message = true, messageposition = RIGHT;
|
||||
else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--insensitive"))
|
||||
fstrncmp = strncasecmp;
|
||||
else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--return-early"))
|
||||
returnearly = true;
|
||||
else if (i == argc - 1) {
|
||||
printf("2\n");
|
||||
usage();
|
||||
|
||||
}
|
||||
/* opts that need 1 arg */
|
||||
else if (!strcmp(argv[i], "-et") || !strcmp(argv[i], "--echo-timeout"))
|
||||
timeout = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height"))
|
||||
panel_height = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--lines"))
|
||||
lines = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--monitor")) {
|
||||
++i;
|
||||
bool is_num = true;
|
||||
for (int j = 0; j < strlen(argv[i]); ++j) {
|
||||
if (!isdigit(argv[i][j])) {
|
||||
is_num = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_num) {
|
||||
selected_monitor = atoi(argv[i]);
|
||||
} else {
|
||||
selected_monitor = -1;
|
||||
selected_monitor_name = argv[i];
|
||||
}
|
||||
}
|
||||
else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--prompt"))
|
||||
prompt = argv[++i];
|
||||
else if (!strcmp(argv[i], "-po") || !strcmp(argv[i], "--prompt-only"))
|
||||
prompt = argv[++i], nostdin = true;
|
||||
else if (!strcmp(argv[i], "-fn") || !strcmp(argv[i], "--font-name"))
|
||||
font = argv[++i];
|
||||
else if (!strcmp(argv[i], "-nb") || !strcmp(argv[i], "--normal-background"))
|
||||
color_bg = color_input_bg = parse_color(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-nf") || !strcmp(argv[i], "--normal-foreground"))
|
||||
color_fg = color_input_fg = parse_color(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-sb") ||
|
||||
!strcmp(argv[i], "--selected-background"))
|
||||
color_prompt_bg = color_selected_bg = parse_color(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-sf") ||
|
||||
!strcmp(argv[i], "--selected-foreground"))
|
||||
color_prompt_fg = color_selected_fg = parse_color(argv[++i]);
|
||||
else {
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
signal(SIGALRM, alarmhandler);
|
||||
alarm(timeout);
|
||||
}
|
||||
if(!nostdin) {
|
||||
readstdin();
|
||||
}
|
||||
|
||||
struct dmenu_panel dmenu;
|
||||
dmenu.selected_monitor = selected_monitor;
|
||||
dmenu.selected_monitor_name = selected_monitor_name;
|
||||
dmenu_init_panel(&dmenu, panel_height, show_in_bottom);
|
||||
|
||||
|
||||
dmenu.on_keyevent = keypress;
|
||||
dmenu.on_keyrepeat = keyrepeat;
|
||||
dmenu.draw = draw;
|
||||
match();
|
||||
|
||||
struct monitor_info *monitor = dmenu.monitor;
|
||||
|
||||
double factor = monitor->scale / ((double)monitor->physical_width / monitor->logical_width);
|
||||
|
||||
window_config.height =round_to_int(dmenu.height / ((double)monitor->physical_width
|
||||
/ monitor->logical_width));
|
||||
window_config.height *= monitor->scale;
|
||||
|
||||
window_config.width = round_to_int(monitor->physical_width * factor);
|
||||
get_text_size(dmenu.surface.cairo, font, NULL, &window_config.text_height,
|
||||
NULL, monitor->scale, false, "Aj");
|
||||
window_config.text_y = (window_config.height / 2.0) - (window_config.text_height / 2.0);
|
||||
|
||||
|
||||
dmenu_show(&dmenu);
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
void
|
||||
appenditem(Item *item, Item **list, Item **last) {
|
||||
if(!*last)
|
||||
*list = item;
|
||||
else
|
||||
(*last)->right = item;
|
||||
item->left = *last;
|
||||
item->right = NULL;
|
||||
*last = item;
|
||||
}
|
||||
|
||||
char *
|
||||
fstrstr(const char *s, const char *sub) {
|
||||
size_t len;
|
||||
|
||||
for(len = strlen(sub); *s; s++)
|
||||
if(!fstrncmp(s, sub, len))
|
||||
return (char *)s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
match(void) {
|
||||
size_t len;
|
||||
Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
|
||||
|
||||
rightmost = leftmost = NULL;
|
||||
len = strlen(text);
|
||||
matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
|
||||
for(item = items; item; item = item->next)
|
||||
if(!fstrncmp(text, item->text, len + 1)) {
|
||||
appenditem(item, &lexact, &exactend);
|
||||
}
|
||||
else if(!fstrncmp(text, item->text, len)) {
|
||||
appenditem(item, &lprefix, &prefixend);
|
||||
}
|
||||
else if(fstrstr(item->text, text)) {
|
||||
appenditem(item, &lsubstr, &substrend);
|
||||
}
|
||||
|
||||
if(lexact) {
|
||||
matches = lexact;
|
||||
itemend = exactend;
|
||||
}
|
||||
if(lprefix) {
|
||||
if(itemend) {
|
||||
itemend->right = lprefix;
|
||||
lprefix->left = itemend;
|
||||
}
|
||||
else
|
||||
matches = lprefix;
|
||||
itemend = prefixend;
|
||||
}
|
||||
if(lsubstr) {
|
||||
if(itemend) {
|
||||
itemend->right = lsubstr;
|
||||
lsubstr->left = itemend;
|
||||
}
|
||||
else
|
||||
matches = lsubstr;
|
||||
}
|
||||
curr = prev = next = sel = matches;
|
||||
/* calcoffsets(); */
|
||||
|
||||
leftmost = matches;
|
||||
|
||||
if(returnearly && !curr->right) {
|
||||
/* handle_return(curr->text); */
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
nextrune(int incr) {
|
||||
size_t n, len;
|
||||
|
||||
len = strlen(text);
|
||||
for(n = cursor + incr; n >= 0 && n < len && (text[n] & 0xc0) == 0x80; n += incr);
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
readstdin(void) {
|
||||
char buf[sizeof text], *p;
|
||||
Item *item, **end;
|
||||
|
||||
for(end = &items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) {
|
||||
itemcount++;
|
||||
|
||||
if((p = strchr(buf, '\n'))) {
|
||||
*p = '\0';
|
||||
}
|
||||
if(!(item = malloc(sizeof *item))) {
|
||||
eprintf("cannot malloc %u bytes\n", sizeof *item);
|
||||
}
|
||||
item->width = -1;
|
||||
if(!(item->text = strdup(buf))) {
|
||||
eprintf("cannot strdup %u bytes\n", strlen(buf)+1);
|
||||
}
|
||||
item->next = item->left = item->right = NULL;
|
||||
/* inputw = MAX(inputw, textw(dc, item->text)); */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
alarmhandler(int signum) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
usage(void) {
|
||||
printf("Usage: dmenu [OPTION]...\n");
|
||||
printf("Display newline-separated input stdin as a menubar\n");
|
||||
printf("\n");
|
||||
printf(" -e, --echo display text from stdin with no user\n");
|
||||
printf(" interaction\n");
|
||||
printf(" -ec, --echo-centre same as -e but align text centrally\n");
|
||||
printf(" -er, --echo-right same as -e but align text right\n");
|
||||
printf(" -et, --echo-timeout SECS close the message after SEC seconds\n");
|
||||
printf(" when using -e, -ec, or -er\n");
|
||||
printf(" -b, --bottom dmenu appears at the bottom of the screen\n");
|
||||
printf(" -h, --height N set dmenu to be N pixels high\n");
|
||||
printf(" -i, --insensitive dmenu matches menu items case insensitively\n");
|
||||
printf(" -l, --lines LINES dmenu lists items vertically, within the\n");
|
||||
printf(" given number of lines\n");
|
||||
printf(" -m, --monitor MONITOR dmenu appears on the given Xinerama screen\n");
|
||||
printf(" (does nothing on wayland, supported for)\n");
|
||||
printf(" compatibility with dmenu.\n");
|
||||
printf(" -p, --prompt PROMPT prompt to be displayed to the left of the\n");
|
||||
printf(" input field\n");
|
||||
printf(" -po, --prompt-only PROMPT same as -p but don't wait for stdin\n");
|
||||
printf(" useful for a prompt with no menu\n");
|
||||
printf(" -r, --return-early return as soon as a single match is found\n");
|
||||
printf(" -fn, --font-name FONT font or font set to be used\n");
|
||||
printf(" -nb, --normal-background COLOR normal background color\n");
|
||||
printf(" #RRGGBB and #RRGGBBAA supported\n");
|
||||
printf(" -nf, --normal-foreground COLOR normal foreground color\n");
|
||||
printf(" -sb, --selected-background COLOR selected background color\n");
|
||||
printf(" -sf, --selected-foreground COLOR selected foreground color\n");
|
||||
printf(" -v, --version display version information\n");
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
102
dmenu_path.c
Normal file
102
dmenu_path.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* See LICENSE file for copyright and license details. */
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define CACHE ".dmenu_cache"
|
||||
|
||||
static void die(const char *s);
|
||||
static int qstrcmp(const void *a, const void *b);
|
||||
static void scan(void);
|
||||
static int uptodate(void);
|
||||
|
||||
static char **items = NULL;
|
||||
static const char *home, *path;
|
||||
|
||||
int
|
||||
main(void) {
|
||||
if(!(home = getenv("HOME")))
|
||||
die("no $HOME");
|
||||
if(!(path = getenv("PATH")))
|
||||
die("no $PATH");
|
||||
if(chdir(home) < 0)
|
||||
die("chdir failed");
|
||||
if(uptodate()) {
|
||||
execlp("cat", "cat", CACHE, NULL);
|
||||
die("exec failed");
|
||||
}
|
||||
scan();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
die(const char *s) {
|
||||
fprintf(stderr, "dmenu_path: %s\n", s);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int
|
||||
qstrcmp(const void *a, const void *b) {
|
||||
return strcmp(*(const char **)a, *(const char **)b);
|
||||
}
|
||||
|
||||
void
|
||||
scan(void) {
|
||||
char buf[PATH_MAX];
|
||||
char *dir, *p;
|
||||
size_t i, count;
|
||||
struct dirent *ent;
|
||||
DIR *dp;
|
||||
FILE *cache;
|
||||
|
||||
count = 0;
|
||||
if(!(p = strdup(path)))
|
||||
die("strdup failed");
|
||||
for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":")) {
|
||||
if(!(dp = opendir(dir)))
|
||||
continue;
|
||||
while((ent = readdir(dp))) {
|
||||
snprintf(buf, sizeof buf, "%s/%s", dir, ent->d_name);
|
||||
if(ent->d_name[0] == '.' || access(buf, X_OK) < 0)
|
||||
continue;
|
||||
if(!(items = realloc(items, ++count * sizeof *items)))
|
||||
die("malloc failed");
|
||||
if(!(items[count-1] = strdup(ent->d_name)))
|
||||
die("strdup failed");
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
qsort(items, count, sizeof *items, qstrcmp);
|
||||
if(!(cache = fopen(CACHE, "w")))
|
||||
die("open failed");
|
||||
for(i = 0; i < count; i++) {
|
||||
if(i > 0 && !strcmp(items[i], items[i-1]))
|
||||
continue;
|
||||
fprintf(cache, "%s\n", items[i]);
|
||||
fprintf(stdout, "%s\n", items[i]);
|
||||
}
|
||||
fclose(cache);
|
||||
free(p);
|
||||
}
|
||||
|
||||
int
|
||||
uptodate(void) {
|
||||
char *dir, *p;
|
||||
time_t mtime;
|
||||
struct stat st;
|
||||
|
||||
if(stat(CACHE, &st) < 0)
|
||||
return 0;
|
||||
mtime = st.st_mtime;
|
||||
if(!(p = strdup(path)))
|
||||
die("strdup failed");
|
||||
for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":"))
|
||||
if(!stat(dir, &st) && st.st_mtime > mtime)
|
||||
return 0;
|
||||
free(p);
|
||||
return 1;
|
||||
}
|
659
draw.c
Normal file
659
draw.c
Normal file
|
@ -0,0 +1,659 @@
|
|||
/* See LICENSE file for copyright and license details. */
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include "draw.h"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
#include "xdg-shell-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
|
||||
static const char overflow[] = "[buffer overflow]";
|
||||
static const int max_chars = 16384;
|
||||
|
||||
struct monitor_info *monitors[16] = {0};
|
||||
static int n_monitors = 0;
|
||||
|
||||
int32_t round_to_int(double val) {
|
||||
return (int32_t)(val + 0.5);
|
||||
}
|
||||
|
||||
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[] = "/dmenu-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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
|
||||
const char *text, double scale, bool markup) {
|
||||
PangoLayout *layout = pango_cairo_create_layout(cairo);
|
||||
PangoAttrList *attrs;
|
||||
if (markup) {
|
||||
char *buf;
|
||||
GError *error = NULL;
|
||||
if (pango_parse_markup(text, -1, 0, &attrs, &buf, NULL, &error)) {
|
||||
pango_layout_set_text(layout, buf, -1);
|
||||
free(buf);
|
||||
} else {
|
||||
/* wlr_log(WLR_ERROR, "pango_parse_markup '%s' -> error %s", text, */
|
||||
/* error->message); */
|
||||
g_error_free(error);
|
||||
markup = false; // fallback to plain text
|
||||
}
|
||||
}
|
||||
if (!markup) {
|
||||
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, bool markup, const char *fmt, ...) {
|
||||
char buf[max_chars];
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (vsnprintf(buf, sizeof(buf), fmt, args) >= max_chars) {
|
||||
strcpy(&buf[sizeof(buf) - sizeof(overflow)], overflow);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
|
||||
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);
|
||||
}
|
||||
|
||||
void pango_printf(cairo_t *cairo, const char *font,
|
||||
double scale, bool markup, const char *fmt, ...) {
|
||||
char buf[max_chars];
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (vsnprintf(buf, sizeof(buf), fmt, args) >= max_chars) {
|
||||
strcpy(&buf[sizeof(buf) - sizeof(overflow)], overflow);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
void dmenu_draw(struct dmenu_panel *panel) {
|
||||
|
||||
cairo_t *cairo = panel->surface.cairo;
|
||||
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
||||
cairo_paint(cairo);
|
||||
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
|
||||
struct monitor_info *m = panel->monitor;
|
||||
double factor = m->scale / ((double)m->physical_width
|
||||
/ m->logical_width);
|
||||
|
||||
int32_t width = round_to_int(m->physical_width * factor);
|
||||
int32_t height = panel->height * m->scale;
|
||||
|
||||
if (panel->draw) {
|
||||
panel->draw(cairo, width, height, m->scale);
|
||||
}
|
||||
wl_surface_attach(panel->surface.surface, panel->surface.buffer, 0, 0);
|
||||
zwlr_layer_surface_v1_set_keyboard_interactivity(panel->surface.layer_surface, true);
|
||||
wl_surface_damage(panel->surface.surface, 0, 0, m->logical_width, panel->height);
|
||||
wl_surface_commit(panel->surface.surface);
|
||||
|
||||
}
|
||||
|
||||
cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) {
|
||||
switch (subpixel) {
|
||||
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB:
|
||||
return CAIRO_SUBPIXEL_ORDER_RGB;
|
||||
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR:
|
||||
return CAIRO_SUBPIXEL_ORDER_BGR;
|
||||
case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB:
|
||||
return CAIRO_SUBPIXEL_ORDER_VRGB;
|
||||
case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR:
|
||||
return CAIRO_SUBPIXEL_ORDER_VBGR;
|
||||
default:
|
||||
return CAIRO_SUBPIXEL_ORDER_DEFAULT;
|
||||
}
|
||||
return CAIRO_SUBPIXEL_ORDER_DEFAULT;
|
||||
}
|
||||
|
||||
void
|
||||
eprintf(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "%s: ", progname);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void layer_surface_configure(void *data,
|
||||
struct zwlr_layer_surface_v1 *surface,
|
||||
uint32_t serial, uint32_t width, uint32_t height) {
|
||||
zwlr_layer_surface_v1_ack_configure(surface, serial);
|
||||
}
|
||||
|
||||
static void layer_surface_closed(void *_data,
|
||||
struct zwlr_layer_surface_v1 *surface) {
|
||||
}
|
||||
|
||||
struct zwlr_layer_surface_v1_listener layer_surface_listener = {
|
||||
.configure = layer_surface_configure,
|
||||
.closed = layer_surface_closed,
|
||||
};
|
||||
|
||||
|
||||
int32_t subpixel;
|
||||
int32_t physical_height;
|
||||
|
||||
|
||||
static void output_geometry(void *data, struct wl_output *wl_output, int32_t x,
|
||||
int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel,
|
||||
const char *make, const char *model, int32_t transform) {
|
||||
struct monitor_info *monitor = data;
|
||||
monitor->subpixel = subpixel;
|
||||
}
|
||||
|
||||
static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
|
||||
int32_t width, int32_t height, int32_t refresh) {
|
||||
struct monitor_info *monitor = data;
|
||||
monitor->physical_width = width;
|
||||
monitor->physical_height = height;
|
||||
}
|
||||
|
||||
static void output_done(void *data, struct wl_output *wl_output) {
|
||||
}
|
||||
|
||||
static void output_scale(void *data, struct wl_output *wl_output,
|
||||
int32_t factor) {
|
||||
struct monitor_info *monitor = data;
|
||||
monitor->scale = factor;
|
||||
}
|
||||
|
||||
struct wl_output_listener output_listener = {
|
||||
.geometry = output_geometry,
|
||||
.mode = output_mode,
|
||||
.done = output_done,
|
||||
.scale = output_scale,
|
||||
};
|
||||
static void xdg_output_handle_logical_position(void *data,
|
||||
struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
|
||||
// Who cares
|
||||
}
|
||||
|
||||
static void xdg_output_handle_logical_size(void *data,
|
||||
struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
|
||||
struct monitor_info *monitor = data;
|
||||
|
||||
monitor->logical_width = width;
|
||||
monitor->logical_height = height;
|
||||
}
|
||||
|
||||
static void xdg_output_handle_done(void *data,
|
||||
struct zxdg_output_v1 *xdg_output) {
|
||||
// Who cares
|
||||
}
|
||||
|
||||
static void xdg_output_handle_name(void *data,
|
||||
struct zxdg_output_v1 *xdg_output, const char *name) {
|
||||
struct monitor_info *monitor = data;
|
||||
strncpy(monitor->name, name, MAX_MONITOR_NAME_LEN);
|
||||
}
|
||||
|
||||
static void xdg_output_handle_description(void *data,
|
||||
struct zxdg_output_v1 *xdg_output, const char *description) {
|
||||
}
|
||||
|
||||
struct zxdg_output_v1_listener xdg_output_listener = {
|
||||
.logical_position = xdg_output_handle_logical_position,
|
||||
.logical_size = xdg_output_handle_logical_size,
|
||||
.done = xdg_output_handle_done,
|
||||
.name = xdg_output_handle_name,
|
||||
.description = xdg_output_handle_description,
|
||||
};
|
||||
|
||||
static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
||||
uint32_t format, int32_t fd, uint32_t size) {
|
||||
|
||||
struct dmenu_panel *panel = data;
|
||||
|
||||
panel->keyboard.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
|
||||
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
||||
close(fd);
|
||||
exit(1);
|
||||
}
|
||||
char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (map_shm == MAP_FAILED) {
|
||||
close(fd);
|
||||
exit(1);
|
||||
}
|
||||
panel->keyboard.xkb_keymap = xkb_keymap_new_from_string(
|
||||
panel->keyboard.xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
|
||||
munmap(map_shm, size);
|
||||
close(fd);
|
||||
|
||||
panel->keyboard.xkb_state = xkb_state_new(panel->keyboard.xkb_keymap);
|
||||
}
|
||||
|
||||
static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
|
||||
uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
|
||||
// Who cares
|
||||
}
|
||||
|
||||
static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
|
||||
uint32_t serial, struct wl_surface *surface) {
|
||||
// Who cares
|
||||
}
|
||||
|
||||
static void keyboard_repeat(struct dmenu_panel *panel) {
|
||||
if (panel->on_keyrepeat) {
|
||||
panel->on_keyrepeat(panel);
|
||||
}
|
||||
|
||||
struct itimerspec spec = { 0 };
|
||||
spec.it_value.tv_sec = panel->repeat_period / 1000;
|
||||
spec.it_value.tv_nsec = (panel->repeat_period % 1000) * 1000000l;
|
||||
timerfd_settime(panel->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 dmenu_panel *panel = data;
|
||||
|
||||
enum wl_keyboard_key_state key_state = _key_state;
|
||||
xkb_keysym_t sym = xkb_state_key_get_one_sym(panel->keyboard.xkb_state, key + 8);
|
||||
if (panel->on_keyevent) {
|
||||
panel->on_keyevent(panel, key_state, sym, panel->keyboard.control,
|
||||
panel->keyboard.shift);
|
||||
|
||||
if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && panel->repeat_period >= 0) {
|
||||
panel->repeat_key_state = key_state;
|
||||
panel->repeat_sym = sym;
|
||||
|
||||
struct itimerspec spec = { 0 };
|
||||
spec.it_value.tv_sec = panel->repeat_delay / 1000;
|
||||
spec.it_value.tv_nsec = (panel->repeat_delay % 1000) * 1000000l;
|
||||
timerfd_settime(panel->repeat_timer, 0, &spec, NULL);
|
||||
} else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) {
|
||||
struct itimerspec spec = { 0 };
|
||||
timerfd_settime(panel->repeat_timer, 0, &spec, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
|
||||
int32_t rate, int32_t delay) {
|
||||
struct dmenu_panel *panel = data;
|
||||
panel->repeat_delay = delay;
|
||||
if (rate > 0) {
|
||||
panel->repeat_period = 1000 / rate;
|
||||
} else {
|
||||
panel->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 dmenu_panel *panel = data;
|
||||
xkb_state_update_mask(panel->keyboard.xkb_state,
|
||||
mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
panel->keyboard.control = xkb_state_mod_name_is_active(panel->keyboard.xkb_state,
|
||||
XKB_MOD_NAME_CTRL,
|
||||
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
|
||||
panel->keyboard.shift = xkb_state_mod_name_is_active(panel->keyboard.xkb_state,
|
||||
XKB_MOD_NAME_SHIFT,
|
||||
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
|
||||
}
|
||||
static const struct wl_keyboard_listener keyboard_listener = {
|
||||
.keymap = keyboard_keymap,
|
||||
.enter = keyboard_enter,
|
||||
.leave = keyboard_leave,
|
||||
.key = keyboard_key,
|
||||
.modifiers = keyboard_modifiers,
|
||||
.repeat_info = keyboard_repeat_info,
|
||||
};
|
||||
|
||||
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
|
||||
enum wl_seat_capability caps) {
|
||||
struct dmenu_panel *panel = data;
|
||||
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
||||
panel->keyboard.kbd = wl_seat_get_keyboard (panel->display_info.seat);
|
||||
wl_keyboard_add_listener (panel->keyboard.kbd, &keyboard_listener, panel);
|
||||
}
|
||||
}
|
||||
static void seat_handle_name(void *data, struct wl_seat *wl_seat,
|
||||
const char *name) {
|
||||
// Who cares
|
||||
}
|
||||
|
||||
const struct wl_seat_listener seat_listener = {
|
||||
.capabilities = seat_handle_capabilities,
|
||||
.name = seat_handle_name,
|
||||
};
|
||||
|
||||
void set_monitor_xdg_output(struct dmenu_panel *panel, struct monitor_info *monitor){
|
||||
monitor->xdg_output =
|
||||
zxdg_output_manager_v1_get_xdg_output(panel->display_info.xdg_output_manager,
|
||||
monitor->output);
|
||||
zxdg_output_v1_add_listener(monitor->xdg_output, &xdg_output_listener,
|
||||
monitor);
|
||||
}
|
||||
|
||||
static void handle_global(void *data, struct wl_registry *registry,
|
||||
uint32_t name, const char *interface, uint32_t version) {
|
||||
struct dmenu_panel *panel = data;
|
||||
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
panel->display_info.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);
|
||||
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
panel->display_info.seat = wl_registry_bind (registry, name, &wl_seat_interface, 4);
|
||||
wl_seat_add_listener (panel->display_info.seat, &seat_listener, panel);
|
||||
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
panel->surface.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||
} else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
|
||||
if(n_monitors >= 16) return;
|
||||
|
||||
monitors[n_monitors] = malloc(sizeof(struct monitor_info));
|
||||
monitors[n_monitors]->panel = panel;
|
||||
memset(monitors[n_monitors]->name, 0, MAX_MONITOR_NAME_LEN);
|
||||
monitors[n_monitors]->output = wl_registry_bind(registry, name, &wl_output_interface, 2);
|
||||
|
||||
wl_output_add_listener(monitors[n_monitors]->output, &output_listener,
|
||||
monitors[n_monitors]);
|
||||
|
||||
if (panel->display_info.xdg_output_manager != NULL) {
|
||||
set_monitor_xdg_output(panel, monitors[n_monitors]);
|
||||
}
|
||||
n_monitors++;
|
||||
|
||||
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
||||
panel->surface.layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
|
||||
|
||||
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||
panel->display_info.xdg_output_manager = wl_registry_bind(registry, name,
|
||||
&zxdg_output_manager_v1_interface, 2);
|
||||
|
||||
for(int m = 0; m < n_monitors; m++){
|
||||
set_monitor_xdg_output(panel, monitors[m]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data, struct wl_registry *registry,
|
||||
uint32_t name) {
|
||||
}
|
||||
|
||||
static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
|
||||
}
|
||||
|
||||
static const struct wl_buffer_listener buffer_listener = {
|
||||
.release = buffer_release
|
||||
};
|
||||
|
||||
struct wl_buffer *dmenu_create_buffer(struct dmenu_panel *panel) {
|
||||
struct monitor_info *m = panel->monitor;
|
||||
double factor = m->scale / ((double)m->physical_width
|
||||
/ m->logical_width);
|
||||
|
||||
int32_t width = round_to_int(m->physical_width * factor);
|
||||
int32_t height = round_to_int(panel->height * m->scale);
|
||||
|
||||
int stride = width * 4;
|
||||
int size = stride * height;
|
||||
|
||||
int fd = create_shm_file(size);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
panel->surface.shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (panel->surface.shm_data == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap failed: %m\n");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(panel->surface.shm, fd, size);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
|
||||
stride, WL_SHM_FORMAT_ARGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
|
||||
wl_buffer_add_listener(buffer, &buffer_listener, panel);
|
||||
|
||||
cairo_surface_t *s = cairo_image_surface_create_for_data(panel->surface.shm_data,
|
||||
CAIRO_FORMAT_ARGB32,
|
||||
width, height, width * 4);
|
||||
panel->surface.cairo = cairo_create(s);
|
||||
cairo_set_antialias(panel->surface.cairo, CAIRO_ANTIALIAS_BEST);
|
||||
cairo_font_options_t *fo = cairo_font_options_create();
|
||||
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
|
||||
cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
|
||||
cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(m->subpixel));
|
||||
cairo_set_font_options(panel->surface.cairo, fo);
|
||||
cairo_font_options_destroy(fo);
|
||||
cairo_save(panel->surface.cairo);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = handle_global,
|
||||
.global_remove = handle_global_remove,
|
||||
};
|
||||
|
||||
|
||||
void dmenu_init_panel(struct dmenu_panel *panel, int32_t height, bool bottom) {
|
||||
if(!setlocale(LC_CTYPE, ""))
|
||||
weprintf("no locale support\n");
|
||||
|
||||