/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #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); }