#include <X11/Xresource.h>
#include <X11/extensions/Xinerama.h>
static void parseposition(char *optarg
);
/* initializers, and their helper routines */
static void parsefonts(const char *s
);
static void ealloccolor(const char *s
, XftColor
*color
);
static void initmonitor(void);
static void initresources(void);
static void initdc(void);
static void initconfig(void);
static void initatoms(void);
/* structure builders, and their helper routines */
static struct Item
*allocitem(const char *label
, const char *output
, char *file
);
static struct Menu
*allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
);
static struct Menu
*buildmenutree(unsigned level
, const char *label
, const char *output
, char *file
);
static struct Menu
*parsestdin(void);
/* text drawer, and its helper routine */
static FcChar32
getnextutf8char(const char *s
, const char **end_ret
);
static int drawtext(XftDraw
*draw
, XftColor
*color
, int x
, int y
, unsigned h
, const char *text
);
/* structure setters, and their helper routines */
static void setupitems(struct Menu
*menu
);
static void setupmenupos(struct Menu
*menu
);
static void setupmenu(struct Menu
*menu
, XClassHint
*classh
);
static void grabpointer(void);
static void grabkeyboard(void);
/* item drawer, and its helper routine */
static Imlib_Image
loadicon(const char *file
);
static void drawitems(struct Menu
*menu
);
/* menu drawers and mappers */
static void drawmenus(struct Menu
*currmenu
);
static void mapmenu(struct Menu
*currmenu
);
static struct Menu
*getmenu(struct Menu
*currmenu
, Window win
);
static struct Item
*getitem(struct Menu
*menu
, int y
);
/* cycle through items */
static struct Item
*itemcycle(struct Menu
*currmenu
, int direction
);
static void run(struct Menu
*currmenu
);
static void cleanmenu(struct Menu
*menu
);
static void cleanup(void);
static Colormap colormap
;
static struct Monitor mon
;
static Atom netatom
[NetLast
];
static int iflag
= 0; /* whether to disable icons */
static int mflag
= 0; /* whether the user specified a monitor with -p */
static int pflag
= 0; /* whether the user specified a position with -p */
static int wflag
= 0; /* whether to let the window manager control XMenu */
/* include config variable */
* Function implementations
/* xmenu: generate menu from stdin and print selected entry to stdout */
main(int argc
, char *argv
[])
while ((ch
= getopt(argc
, argv
, "ip:w")) != -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
);
imlib_set_cache_size(2048 * 1024);
imlib_context_set_dither(1);
imlib_context_set_display(dpy
);
imlib_context_set_visual(visual
);
imlib_context_set_colormap(colormap
);
classh
.res_class
= PROGNAME
;
classh
.res_name
= PROGNAME
;
/* generate menus and set them up */
errx(1, "no menu generated");
setupmenu(rootmenu
, &classh
);
/* grab mouse and keyboard */
/* parse position string from -p,
* put results on config.posx, config.posy, and config.monitor */
parseposition(char *optarg
)
n
= strtol(s
, &endp
, 10);
if (errno
== ERANGE
|| n
> INT_MAX
|| n
< 0 || endp
== s
|| *endp
!= 'x')
n
= strtol(s
, &endp
, 10);
if (errno
== ERANGE
|| n
> INT_MAX
|| n
< 0 || endp
== s
)
if (strncasecmp(s
, "CUR", 3) == 0) {
n
= strtol(s
, &endp
, 10);
if (errno
== ERANGE
|| n
> INT_MAX
|| n
< 0 || endp
== s
|| *endp
!= '\0')
} else if (*endp
!= '\0') {
errx(1, "improper position: %s", optarg
);
parsefonts(const char *s
)
if ((dc
.fonts
= calloc(dc
.nfonts
, sizeof *dc
.fonts
)) == NULL
)
while (*p
!= '\0' && *p
!= ',') {
if ((dc
.fonts
[nfont
++] = XftFontOpenName(dpy
, screen
, buf
)) == NULL
)
errx(1, "cannot load font");
/* get color from color string */
ealloccolor(const char *s
, XftColor
*color
)
if(!XftColorAllocName(dpy
, visual
, colormap
, s
, color
))
errx(1, "cannot allocate color: %s", s
);
/* query monitor information and cursor position */
XineramaScreenInfo
*info
= NULL
;
Window dw
; /* dummy variable */
int di
; /* dummy variable */
unsigned du
; /* dummy variable */
int cursx
, cursy
; /* cursor position */
XQueryPointer(dpy
, rootwin
, &dw
, &dw
, &cursx
, &cursy
, &di
, &di
, &du
);
mon
.w
= DisplayWidth(dpy
, screen
);
mon
.h
= DisplayHeight(dpy
, screen
);
if ((info
= XineramaQueryScreens(dpy
, &nmons
)) != NULL
) {
if (!mflag
|| (mflag
&& (config
.monitor
< 0 || config
.monitor
>= nmons
))) {
for (i
= 0; i
< nmons
; i
++) {
if (cursx
>= info
[i
].x_org
&& cursx
<= info
[i
].x_org
+ info
[i
].width
&&
cursy
>= info
[i
].y_org
&& cursy
<= info
[i
].y_org
+ info
[i
].height
) {
mon
.x
= info
[selmon
].x_org
;
mon
.y
= info
[selmon
].y_org
;
mon
.w
= info
[selmon
].width
;
mon
.h
= info
[selmon
].height
;
/* 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)
config
.border_pixels
= n
;
if (XrmGetResource(xdb
, "xmenu.separatorWidth", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
config
.separator_pixels
= n
;
if (XrmGetResource(xdb
, "xmenu.height", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
config
.height_pixels
= n
;
if (XrmGetResource(xdb
, "xmenu.width", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.gap", "*", &type
, &xval
) == True
)
if ((n
= strtol(xval
.addr
, NULL
, 10)) > 0)
if (XrmGetResource(xdb
, "xmenu.background", "*", &type
, &xval
) == True
)
config
.background_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.foreground", "*", &type
, &xval
) == True
)
config
.foreground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.selbackground", "*", &type
, &xval
) == True
)
config
.selbackground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.selforeground", "*", &type
, &xval
) == True
)
config
.selforeground_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.separator", "*", &type
, &xval
) == True
)
config
.separator_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.border", "*", &type
, &xval
) == True
)
config
.border_color
= strdup(xval
.addr
);
if (XrmGetResource(xdb
, "xmenu.font", "*", &type
, &xval
) == True
)
config
.font
= strdup(xval
.addr
);
ealloccolor(config
.background_color
, &dc
.normal
[ColorBG
]);
ealloccolor(config
.foreground_color
, &dc
.normal
[ColorFG
]);
ealloccolor(config
.selbackground_color
, &dc
.selected
[ColorBG
]);
ealloccolor(config
.selforeground_color
, &dc
.selected
[ColorFG
]);
ealloccolor(config
.separator_color
, &dc
.separator
);
ealloccolor(config
.border_color
, &dc
.border
);
dc
.gc
= XCreateGC(dpy
, rootwin
, 0, NULL
);
/* calculate configuration values that are not set manually */
config
.screenw
= DisplayWidth(dpy
, screen
);
config
.screenh
= DisplayHeight(dpy
, screen
);
config
.iconsize
= config
.height_pixels
- config
.iconpadding
* 2;
utf8string
= XInternAtom(dpy
, "UTF8_STRING", False
);
wmdelete
= XInternAtom(dpy
, "WM_DELETE_WINDOW", False
);
netatom
[NetWMName
] = XInternAtom(dpy
, "_NET_WM_NAME", False
);
netatom
[NetWMWindowType
] = XInternAtom(dpy
, "_NET_WM_WINDOW_TYPE", False
);
netatom
[NetWMWindowTypePopupMenu
] = XInternAtom(dpy
, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False
);
allocitem(const char *label
, const char *output
, char *file
)
if ((item
= malloc(sizeof *item
)) == NULL
)
if ((item
->label
= strdup(label
)) == NULL
)
item
->output
= item
->label
;
if ((item
->output
= strdup(output
)) == NULL
)
if ((item
->file
= strdup(file
)) == NULL
)
item
->labellen
= strlen(item
->label
);
/* allocate a menu and create its window */
allocmenu(struct Menu
*parent
, struct Item
*list
, unsigned level
)
XSetWindowAttributes swa
;
if ((menu
= malloc(sizeof *menu
)) == NULL
)
menu
->w
= 0; /* calculated by setupmenu() */
menu
->h
= 0; /* calculated by setupmenu() */
menu
->x
= 0; /* calculated by setupmenu() */
menu
->y
= 0; /* calculated by setupmenu() */
swa
.override_redirect
= (wflag
) ? False
: 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
;
swa
.event_mask
|= StructureNotifyMask
;
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
, char *file
)
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
, file
);
/* 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 */
char *file
, *label
, *output
;
while (fgets(buf
, BUFSIZ
, stdin
) != NULL
) {
/* get the indentation level */
level
= strspn(buf
, "\t");
label
= strtok(s
, "\t\n");
if (label
!= NULL
&& strncmp(label
, "IMG:", 4) == 0) {
label
= strtok(NULL
, "\t\n");
output
= strtok(NULL
, "\n");
rootmenu
= buildmenutree(level
, label
, output
, file
);
/* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */
getnextutf8char(const char *s
, const char **next_ret
)
static const unsigned char utfbyte
[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
static const unsigned char utfmask
[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const FcChar32 utfmin
[] = {0, 0x00, 0x80, 0x800, 0x10000};
static const FcChar32 utfmax
[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
/* 0xFFFD is the replacement character, used to represent unknown characters */
static const FcChar32 unknown
= 0xFFFD;
FcChar32 ucode
; /* FcChar32 type holds 32 bits */
size_t usize
= 0; /* n' of bytes of the utf8 character */
/* get code of first byte of utf8 character */
for (i
= 0; i
< sizeof utfmask
; i
++) {
if (((unsigned char)*s
& utfmask
[i
]) == utfbyte
[i
]) {
ucode
= (unsigned char)*s
& ~utfmask
[i
];
/* if first byte is a continuation byte or is not allowed, return unknown */
if (i
== sizeof utfmask
|| usize
== 0)
/* check the other usize-1 bytes */
for (i
= 1; i
< usize
; i
++) {
/* if byte is nul or is not a continuation byte, return unknown */
if (*s
== '\0' || ((unsigned char)*s
& utfmask
[0]) != utfbyte
[0])
/* 6 is the number of relevant bits in the continuation byte */
ucode
= (ucode
<< 6) | ((unsigned char)*s
& ~utfmask
[0]);
/* check if ucode is invalid or in utf-16 surrogate halves */
if (!BETWEEN(ucode
, utfmin
[usize
], utfmax
[usize
])
|| BETWEEN (ucode
, 0xD800, 0xDFFF))
/* draw text into XftDraw */
drawtext(XftDraw
*draw
, XftColor
*color
, int x
, int y
, unsigned h
, const char *text
)
ucode
= getnextutf8char(s
, &nexts
);
for (i
= 0; i
< dc
.nfonts
; i
++) {
charexists
= XftCharExists(dpy
, dc
.fonts
[i
], ucode
);
XftTextExtentsUtf8(dpy
, currfont
, (XftChar8
*)s
,
texty
= y
+ (h
+ currfont
->ascent
) / 2;
XftDrawStringUtf8(draw
, color
, currfont
, x
, texty
,
/* setup the height, width and icon of the items of a menu */
setupitems(struct Menu
*menu
)
menu
->w
= config
.width_pixels
;
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
->label
== NULL
) /* height for separator item */
item
->h
= config
.separator_pixels
;
item
->h
= config
.height_pixels
;
textwidth
= drawtext(NULL
, NULL
, 0, 0, item
->h
, item
->label
);
* the item width depends on the size of its label (textwidth),
* and it is only used to calculate the width of the menu (which
* is equal to the width of the largest item).
* the horizontal padding appears 4 times through the width of a
* item: before and after its icon, and before and after its triangle.
* if the iflag is set (icons are disabled) then the horizontal
* padding appears 3 times: before the label and around the triangle.
itemwidth
= textwidth
+ config
.triangle_width
+ config
.horzpadding
* 3;
itemwidth
+= (iflag
) ? 0 : config
.iconsize
+ config
.horzpadding
;
menu
->w
= MAX(menu
->w
, itemwidth
);
/* setup the position of a menu */
setupmenupos(struct Menu
*menu
)
width
= menu
->w
+ config
.border_pixels
* 2;
height
= menu
->h
+ config
.border_pixels
* 2;
if (menu
->parent
== NULL
) { /* if root menu, calculate in respect to cursor */
if (pflag
|| (config
.posx
> mon
.x
&& mon
.x
+ mon
.w
- config
.posx
>= width
))
else if (config
.posx
> width
)
menu
->x
= config
.posx
- width
;
if (pflag
|| (config
.posy
> mon
.y
&& mon
.y
+ mon
.h
- config
.posy
>= height
))
else if (config
.screenh
> height
)
menu
->y
= mon
.y
+ mon
.h
- height
;
} else { /* else, calculate in respect to parent menu */
parentwidth
= menu
->parent
->x
+ menu
->parent
->w
+ config
.border_pixels
+ config
.gap_pixels
;
if (mon
.x
+ mon
.w
- parentwidth
>= width
)
else if (menu
->parent
->x
> menu
->w
+ config
.border_pixels
+ config
.gap_pixels
)
menu
->x
= menu
->parent
->x
- menu
->w
- config
.border_pixels
- config
.gap_pixels
;
if (mon
.y
+ mon
.h
- (menu
->caller
->y
+ menu
->parent
->y
) > height
)
menu
->y
= menu
->caller
->y
+ menu
->parent
->y
;
else if (mon
.y
+ mon
.h
- menu
->parent
->y
> height
)
menu
->y
= menu
->parent
->y
;
else if (mon
.y
+ mon
.h
> height
)
menu
->y
= mon
.y
+ mon
.h
- height
;
/* recursivelly setup menu configuration and its pixmap */
setupmenu(struct Menu
*menu
, XClassHint
*classh
)
/* setup size and position of menus */
/* update menu geometry */
changes
.border_width
= config
.border_pixels
;
changes
.height
= menu
->h
;
XConfigureWindow(dpy
, menu
->win
, CWBorderWidth
| CWWidth
| CWHeight
| CWX
| CWY
, &changes
);
/* set window title (used if wflag is on) */
if (menu
->parent
== NULL
) {
title
= classh
->res_name
;
title
= menu
->caller
->output
;
XStringListToTextProperty(&title
, 1, &wintitle
);
/* 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
, &wintitle
, NULL
, NULL
, 0, &sizeh
, NULL
, classh
);
/* set WM protocols and ewmh window properties */
XSetWMProtocols(dpy
, menu
->win
, &wmdelete
, 1);
XChangeProperty(dpy
, menu
->win
, netatom
[NetWMName
], utf8string
, 8,
PropModeReplace
, (unsigned char *)title
, strlen(title
));
XChangeProperty(dpy
, menu
->win
, netatom
[NetWMWindowType
], XA_ATOM
, 32,
(unsigned char *)&netatom
[NetWMWindowTypePopupMenu
], 1);
/* calculate positions of submenus */
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
->submenu
!= NULL
)
setupmenu(item
->submenu
, classh
);
/* 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");
/* load and scale icon */
loadicon(const char *file
)
icon
= imlib_load_image(file
);
errx(1, "cannot load icon %s", file
);
imlib_context_set_image(icon
);
width
= imlib_image_get_width();
height
= imlib_image_get_height();
imgsize
= MIN(width
, height
);
icon
= imlib_create_cropped_scaled_image(0, 0, imgsize
, imgsize
,
/* draw pixmap for the selected and unselected version of each item on menu */
drawitems(struct Menu
*menu
)
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
item
->unsel
= XCreatePixmap(dpy
, menu
->win
, menu
->w
, item
->h
,
DefaultDepth(dpy
, screen
));
XSetForeground(dpy
, dc
.gc
, dc
.normal
[ColorBG
].pixel
);
XFillRectangle(dpy
, item
->unsel
, dc
.gc
, 0, 0, menu
->w
, item
->h
);
if (item
->label
== NULL
) { /* item is separator */
XSetForeground(dpy
, dc
.gc
, dc
.separator
.pixel
);
XDrawLine(dpy
, item
->unsel
, dc
.gc
, config
.horzpadding
, y
,
menu
->w
- config
.horzpadding
, y
);
item
->sel
= XCreatePixmap(dpy
, menu
->win
, menu
->w
, item
->h
,
DefaultDepth(dpy
, screen
));
XSetForeground(dpy
, dc
.gc
, dc
.selected
[ColorBG
].pixel
);
XFillRectangle(dpy
, item
->sel
, dc
.gc
, 0, 0, menu
->w
, item
->h
);
x
+= (iflag
) ? 0 : config
.horzpadding
+ config
.iconsize
;
dsel
= XftDrawCreate(dpy
, item
->sel
, visual
, colormap
);
dunsel
= XftDrawCreate(dpy
, item
->unsel
, visual
, colormap
);
XSetForeground(dpy
, dc
.gc
, dc
.selected
[ColorFG
].pixel
);
drawtext(dsel
, &dc
.selected
[ColorFG
], x
, 0, item
->h
, item
->label
);
XSetForeground(dpy
, dc
.gc
, dc
.normal
[ColorFG
].pixel
);
drawtext(dunsel
, &dc
.normal
[ColorFG
], x
, 0, item
->h
, item
->label
);
if (item
->submenu
!= NULL
) {
x
= menu
->w
- config
.triangle_width
- config
.horzpadding
;
y
= (item
->h
- config
.triangle_height
+ 1) / 2;
{x
+ config
.triangle_width
, y
+ config
.triangle_height
/2},
{x
, y
+ config
.triangle_height
},
XSetForeground(dpy
, dc
.gc
, dc
.selected
[ColorFG
].pixel
);
XFillPolygon(dpy
, item
->sel
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
XSetForeground(dpy
, dc
.gc
, dc
.normal
[ColorFG
].pixel
);
XFillPolygon(dpy
, item
->unsel
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
if (item
->file
!= NULL
&& !iflag
) {
item
->icon
= loadicon(item
->file
);
imlib_context_set_image(item
->icon
);
imlib_context_set_drawable(item
->sel
);
imlib_render_image_on_drawable(config
.horzpadding
, config
.iconpadding
);
imlib_context_set_drawable(item
->unsel
);
imlib_render_image_on_drawable(config
.horzpadding
, config
.iconpadding
);
/* copy pixmaps of items of the current menu and of its ancestors into menu window */
drawmenus(struct Menu
*currmenu
)
for (menu
= currmenu
; menu
!= NULL
; menu
= menu
->parent
) {
for (item
= menu
->list
; item
!= NULL
; item
= item
->next
) {
if (item
== menu
->selected
)
XCopyArea(dpy
, item
->sel
, menu
->win
, dc
.gc
, 0, 0,
menu
->w
, item
->h
, 0, item
->y
);
XCopyArea(dpy
, item
->unsel
, menu
->win
, dc
.gc
, 0, 0,
menu
->w
, item
->h
, 0, item
->y
);
/* 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
) {
XMoveWindow(dpy
, menu
->win
, menu
->x
, menu
->y
);
XMapWindow(dpy
, menu
->win
);
/* 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
)
/* 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
;
menu
= getmenu(currmenu
, ev
.xconfigure
.window
);
menu
->x
= ev
.xconfigure
.x
;
menu
->y
= ev
.xconfigure
.y
;
if ((unsigned long) ev
.xclient
.data
.l
[0] != wmdelete
)
menu
= getmenu(currmenu
, ev
.xclient
.window
);
if (menu
->parent
== NULL
)
return; /* closing the root menu closes the program */
/* recursivelly free pixmaps and destroy windows */
cleanmenu(struct Menu
*menu
)
if (item
->submenu
!= NULL
)
cleanmenu(item
->submenu
);
XFreePixmap(dpy
, item
->unsel
);
XFreePixmap(dpy
, item
->sel
);
if (tmp
->label
!= tmp
->output
)
imlib_context_set_image(tmp
->icon
);
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 [-iw] [-p position] [title]\n");