#define LEN(x) (sizeof (x) / sizeof (x[0]))
enum {ColorFG
, ColorBG
, ColorLast
};
/* draw context structure */
unsigned long unpressed
[ColorLast
];
unsigned long pressed
[ColorLast
];
unsigned long decoration
[ColorLast
];
/* menu geometry structure */
int itemb
; /* item border */
int itemw
; /* item width */
int itemh
; /* item height */
int border
; /* window border */
/* screen geometry structure */
int cursx
, cursy
; /* cursor position */
int screenw
, screenh
; /* screen width and height */
/* menu item structure */
/* function declarations */
static unsigned long getcolor(const char *s
);
static void setupdc(void);
static void setupgeom(void);
static void setupgrab(void);
static struct Item
*allocitem(size_t count
, const char *label
, const char *output
);
static struct Menu
*allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
);
static void getmenuitem(Window win
, int x
, int y
,
struct Menu
**menu_ret
, struct Item
**item_ret
);
static void drawmenu(void);
static void calcscreengeom(void);
static void calcmenu(struct Menu
*menu
);
static void setcurrmenu(struct Menu
*currmenu_new
);
static void parsestdin(void);
static void cleanupexit(void);
static Colormap colormap
;
static struct Menu
*rootmenu
= NULL
;
static struct Menu
*currmenu
= NULL
;
static struct Geometry geom
;
static struct ScreenGeometry sgeom
;
static Bool override_redirect
= True
;
main(int argc
, char *argv
[])
while ((ch
= getopt(argc
, argv
, "w")) != -1) {
override_redirect
= False
;
/* open connection to server and set X variables */
if ((dpy
= XOpenDisplay(NULL
)) == NULL
)
errx(1, "cannot open display");
screen
= DefaultScreen(dpy
);
rootwin
= RootWindow(dpy
, screen
);
colormap
= DefaultColormap(dpy
, screen
);
/* generate menus and recalculate them */
errx(1, "no menu generated");
return 1; /* UNREACHABLE */
/* get color from color string */
if(!XAllocNamedColor(dpy
, colormap
, s
, &color
, &color
))
errx(1, "cannot allocate color: %s", s
);
dc
.unpressed
[ColorBG
] = getcolor(UNPRESSEDBG
);
dc
.unpressed
[ColorFG
] = getcolor(UNPRESSEDFG
);
dc
.pressed
[ColorBG
] = getcolor(PRESSEDBG
);
dc
.pressed
[ColorFG
] = getcolor(PRESSEDFG
);
dc
.decoration
[ColorBG
] = getcolor(DECORATIONBG
);
dc
.decoration
[ColorFG
] = getcolor(DECORATIONFG
);
if ((dc
.font
= XLoadQueryFont(dpy
, FONT
)) == NULL
)
errx(1, "cannot load font");
dc
.fonth
= dc
.font
->ascent
+ dc
.font
->descent
;
/* create GC and set its font */
dc
.gc
= XCreateGC(dpy
, rootwin
, 0, NULL
);
XSetFont(dpy
, dc
.gc
, dc
.font
->fid
);
/* init menu geometry values */
geom
.itemh
= dc
.fonth
+ ITEMB
* 2;
XGrabPointer(dpy
, rootwin
, True
, ButtonPressMask
| ButtonReleaseMask
,
GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
allocitem(size_t count
, const char *label
, const char *output
)
if ((item
= malloc(sizeof *item
)) == NULL
)
if ((item
->label
= strdup(label
)) == NULL
)
if ((item
->output
= strdup(output
)) == NULL
)
item
->y
= count
* geom
.itemh
;
allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
)
XSetWindowAttributes swa
;
if ((menu
= malloc(sizeof *menu
)) == NULL
)
swa
.override_redirect
= override_redirect
;
swa
.background_pixel
= dc
.decoration
[ColorBG
];
swa
.border_pixel
= dc
.decoration
[ColorFG
];
swa
.event_mask
= ExposureMask
| KeyPressMask
| ButtonPressMask
| ButtonReleaseMask
menu
->win
= XCreateWindow(dpy
, rootwin
, 0, 0, geom
.itemw
, geom
.itemh
, geom
.border
,
CopyFromParent
, CopyFromParent
, CopyFromParent
,
CWOverrideRedirect
| CWBackPixel
| CWBorderPixel
| CWEventMask
,
/* create menus and items from the stdin */
struct Menu
*prevmenu
= NULL
;
size_t count
= 0; /* number of items in the current menu */
while (fgets(buf
, BUFSIZ
, stdin
) != NULL
) {
while (*s
!= '\0' && *s
!= '\t' && *s
!= '\n')
if (*s
!= '\0' && *s
!= '\n')
while (*s
!= '\0' && *s
!= '\n')
item
= allocitem(count
, label
, output
);
if (prevmenu
== NULL
) { /* there is no menu yet */
menu
= allocmenu(NULL
, item
, level
);
} else if (level
< prevmenu
->level
) { /* item is continuation of a parent menu*/
for (menu
= prevmenu
, i
= level
;
menu
!= NULL
&& i
< prevmenu
->level
;
menu
= menu
->parent
, i
++)
errx(1, "reached NULL menu");
for (p
= menu
->list
; p
->next
!= NULL
; p
= p
->next
)
} else if (level
== prevmenu
->level
) { /* item is a continuation of current menu */
for (p
= prevmenu
->list
; p
->next
!= NULL
; p
= p
->next
)
} else if (level
> prevmenu
->level
) { /* item begins a new menu */
menu
= allocmenu(prevmenu
, item
, level
);
for (p
= prevmenu
->list
; p
->next
!= NULL
; p
= p
->next
)
/* calculate screen geometry */
Window w1
, w2
; /* unused variables */
int a
, b
; /* unused variables */
unsigned mask
; /* unused variable */
XQueryPointer(dpy
, rootwin
, &w1
, &w2
, &sgeom
.cursx
, &sgeom
.cursy
, &a
, &b
, &mask
);
sgeom
.screenw
= DisplayWidth(dpy
, screen
);
sgeom
.screenh
= DisplayHeight(dpy
, screen
);
/* recursivelly calculate height and position of the menus */
calcmenu(struct Menu
*menu
)
/* calculate number of items */
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
)
menu
->h
= geom
.itemh
* i
;
/* calculate menu's x and y positions */
if (menu
->parent
== NULL
) { /* if root menu, calculate in respect to cursor */
if (sgeom
.screenw
- sgeom
.cursx
>= menu
->w
)
else if (sgeom
.cursx
> menu
->w
)
menu
->x
= sgeom
.cursx
- menu
->w
;
if (sgeom
.screenh
- sgeom
.cursy
>= menu
->h
)
else if (sgeom
.screenh
> menu
->h
)
menu
->y
= sgeom
.screenh
- menu
->h
;
} else { /* else, calculate in respect to parent menu */
/* search for the item in parent menu that generates this menu */
for (p
= menu
->parent
->list
; p
->submenu
!= menu
; p
= p
->next
)
if (sgeom
.screenw
- (menu
->parent
->x
+ menu
->parent
->w
) >= menu
->w
)
menu
->x
= menu
->parent
->x
+ menu
->parent
->w
;
else if (menu
->parent
->x
> menu
->w
)
menu
->x
= menu
->parent
->x
- menu
->w
;
if (sgeom
.screenh
- p
->y
> menu
->h
)
else if (sgeom
.screenh
- menu
->parent
->y
> menu
->h
)
menu
->y
= menu
->parent
->y
;
else if (sgeom
.screenh
> menu
->h
)
menu
->y
= sgeom
.screenh
- menu
->h
;
/* calculate position of each item in the menu */
for (i
= 0, item
= menu
->list
; item
!= NULL
; item
= item
->next
, i
++) {
item
->y
= menu
->y
+ i
* geom
.itemh
;
/* update menu geometry */
changes
.height
= menu
->h
;
XConfigureWindow(dpy
, menu
->win
, CWHeight
| CWX
| CWY
, &changes
);
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
->submenu
!= NULL
)
/* get menu and item of given window and position */
getmenuitem(Window win
, int x
, int y
,
struct Menu
**menu_ret
, struct Item
**item_ret
)
struct Menu
*menu
= NULL
;
struct Item
*item
= NULL
;
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (x
>= item
->x
&& x
<= item
->x
+ geom
.itemw
&&
y
>= item
->y
&& y
<= item
->y
+ geom
.itemh
) {
/* set currentmenu to menu, umap previous menus and map current menu and its parents */
setcurrmenu(struct Menu
*currmenu_new
)
if (currmenu_new
== currmenu
)
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
XUnmapWindow(dpy
, menu
->win
);
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
)
XMapWindow(dpy
, menu
->win
);
/* draw items of the current menu and of its ancestors */
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
size_t nitems
; /* number of items before current item */
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
/* determine item color */
if (item
== menu
->selected
)
/* calculate item's y position */
XSetForeground(dpy
, dc
.gc
, color
[ColorBG
]);
XFillRectangle(dpy
, menu
->win
, dc
.gc
, 0, y
,
labellen
= strlen(item
->label
);
labely
= y
+ dc
.fonth
+ geom
.itemb
;
XSetForeground(dpy
, dc
.gc
, color
[ColorFG
]);
XDrawString(dpy
, menu
->win
, dc
.gc
, labelx
, labely
, item
->label
, labellen
);
/* draw triangle, if item contains a submenu */
if (item
->submenu
!= NULL
) {
int trianglex
= geom
.itemw
- (geom
.itemb
+ dc
.fonth
);
int triangley
= y
+ geom
.itemb
;
{trianglex
+ dc
.fonth
, triangley
+ dc
.fonth
/2},
{trianglex
, triangley
+ dc
.fonth
},
XFillPolygon(dpy
, menu
->win
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
struct Item
*previtem
= NULL
;
while (!XNextEvent(dpy
, &ev
)) {
getmenuitem(ev
.xbutton
.window
, ev
.xbutton
.x_root
, ev
.xbutton
.y_root
,
if (menu
!= NULL
&& item
!= NULL
) {
if (item
->submenu
!= NULL
)
setcurrmenu(item
->submenu
);
} else if (menu
->selected
!= item
)
getmenuitem(ev
.xbutton
.window
, ev
.xbutton
.x_root
, ev
.xbutton
.y_root
,
if (menu
!= NULL
&& item
!= NULL
) {
if (item
->submenu
!= NULL
) {
setcurrmenu(item
->submenu
);
printf("%s\n", item
->output
);
(void)fprintf(stderr
, "usage: xmenu [-w] menuname\n");