mirror of
https://github.com/leahneukirchen/cwm.git
synced 2023-08-10 21:13:12 +03:00
544b4da339
* refs/heads/master: Stop asking for events (NoEventMask) from menu window once done with the menu (we don't destroy it, only unmap). Allow 'transientfor' clients to inherit group and bwidth either during init or via property notify events. Previously only the flags were set but nothing was in the path to apply said flags and/or bwidth. Required slight of re-orgnaization of client_init. merge from base, from sashan@: Use the original client border width to adjust initial placement of clients containing {P,US}Position requests where they are explicitly set to 'ignore' in cwmrc(5); clients are unaware that their border will be altered (removed in this case) when calcuating position and thus end up a factor of their original border width off once mapped by cwm(1). cwm(1) will essentially shift the client to the edge if the original request's position and border match.
638 lines
15 KiB
C
638 lines
15 KiB
C
/*
|
|
* calmwm - the calm window manager
|
|
*
|
|
* Copyright (c) 2008 Owain G. Ainsworth <oga@openbsd.org>
|
|
* Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* $OpenBSD$
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include "queue.h"
|
|
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "calmwm.h"
|
|
|
|
#define PROMPT_SCHAR "\xc2\xbb"
|
|
#define PROMPT_ECHAR "\xc2\xab"
|
|
|
|
enum ctltype {
|
|
CTL_NONE = -1,
|
|
CTL_ERASEONE = 0, CTL_WIPE, CTL_UP, CTL_DOWN, CTL_RETURN,
|
|
CTL_TAB, CTL_ABORT, CTL_ALL
|
|
};
|
|
|
|
struct menu_ctx {
|
|
struct screen_ctx *sc;
|
|
char searchstr[MENU_MAXENTRY + 1];
|
|
char dispstr[MENU_MAXENTRY*2 + 1];
|
|
char promptstr[MENU_MAXENTRY + 1];
|
|
int list;
|
|
int listing;
|
|
int changed;
|
|
int prev;
|
|
int entry;
|
|
int num;
|
|
int flags;
|
|
struct geom geom;
|
|
void (*match)(struct menu_q *, struct menu_q *, char *);
|
|
void (*print)(struct menu *, int);
|
|
};
|
|
static struct menu *menu_handle_key(XEvent *, struct menu_ctx *,
|
|
struct menu_q *, struct menu_q *);
|
|
static void menu_handle_move(struct menu_ctx *,
|
|
struct menu_q *, int, int);
|
|
static struct menu *menu_handle_release(struct menu_ctx *,
|
|
struct menu_q *, int, int);
|
|
static void menu_draw(struct menu_ctx *, struct menu_q *,
|
|
struct menu_q *);
|
|
static void menu_draw_entry(struct menu_ctx *, struct menu_q *,
|
|
int, int);
|
|
static int menu_calc_entry(struct menu_ctx *, int, int);
|
|
static struct menu *menu_complete_path(struct menu_ctx *);
|
|
static int menu_keycode(XKeyEvent *, enum ctltype *, char *);
|
|
|
|
struct menu *
|
|
menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt,
|
|
const char *initial, int flags,
|
|
void (*match)(struct menu_q *, struct menu_q *, char *),
|
|
void (*print)(struct menu *, int))
|
|
{
|
|
struct menu_ctx mc;
|
|
struct menu_q resultq;
|
|
struct menu *mi = NULL;
|
|
XEvent e;
|
|
Window focuswin;
|
|
int focusrevert, xsave, ysave, xcur, ycur;
|
|
|
|
TAILQ_INIT(&resultq);
|
|
|
|
xu_ptr_getpos(sc->rootwin, &xsave, &ysave);
|
|
|
|
(void)memset(&mc, 0, sizeof(mc));
|
|
mc.sc = sc;
|
|
mc.flags = flags;
|
|
mc.match = match;
|
|
mc.print = print;
|
|
mc.entry = mc.prev = -1;
|
|
mc.geom.x = xsave;
|
|
mc.geom.y = ysave;
|
|
|
|
if (mc.flags & CWM_MENU_LIST)
|
|
mc.list = 1;
|
|
|
|
(void)strlcpy(mc.promptstr, prompt, sizeof(mc.promptstr));
|
|
if (initial != NULL)
|
|
(void)strlcpy(mc.searchstr, initial, sizeof(mc.searchstr));
|
|
else
|
|
mc.searchstr[0] = '\0';
|
|
|
|
XSelectInput(X_Dpy, sc->menu.win, MENUMASK);
|
|
XMapRaised(X_Dpy, sc->menu.win);
|
|
|
|
if (XGrabPointer(X_Dpy, sc->menu.win, False, MENUGRABMASK,
|
|
GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_QUESTION],
|
|
CurrentTime) != GrabSuccess) {
|
|
XUnmapWindow(X_Dpy, sc->menu.win);
|
|
return(NULL);
|
|
}
|
|
|
|
XGetInputFocus(X_Dpy, &focuswin, &focusrevert);
|
|
XSetInputFocus(X_Dpy, sc->menu.win, RevertToPointerRoot, CurrentTime);
|
|
|
|
/* make sure keybindings don't remove keys from the menu stream */
|
|
XGrabKeyboard(X_Dpy, sc->menu.win, True,
|
|
GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
|
|
for (;;) {
|
|
mc.changed = 0;
|
|
|
|
XWindowEvent(X_Dpy, sc->menu.win, MENUMASK, &e);
|
|
|
|
switch (e.type) {
|
|
case KeyPress:
|
|
if ((mi = menu_handle_key(&e, &mc, menuq, &resultq))
|
|
!= NULL)
|
|
goto out;
|
|
/* FALLTHROUGH */
|
|
case Expose:
|
|
menu_draw(&mc, menuq, &resultq);
|
|
break;
|
|
case MotionNotify:
|
|
menu_handle_move(&mc, &resultq,
|
|
e.xbutton.x, e.xbutton.y);
|
|
break;
|
|
case ButtonRelease:
|
|
if ((mi = menu_handle_release(&mc, &resultq,
|
|
e.xbutton.x, e.xbutton.y)) != NULL)
|
|
goto out;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
out:
|
|
if ((mc.flags & CWM_MENU_DUMMY) == 0 && mi->dummy) {
|
|
/* no mouse based match */
|
|
free(mi);
|
|
mi = NULL;
|
|
}
|
|
|
|
XSelectInput(X_Dpy, sc->menu.win, NoEventMask);
|
|
XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime);
|
|
/* restore if user didn't move */
|
|
xu_ptr_getpos(sc->rootwin, &xcur, &ycur);
|
|
if (xcur == mc.geom.x && ycur == mc.geom.y)
|
|
xu_ptr_setpos(sc->rootwin, xsave, ysave);
|
|
XUngrabPointer(X_Dpy, CurrentTime);
|
|
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, 0, 0, 1, 1);
|
|
XUnmapWindow(X_Dpy, sc->menu.win);
|
|
XUngrabKeyboard(X_Dpy, CurrentTime);
|
|
|
|
return(mi);
|
|
}
|
|
|
|
static struct menu *
|
|
menu_complete_path(struct menu_ctx *mc)
|
|
{
|
|
struct screen_ctx *sc = mc->sc;
|
|
struct menu *mi, *mr;
|
|
struct menu_q menuq;
|
|
int mflags = (CWM_MENU_DUMMY);
|
|
|
|
mr = xcalloc(1, sizeof(*mr));
|
|
|
|
TAILQ_INIT(&menuq);
|
|
|
|
if ((mi = menu_filter(sc, &menuq, mc->searchstr, NULL, mflags,
|
|
search_match_path, search_print_text)) != NULL) {
|
|
mr->abort = mi->abort;
|
|
mr->dummy = mi->dummy;
|
|
if (mi->text[0] != '\0')
|
|
snprintf(mr->text, sizeof(mr->text), "%s \"%s\"",
|
|
mc->searchstr, mi->text);
|
|
else if (!mr->abort)
|
|
strlcpy(mr->text, mc->searchstr, sizeof(mr->text));
|
|
}
|
|
|
|
menuq_clear(&menuq);
|
|
|
|
return(mr);
|
|
}
|
|
|
|
static struct menu *
|
|
menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
|
|
struct menu_q *resultq)
|
|
{
|
|
struct menu *mi;
|
|
enum ctltype ctl;
|
|
char chr[32];
|
|
size_t len;
|
|
int clen, i;
|
|
wchar_t wc;
|
|
|
|
if (menu_keycode(&e->xkey, &ctl, chr) < 0)
|
|
return(NULL);
|
|
|
|
switch (ctl) {
|
|
case CTL_ERASEONE:
|
|
if ((len = strlen(mc->searchstr)) > 0) {
|
|
clen = 1;
|
|
while (mbtowc(&wc, &mc->searchstr[len-clen], MB_CUR_MAX) == -1)
|
|
clen++;
|
|
for (i = 1; i <= clen; i++)
|
|
mc->searchstr[len - i] = '\0';
|
|
mc->changed = 1;
|
|
}
|
|
break;
|
|
case CTL_UP:
|
|
mi = TAILQ_LAST(resultq, menu_q);
|
|
if (mi == NULL)
|
|
break;
|
|
|
|
TAILQ_REMOVE(resultq, mi, resultentry);
|
|
TAILQ_INSERT_HEAD(resultq, mi, resultentry);
|
|
break;
|
|
case CTL_DOWN:
|
|
mi = TAILQ_FIRST(resultq);
|
|
if (mi == NULL)
|
|
break;
|
|
|
|
TAILQ_REMOVE(resultq, mi, resultentry);
|
|
TAILQ_INSERT_TAIL(resultq, mi, resultentry);
|
|
break;
|
|
case CTL_RETURN:
|
|
/*
|
|
* Return whatever the cursor is currently on. Else
|
|
* even if dummy is zero, we need to return something.
|
|
*/
|
|
if ((mi = TAILQ_FIRST(resultq)) == NULL) {
|
|
mi = xmalloc(sizeof(*mi));
|
|
(void)strlcpy(mi->text,
|
|
mc->searchstr, sizeof(mi->text));
|
|
mi->dummy = 1;
|
|
}
|
|
mi->abort = 0;
|
|
return(mi);
|
|
case CTL_WIPE:
|
|
mc->searchstr[0] = '\0';
|
|
mc->changed = 1;
|
|
break;
|
|
case CTL_TAB:
|
|
if ((mi = TAILQ_FIRST(resultq)) != NULL) {
|
|
/*
|
|
* - We are in exec_path menu mode
|
|
* - It is equal to the input
|
|
* We got a command, launch the file menu
|
|
*/
|
|
if ((mc->flags & CWM_MENU_FILE) &&
|
|
(strncmp(mc->searchstr, mi->text,
|
|
strlen(mi->text))) == 0)
|
|
return(menu_complete_path(mc));
|
|
|
|
/*
|
|
* Put common prefix of the results into searchstr
|
|
*/
|
|
(void)strlcpy(mc->searchstr,
|
|
mi->text, sizeof(mc->searchstr));
|
|
while ((mi = TAILQ_NEXT(mi, resultentry)) != NULL) {
|
|
i = 0;
|
|
while (tolower(mc->searchstr[i]) ==
|
|
tolower(mi->text[i]))
|
|
i++;
|
|
mc->searchstr[i] = '\0';
|
|
}
|
|
mc->changed = 1;
|
|
}
|
|
break;
|
|
case CTL_ALL:
|
|
mc->list = !mc->list;
|
|
break;
|
|
case CTL_ABORT:
|
|
mi = xmalloc(sizeof(*mi));
|
|
mi->text[0] = '\0';
|
|
mi->dummy = 1;
|
|
mi->abort = 1;
|
|
return(mi);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (chr[0] != '\0') {
|
|
mc->changed = 1;
|
|
(void)strlcat(mc->searchstr, chr, sizeof(mc->searchstr));
|
|
}
|
|
|
|
if (mc->changed) {
|
|
if (mc->searchstr[0] != '\0')
|
|
(*mc->match)(menuq, resultq, mc->searchstr);
|
|
} else if (!mc->list && mc->listing) {
|
|
TAILQ_INIT(resultq);
|
|
mc->listing = 0;
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
static void
|
|
menu_draw(struct menu_ctx *mc, struct menu_q *menuq, struct menu_q *resultq)
|
|
{
|
|
struct screen_ctx *sc = mc->sc;
|
|
struct menu *mi;
|
|
struct geom area;
|
|
int n, xsave, ysave;
|
|
XGlyphInfo extents;
|
|
|
|
if (mc->list) {
|
|
if (TAILQ_EMPTY(resultq)) {
|
|
/* Copy them all over. */
|
|
TAILQ_FOREACH(mi, menuq, entry)
|
|
TAILQ_INSERT_TAIL(resultq, mi, resultentry);
|
|
|
|
mc->listing = 1;
|
|
} else if (mc->changed)
|
|
mc->listing = 0;
|
|
}
|
|
|
|
(void)snprintf(mc->dispstr, sizeof(mc->dispstr), "%s%s%s%s",
|
|
mc->promptstr, PROMPT_SCHAR, mc->searchstr, PROMPT_ECHAR);
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont,
|
|
(const FcChar8*)mc->dispstr, strlen(mc->dispstr), &extents);
|
|
mc->geom.w = extents.xOff;
|
|
mc->geom.h = sc->xftfont->height + 1;
|
|
mc->num = 1;
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry) {
|
|
(*mc->print)(mi, mc->listing);
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont,
|
|
(const FcChar8*)mi->print,
|
|
MIN(strlen(mi->print), MENU_MAXENTRY), &extents);
|
|
mc->geom.w = MAX(mc->geom.w, extents.xOff);
|
|
mc->geom.h += sc->xftfont->height + 1;
|
|
mc->num++;
|
|
}
|
|
|
|
area = screen_area(sc, mc->geom.x, mc->geom.y, CWM_GAP);
|
|
area.w += area.x - Conf.bwidth * 2;
|
|
area.h += area.y - Conf.bwidth * 2;
|
|
|
|
xsave = mc->geom.x;
|
|
ysave = mc->geom.y;
|
|
|
|
/* Never hide the top, or left side, of the menu. */
|
|
if (mc->geom.x + mc->geom.w >= area.w)
|
|
mc->geom.x = area.w - mc->geom.w;
|
|
if (mc->geom.x < area.x) {
|
|
mc->geom.x = area.x;
|
|
mc->geom.w = MIN(mc->geom.w, (area.w - area.x));
|
|
}
|
|
if (mc->geom.y + mc->geom.h >= area.h)
|
|
mc->geom.y = area.h - mc->geom.h;
|
|
if (mc->geom.y < area.y) {
|
|
mc->geom.y = area.y;
|
|
mc->geom.h = MIN(mc->geom.h, (area.h - area.y));
|
|
}
|
|
|
|
if (mc->geom.x != xsave || mc->geom.y != ysave)
|
|
xu_ptr_setpos(sc->rootwin, mc->geom.x, mc->geom.y);
|
|
|
|
XClearWindow(X_Dpy, sc->menu.win);
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, mc->geom.x, mc->geom.y,
|
|
mc->geom.w, mc->geom.h);
|
|
|
|
n = 1;
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
|
&sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont,
|
|
0, sc->xftfont->ascent,
|
|
(const FcChar8*)mc->dispstr, strlen(mc->dispstr));
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry) {
|
|
int y = n * (sc->xftfont->height + 1) + sc->xftfont->ascent + 1;
|
|
|
|
/* Stop drawing when menu doesn't fit inside the screen. */
|
|
if (mc->geom.y + y > area.h)
|
|
break;
|
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
|
&sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont,
|
|
0, y,
|
|
(const FcChar8*)mi->print, strlen(mi->print));
|
|
n++;
|
|
}
|
|
if (n > 1)
|
|
menu_draw_entry(mc, resultq, 1, 1);
|
|
}
|
|
|
|
static void
|
|
menu_draw_entry(struct menu_ctx *mc, struct menu_q *resultq,
|
|
int entry, int active)
|
|
{
|
|
struct screen_ctx *sc = mc->sc;
|
|
struct menu *mi;
|
|
int color, i = 1;
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry)
|
|
if (entry == i++)
|
|
break;
|
|
if (mi == NULL)
|
|
return;
|
|
|
|
color = (active) ? CWM_COLOR_MENU_FG : CWM_COLOR_MENU_BG;
|
|
XftDrawRect(sc->menu.xftdraw, &sc->xftcolor[color], 0,
|
|
(sc->xftfont->height + 1) * entry, mc->geom.w,
|
|
(sc->xftfont->height + 1) + sc->xftfont->descent);
|
|
color = (active) ? CWM_COLOR_MENU_FONT_SEL : CWM_COLOR_MENU_FONT;
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
|
&sc->xftcolor[color], sc->xftfont,
|
|
0, (sc->xftfont->height + 1) * entry + sc->xftfont->ascent + 1,
|
|
(const FcChar8*)mi->print, strlen(mi->print));
|
|
}
|
|
|
|
static void
|
|
menu_handle_move(struct menu_ctx *mc, struct menu_q *resultq, int x, int y)
|
|
{
|
|
mc->prev = mc->entry;
|
|
mc->entry = menu_calc_entry(mc, x, y);
|
|
|
|
if (mc->prev == mc->entry)
|
|
return;
|
|
|
|
if (mc->prev != -1)
|
|
menu_draw_entry(mc, resultq, mc->prev, 0);
|
|
if (mc->entry != -1) {
|
|
XChangeActivePointerGrab(X_Dpy, MENUGRABMASK,
|
|
Conf.cursor[CF_NORMAL], CurrentTime);
|
|
menu_draw_entry(mc, resultq, mc->entry, 1);
|
|
}
|
|
}
|
|
|
|
static struct menu *
|
|
menu_handle_release(struct menu_ctx *mc, struct menu_q *resultq, int x, int y)
|
|
{
|
|
struct menu *mi;
|
|
int entry, i = 1;
|
|
|
|
entry = menu_calc_entry(mc, x, y);
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry)
|
|
if (entry == i++)
|
|
break;
|
|
if (mi == NULL) {
|
|
mi = xmalloc(sizeof(*mi));
|
|
mi->text[0] = '\0';
|
|
mi->dummy = 1;
|
|
}
|
|
return(mi);
|
|
}
|
|
|
|
static int
|
|
menu_calc_entry(struct menu_ctx *mc, int x, int y)
|
|
{
|
|
struct screen_ctx *sc = mc->sc;
|
|
int entry;
|
|
|
|
entry = y / (sc->xftfont->height + 1);
|
|
|
|
/* in bounds? */
|
|
if (x < 0 || x > mc->geom.w || y < 0 ||
|
|
y > (sc->xftfont->height + 1) * mc->num ||
|
|
entry < 0 || entry >= mc->num)
|
|
entry = -1;
|
|
|
|
if (entry == 0)
|
|
entry = -1;
|
|
|
|
return(entry);
|
|
}
|
|
|
|
static int
|
|
menu_keycode(XKeyEvent *ev, enum ctltype *ctl, char *chr)
|
|
{
|
|
KeySym ks;
|
|
|
|
*ctl = CTL_NONE;
|
|
chr[0] = '\0';
|
|
|
|
ks = XkbKeycodeToKeysym(X_Dpy, ev->keycode, 0,
|
|
(ev->state & ShiftMask) ? 1 : 0);
|
|
|
|
/* Look for control characters. */
|
|
switch (ks) {
|
|
case XK_BackSpace:
|
|
*ctl = CTL_ERASEONE;
|
|
break;
|
|
case XK_KP_Enter:
|
|
case XK_Return:
|
|
*ctl = CTL_RETURN;
|
|
break;
|
|
case XK_Tab:
|
|
*ctl = CTL_TAB;
|
|
break;
|
|
case XK_Up:
|
|
*ctl = CTL_UP;
|
|
break;
|
|
case XK_Down:
|
|
*ctl = CTL_DOWN;
|
|
break;
|
|
case XK_Escape:
|
|
*ctl = CTL_ABORT;
|
|
break;
|
|
}
|
|
|
|
if (*ctl == CTL_NONE && (ev->state & ControlMask)) {
|
|
switch (ks) {
|
|
case XK_s:
|
|
case XK_S:
|
|
/* Emacs "next" */
|
|
*ctl = CTL_DOWN;
|
|
break;
|
|
case XK_r:
|
|
case XK_R:
|
|
/* Emacs "previous" */
|
|
*ctl = CTL_UP;
|
|
break;
|
|
case XK_u:
|
|
case XK_U:
|
|
*ctl = CTL_WIPE;
|
|
break;
|
|
case XK_h:
|
|
case XK_H:
|
|
*ctl = CTL_ERASEONE;
|
|
break;
|
|
case XK_a:
|
|
case XK_A:
|
|
*ctl = CTL_ALL;
|
|
break;
|
|
case XK_bracketleft:
|
|
*ctl = CTL_ABORT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*ctl == CTL_NONE && (ev->state & Mod1Mask)) {
|
|
switch (ks) {
|
|
case XK_j:
|
|
case XK_J:
|
|
/* Vi "down" */
|
|
*ctl = CTL_DOWN;
|
|
break;
|
|
case XK_k:
|
|
case XK_K:
|
|
/* Vi "up" */
|
|
*ctl = CTL_UP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*ctl != CTL_NONE)
|
|
return(0);
|
|
|
|
if (XLookupString(ev, chr, 32, &ks, NULL) < 0)
|
|
return(-1);
|
|
|
|
return(0);
|
|
}
|
|
|
|
void
|
|
menuq_add(struct menu_q *mq, void *ctx, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
struct menu *mi;
|
|
|
|
mi = xcalloc(1, sizeof(*mi));
|
|
mi->ctx = ctx;
|
|
|
|
va_start(ap, fmt);
|
|
if (fmt != NULL)
|
|
(void)vsnprintf(mi->text, sizeof(mi->text), fmt, ap);
|
|
else
|
|
mi->text[0] = '\0';
|
|
va_end(ap);
|
|
|
|
TAILQ_INSERT_TAIL(mq, mi, entry);
|
|
}
|
|
|
|
void
|
|
menuq_clear(struct menu_q *mq)
|
|
{
|
|
struct menu *mi;
|
|
|
|
while ((mi = TAILQ_FIRST(mq)) != NULL) {
|
|
TAILQ_REMOVE(mq, mi, entry);
|
|
free(mi);
|
|
}
|
|
}
|
|
|
|
void
|
|
menu_windraw(struct screen_ctx *sc, Window win, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int i;
|
|
char *text;
|
|
XGlyphInfo extents;
|
|
|
|
va_start(ap, fmt);
|
|
i = vasprintf(&text, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (i < 0 || text == NULL)
|
|
err(1, "vasprintf");
|
|
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont, (const FcChar8*)text,
|
|
strlen(text), &extents);
|
|
|
|
XReparentWindow(X_Dpy, sc->menu.win, win, 0, 0);
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, 0, 0,
|
|
extents.xOff, sc->xftfont->height);
|
|
XMapWindow(X_Dpy, sc->menu.win);
|
|
XClearWindow(X_Dpy, sc->menu.win);
|
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw, &sc->xftcolor[CWM_COLOR_MENU_FONT],
|
|
sc->xftfont, 0, sc->xftfont->ascent + 1,
|
|
(const FcChar8*)text, strlen(text));
|
|
|
|
free(text);
|
|
}
|