abab13dcae6eddbf5a2b5e73d1fc680596e58f93
#include <X11/Xresource.h>
#define LEN(x) (sizeof (x) / sizeof (x[0]))
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
enum {ColorFG
, ColorBG
, ColorLast
};
/* draw context structure */
XftColor normal
[ColorLast
];
XftColor selected
[ColorLast
];
/* menu geometry structure */
int border
; /* window border width */
int separator
; /* menu separator width */
int itemw
, itemh
; /* item width and height */
int cursx
, cursy
; /* cursor position */
int screenw
, screenh
; /* screen width and height */
/* menu item structure */
char *label
; /* string to be drawed on menu */
char *output
; /* string to be outputed when item is clicked */
int y
; /* item y position relative to menu */
size_t labellen
; /* strlen(label) */
struct Item
*prev
; /* previous item */
struct Item
*next
; /* next item */
struct Menu
*submenu
; /* submenu spawned by clicking on item */
struct Menu
*parent
; /* parent menu */
struct Item
*caller
; /* item that spawned the menu */
struct Item
*list
; /* list of items contained by the menu */
struct Item
*selected
; /* item currently selected in the menu */
int x
, y
, w
, h
; /* menu geometry */
unsigned level
; /* menu level relative to root */
Drawable pixmap
; /* pixmap to draw the menu on */
Window win
; /* menu window to map on the screen */
/* functions declarations */
static void getresources(void);
static void getcolor(const char *s
, XftColor
*color
);
static void setupdc(void);
static void calcgeom(struct Geometry
*geom
);
static struct Item
*allocitem(const char *label
, const char *output
);
static struct Menu
*allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
);
static struct Menu
*buildmenutree(unsigned level
, const char *label
, const char *output
);
static struct Menu
*parsestdin(void);
static void calcmenu(struct Geometry
*geom
, struct Menu
*menu
);
static void grabpointer(void);
static void grabkeyboard(void);
static struct Menu
*getmenu(struct Menu
*currmenu
, Window win
);
static struct Item
*getitem(struct Menu
*menu
, int y
);
static void mapmenu(struct Menu
*currmenu
);
static void drawseparator(struct Menu
*menu
, struct Item
*item
);
static void drawitem(struct Menu
*menu
, struct Item
*item
, XftColor
*color
);
static void drawmenu(struct Menu
*currmenu
);
static struct Item
*itemcycle(struct Menu
*currmenu
, int direction
);
static void run(struct Menu
*currmenu
);
static void freemenu(struct Menu
*menu
);
static void cleanup(void);
static Colormap colormap
;
/* xmenu: generate menu from stdin and print selected entry to stdout */
main(int argc
, char *argv
[])
while ((ch
= getopt(argc
, argv
, "")) != -1) {
/* open connection to server and set X variables */
if ((dpy
= XOpenDisplay(NULL
)) == NULL
)
errx(1, "cannot open display");
screen
= DefaultScreen(dpy
);
visual
= DefaultVisual(dpy
, screen
);
rootwin
= RootWindow(dpy
, screen
);
colormap
= DefaultColormap(dpy
, screen
);
/* generate menus and recalculate them */
errx(1, "no menu generated");
calcmenu(&geom
, rootmenu
);
/* grab mouse and keyboard */
/* read xrdb for configuration options */
if ((xrm
= XResourceManagerString(dpy
)) == NULL
)
xdb
= XrmGetStringDatabase(xrm
);
if (XrmGetResource(xdb
, "xmenu.borderWidth", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.separatorWidth", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.padding", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.width", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.background", "*", &type
, &xval
) == True
)
background_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.foreground", "*", &type
, &xval
) == True
)
foreground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.selbackground", "*", &type
, &xval
) == True
)
selbackground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.selforeground", "*", &type
, &xval
) == True
)
selforeground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.separator", "*", &type
, &xval
) == True
)
separator_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.border", "*", &type
, &xval
) == True
)
border_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.font", "*", &type
, &xval
) == True
)
font
= strdup(xval
.addr
);
/* get color from color string */
getcolor(const char *s
, XftColor
*color
)
if(!XftColorAllocName(dpy
, visual
, colormap
, s
, color
))
errx(1, "cannot allocate color: %s", s
);
getcolor(background_color
, &dc
.normal
[ColorBG
]);
getcolor(foreground_color
, &dc
.normal
[ColorFG
]);
getcolor(selbackground_color
, &dc
.selected
[ColorBG
]);
getcolor(selforeground_color
, &dc
.selected
[ColorFG
]);
getcolor(separator_color
, &dc
.separator
);
getcolor(border_color
, &dc
.border
);
if ((dc
.font
= XftFontOpenName(dpy
, screen
, font
)) == NULL
)
errx(1, "cannot load font");
dc
.gc
= XCreateGC(dpy
, rootwin
, 0, NULL
);
/* calculate menu and screen geometry */
calcgeom(struct Geometry
*geom
)
Window w1
, w2
; /* unused variables */
int a
, b
; /* unused variables */
unsigned mask
; /* unused variable */
XQueryPointer(dpy
, rootwin
, &w1
, &w2
, &geom
->cursx
, &geom
->cursy
, &a
, &b
, &mask
);
geom
->screenw
= DisplayWidth(dpy
, screen
);
geom
->screenh
= DisplayHeight(dpy
, screen
);
geom
->itemh
= dc
.font
->height
+ padding_pixels
* 2;
geom
->itemw
= width_pixels
;
geom
->border
= border_pixels
;
geom
->separator
= separator_pixels
;
allocitem(const char *label
, const char *output
)
if ((item
= malloc(sizeof *item
)) == NULL
)
if ((item
->label
= strdup(label
)) == NULL
)
item
->output
= item
->label
;
if ((item
->output
= strdup(output
)) == NULL
)
item
->labellen
= strlen(item
->label
);
allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
)
XSetWindowAttributes swa
;
if ((menu
= malloc(sizeof *menu
)) == NULL
)
menu
->w
= 0; /* calculated by calcmenu() */
menu
->h
= 0; /* calculated by calcmenu() */
menu
->x
= 0; /* calculated by calcmenu() */
menu
->y
= 0; /* calculated by calcmenu() */
swa
.override_redirect
= True
;
swa
.background_pixel
= dc
.normal
[ColorBG
].pixel
;
swa
.border_pixel
= dc
.border
.pixel
;
swa
.save_under
= True
; /* pop-up windows should save_under*/
swa
.event_mask
= ExposureMask
| KeyPressMask
| ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
| LeaveWindowMask
;
menu
->win
= XCreateWindow(dpy
, rootwin
, 0, 0, 1, 1, 0,
CopyFromParent
, CopyFromParent
, CopyFromParent
,
CWOverrideRedirect
| CWBackPixel
|
CWBorderPixel
| CWEventMask
| CWSaveUnder
,
/* build the menu tree */
buildmenutree(unsigned level
, const char *label
, const char *output
)
static struct Menu
*prevmenu
= NULL
; /* menu the previous item was added to */
static struct Menu
*rootmenu
= NULL
; /* menu to be returned */
struct Item
*curritem
= NULL
; /* item currently being read */
struct Item
*item
; /* dummy item for loops */
struct Menu
*menu
; /* dummy menu for loops */
curritem
= allocitem(label
, output
);
/* put the item in the menu tree */
if (prevmenu
== NULL
) { /* there is no menu yet */
menu
= allocmenu(NULL
, curritem
, level
);
} else if (level
< prevmenu
->level
) { /* item is continuation of a parent menu */
/* go up the menu tree until find the menu this item continues */
for (menu
= prevmenu
, i
= level
;
menu
!= NULL
&& i
!= prevmenu
->level
;
menu
= menu
->parent
, i
++)
errx(1, "reached NULL menu");
/* find last item in the new menu */
for (item
= menu
->list
; item
->next
!= NULL
; item
= item
->next
)
} else if (level
== prevmenu
->level
) { /* item is a continuation of current menu */
/* find last item in the previous menu */
for (item
= prevmenu
->list
; item
->next
!= NULL
; item
= item
->next
)
} else if (level
> prevmenu
->level
) { /* item begins a new menu */
menu
= allocmenu(prevmenu
, curritem
, level
);
/* find last item in the previous menu */
for (item
= prevmenu
->list
; item
->next
!= NULL
; item
= item
->next
)
/* create menus and items from the stdin */
while (fgets(buf
, BUFSIZ
, stdin
) != NULL
) {
/* get the indentation level */
level
= strspn(buf
, "\t");
label
= strtok(s
, "\t\n");
output
= strtok(NULL
, "\n");
rootmenu
= buildmenutree(level
, label
, output
);
/* recursivelly calculate menu geometry and set window hints */
calcmenu(struct Geometry
*geom
, struct Menu
*menu
)
static XClassHint classh
= {PROGNAME
, PROGNAME
};
/* calculate items positions and menu width and height */
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
->label
== NULL
) /* height for separator item */
item
->h
= geom
->separator
;
XftTextExtentsUtf8(dpy
, dc
.font
, (XftChar8
*)item
->label
,
labelwidth
= ext
.xOff
+ dc
.font
->height
* 2;
menu
->w
= MAX(menu
->w
, labelwidth
);
/* calculate menu's x and y positions */
width
= menu
->w
+ geom
->border
* 2;
height
= menu
->h
+ geom
->border
* 2;
if (menu
->parent
== NULL
) { /* if root menu, calculate in respect to cursor */
if (geom
->screenw
- geom
->cursx
>= menu
->w
)
else if (geom
->cursx
> width
)
menu
->x
= geom
->cursx
- width
;
if (geom
->screenh
- geom
->cursy
>= height
)
else if (geom
->screenh
> height
)
menu
->y
= geom
->screenh
- height
;
} else { /* else, calculate in respect to parent menu */
if (geom
->screenw
- (menu
->parent
->x
+ menu
->parent
->w
+ geom
->border
) >= width
)
menu
->x
= menu
->parent
->x
+ menu
->parent
->w
+ geom
->border
;
else if (menu
->parent
->x
> menu
->w
+ geom
->border
)
menu
->x
= menu
->parent
->x
- menu
->w
- geom
->border
;
if (geom
->screenh
- (menu
->caller
->y
+ menu
->parent
->y
) > height
)
menu
->y
= menu
->caller
->y
+ menu
->parent
->y
;
else if (geom
->screenh
- menu
->parent
->y
> height
)
menu
->y
= menu
->parent
->y
;
else if (geom
->screenh
> height
)
menu
->y
= geom
->screenh
- height
;
/* update menu geometry */
changes
.border_width
= geom
->border
;
changes
.height
= menu
->h
;
XConfigureWindow(dpy
, menu
->win
, CWBorderWidth
| CWWidth
| CWHeight
| CWX
| CWY
, &changes
);
/* set window manager hints */
sizeh
.flags
= PMaxSize
| PMinSize
;
sizeh
.min_width
= sizeh
.max_width
= menu
->w
;
sizeh
.min_height
= sizeh
.max_height
= menu
->h
;
XSetWMProperties(dpy
, menu
->win
, NULL
, NULL
, NULL
, 0, &sizeh
,
/* create pixmap and XftDraw */
menu
->pixmap
= XCreatePixmap(dpy
, menu
->win
, menu
->w
, menu
->h
,
DefaultDepth(dpy
, screen
));
menu
->draw
= XftDrawCreate(dpy
, menu
->pixmap
, visual
, colormap
);
/* calculate positions of submenus */
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
->submenu
!= NULL
)
calcmenu(geom
, item
->submenu
);
/* try to grab pointer, we may have to wait for another process to ungrab */
struct timespec ts
= { .tv_sec
= 0, .tv_nsec
= 1000000 };
for (i
= 0; i
< 1000; i
++) {
if (XGrabPointer(dpy
, rootwin
, True
, ButtonPressMask
,
GrabModeAsync
, GrabModeAsync
, None
,
None
, CurrentTime
) == GrabSuccess
)
errx(1, "cannot grab keyboard");
/* try to grab keyboard, we may have to wait for another process to ungrab */
struct timespec ts
= { .tv_sec
= 0, .tv_nsec
= 1000000 };
for (i
= 0; i
< 1000; i
++) {
if (XGrabKeyboard(dpy
, rootwin
, True
, GrabModeAsync
,
GrabModeAsync
, CurrentTime
) == GrabSuccess
)
errx(1, "cannot grab keyboard");
/* get menu of given window */
getmenu(struct Menu
*currmenu
, Window win
)
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
)
/* get item of given menu and position */
getitem(struct Menu
*menu
, int y
)
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
)
if (y
>= item
->y
&& y
<= item
->y
+ item
->h
)
/* umap previous menus and map current menu and its parents */
mapmenu(struct Menu
*currmenu
)
static struct Menu
*prevmenu
= NULL
;
struct Menu
*menu
, *menu_
;
struct Menu
*lcamenu
; /* lowest common ancestor menu */
unsigned minlevel
; /* level of the closest to root menu */
unsigned maxlevel
; /* level of the closest to root menu */
/* do not remap current menu if it wasn't updated*/
if (prevmenu
== currmenu
)
/* if this is the first time mapping, skip calculations */
XMapWindow(dpy
, currmenu
->win
);
/* find lowest common ancestor menu */
minlevel
= MIN(currmenu
->level
, prevmenu
->level
);
maxlevel
= MAX(currmenu
->level
, prevmenu
->level
);
if (currmenu
->level
== maxlevel
) {
while (menu
->level
> minlevel
)
/* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
for (menu
= prevmenu
; menu
!= lcamenu
; menu
= menu
->parent
) {
XUnmapWindow(dpy
, menu
->win
);
/* map menus from currmenu (inclusive) until lcamenu (exclusive) */
for (menu
= currmenu
; menu
!= lcamenu
; menu
= menu
->parent
) {
XMapWindow(dpy
, menu
->win
);
/* draw separator item */
drawseparator(struct Menu
*menu
, struct Item
*item
)
XSetForeground(dpy
, dc
.gc
, dc
.separator
.pixel
);
XDrawLine(dpy
, menu
->pixmap
, dc
.gc
, 0, y
, menu
->w
, y
);
drawitem(struct Menu
*menu
, struct Item
*item
, XftColor
*color
)
y
= item
->y
+ item
->h
/2 + dc
.font
->ascent
/2 - 1;
XSetForeground(dpy
, dc
.gc
, color
[ColorFG
].pixel
);
XftDrawStringUtf8(menu
->draw
, &color
[ColorFG
], dc
.font
,
x
, y
, item
->label
, item
->labellen
);
/* draw triangle, if item contains a submenu */
if (item
->submenu
!= NULL
) {
x
= menu
->w
- dc
.font
->height
/2 - triangle_width
/2;
y
= item
->y
+ item
->h
/2 - triangle_height
/2 - 1;
{x
+ triangle_width
, y
+ triangle_height
/2},
{x
, y
+ triangle_height
},
XFillPolygon(dpy
, menu
->pixmap
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
/* draw items of the current menu and of its ancestors */
drawmenu(struct Menu
*currmenu
)
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
/* determine item color */
if (item
== menu
->selected
&& item
->label
!= NULL
)
XSetForeground(dpy
, dc
.gc
, color
[ColorBG
].pixel
);
XFillRectangle(dpy
, menu
->pixmap
, dc
.gc
, 0, item
->y
,
if (item
->label
== NULL
) /* item is a separator */
drawseparator(menu
, item
);
else /* item is a regular item */
drawitem(menu
, item
, color
);
XCopyArea(dpy
, menu
->pixmap
, menu
->win
, dc
.gc
, 0, 0,
/* cycle through the items; non-zero direction is next, zero is prev */
itemcycle(struct Menu
*currmenu
, int direction
)
if (direction
== ITEMNEXT
) {
if (currmenu
->selected
== NULL
)
else if (currmenu
->selected
->next
!= NULL
)
item
= currmenu
->selected
->next
;
while (item
!= NULL
&& item
->label
== NULL
)
for (lastitem
= currmenu
->list
;
lastitem
!= NULL
&& lastitem
->next
!= NULL
;
lastitem
= lastitem
->next
)
if (currmenu
->selected
== NULL
)
else if (currmenu
->selected
->prev
!= NULL
)
item
= currmenu
->selected
->prev
;
while (item
!= NULL
&& item
->label
== NULL
)
run(struct Menu
*currmenu
)
struct Item
*previtem
= NULL
;
while (!XNextEvent(dpy
, &ev
)) {
if (ev
.xexpose
.count
== 0)
menu
= getmenu(currmenu
, ev
.xbutton
.window
);
item
= getitem(menu
, ev
.xbutton
.y
);
if (menu
== NULL
|| item
== NULL
|| previtem
== item
)
if (item
->submenu
!= NULL
) {
currmenu
= item
->submenu
;
currmenu
->selected
= NULL
;
menu
= getmenu(currmenu
, ev
.xbutton
.window
);
item
= getitem(menu
, ev
.xbutton
.y
);
if (menu
== NULL
|| item
== NULL
)
break; /* ignore separators */
if (item
->submenu
!= NULL
) {
currmenu
= item
->submenu
;
printf("%s\n", item
->output
);
currmenu
->selected
= currmenu
->list
;
menu
= getmenu(currmenu
, ev
.xbutton
.window
);
ksym
= XkbKeycodeToKeysym(dpy
, ev
.xkey
.keycode
, 0, 0);
/* esc closes xmenu when current menu is the root menu */
if (ksym
== XK_Escape
&& currmenu
->parent
== NULL
)
/* Shift-Tab = ISO_Left_Tab */
if (ksym
== XK_Tab
&& (ev
.xkey
.state
& ShiftMask
))
if (ksym
== XK_ISO_Left_Tab
|| ksym
== XK_Up
) {
item
= itemcycle(currmenu
, ITEMPREV
);
} else if (ksym
== XK_Tab
|| ksym
== XK_Down
) {
item
= itemcycle(currmenu
, ITEMNEXT
);
} else if ((ksym
== XK_Return
|| ksym
== XK_Right
) &&
currmenu
->selected
!= NULL
) {
item
= currmenu
->selected
;
} else if ((ksym
== XK_Escape
|| ksym
== XK_Left
) &&
currmenu
->parent
!= NULL
) {
item
= currmenu
->parent
->selected
;
currmenu
= currmenu
->parent
;
currmenu
->selected
= item
;
currmenu
->selected
= NULL
;
/* recursivelly free pixmaps and destroy windows */
freemenu(struct Menu
*menu
)
if (item
->submenu
!= NULL
)
if (tmp
->label
!= tmp
->output
)
XFreePixmap(dpy
, menu
->pixmap
);
XftDrawDestroy(menu
->draw
);
XDestroyWindow(dpy
, menu
->win
);
XUngrabPointer(dpy
, CurrentTime
);
XUngrabKeyboard(dpy
, CurrentTime
);
XftColorFree(dpy
, visual
, colormap
, &dc
.normal
[ColorBG
]);
XftColorFree(dpy
, visual
, colormap
, &dc
.normal
[ColorFG
]);
XftColorFree(dpy
, visual
, colormap
, &dc
.selected
[ColorBG
]);
XftColorFree(dpy
, visual
, colormap
, &dc
.selected
[ColorFG
]);
XftColorFree(dpy
, visual
, colormap
, &dc
.separator
);
XftColorFree(dpy
, visual
, colormap
, &dc
.border
);
(void)fprintf(stderr
, "usage: xmenu\n");