#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 */
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 width */
int separator
; /* menu separator width */
/* screen geometry structure */
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
*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 */
/* 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(const char *label
, const char *output
);
static struct Menu
*allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
);
static void getmenuitem(Window win
, 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 freewindow(struct Menu
*menu
);
static void cleanup(void);
static Colormap colormap
;
static struct Menu
*rootmenu
= NULL
;
static struct Menu
*currmenu
= NULL
;
static struct Geometry geom
;
static struct ScreenGeometry screengeom
;
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");
XMapWindow(dpy
, rootmenu
->win
);
/* 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;
geom
.separator
= SEPARATOR
;
XGrabPointer(dpy
, rootwin
, True
, ButtonPressMask
| ButtonReleaseMask
,
GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
allocitem(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
->h
= item
->label
? geom
.itemh
: geom
.separator
;
item
->labellen
= strlen(item
->label
);
allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
)
XSetWindowAttributes swa
;
if ((menu
= malloc(sizeof *menu
)) == NULL
)
menu
->h
= 0; /* calculated by calcmenu() */
menu
->x
= 0; /* calculated by calcmenu() */
menu
->y
= 0; /* calculated by calcmenu() */
swa
.override_redirect
= override_redirect
;
swa
.background_pixel
= dc
.decoration
[ColorBG
];
swa
.border_pixel
= dc
.decoration
[ColorFG
];
swa
.event_mask
= ExposureMask
| KeyPressMask
| ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
| LeaveWindowMask
;
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 Item
*curritem
= NULL
; /* item currently being read */
struct Menu
*prevmenu
= NULL
; /* menu the previous item was added to */
struct Item
*item
; /* dummy item for for loops */
struct Menu
*menu
; /* dummy menu for for loops */
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')
curritem
= allocitem(label
, output
);
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*/
for (menu
= prevmenu
, i
= level
;
menu
!= NULL
&& i
< prevmenu
->level
;
menu
= menu
->parent
, i
++)
errx(1, "reached NULL menu");
for (item
= menu
->list
; item
->next
!= NULL
; item
= item
->next
)
} else if (level
== prevmenu
->level
) { /* item is a continuation of current 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
);
for (item
= prevmenu
->list
; item
->next
!= NULL
; item
= item
->next
)
/* calculate screen geometry */
Window w1
, w2
; /* unused variables */
int a
, b
; /* unused variables */
unsigned mask
; /* unused variable */
XQueryPointer(dpy
, rootwin
, &w1
, &w2
, &screengeom
.cursx
, &screengeom
.cursy
, &a
, &b
, &mask
);
screengeom
.screenw
= DisplayWidth(dpy
, screen
);
screengeom
.screenh
= DisplayHeight(dpy
, screen
);
/* recursivelly calculate height and position of the menus */
calcmenu(struct Menu
*menu
)
/* 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 */
menu
->h
+= geom
.separator
;
labelwidth
= XTextWidth(dc
.font
, item
->label
, item
->labellen
) + dc
.fonth
* 2;
menu
->w
= MAX(menu
->w
, labelwidth
);
/* calculate menu's x and y positions */
if (menu
->parent
== NULL
) { /* if root menu, calculate in respect to cursor */
if (screengeom
.screenw
- screengeom
.cursx
>= menu
->w
)
menu
->x
= screengeom
.cursx
;
else if (screengeom
.cursx
> menu
->w
)
menu
->x
= screengeom
.cursx
- menu
->w
;
if (screengeom
.screenh
- screengeom
.cursy
>= menu
->h
)
menu
->y
= screengeom
.cursy
;
else if (screengeom
.screenh
> menu
->h
)
menu
->y
= screengeom
.screenh
- menu
->h
;
} else { /* else, calculate in respect to parent menu */
/* search for the item in parent menu that generates this menu */
for (item
= menu
->parent
->list
; item
->submenu
!= menu
; item
= item
->next
)
if (screengeom
.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 (screengeom
.screenh
- (item
->y
+ menu
->parent
->y
) > menu
->h
)
menu
->y
= item
->y
+ menu
->parent
->y
;
else if (screengeom
.screenh
- menu
->parent
->y
> menu
->h
)
menu
->y
= menu
->parent
->y
;
else if (screengeom
.screenh
> menu
->h
)
menu
->y
= screengeom
.screenh
- menu
->h
;
/* update menu geometry */
changes
.height
= menu
->h
;
XConfigureWindow(dpy
, menu
->win
, CWWidth
| CWHeight
| CWX
| CWY
, &changes
);
/* set window manager size hints */
sizeh
.flags
= PMaxSize
| PMinSize
;
sizeh
.min_width
= sizeh
.max_width
= menu
->w
;
sizeh
.min_height
= sizeh
.max_height
= menu
->h
;
XSetWMNormalHints(dpy
, menu
->win
, &sizeh
);
menu
->pixmap
= XCreatePixmap(dpy
, menu
->win
, menu
->w
, menu
->h
,
DefaultDepth(dpy
, screen
));
/* calculate positions of submenus */
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 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 (y
>= item
->y
&& y
<= item
->y
+ item
->h
) {
/* set currentmenu to menu, umap previous menus and map current menu and its parents */
setcurrmenu(struct Menu
*currmenu_new
)
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 */
if (currmenu_new
== currmenu
)
/* find lowest common ancestor menu */
minlevel
= MIN(currmenu_new
->level
, currmenu
->level
);
maxlevel
= MAX(currmenu_new
->level
, currmenu
->level
);
if (currmenu_new
->level
== maxlevel
) {
while (menu
->level
> minlevel
)
/* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
for (menu
= currmenu
; 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 items of the current menu and of its ancestors */
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
/* determine item color */
else if (item
== menu
->selected
)
XSetForeground(dpy
, dc
.gc
, color
[ColorBG
]);
XDrawRectangle(dpy
, menu
->pixmap
, dc
.gc
, 0, item
->y
,
XFillRectangle(dpy
, menu
->pixmap
, dc
.gc
, 0, item
->y
,
/* continue if item is a separator */
labely
= item
->y
+ dc
.fonth
+ geom
.itemb
;
XSetForeground(dpy
, dc
.gc
, color
[ColorFG
]);
XDrawString(dpy
, menu
->pixmap
, dc
.gc
, labelx
, labely
,
item
->label
, item
->labellen
);
/* draw triangle, if item contains a submenu */
if (item
->submenu
!= NULL
) {
int trianglex
= menu
->w
- dc
.fonth
+ geom
.itemb
- 1;
int triangley
= item
->y
+ (3 * item
->h
)/8 -1;
{trianglex
+ item
->h
/8 + 1, item
->y
+ item
->h
/2},
{trianglex
, triangley
+ item
->h
/4 + 2},
XFillPolygon(dpy
, menu
->pixmap
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
XCopyArea(dpy
, menu
->pixmap
, menu
->win
, dc
.gc
, 0, item
->y
,
menu
->w
, item
->h
, 0, item
->y
);
struct Item
*previtem
= NULL
;
while (!XNextEvent(dpy
, &ev
)) {
getmenuitem(ev
.xbutton
.window
, ev
.xbutton
.y
, &menu
, &item
);
if (menu
!= NULL
&& item
!= NULL
) {
if (item
->submenu
!= NULL
)
setcurrmenu(item
->submenu
);
} else if (menu
->selected
!= item
) {
getmenuitem(ev
.xbutton
.window
, ev
.xbutton
.y
, &menu
, &item
);
if (menu
!= NULL
&& item
!= NULL
) {
break; /* ignore separators */
if (item
->submenu
!= NULL
) {
setcurrmenu(item
->submenu
);
printf("%s\n", item
->output
);
currmenu
->selected
= NULL
;
/* recursivelly free a pixmap */
freewindow(struct Menu
*menu
)
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
)
if (item
->submenu
!= NULL
)
freewindow(item
->submenu
);
XFreePixmap(dpy
, menu
->pixmap
);
XDestroyWindow(dpy
, menu
->win
);
(void)fprintf(stderr
, "usage: xmenu [-w]\n");