├── 91menu.1 ├── 91menu.c ├── 91menu.png ├── LICENSE ├── Makefile ├── README.md ├── config.def.h ├── drw.c ├── drw.h ├── utf8.c ├── utf8.h ├── util.c └── util.h /91menu.1: -------------------------------------------------------------------------------- 1 | .TH 91MENU 1 91menu\-VERSION 2 | .SH NAME 3 | 91menu \- graphical mouse-driven menu 4 | .SH SYNOPSIS 5 | .B 91menu 6 | .RB [ \-clmruv ] 7 | .RB [ \-f 8 | .IR file ] 9 | .RB [ \-bd 10 | .IR color ] 11 | .RB [ \-bg 12 | .IR color ] 13 | .RB [ \-fg 14 | .IR color ] 15 | .RB [ \-g 16 | .IR geometry ] 17 | .RB [ \-nv 18 | .IR color ] 19 | .RB [ \-sl 20 | .IR color ] 21 | .RB [ \-ft 22 | .IR font ] 23 | .SH OPTIONS 24 | .TP 25 | .B \-c 26 | Aligns the items to the center of the window. 27 | .TP 28 | .B \-l 29 | Aligns the items to the left side of the window. 30 | .TP 31 | .B \-m 32 | Grabs every mouse buttons specified in config.h outside of the menu window. 33 | .TP 34 | .B \-r 35 | Aligns the items to the right side of the window. 36 | .TP 37 | .B \-u 38 | Displays the 91menu usage message. 39 | .TP 40 | .B \-v 41 | Displays the 91menu version. 42 | .TP 43 | .B \-f file 44 | Specifies the file where 91menu should read the last selected item and write the selected one. 45 | .TP 46 | .B \-bd color 47 | Specifies the border color of the menu. 48 | .TP 49 | .B \-bg color 50 | Specifies the background color of the menu. 51 | .TP 52 | .B \-fg color 53 | Specifies the foreground color of the menu. 54 | .TP 55 | .B \-g geometry 56 | Specifies the menu geometry. The form is [=][{xX}][{+-}{+-}]. See XParseGeometry(3) 57 | .TP 58 | .B \-nv color 59 | Specifies the inverted text color of the menu. 60 | .TP 61 | .B \-sl color 62 | Specifies the selection color of the menu. 63 | .SH DESCRIPTION 64 | .B 91menu 65 | is a graphical menu, meant to be a more versatile alternative to 9menu (to be more than a program launcher). The purpose of 91menu is to take a list of ordenated newline\-separated items from the sandard input, and display them verticaly in the same order, into a menu where items are selectable. 66 | 67 | An item can be selected by pressing any mouse buttons over it (see config.h for buttons). When an item is selected, it is printed on the standard output, 91menu terminates, and the selected item is written as "last selected item" to a file specified with the -f option (or by default in the file specified in config.h). The next time 91menu will be executed, it will popup with the last selected item under the mouse cursor. The file specified with the -f option (or config.h) is used for reading and writing, and the readed item is overwriten by the selected one. 68 | If some grabbed mouse buttons are pressed outside of the window, 91menu terminates, and nothing is printed on the standard output. 69 | .SH USAGE 70 | Unlike 9menu, 91menu only prints the selected item to standard output (instead of running a given command), this point makes 91menu way more flexible, and particulary usefull combined with a bunch of shell scripts. 71 | .SH SEE ALSO 72 | .IR 9menu(1) 73 | -------------------------------------------------------------------------------- /91menu.c: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "drw.h" 15 | #include "util.h" 16 | 17 | #define _POSIX_X_SOURCE 200809L 18 | #define _XOPEN_SOURCE 700 19 | 20 | enum { BD, BG, FG, NV, SL, COLORNB }; 21 | enum { LEFT, CENTER, RIGHT }; 22 | 23 | static void cleanup(void); 24 | static Fnt createfont(const char *, FcPattern *); 25 | static Clr *createscheme(const char *[], int); 26 | static void drawmenu(int); 27 | static int fetchlastsel(char *); 28 | static void getextw(char *, unsigned int, Fnt *, unsigned int *); 29 | static void grabbuttons(void); 30 | static void grabfocus(void); 31 | static int loadcolor(const char *, Clr *); 32 | static void readstdin(void); 33 | static int run(void); 34 | static int savelastsel(char *, char *); 35 | static void usage(void); 36 | static void winsetup(XWindowAttributes *); 37 | 38 | static Fnt font; 39 | static XIC xic; 40 | static int itemnb; 41 | static int screen; 42 | static Window win, root; 43 | static Display *dpy; 44 | static Drw *drw; 45 | static Item *items; 46 | static Clr *scheme; 47 | 48 | #include "config.h" 49 | 50 | int 51 | main(int argc, char *argv[]) 52 | { 53 | XWindowAttributes wa; 54 | int i, sel; 55 | 56 | for (i = 1; i < argc; i++) { 57 | if (!strcmp(argv[i], "-c")) 58 | textpos = CENTER; 59 | else if (!strcmp(argv[i], "-l")) 60 | textpos = LEFT; 61 | else if (!strcmp(argv[i], "-m")) 62 | grabmouse = 1; 63 | else if (!strcmp(argv[i], "-r")) 64 | textpos = RIGHT; 65 | else if (!strcmp(argv[i], "-u")) { 66 | usage(); 67 | exit(0); 68 | } else if (!strcmp(argv[i], "-v")) { 69 | puts("91menu-"VERSION); 70 | exit(0); 71 | } else if (i + 1 == argc) 72 | usage(); 73 | else if (!strcmp(argv[i], "-f")) 74 | lastselfile = argv[++i]; 75 | else if (!strcmp(argv[i], "-bd")) 76 | colornames[BD] = argv[++i]; 77 | else if (!strcmp(argv[i], "-bg")) 78 | colornames[BG] = argv[++i]; 79 | else if (!strcmp(argv[i], "-fg")) 80 | colornames[FG] = argv[++i]; 81 | else if (!strcmp(argv[i], "-g")) 82 | geometry = argv[++i]; 83 | else if (!strcmp(argv[i], "-nv")) 84 | colornames[NV] = argv[++i]; 85 | else if (!strcmp(argv[i], "-sl")) 86 | colornames[SL] = argv[++i]; 87 | else if (!strcmp(argv[i], "-ft")) 88 | fontname = argv[++i]; 89 | else 90 | usage(); 91 | } 92 | 93 | if (setlocale(LC_CTYPE, "") == NULL || !XSupportsLocale()) 94 | error("no locale support"); 95 | if ((dpy = XOpenDisplay(NULL)) == NULL) 96 | error("cannot open display"); 97 | screen = DefaultScreen(dpy); 98 | root = RootWindow(dpy, screen); 99 | if (!XGetWindowAttributes(dpy, root, &wa)) 100 | error("could not get window attributes: 0x%lx", root); 101 | drw = drw_create(dpy, root, screen, 0, 0, wa.width, wa.height); 102 | font = createfont(fontname, NULL); 103 | scheme = createscheme(colornames, COLORNB); 104 | readstdin(); 105 | winsetup(&wa); 106 | 107 | sel = run(); 108 | cleanup(); 109 | if (sel >= 0) { 110 | printf("%s\n", items[sel].text); 111 | if (savelastsel(lastselfile, items[sel].text)) 112 | warning("can't open %s for writing", lastselfile); 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | static void 119 | cleanup(void) 120 | { 121 | free(scheme); 122 | drw_free(drw); 123 | XSync(dpy, False); 124 | XCloseDisplay(dpy); 125 | } 126 | 127 | static Fnt 128 | createfont(const char *fname, FcPattern *fpattern) 129 | { 130 | Fnt f; 131 | FcBool iscolorfont; 132 | FcPattern *pattern = NULL; 133 | XftFont *xfont = NULL; 134 | 135 | if (fname) { 136 | if ((xfont = XftFontOpenName(dpy, screen, fname)) == NULL) 137 | error("could not load font from name: %s", fname); 138 | if ((pattern = FcNameParse((FcChar8 *) fname)) == NULL) 139 | error("could not load font from pattern: %s", fname); 140 | } else if (fpattern) { 141 | if ((xfont = XftFontOpenPattern(dpy, fpattern))) 142 | error("could not load font from pattern"); 143 | } else 144 | error("cannot load unspecified font"); 145 | /* avoid color fonts to be used */ 146 | if (FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscolorfont) == 147 | FcResultMatch && iscolorfont) { 148 | XftFontClose(dpy, xfont); 149 | error("could not load color fonts"); 150 | } 151 | f.xfont = xfont; 152 | f.pattern = pattern; 153 | return f; 154 | } 155 | 156 | static Clr * 157 | createscheme(const char *cnames[], int cnb) 158 | { 159 | int i; 160 | Clr *s = NULL; 161 | 162 | if (cnames == NULL || cnb < 0) 163 | error("color name: NULL pointer"); 164 | if ((s = calloc(cnb, sizeof(Clr))) == NULL) 165 | error("could not allocate color scheme"); 166 | for (i = 0; i < cnb; i++) { 167 | if (loadcolor(cnames[i], &s[i])) 168 | error("could not load color: %s\n", cnames[i]); 169 | } 170 | return s; 171 | } 172 | 173 | static void 174 | drawmenu(int nosel) 175 | { 176 | int i; 177 | int sel; 178 | int x, y; 179 | 180 | /* draw background */ 181 | drw_drawrect(drw, 0, 0, drw->w, drw->h, BG); 182 | /* draw selected item box */ 183 | if ((sel = drw_getpointersel(drw, itemnb)) >= 0 && !nosel) 184 | drw_drawrect(drw, 0, sel * (drw->h / itemnb), drw->w, drw->h / itemnb, SL); 185 | 186 | for (i = 0; i < itemnb; i++) { 187 | switch (textpos) { 188 | case LEFT: 189 | x = gappx; 190 | break; 191 | case CENTER: 192 | x = (drw->w - items[i].extw) / 2; 193 | break; 194 | case RIGHT: 195 | x = drw->w - items[i].extw - gappx; 196 | break; 197 | default: 198 | /* default = CENTER, mutes warning: x may be used uninitialized */ 199 | x = (drw->w - items[i].extw) / 2; 200 | } 201 | y = i * (drw->h / itemnb) + 0.5 * drw->font.xfont->ascent + 202 | (drw->h / itemnb - drw->font.xfont->descent) / 2; 203 | if (i == sel) 204 | drw_drawtext(drw, items[i].text, strlen(items[i].text), &font, &scheme[NV], x, y); 205 | else 206 | drw_drawtext(drw, items[i].text, strlen(items[i].text), &font, &scheme[FG], x, y); 207 | } 208 | drw_map(drw, win, 0, 0, drw->w, drw->h); 209 | } 210 | 211 | static int 212 | fetchlastsel(char *file) 213 | { 214 | char buf[BUFSIZ]; 215 | int i; 216 | FILE *fp; 217 | char *p; 218 | 219 | if ((fp = fopen(file, "r")) == NULL) 220 | return 0; 221 | if (fgets(buf, sizeof(buf), fp)) { 222 | fclose(fp); 223 | if ((p = strchr(buf, '\n'))!= NULL) 224 | *p = '\0'; 225 | for (i = 0; i < itemnb; i++) { 226 | if (!strcmp(items[i].text, buf)) 227 | return i; 228 | } 229 | } 230 | return 0; 231 | } 232 | 233 | static void 234 | getextw(char *str, unsigned int size, Fnt *font, unsigned int *extw) 235 | { 236 | XGlyphInfo ext; 237 | 238 | if (font->xfont == NULL || str == NULL) 239 | return; 240 | XftTextExtentsUtf8(dpy, font->xfont, (XftChar8 *) str, size, &ext); 241 | if (extw != NULL) 242 | *extw = ext.xOff; 243 | } 244 | 245 | static void 246 | grabbuttons(void) 247 | { 248 | int i; 249 | 250 | for (i = 1; i - 1 < sizeof(buttons) / sizeof(unsigned int); i++) { 251 | if (buttons[i - 1]) { 252 | XUngrabButton(dpy, i, AnyModifier, root); 253 | XGrabButton(dpy, i, AnyModifier, root, True, 254 | ButtonPressMask | ButtonReleaseMask, 255 | GrabModeAsync, GrabModeSync, None, None); 256 | } 257 | } 258 | } 259 | 260 | static void 261 | grabfocus(void) 262 | { 263 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; 264 | Window focuswin; 265 | int i, revertwin; 266 | 267 | for (i = 0; i < 100; ++i) { 268 | XGetInputFocus(dpy, &focuswin, &revertwin); 269 | if (focuswin == win) 270 | return; 271 | XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 272 | nanosleep(&ts, NULL); 273 | } 274 | error("cannot grab focus"); 275 | } 276 | 277 | static int 278 | loadcolor(const char *cname, Clr *c) 279 | { 280 | if (cname == NULL || c == NULL) 281 | return 1; 282 | if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), 283 | DefaultColormap(dpy, screen), cname, c)) 284 | error("could not allocate color: %s", cname); 285 | return 0; 286 | } 287 | 288 | static void 289 | readstdin(void) 290 | { 291 | char buf[BUFSIZ]; 292 | unsigned int extw; 293 | int i; 294 | char *p = NULL; 295 | 296 | for (i = 0; fgets(buf, sizeof(buf), stdin); i++) { 297 | items = erealloc(items, (i + 1) * sizeof(Item)); 298 | if ((p = strchr(buf, '\n'))!= NULL) 299 | *p = '\0'; 300 | if ((items[i].text = strdup(buf)) == NULL) 301 | error("strdup"); 302 | extw = 0; 303 | getextw(items[i].text, strlen(items[i].text), &font, &extw); 304 | items[i].extw = extw; 305 | } 306 | itemnb = i; 307 | } 308 | 309 | static int 310 | run(void) 311 | { 312 | XEvent ev; 313 | unsigned int button; 314 | 315 | drw_setscheme(drw, scheme); 316 | drw_setfont(drw, font); 317 | if (grabmouse) 318 | grabbuttons(); 319 | XSync(dpy, False); 320 | while (!XNextEvent(dpy, &ev)) { 321 | if (XFilterEvent(&ev, win)) 322 | continue; 323 | switch (ev.type) { 324 | case ButtonPress: 325 | case ButtonRelease: 326 | button = ev.xbutton.button - 1; 327 | if (button < sizeof(buttons) / sizeof(unsigned int) && 328 | buttons[ev.xbutton.button - 1] && buttonevent == ev.type) 329 | return drw_getpointersel(drw, itemnb); 330 | drawmenu(0); 331 | break; 332 | case DestroyNotify: 333 | if (ev.xdestroywindow.window != win) 334 | break; 335 | cleanup(); 336 | exit(1); 337 | case Expose: 338 | if (ev.xexpose.count == 0) 339 | drawmenu(0); 340 | break; 341 | case FocusIn: 342 | grabfocus(); 343 | break; 344 | case LeaveNotify: 345 | drawmenu(1); 346 | break; 347 | case MotionNotify: 348 | drawmenu(0); 349 | break; 350 | case VisibilityNotify: 351 | if (ev.xvisibility.state != VisibilityUnobscured) 352 | XRaiseWindow(dpy, win); 353 | break; 354 | } 355 | } 356 | return -1; 357 | } 358 | 359 | static int 360 | savelastsel(char *file, char *sel) 361 | { 362 | FILE *fp = NULL; 363 | 364 | if ((fp = fopen(file, "w")) == NULL) 365 | return 1; 366 | fprintf(fp, "%s", sel); 367 | fclose(fp); 368 | return 0; 369 | } 370 | 371 | static void 372 | usage(void) 373 | { 374 | fputs("usage: 91menu [-clmruv] [-f file] [-bd color] [-bg color] [-fg color]\n" 375 | " [-g geometry] [-nv color] [-sl color] [-ft font]\n", stderr); 376 | exit(1); 377 | } 378 | 379 | static void 380 | winsetup(XWindowAttributes *wa) 381 | { 382 | XSetWindowAttributes swa; 383 | XClassHint xch = {"91menu", "91menu"}; 384 | XIM xim; 385 | unsigned int w, h; 386 | unsigned int dw = 0, dh = 0; 387 | int i, x, y, sel; 388 | 389 | /* compute window width/height */ 390 | w = minwidth; 391 | for (i = 0; i < itemnb; i++) { 392 | if (items[i].extw > w) 393 | w = items[i].extw; 394 | } 395 | w += 2 * gappx; 396 | h = itemnb * (font.xfont->ascent + font.xfont->descent) + itemnb * 2 * padpx; 397 | 398 | /* compute pointer coordinate */ 399 | if (drw_getpointer(drw, &x, &y)) 400 | error("cannot query pointer"); 401 | 402 | sel = fetchlastsel(lastselfile); 403 | 404 | /* move the menu to center the first item on the pointer */ 405 | x -= borderpx + w / 2; 406 | y -= borderpx + h / itemnb / 2 + sel * (h / itemnb); 407 | 408 | /* corrects menu coordinates if the menu isn't completly visible on the screen */ 409 | if (x < 0) 410 | x = 0; 411 | else if (x + w > wa->width) 412 | x = wa->width - w - borderpx * 2; 413 | if (y < 0) 414 | y = 0; 415 | else if (y + h > wa->height) 416 | y = wa->height - h - borderpx * 2; 417 | 418 | /* use a predefined geometry */ 419 | if (geometry != NULL) { 420 | XParseGeometry(geometry, &x, &y, &dw, &dh); 421 | if (dw > w && dh > h) { 422 | w = dw; 423 | h = dh; 424 | } 425 | } 426 | 427 | drw_resize(drw, x + borderpx, y + borderpx, w, h); 428 | 429 | /* move pointer to the center of the first item */ 430 | drw_movepointer(drw, x + borderpx + w / 2, y + borderpx + h / itemnb / 2 + sel * (h / itemnb)); 431 | 432 | /* window setup */ 433 | swa.override_redirect = True; 434 | swa.background_pixel = scheme[BG].pixel; 435 | swa.event_mask = ButtonPressMask | ButtonReleaseMask | LeaveWindowMask | 436 | PointerMotionMask | ButtonMotionMask | ExposureMask | 437 | StructureNotifyMask; 438 | win = XCreateWindow(dpy, root, x, y, w, h, borderpx, CopyFromParent, 439 | CopyFromParent, CopyFromParent, CWOverrideRedirect | 440 | CWBackPixel | CWEventMask, &swa); 441 | XSetWindowBorder(dpy, win, scheme[BD].pixel); 442 | XSetClassHint(dpy, win, &xch); 443 | 444 | if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) 445 | error("cannot open input device"); 446 | xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 447 | XNClientWindow, win, XNFocusWindow, win, NULL); 448 | XMapRaised(dpy, win); 449 | } 450 | -------------------------------------------------------------------------------- /91menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoupLobet/91menu/ee22d30c62421d956cde01c591a8b16f604fa4b5/91menu.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Loup Lobet 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # See LICENCE file for copyright and licence details 2 | 3 | VERSION = 1.1 4 | CC = cc 5 | PROGNAME = 91menu 6 | PREFIX = /usr/local 7 | MANPREFIX = ${PREFIX}/share/man 8 | # OpenBSD uncomment 9 | #MANPREFIX = ${PREFIX}/man 10 | 11 | X11INC = /usr/X11R6/include 12 | X11LIB = /usr/X11R6/lib 13 | FREETYPEINC = /usr/include/freetype2 14 | # OpenBSD uncomment 15 | #FREETYPEINC = ${X11INC}/freetype2 16 | FREETYPELIB = -lfontconfig -lXft 17 | 18 | INCS = -I${X11INC} -I${FREETYPEINC} 19 | LIBS = -L${X11LIB} -lX11 ${FREETYPELIB} #$(XINERAMALIBS) 20 | 21 | CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=2000809L 22 | CFLAGS = -std=c99 -pedantic -Wall -Os ${CPPFLAGS} ${INCS} 23 | LDFLAGS = ${LIBS} 24 | 25 | all: clean options 91menu clean_object 26 | 27 | options: 28 | @echo 91menu build options: 29 | @echo "LDFLAGS = ${LDFLAGS}" 30 | @echo "CFLAGS = ${CFLAGS}" 31 | @echo "CC = ${CC}" 32 | 33 | config.h: 34 | cp config.def.h $@ 35 | 36 | .c.o : 37 | ${CC} -c ${CFLAGS} $< 38 | 39 | 91menu: 91menu.o drw.o utf8.o util.o 40 | ${CC} -o $@ 91menu.o drw.o utf8.o util.o ${LDFLAGS} 41 | 42 | clean_object: 43 | rm -f 91menu.o drw.o utf8.o util.o 44 | clean: 45 | rm -f 91menu 91menu.o drw.o utf8.o util.o *.core 46 | [ -e config.h ] || cp config.def.h config.h 47 | 48 | install: all 49 | mkdir -p ${DESTDIR}${PREFIX}/bin 50 | cp -r 91menu ${DESTDIR}${PREFIX}/bin 51 | chmod 755 ${DESTDIR}${PREFIX}/bin/91menu 52 | sed "s/VERSION/${VERSION}/g" <91menu.1 >${DESTDIR}${MANPREFIX}/man1/91menu.1 53 | 54 | 55 | uninstall: 56 | rm -f ${DESTDIR}${PREFIX}/bin/91menu 57 | rm -f ${DESTDIR}${MANPREFIX}/man1/91menu.1 58 | 59 | .PHONY: all options clean install uninstall 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 91menu 2 | ![91menu](https://user-images.githubusercontent.com/48242788/117937705-953e5d00-b306-11eb-916f-af1b60664689.png) 3 | 4 | `$ make` 5 | 6 | `# make install` 7 | 8 | `$ man 91menu` 9 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | static char *lastselfile = "/tmp/91menu"; /* file where the last selected item will be written */ 4 | 5 | /* appearance */ 6 | static unsigned int minwidth = 20; /* minimal menu width */ 7 | static unsigned int borderpx = 1; /* window border width */ 8 | static unsigned int gappx = 10; /* left and right side gaps */ 9 | static unsigned int padpx = 2; /* padding between menu lines overrided by a geometry and -g */ 10 | static unsigned int textpos = CENTER; /* horizontal text position LEFT/CENTER/RIGTH */ 11 | 12 | /* mouse */ 13 | static unsigned int buttons[] = { 1, 0, 1 }; /* mouse buttons, 0 untracked, 1: selection only */ 14 | static unsigned int grabmouse = 1; /* 0 = ignore mouse clicks outside of the window */ 15 | static unsigned int buttonevent = ButtonPress; /* mouse event to select an item [ButtonPress|ButtonRelease] */ 16 | 17 | /* geometry see XParseGeometry(3), NULL = no geometry */ 18 | static char *geometry = NULL; 19 | 20 | /* font */ 21 | static const char *fontname = "GoMono Nerd Font:pixelsize=11:antialias=true;autohint=true"; 22 | 23 | /* color scheme */ 24 | static const char *colornames[COLORNB] = { 25 | [BD] = "#586e75", /* border */ 26 | [BG] = "#002b36", /* background */ 27 | [FG] = "#586e75", /* foreground */ 28 | [NV] = "#93a1a1", /* inverted text */ 29 | [SL] = "#073642", /* selection */ 30 | }; 31 | -------------------------------------------------------------------------------- /drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "utf8.h" 12 | #include "util.h" 13 | #include "drw.h" 14 | 15 | Drw * 16 | drw_create(Display *dpy, Window root, int screen, int x, int y, unsigned int w, unsigned int h) 17 | { 18 | Drw *drw = emalloc(sizeof(Drw)); 19 | 20 | drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 21 | drw->gc = XCreateGC(dpy, root, 0, NULL); 22 | drw->root = root; 23 | drw->w = w; 24 | drw->h = h; 25 | drw->screen = screen; 26 | drw->x = 0; 27 | drw-> y = 0; 28 | drw->dpy = dpy; 29 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 30 | return drw; 31 | } 32 | 33 | void 34 | drw_drawrect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int color) 35 | { 36 | if (drw == NULL) 37 | return; 38 | XSetForeground(drw->dpy, drw->gc, drw->scheme[color].pixel); 39 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 40 | } 41 | 42 | void 43 | drw_drawtext(Drw *drw, char *text, unsigned int size, Fnt *font, Clr *color, int x, int y) 44 | { 45 | char buf[1024]; 46 | long utf8codepoint = 0; 47 | int charexists = 0; 48 | int len; 49 | int utf8strlen, utf8charlen; 50 | const char *utf8str = NULL; 51 | XftDraw *xftdrw = NULL; 52 | 53 | xftdrw = XftDrawCreate(drw->dpy, drw->drawable, DefaultVisual(drw->dpy, drw->screen), 54 | DefaultColormap(drw->dpy, drw->screen)); 55 | utf8strlen = 0; 56 | utf8str = text; 57 | while(*text) { 58 | utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); 59 | charexists = charexists || XftCharExists(drw->dpy, font->xfont, utf8codepoint); 60 | if (charexists) { 61 | utf8strlen += utf8charlen; 62 | text += utf8charlen; 63 | } 64 | if (!charexists) 65 | text++; 66 | else 67 | charexists = 0; 68 | } 69 | if (utf8strlen) { 70 | len = MIN(utf8strlen, sizeof(buf) - 1); 71 | if (len) { 72 | memcpy(buf, utf8str, len); 73 | buf[len] = '\0'; 74 | XftDrawStringUtf8(xftdrw, color, font->xfont, x, y, 75 | (XftChar8 *) buf, len); 76 | } 77 | } 78 | if (xftdrw) 79 | XftDrawDestroy(xftdrw); 80 | } 81 | 82 | 83 | void 84 | drw_free(Drw *drw) 85 | { 86 | if (drw == NULL) 87 | return; 88 | XFreePixmap(drw->dpy, drw->drawable); 89 | XFreeGC(drw->dpy, drw->gc); 90 | free(drw); 91 | } 92 | 93 | int 94 | drw_getpointer(Drw *drw, int *x, int *y) 95 | { 96 | /* dummy variables */ 97 | Window dw; 98 | unsigned int du; 99 | int dx, dy; 100 | 101 | if (!XQueryPointer(drw->dpy, drw->root, &dw, &dw, x, y, &dx, &dy, &du)) 102 | return 1; 103 | return 0; 104 | } 105 | 106 | int 107 | drw_getpointersel(Drw *drw, int itemnb) 108 | { 109 | int i; 110 | int px, py, x, y; 111 | 112 | if (drw == NULL) 113 | return -1; 114 | if (drw_getpointer(drw, &px, &py)) 115 | error("cannot query pointer"); 116 | x = drw->x; 117 | y = drw->y; 118 | for (i = 0; i < itemnb; i++) { 119 | if (x <= px && px < (x + drw->w) && 120 | y <= py && py < (y + drw->h / itemnb)) 121 | return i; 122 | y += drw->h / itemnb; 123 | } 124 | return -1; 125 | } 126 | 127 | void 128 | drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 129 | { 130 | if (drw == NULL) 131 | return; 132 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 133 | XSync(drw->dpy, False); 134 | } 135 | 136 | void 137 | drw_movepointer(Drw *drw, int x, int y) 138 | { 139 | if (drw == NULL) 140 | return; 141 | XWarpPointer(drw->dpy, None, drw->root, 0, 0, 0, 0, x, y); 142 | } 143 | 144 | void 145 | drw_resize(Drw *drw, int x, int y, unsigned int w, unsigned int h) 146 | { 147 | if (drw == NULL) 148 | return; 149 | drw->w = w; 150 | drw->h = h; 151 | drw->x = x; 152 | drw->y = y; 153 | if (drw->drawable) 154 | XFreePixmap(drw->dpy, drw->drawable); 155 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); 156 | } 157 | 158 | void 159 | drw_setfont(Drw *drw, Fnt font) 160 | { 161 | if (drw != NULL) 162 | drw->font = font; 163 | } 164 | 165 | void 166 | drw_setscheme(Drw *drw, Clr *scheme) 167 | { 168 | if (drw != NULL) 169 | drw->scheme = scheme; 170 | } 171 | -------------------------------------------------------------------------------- /drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | typedef XftColor Clr; 4 | 5 | typedef struct { 6 | FcPattern *pattern; 7 | XftFont *xfont; 8 | } Fnt; 9 | 10 | typedef struct { 11 | Drawable drawable; 12 | Fnt font; 13 | GC gc; 14 | Window win, root; 15 | unsigned int w, h; 16 | int screen; 17 | int x, y; 18 | Display *dpy; 19 | Clr *scheme; 20 | } Drw; 21 | 22 | typedef struct Item { 23 | unsigned int extw; 24 | char *text; 25 | } Item; 26 | 27 | Drw *drw_create(Display *, Window, int, int, int, unsigned int, unsigned int); 28 | void drw_drawrect(Drw *, int, int, unsigned int, unsigned int, int); 29 | void drw_drawtext(Drw *, char *, unsigned int, Fnt *, Clr *, int, int); 30 | void drw_free(Drw *); 31 | int drw_getpointer(Drw *, int *, int *); 32 | int drw_getpointersel(Drw *, int); 33 | void drw_map(Drw *, Window, int , int , unsigned int , unsigned int ); 34 | void drw_movepointer(Drw *, int, int); 35 | void drw_resize(Drw *, int, int, unsigned int, unsigned int); 36 | void drw_setfont(Drw *, Fnt); 37 | void drw_setscheme(Drw *, Clr *); 38 | -------------------------------------------------------------------------------- /utf8.c: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | /* This piece of software comes from dmenu : http://tools.suckless.org/dmenu/ */ 3 | 4 | #define UTF_INVALID 0xFFFD 5 | #define UTF_SIZ 4 6 | 7 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 8 | 9 | const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 10 | const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 11 | const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 12 | const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 13 | 14 | long 15 | utf8decodebyte(const char c, int *i) 16 | { 17 | for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) 18 | if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) 19 | return (unsigned char)c & ~utfmask[*i]; 20 | return 0; 21 | } 22 | 23 | int 24 | utf8validate(long *u, int i) 25 | { 26 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 27 | *u = UTF_INVALID; 28 | for (i = 1; *u > utfmax[i]; ++i) 29 | ; 30 | return i; 31 | } 32 | 33 | int 34 | utf8decode(const char *c, long *u, int clen) 35 | { 36 | int i, j, len, type; 37 | long udecoded; 38 | 39 | *u = UTF_INVALID; 40 | if (!clen) 41 | return 0; 42 | udecoded = utf8decodebyte(c[0], &len); 43 | if (!BETWEEN(len, 1, UTF_SIZ)) 44 | return 1; 45 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 46 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 47 | if (type) 48 | return j; 49 | } 50 | if (j < len) 51 | return 0; 52 | *u = udecoded; 53 | utf8validate(u, len); 54 | 55 | return len; 56 | } 57 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | /* This piece of software comes from dmenu : http://tools.suckless.org/dmenu/ */ 3 | 4 | #define UTF_SIZ 4 5 | 6 | long utf8decodebyte(const char, int *); 7 | int utf8validate(long *, int); 8 | int utf8decode(const char *, long *, int); 9 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void 11 | error(const char *fmt, ...) 12 | { 13 | va_list ap; 14 | 15 | va_start(ap, fmt); 16 | (void)fprintf(stderr, "91menu: Error: "); 17 | (void)vfprintf(stderr, fmt, ap); 18 | va_end(ap); 19 | fputc('\n', stderr); 20 | exit(1); 21 | } 22 | 23 | void 24 | warning(const char *fmt, ...) 25 | { 26 | va_list ap; 27 | 28 | va_start(ap, fmt); 29 | (void)fprintf(stderr, "91menu: Warning: "); 30 | (void)vfprintf(stderr, fmt, ap); 31 | va_end(ap); 32 | fputc('\n', stderr); 33 | } 34 | 35 | void * 36 | emalloc(unsigned int n) 37 | { 38 | void *p = NULL; 39 | 40 | p = malloc(n); 41 | if (p == NULL) 42 | error("malloc"); 43 | memset(p, 0, n); 44 | return p; 45 | } 46 | 47 | void * 48 | erealloc(void *q, unsigned int n) 49 | { 50 | void *p = NULL; 51 | 52 | p = realloc(q, n); 53 | if (p == NULL) 54 | error("realloc"); 55 | return p; 56 | } 57 | 58 | long 59 | estrtol(const char *nptr, int base) 60 | { 61 | char *endptr = NULL; 62 | errno = 0; 63 | long out; 64 | 65 | out = strtol(nptr, &endptr, base); 66 | if ((endptr == nptr) || ((out == LONG_MAX || out == LONG_MIN) 67 | && errno == ERANGE)) 68 | error("invalid integer: %s", nptr); 69 | return out; 70 | } 71 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENCE file for copyright and licence details */ 2 | 3 | #define MIN(A, B) (((A) > (B)) ? (B) : (A)) 4 | #define MAX(A, B) (((A) < (B)) ? (B) : (A)) 5 | 6 | void error(const char *, ...); 7 | void warning(const char *, ...); 8 | void *emalloc(unsigned int); 9 | void *erealloc(void *, unsigned int); 10 | int estrtol(const char *nptr, int base); 11 | --------------------------------------------------------------------------------