This repository has been archived on 2020-12-21. You can view files and clone it, but cannot push or open issues or pull requests.
nenu/nenu.c

559 lines
12 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xinerama.h>
#include <fontconfig/fontconfig.h>
#include <ctype.h>
#include <strings.h>
#define MAX(A, B) (A > B ? A : B)
typedef struct option option;
struct option {
FcChar8 *text;
option *next, *prev;
};
static void insert(FcChar8 *str, ssize_t n);
static size_t nextrune(int inc);
static void copy_first();
static void handle_key(XKeyEvent ke);
static void update_valid_options();
static void load_font(FcChar8 *fontstr);
static void grab_keyboard_pointer();
static void update_size();
static void set_position();
static void keep_in_screen();
static void read_input();
static void setup();
#define PADDING 4
#define BORDER_WIDTH 1
#define MAX_LEN 512
#define TAB_WIDTH 4
char bg_name[] = "#161510";
char fg_name[] = "#ccbc8e";
char font_str[] = "Fantasque Sans Mono:pixelsize=14:\
antialias=true:autohint=false";
static Window root, win;
static int screen;
static Display *display;
static GC gc;
static Drawable buf;
static XftDraw *draw;
static Pixmap nullpixmap;
static int max_width, max_height;
static int draw_options;
static XIM xim;
static XIC xic;
static XftColor fg, bg;
static XftFont *font;
static int ascent, descent;
static int prompt_width, cursor_width;
static int x = -1, y = -1;
static int w = 0, h = 0;
static int exit_on_one = 0;
static int first_on_exit = 0;
static int text_input = 1;
static int read_options = 1;
static int print_on_exit = 1;
static int absolute_position = 0;
static int grab = 1;
static size_t cursor = 0;
static FcChar8 text[MAX_LEN] = {'\0'};
static FcChar8 prompt[MAX_LEN] = {'\0'};
static option *options = NULL;
static option *valid = NULL;
void clean_resources() {
XUngrabKeyboard(display, CurrentTime);
XUngrabPointer(display, CurrentTime);
XFreeGC(display, gc);
XFreePixmap(display, buf);
XFreePixmap(display, nullpixmap);
}
void die(char *msg) {
fprintf(stderr, "nenu [ERROR]: %s\n", msg);
exit(EXIT_FAILURE);
}
void finish() {
if (print_on_exit) {
if ((first_on_exit || exit_on_one) && valid)
printf("%s\n", valid->text);
else
printf("%s\n", text);
}
clean_resources();
exit(EXIT_SUCCESS);
}
int text_width(FcChar8 *s) {
XGlyphInfo g;
int i, len = strlen(s);
/* XftTextExtentsUtf8 doesn't seem to know what a space is. */
char str[MAX_LEN];
strcpy(str, s);
for (i = 0; i < len; i++)
if (str[i] == ' ') str[i] = '-';
XftTextExtentsUtf8(display, font, str, len, &g);
return g.width;
}
void draw_string(FcChar8 *s, int x, int y) {
if (x > w * 2 || y > h * 2) return;
XftDrawStringUtf8(draw, &fg, font, x, y,
(FcChar8 *) s, strlen(s));
}
void render_options(int oy) {
option *o = valid;
int flip = 2;
while (flip) {
if (flip == 1 && o == valid) break;
if (o) {
draw_string(o->text, PADDING, oy + ascent);
oy += ascent + descent;
o = o->next;
} else if (--flip && valid) {
for (o = valid->prev; o && o->prev; o = o->prev);
}
}
}
void render() {
int cursor_pos;
FcChar8 t;
update_size();
XftDrawRect(draw, &bg, 0, 0, w, h);
if (prompt[0])
draw_string(prompt, PADDING, PADDING + ascent);
if (text_input) {
draw_string(text, PADDING + prompt_width,
PADDING + ascent);
t = text[cursor];
text[cursor] = '\0';
cursor_pos = prompt_width + text_width(text);
text[cursor] = t;
draw_string("_", PADDING + 1 + cursor_pos,
PADDING + ascent);
}
if (draw_options)
render_options(PADDING + ((text_input || prompt[0]) ?
ascent + descent : 0));
XCopyArea(display, buf, win, gc, 0, 0, w, h, 0, 0);
}
void insert(FcChar8 *str, ssize_t n) {
int i, j;
if (strlen(text) + n > MAX_LEN)
return;
memmove(&text[cursor + n], &text[cursor],
MAX_LEN - cursor - MAX(n, 0));
if (n > 0)
memcpy(&text[cursor], str, n);
cursor += n;
}
void copy_first() {
if (valid) {
strcpy(text, valid->text);
while (text[cursor] != '\0' && cursor < MAX_LEN - 1)
cursor = nextrune(+1);
update_valid_options();
}
}
/* return location of next utf8 rune in the given direction
* -- thanks dmenu */
size_t nextrune(int inc) {
ssize_t n;
for (n = cursor + inc;
n + inc >= 0 && (text[n] & 0xc0) == 0x80;
n += inc);
return n;
}
void handle_key(XKeyEvent ke) {
FcChar8 buf[32];
int len, n;
KeySym keysym = NoSymbol;
Status status;
option *o;
len = XmbLookupString(xic, &ke, buf, sizeof(buf),
&keysym, &status);
if (status == XBufferOverflow)
return;
if (text_input) {
switch(keysym) {
default:
if (!iscntrl(*buf)) {
insert(buf, len);
update_valid_options();
}
break;
case XK_Delete:
if (text[cursor] == '\0') break;
cursor = nextrune(+1);
/* Fall through to backspace */
case XK_BackSpace:
if (cursor == 0)
exit(0);
insert(NULL, nextrune(-1) - cursor);
update_valid_options();
break;
case XK_Tab:
copy_first();
break;
}
}
switch(keysym) {
case XK_Return:
finish();
break;
case XK_Escape:
exit(1);
break;
}
if (exit_on_one) {
for (n = 0, o = valid; o; o = o->next) n++;
if (n == 1) finish();
}
}
void update_valid_options() {
option *head, *o, *v;
for (o = valid; o && o->prev; o = o->prev);
while (o) {
v = o->next;
free(o);
o = v;
}
v = head = calloc(sizeof(option), 1);
for (o = options; o; o = o->next) {
if (strncmp(text, o->text, strlen(text)) == 0) {
v->next = malloc(sizeof(option));
v->next->prev = v;
v = v->next;
v->text = o->text;
v->next = NULL;
}
}
if (head->next)
head->next->prev = NULL;
valid = head->next;
free(head);
}
void load_font(FcChar8 *font_str) {
FcPattern *pattern, *match;
FcResult result;
if (font_str[0] == '-')
pattern = XftXlfdParse(font_str, False, False);
else
pattern = FcNameParse((FcChar8 *) font_str);
if (!pattern)
die("Failed to get font pattern");
match = FcFontMatch(NULL, pattern, &result);
if (!match)
die("Failed to get font match");
if (!(font = XftFontOpenPattern(display, match))) {
FcPatternDestroy(match);
die("Failed to open font");
}
ascent = font->ascent;
descent = font->descent;
}
void grab_keyboard_pointer() {
int i, p = 1, k = 1;
XColor dummycolor;
Cursor nullcursor;
nullpixmap = XCreatePixmap(display, root, 1, 1, 1);
nullcursor = XCreatePixmapCursor(display,
nullpixmap, nullpixmap,
&dummycolor, &dummycolor, 0, 0);
if (!grab) return;
/* Keep trying for pointer. */
for (; p; ) {
if (XGrabPointer(display, root, False,
ButtonPressMask|ButtonReleaseMask,
GrabModeAsync, GrabModeAsync,
win, nullcursor, CurrentTime)
== GrabSuccess)
p = 0;
usleep(10000);
}
/* Because we won't grab keyboard until pointer has been
* gotten. Then only try a few times for keyboard */
for (i = 0; k && i < 1000; i++) {
if (XGrabKeyboard(display, root, True,
GrabModeAsync, GrabModeAsync,
CurrentTime) == GrabSuccess)
k = 0;
usleep(1000);
}
if (k) die("Failed to grab keyboard!");
}
void update_size() {
int ow = w, oh = h;
int tw, bw, bh;
option *o;
prompt_width = text_width(prompt);
cursor_width = text_width("_");
bw = w = (text_input ? cursor_width : 0)
+ prompt_width + text_width(text);
bh = h = (text_input || prompt[0]) ? ascent + descent : 0;
for (o = valid; o && o->prev; o = o->prev);
for (; o; o = o->next) {
tw = text_width(o->text);
if (tw > w) w = tw;
h += ascent + descent;
}
if (h > max_height)
{
w = bw;
h = bh;
draw_options = 0;
} else draw_options = 1;
w += PADDING * 2;
h += PADDING * 2;
if (ow == w && oh == h) return;
if (buf) XFreePixmap(display, buf);
buf = XCreatePixmap(display, win, w, h,
DefaultDepth(display, screen));
XResizeWindow(display, win, w, h);
XftDrawChange(draw, buf);
}
void set_position() {
Window ww;
int c, v;
/* if x,y not set then set to cursor position. */
if (x == -1 && y == -1)
XQueryPointer(display, root, &ww, &ww,
&x, &y, &c, &c, &v);
if (!absolute_position)
keep_in_screen();
XMoveWindow(display, win, x, y);
}
void keep_in_screen() {
XineramaScreenInfo *info;
int i, count;
if ((info = XineramaQueryScreens(display, &count))) {
for (i = 0; i < count; i++)
if (info[i].x_org <= x
&& x <= info[i].x_org + info[i].width
&& info[i].y_org <= y
&& y <= info[i].y_org + info[i].height)
break;
if (i == count) return;
if (x + w > info[i].x_org + info[i].width)
x = info[i].x_org + info[i].width
- w - BORDER_WIDTH - 1;
if (x < info[i].x_org) x = info[i].x_org;
if (y + h > info[i].y_org + info[i].height)
y = info[i].y_org + info[i].height
- h - BORDER_WIDTH - 1;
if (y < info[i].y_org) y = info[i].y_org;
}
}
void read_input() {
FcChar8 line[512];
int l;
option *o, *head;
o = head = calloc(sizeof(option), 1);
while (fgets(line, sizeof(line), stdin)) {
o->next = calloc(sizeof(option), 1);
o->next->prev = o;
o = o->next;
l = strlen(line);
o->text = calloc(sizeof(char), l);
strncpy(o->text, line, l - 1);
o->text[l - 1] = '\0';
}
options = head->next;
options->prev = NULL;
free(head);
update_valid_options();
}
void setup() {
XSetWindowAttributes attributes;
XWindowAttributes window_attributes;
Visual *vis;
Colormap cmap;
int ignore;
display = XOpenDisplay(NULL);
screen = DefaultScreen(display);
vis = XDefaultVisual(display, screen);
cmap = DefaultColormap(display, screen);
if (XGetGeometry(display, RootWindow(display, screen), &root,
&ignore, &ignore,
&max_width, &max_height, &ignore, &ignore) == False)
die("Failed to get root Geometry!");
if (!XftColorAllocName(display, vis, cmap, fg_name, &fg))
die("Failed to allocate foreground color");
if (!XftColorAllocName(display, vis, cmap, bg_name, &bg))
die("Failed to allocate background color");
/* Setup and map window */
attributes.border_pixel = fg.pixel;
attributes.background_pixel = bg.pixel;
attributes.override_redirect = True;
attributes.event_mask =
ExposureMask|KeyPressMask|ButtonPressMask|ButtonReleaseMask;
win = XCreateWindow(display, root,
0, 0, 1, 1, BORDER_WIDTH,
DefaultDepth(display, 0),
CopyFromParent, CopyFromParent,
CWBackPixel|CWOverrideRedirect|CWEventMask|CWBorderPixel,
&attributes);
xim = XOpenIM(display, NULL, NULL, NULL);
xic = XCreateIC(xim, XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL);
gc = XCreateGC(display, win, 0, 0);
buf = XCreatePixmap(display, win, 1, 1,
DefaultDepth(display, screen));
draw = XftDrawCreate(display, buf, vis, cmap);
load_font(font_str);
}
int main(int argc, char *argv[]) {
XEvent ev;
int i, time, pending;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--pos") == 0) {
x = atoi(strsep(&argv[++i], ","));
y = atoi(argv[i]);
} else if (strcmp(argv[i], "--abs") == 0) {
absolute_position = 1;
} else if (strcmp(argv[i], "--fg") == 0) {
strcpy(fg_name, argv[++i]);
} else if (strcmp(argv[i], "--bg") == 0) {
strcpy(bg_name, argv[++i]);
} else if (strcmp(argv[i], "--fn") == 0) {
strcpy(font_str, argv[++i]);
} else if (argv[i][0] == '-') {
switch (argv[i][1])
{
case 'o': exit_on_one = 1; break;
case 'f': first_on_exit = 1; break;
case 't': text_input = 0; break;
case 'q': print_on_exit = 0; break;
case 'n': read_options = 0; break;
case 'g': grab = 0; break;
default:
fprintf(stderr, "Unknown option: %s\n",
argv[i]);
exit(EXIT_FAILURE);
}
} else {
strcpy(prompt, argv[i]);
}
}
setup();
if (read_options) read_input();
XMapWindow(display, win);
set_position();
grab_keyboard_pointer();
for(;;) {
XNextEvent(display, &ev);
switch (ev.type) {
case KeyPress:
handle_key(ev.xkey);
render();
break;
case Expose:
XRaiseWindow(display, win);
render();
break;
}
}
clean_resources();
exit(EXIT_FAILURE);
}