92b1581c848260e720c257467d431be4916c3728
#include <X11/Xresource.h>
/* initializers, and their helper routines */
static void ealloccolor(const char *s
, XftColor
*color
);
static void initresources(void);
static void initdc(void);
static void initscreengeom(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);
static Imlib_Image
loadicon(const char *file
, int size
);
/* structure setters, and their helper routines */
static void setupmenusize(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);
/* window drawers and mappers */
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
);
/* main event loop, and its helper routines */
static struct Menu
*getmenu(struct Menu
*currmenu
, Window win
);
static struct Item
*getitem(struct Menu
*menu
, int y
);
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 Atom netatom
[NetLast
];
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
, "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 */
/* 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
);
/* 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
);
if ((dc
.font
= XftFontOpenName(dpy
, screen
, config
.font
)) == NULL
)
errx(1, "cannot load font");
dc
.gc
= XCreateGC(dpy
, rootwin
, 0, NULL
);
/* calculate screen geometry */
Window dw
; /* dummy variable */
int di
; /* dummy variable */
unsigned du
; /* dummy variable */
XQueryPointer(dpy
, rootwin
, &dw
, &dw
, &config
.cursx
, &config
.cursy
, &di
, &di
, &du
);
config
.screenw
= DisplayWidth(dpy
, screen
);
config
.screenh
= DisplayHeight(dpy
, screen
);
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
);
/* load and scale icon */
loadicon(const char *file
, int size
)
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
, size
, size
);
/* setup the size of a menu and the position of its items */
setupmenusize(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
;
/* get length of item->label rendered in the font */
XftTextExtentsUtf8(dpy
, dc
.font
, (XftChar8
*)item
->label
,
labelwidth
= ext
.xOff
+ item
->h
* 2;
menu
->w
= MAX(menu
->w
, labelwidth
);
item
->icon
= loadicon(item
->file
, item
->h
- config
.iconpadding
* 2);
/* 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 (config
.screenw
- config
.cursx
>= menu
->w
)
else if (config
.cursx
> width
)
menu
->x
= config
.cursx
- width
;
if (config
.screenh
- config
.cursy
>= height
)
else if (config
.screenh
> height
)
menu
->y
= config
.screenh
- height
;
} else { /* else, calculate in respect to parent menu */
if (config
.screenw
- (menu
->parent
->x
+ menu
->parent
->w
+ config
.border_pixels
+ config
.gap_pixels
) >= width
)
menu
->x
= menu
->parent
->x
+ menu
->parent
->w
+ config
.border_pixels
+ config
.gap_pixels
;
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 (config
.screenh
- (menu
->caller
->y
+ menu
->parent
->y
) > height
)
menu
->y
= menu
->caller
->y
+ menu
->parent
->y
;
else if (config
.screenh
- menu
->parent
->y
> height
)
menu
->y
= menu
->parent
->y
;
else if (config
.screenh
> height
)
menu
->y
= config
.screenh
- 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
);
/* 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
);
/* 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");
/* 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
);
/* 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
+ dc
.font
->ascent
) / 2;
XSetForeground(dpy
, dc
.gc
, color
[ColorFG
].pixel
);
XftDrawStringUtf8(menu
->draw
, &color
[ColorFG
], dc
.font
,
x
, y
, (XftChar8
*)item
->label
, item
->labellen
);
/* draw triangle, if item contains a submenu */
if (item
->submenu
!= NULL
) {
x
= menu
->w
- (item
->h
+ config
.triangle_width
+ 1) / 2;
y
= item
->y
+ (item
->h
- config
.triangle_height
+ 1) / 2;
{x
+ config
.triangle_width
, y
+ config
.triangle_height
/2},
{x
, y
+ config
.triangle_height
},
XFillPolygon(dpy
, menu
->pixmap
, dc
.gc
, triangle
, LEN(triangle
),
Convex
, CoordModeOrigin
);
if (item
->file
!= NULL
) {
y
= item
->y
+ config
.iconpadding
;
imlib_context_set_drawable(menu
->pixmap
);
imlib_context_set_image(item
->icon
);
imlib_render_image_on_drawable(x
, y
);
/* 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,
/* 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
);
if (tmp
->label
!= tmp
->output
)
imlib_context_set_image(tmp
->icon
);
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 [-w] [title]\n");