working, added scripts from selecttext and showmessage, fixed -a

This commit is contained in:
Mytchel Hammond 2015-04-04 18:55:08 +13:00
commit dda3160389
10 changed files with 730 additions and 0 deletions

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Mytchel Hammond
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

33
Makefile Normal file
View File

@ -0,0 +1,33 @@
DESTDIR?=
PREFIX?=/usr
all: nenu
nenu: nenu.c config.h
gcc -o nenu nenu.c \
-lX11 \
-lXft \
-lXrender \
`pkg-config --cflags freetype2` \
`pkg-config --cflags fontconfig` \
`pkg-config --libs fontconfig` \
`pkg-config --libs freetype2`
.PHONY:
clean:
rm nenu
.PHONY:
install: nenu nexec nwindow ntime
install -Dm 755 nenu ${DESTDIR}${PREFIX}/nenu
install -Dm 755 nexec ${DESTDIR}${PREFIX}/nexec
install -Dm 755 nwindow ${DESTDIR}${PREFIX}/nwindow
install -Dm 755 ntime ${DESTDIR}${PREFIX}/ntime
.PHONY:
uninstall:
rm ${DESTDIR}${PREFIX}/bin/nenu
rm ${DESTDIR}${PREFIX}/bin/nexec
rm ${DESTDIR}${PREFIX}/bin/nwindow
rm ${DESTDIR}${PREFIX}/bin/ntime

34
README.md Normal file
View File

@ -0,0 +1,34 @@
nenu
==========
Nenu is a X11 utility similar to dmenu (but more like the menu's in cwm) that
takes input from stdin and presents it to the user in a way they they can
select what to return in a number of different ways.
A remake and combination of [selecttext](http://github.com/mytch444/selecttext)
and [showmessage](http://github.com/mytch444/showmessage) that is used to
display messages, get choices and get input from the user.
By default nenu takes input from stdin and display it to the user in a vertical
list. The user then types which reduces the list to those that match. Once the
user presses return nenu exits and prints what was typed to stdout.
The last non-flag argument given is used as the header.
Avaliable flags are -a, -c, -n and -e.
-a: exit as soon as there is only one match.
-c: on exit return the match at the begining of the list. If there is no match
nenu returns the user input.
-n: no input is taken and the -c flag is set. This is useful if you want to use
nenu to show the user something but take no input.
-e: takes no input from stdin, nenu become a text input box.
-p x,y: opens the window at x,y rather than mouse coords.
-ap: don't shift the window so it stays inside the screen.
Look in nexec, ntime and nwindow for examples of use. These are useable scripts
in their own right.

9
config.h Normal file
View File

@ -0,0 +1,9 @@
#define PADDING 4
#define BORDER_WIDTH 2
#define MAX_LENGTH 512
char bg_name[] = "#000000";
char fg_name[] = "#cccccc";
char fontstr[] = "DejaVuSansMono:pixelsize=12:antialias=true:autohint=false";

BIN
nenu Executable file

Binary file not shown.

585
nenu.c Normal file
View File

@ -0,0 +1,585 @@
#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 <fontconfig/fontconfig.h>
#include <ctype.h>
#include <strings.h>
#define MAX(A, B) (A > B ? A : B)
#include "nenu.h"
#include "config.h"
Window root, win;
int screen;
Display *display;
GC gc;
Drawable buf;
XftDraw *draw;
Pixmap nullpixmap;
XIM xim;
XIC xic;
int x = -1, y = -1;
int w = 0, h = 0;
int maxh;
XftColor fg, bg;
XftFont *font;
int ascent, descent;
int message_width, cursor_width;
int quit;
int exit_on_one, complete_on_exit, input_bar, read_options, absolute_position;
size_t cursor;
char text[MAX_LENGTH];
char *message;
option *options;
option *current, *valid;
void usage() {
printf(
"usage: nenu [-a|-c|-n|-e|-p x,y|-ap] [header]\n\n"
"nenu takes options from stdin and display them, allowing the user to input \n"
"their desired option\n\n"
" -a : exits as soon as there is only one match.\n"
" -c : on exit return the match at the begining of the list. if there is\n"
" no match nenu returns the user input\n"
" -n : no input bar is displayed and -c is put on.\n"
" -e : takes no input from stdin\n"
" -p x,y: set's window position\n"
" -ap : don't shift the window so it stays inside the screen\n"
);
}
void die(char *msg) {
fprintf(stderr, "nenu [ERROR]: %s\n", msg);
exit(EXIT_FAILURE);
}
void finish() {
if (complete_on_exit) {
if (current) printf("%s\n", current->text);
else if (valid) printf("%s\n", valid->text);
} else if (exit_on_one) {
if (valid) printf("%s\n", valid->text);
} else {
printf("%s\n", text);
}
quit = 1;
}
int text_width(char *str) {
XGlyphInfo g;
int len = strlen(str);
if (len == 0) return 0;
// If last char is space TextExtents seems to ignore it.
if (str[len - 1] == ' ') {
char *s = malloc(sizeof(char) * len);
strcpy(s, str);
s[len - 1] = '-';
str = s;
}
XftTextExtentsUtf8(display, font, str, len, &g);
return g.width;
}
void draw_string(char *str, int x, int y) {
XftDrawStringUtf8(draw, &fg, font, x, y, (FcChar8 *) str, strlen(str));
}
void render_options(int oy) {
option *o;
int flipped;
if (current)
o = current;
else
o = valid;
for (flipped = 0; o; o = !o->next && !(flipped++) ? valid : o->next) {
if (flipped && o == current)
break;
draw_string(o->text, PADDING, oy + ascent);
oy += ascent + descent;
if (oy > maxh)
break;
}
}
void render() {
char *cursor_text;
int cursor_pos;
update_size();
XftDrawRect(draw, &bg, 0, 0, w, h);
if (input_bar) {
cursor_text = malloc(sizeof(char) * (cursor + 1));
strncpy(cursor_text, text, cursor);
cursor_text[cursor] = '\0';
cursor_pos = text_width(cursor_text);
free(cursor_text);
draw_string(message, PADDING, PADDING + ascent);
draw_string(text, PADDING + message_width, PADDING + ascent);
draw_string("_", PADDING + message_width + cursor_pos,
PADDING + ascent);
render_options(PADDING + ascent + 5);
} else
render_options(PADDING);
XCopyArea(display, buf, win, gc, 0, 0, w, h, 0, 0);
}
void insert(char *str, ssize_t n) {
int i, j;
if (!input_bar)
return;
if (strlen(text) + n > MAX_LENGTH)
return;
memmove(&text[cursor + n], &text[cursor], MAX_LENGTH - cursor - MAX(n, 0));
if (n > 0)
memcpy(&text[cursor], str, n);
cursor += n;
}
// 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_button(XButtonEvent be) {
option *o;
switch(be.button) {
case Button4:
if (current && current->prev)
current = current->prev;
else {
for (o = valid; o && o->next; o = o->next);
current = o;
}
break;
case Button5:
if (current && current->next)
current = current->next;
else
current = valid;
break;
case Button2:
finish();
break;
}
render();
}
void handle_key(XKeyEvent ke) {
char 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 (ke.state & ControlMask) {
switch(keysym) {
// Emacs
case XK_p:
keysym = XK_Up;
break;
case XK_n:
keysym = XK_Down;
break;
case XK_f:
keysym = XK_Right;
break;
case XK_b:
keysym = XK_Left;
break;
case XK_d:
keysym = XK_Delete;
break;
// Vim
case XK_j:
keysym = XK_Down;
break;
case XK_k:
keysym = XK_Up;
break;
case XK_h:
keysym = XK_Left;
break;
case XK_l:
keysym = XK_Right;
break;
}
} else if (ke.state & Mod1Mask) {
}
switch(keysym) {
default:
if (input_bar) {
if (!iscntrl(*buf))
insert(buf, len);
update_valid_options();
}
break;
case XK_Delete: // How the fuck does this work?
if (text[cursor] == '\0')
return;
cursor = nextrune(+1);
update_valid_options();
case XK_BackSpace:
if (input_bar) {
if (cursor == 0)
exit(0);
insert(NULL, nextrune(-1) - cursor);
update_valid_options();
}
break;
case XK_Tab:
select_forward_match();
update_valid_options();
break;
case XK_Left:
if (cursor != 0)
cursor = nextrune(-1);
break;
case XK_Right:
if (text[cursor] != '\0' && cursor < MAX_LENGTH - 1)
cursor = nextrune(+1);
break;
case XK_Up:
if (current->prev) {
current = current->prev;
} else {
for (o = valid; o && o->next; o = o->next);
current = o;
}
break;
case XK_Down:
if (current->next)
current = current->next;
else
current = valid;
break;
case XK_Return:
finish();
break;
case XK_Escape:
exit(0);
break;
}
if (exit_on_one) {
for (n = 0, o = valid; o; o = o->next) n++;
if (n == 1) {
current = valid;
finish();
}
}
render();
}
void update_valid_options() {
option *head = malloc(sizeof(option));
head->next = NULL;
option *v = head;
option *o;
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;
}
}
valid = head->next;
if (valid)
valid->prev = NULL;
free(head);
current = valid;
}
void select_forward_match() {
option *o;
int i, can_move = -1, good = 1;
char c;
if (!valid)
return;
while (good) {
c = valid->text[++can_move];
if (!c) break;
for (o = valid; o && good; o = o->next)
if (o->text[can_move] != c)
good = 0;
}
if (can_move > 0)
for (i = 0; i < can_move; i++)
text[i] = valid->text[i];
cursor = can_move;
}
void load_font() {
FcPattern *pattern, *match;
FcResult result;
if (fontstr[0] == '-')
pattern = XftXlfdParse(fontstr, False, False);
else
pattern = FcNameParse((FcChar8 *) fontstr);
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);
for (i = 0; (k || p) && i < 1000; i++) {
if (p && XGrabPointer(display, root, False, ButtonPressMask,
GrabModeAsync, GrabModeAsync,
win, nullcursor, CurrentTime)
== GrabSuccess) p = 0;
if (k && XGrabKeyboard(display, root, True,
GrabModeAsync, GrabModeAsync,
CurrentTime) == GrabSuccess) k = 0;
usleep(1000);
}
if (p) die("Failed to grab pointer!");
if (k) die("Failed to grab keyboard!");
}
void update_size() {
int tw;
int ow = w, oh = h;
option *o;
w = message_width + cursor_width + text_width(text);
h = input_bar ? ascent + descent : 0;
for (o = valid; o; o = o->next) {
tw = text_width(o->text);
if (tw > w)
w = tw;
h += ascent + descent;
if (h >= maxh)
break;
}
w += PADDING * 2;
h += PADDING * 2;
if (ow == w && oh == h)
return;
XResizeWindow(display, win, w, h);
if (buf)
XFreePixmap(display, buf);
buf = XCreatePixmap(display, win, w, h, DefaultDepth(display, screen));
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);
XMoveWindow(display, win, x, y);
}
void read_input() {
char line[512];
option *o, *head;
head = malloc(sizeof(option));
head->next = NULL;
o = head;
while (fgets(line, sizeof(line), stdin)) {
o->next = malloc(sizeof(option));
o->next->prev = o;
o = o->next;
o->text = malloc(sizeof(char) * (strlen(line) + 1));
strcpy(o->text, line);
o->text[strlen(o->text) - 1] = '\0';
o->next = NULL;
}
options = head->next;
options->prev = NULL;
free(head);
update_valid_options();
}
int main(int argc, char *argv[]) {
XSetWindowAttributes attributes;
XWindowAttributes window_attributes;
Visual *vis;
Colormap cmap;
XEvent ev;
int i;
exit_on_one = 0;
complete_on_exit = 0;
input_bar = 1;
read_options = 1;
absolute_position = 0;
cursor = 0;
message = "";
for (i = 0; i < MAX_LENGTH; i++)
text[i] = '\0';
valid = NULL;
current = NULL;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0) {
usage();
return 0;
} else if (strcmp(argv[i], "-a") == 0) {
exit_on_one = 1;
} else if (strcmp(argv[i], "-c") == 0) {
complete_on_exit = 1;
} else if (strcmp(argv[i], "-n") == 0) {
complete_on_exit = 1;
input_bar = 0;
} else if (strcmp(argv[i], "-e") == 0) {
read_options = 0;
} else if (strcmp(argv[i], "-p") == 0) {
i++;
x = atoi(strsep(&argv[i], ","));
y = atoi(argv[i]);
} else if (strcmp(argv[i], "-ap") == 0) {
absolute_position = 1;
} else {
message = argv[i];
}
}
if (read_options)
read_input();
display = XOpenDisplay(NULL);
root = RootWindow(display, 0);
screen = DefaultScreen(display);
vis = XDefaultVisual(display, screen);
cmap = DefaultColormap(display, screen);
maxh = DisplayHeight(display, 0) - y;
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.override_redirect = True;
attributes.event_mask = ExposureMask | KeyPressMask | ButtonPressMask;
win = XCreateWindow(display, root,
0, 0, 10, 10, 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, 10, 10, DefaultDepth(display, screen));
draw = XftDrawCreate(display, buf, vis, cmap);
load_font();
message_width = text_width(message);
cursor_width = text_width("_");
update_size();
set_position();
XMapWindow(display, win);
render();
grab_keyboard_pointer();
quit = 0;
while (!quit && !XNextEvent(display, &ev)) {
if (ev.type == KeyPress)
handle_key(ev.xkey);
else if (ev.type == ButtonPress)
handle_button(ev.xbutton);
else if (ev.type == Expose) {
XRaiseWindow(display, win);
render();
}
}
XUngrabKeyboard(display, CurrentTime);
XUngrabPointer(display, CurrentTime);
XFreeGC(display, gc);
XFreePixmap(display, buf);
XFreePixmap(display, nullpixmap);
return 0;
}

33
nenu.h Normal file
View File

@ -0,0 +1,33 @@
typedef struct option option;
struct option {
char *text;
option *next, *prev;
};
void usage();
void die(char *msg);
void finish();
int text_width(char *str);
void draw_string(char *str, int x, int y);
void render_options(int oy);
void render();
void insert(char *str, ssize_t n);
size_t nextrune(int inc);
void handle_button(XButtonEvent be);
void handle_key(XKeyEvent ke);
void update_valid_options();
void select_forward_match();
void make_cursor();
void load_font();
void grab_keyboard_pointer();
void update_size();
void set_position();
void read_input();

2
nexec Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
lsx `echo $PATH | sed "s/:/\n/g"` | sort | nenu -c "exec: " | ${SHELL:-"/bin/sh"}

2
ntime Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
date +"%H:%M:%S %a %h %d %Y" | nenu -n

9
nwindow Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
# use with suckless's lsw and wmutils's chwso to show menu of mapped windows,
# select one then raise it.
LSW=/usr/local/bin/lsw
WINDOWS=`$LSW | cut -d' ' -f2- | nl -n rz -w $((\`$LSW | wc -l | wc -c\` - 1)) -s ' '`
N=`echo $WINDOWS | nenu -n | cut -d' ' -f1`
[ ! -z $N ] && $LSW | head -$N | tail -1 | cut -d' ' -f1 | xargs chwso -r