Blame


1 15aae8ae 2023-05-28 aa #include <ctype.h>
2 15aae8ae 2023-05-28 aa #include <errno.h>
3 cb28fcb8 2023-05-26 aa #include <stdarg.h>
4 15aae8ae 2023-05-28 aa #include <stdbool.h>
5 cb28fcb8 2023-05-26 aa #include <stdio.h>
6 cb28fcb8 2023-05-26 aa #include <stdlib.h>
7 cb28fcb8 2023-05-26 aa #include <string.h>
8 15aae8ae 2023-05-28 aa #include <time.h>
9 cb28fcb8 2023-05-26 aa #include <unistd.h>
10 cb28fcb8 2023-05-26 aa
11 cb28fcb8 2023-05-26 aa #include <X11/Xlib.h>
12 cb28fcb8 2023-05-26 aa #include <X11/Xutil.h>
13 cb28fcb8 2023-05-26 aa #include <X11/Xos.h>
14 cb28fcb8 2023-05-26 aa #include <X11/Xatom.h>
15 77968eb5 2023-05-29 aa #include <X11/Xft/Xft.h>
16 cb28fcb8 2023-05-26 aa
17 7c7bbaee 2023-05-27 aa #define PROGNAME "xitems"
18 843ffc79 2023-05-28 aa #define MAXKS 32
19 7c7bbaee 2023-05-27 aa
20 15aae8ae 2023-05-28 aa /* usage -- print usage information and die. */
21 cb28fcb8 2023-05-26 aa static void
22 7c7bbaee 2023-05-27 aa usage(void)
23 7c7bbaee 2023-05-27 aa {
24 15aae8ae 2023-05-28 aa printf(
25 15aae8ae 2023-05-28 aa "usage: " PROGNAME " [-font font] [-bg colour] [-fg colour]\n"
26 f1adc4ca 2023-05-28 aa " [-sbg colour] [-sfg colour] [-bc colour] [-bw width]\n"
27 15aae8ae 2023-05-28 aa " [-hp padding] [-vp padding] [-x x] [-y y]\n");
28 7c7bbaee 2023-05-27 aa exit(1);
29 7c7bbaee 2023-05-27 aa }
30 7c7bbaee 2023-05-27 aa
31 15aae8ae 2023-05-28 aa /* die -- print formatted string, and exit with the status eval. */
32 7c7bbaee 2023-05-27 aa static void
33 15aae8ae 2023-05-28 aa die(int eval, const char *fmt, ...)
34 cb28fcb8 2023-05-26 aa {
35 7c7bbaee 2023-05-27 aa fputs(PROGNAME ": ", stderr);
36 cb28fcb8 2023-05-26 aa if (fmt) {
37 cb28fcb8 2023-05-26 aa va_list argp;
38 cb28fcb8 2023-05-26 aa va_start(argp, fmt);
39 cb28fcb8 2023-05-26 aa vfprintf(stderr, fmt, argp);
40 cb28fcb8 2023-05-26 aa va_end(argp);
41 cb28fcb8 2023-05-26 aa }
42 cb28fcb8 2023-05-26 aa fputc('\n', stderr);
43 cb28fcb8 2023-05-26 aa exit(eval);
44 cb28fcb8 2023-05-26 aa }
45 cb28fcb8 2023-05-26 aa
46 843ffc79 2023-05-28 aa /* warn -- print formatted string to stderr. */
47 843ffc79 2023-05-28 aa static void
48 843ffc79 2023-05-28 aa warn(const char *fmt, ...)
49 843ffc79 2023-05-28 aa {
50 843ffc79 2023-05-28 aa fputs(PROGNAME ": ", stderr);
51 843ffc79 2023-05-28 aa if (fmt) {
52 843ffc79 2023-05-28 aa va_list argp;
53 843ffc79 2023-05-28 aa va_start(argp, fmt);
54 843ffc79 2023-05-28 aa vfprintf(stderr, fmt, argp);
55 843ffc79 2023-05-28 aa va_end(argp);
56 843ffc79 2023-05-28 aa }
57 843ffc79 2023-05-28 aa fputc('\n', stderr);
58 843ffc79 2023-05-28 aa }
59 843ffc79 2023-05-28 aa
60 6cdc5a3d 2023-05-28 aa enum direction {
61 6cdc5a3d 2023-05-28 aa DIR_UP,
62 6cdc5a3d 2023-05-28 aa DIR_DOWN
63 6cdc5a3d 2023-05-28 aa };
64 6cdc5a3d 2023-05-28 aa
65 cb28fcb8 2023-05-26 aa /* doubly-linked cyclic list */
66 cb28fcb8 2023-05-26 aa struct item {
67 cb28fcb8 2023-05-26 aa struct item *prev;
68 cb28fcb8 2023-05-26 aa struct item *next;
69 843ffc79 2023-05-28 aa char *s;
70 843ffc79 2023-05-28 aa KeySym ks[MAXKS]; /* array of associated keysyms */
71 843ffc79 2023-05-28 aa size_t len; /* length of string */
72 843ffc79 2023-05-28 aa size_t nks; /* number of associated keysyms */
73 15aae8ae 2023-05-28 aa bool dirty; /* should be redrawn */
74 cb28fcb8 2023-05-26 aa };
75 cb28fcb8 2023-05-26 aa
76 15aae8ae 2023-05-28 aa static struct item *first = NULL; /* first member of the list */
77 cb28fcb8 2023-05-26 aa static struct item *selected = NULL;
78 15aae8ae 2023-05-28 aa
79 15aae8ae 2023-05-28 aa /* command-line options and X resources */
80 15aae8ae 2023-05-28 aa static char *o_font = NULL;
81 f1adc4ca 2023-05-28 aa static char *o_bg = NULL, *o_fg = NULL, *o_sbg = NULL, *o_sfg = NULL,
82 f1adc4ca 2023-05-28 aa *o_bc = NULL;
83 15aae8ae 2023-05-28 aa static int o_x = -1, o_y = -1;
84 15aae8ae 2023-05-28 aa static int o_bw = -1;
85 15aae8ae 2023-05-28 aa static int o_hp = -1, o_vp = -1;
86 15aae8ae 2023-05-28 aa
87 15aae8ae 2023-05-28 aa /* X globals */
88 2d5f9ee6 2023-05-29 aa enum {
89 2d5f9ee6 2023-05-29 aa PIXEL_BG = 0,
90 2d5f9ee6 2023-05-29 aa PIXEL_BC,
91 2d5f9ee6 2023-05-29 aa };
92 2d5f9ee6 2023-05-29 aa #define PIXEL_N 2
93 2d5f9ee6 2023-05-29 aa
94 15aae8ae 2023-05-28 aa static Display *dpy = NULL;
95 cb28fcb8 2023-05-26 aa static int screen;
96 15aae8ae 2023-05-28 aa static Window win;
97 15aae8ae 2023-05-28 aa static int height, width; /* height and width of one item */
98 77968eb5 2023-05-29 aa static XftFont *font;
99 77968eb5 2023-05-29 aa static XftColor c_fg, c_sfg, c_sbg;
100 2d5f9ee6 2023-05-29 aa static unsigned long pixels[PIXEL_N];
101 cb28fcb8 2023-05-26 aa
102 843ffc79 2023-05-28 aa /* selpos -- mark the item at position y (from top) as selected. */
103 6cdc5a3d 2023-05-28 aa static void
104 843ffc79 2023-05-28 aa selpos(int y)
105 6cdc5a3d 2023-05-28 aa {
106 6cdc5a3d 2023-05-28 aa struct item *unselected = selected;
107 6cdc5a3d 2023-05-28 aa int top = 1;
108 6cdc5a3d 2023-05-28 aa
109 6cdc5a3d 2023-05-28 aa selected = first;
110 6cdc5a3d 2023-05-28 aa
111 6cdc5a3d 2023-05-28 aa if (y <= 0)
112 6cdc5a3d 2023-05-28 aa goto end;
113 6cdc5a3d 2023-05-28 aa
114 843ffc79 2023-05-28 aa for (selected = first; selected != first->prev;
115 843ffc79 2023-05-28 aa selected = selected->next) {
116 6cdc5a3d 2023-05-28 aa if (y >= top && y < top+height)
117 6cdc5a3d 2023-05-28 aa break;
118 6cdc5a3d 2023-05-28 aa top += height;
119 843ffc79 2023-05-28 aa }
120 6cdc5a3d 2023-05-28 aa
121 6cdc5a3d 2023-05-28 aa end:
122 6cdc5a3d 2023-05-28 aa if (unselected != selected)
123 6cdc5a3d 2023-05-28 aa unselected->dirty = selected->dirty = true;
124 6cdc5a3d 2023-05-28 aa }
125 6cdc5a3d 2023-05-28 aa
126 2d5f9ee6 2023-05-29 aa /* redraw -- redraw the entire window. */
127 15aae8ae 2023-05-28 aa static void
128 15aae8ae 2023-05-28 aa redraw(void)
129 15aae8ae 2023-05-28 aa {
130 15aae8ae 2023-05-28 aa struct item *it = first;
131 15aae8ae 2023-05-28 aa int y = 0;
132 15aae8ae 2023-05-28 aa
133 77968eb5 2023-05-29 aa static XftDraw *d = NULL;
134 77968eb5 2023-05-29 aa
135 77968eb5 2023-05-29 aa if (!d && !(d = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen),
136 77968eb5 2023-05-29 aa DefaultColormap(dpy, screen))))
137 77968eb5 2023-05-29 aa die(1, "couldn't create XftDraw");
138 77968eb5 2023-05-29 aa
139 15aae8ae 2023-05-28 aa do {
140 15aae8ae 2023-05-28 aa if (it->dirty) {
141 2d5f9ee6 2023-05-29 aa XftColor *c;
142 77968eb5 2023-05-29 aa
143 b64495df 2023-05-29 aa XClearArea(dpy, win, 0, y, width, height, False);
144 15aae8ae 2023-05-28 aa
145 15aae8ae 2023-05-28 aa if (it == selected) {
146 2d5f9ee6 2023-05-29 aa c = &c_sfg;
147 77968eb5 2023-05-29 aa XftDrawRect(d, &c_sbg, 0, y, width, height);
148 b64495df 2023-05-29 aa } else
149 2d5f9ee6 2023-05-29 aa c = &c_fg;
150 15aae8ae 2023-05-28 aa
151 2d5f9ee6 2023-05-29 aa XftDrawStringUtf8(d, c, font,
152 77968eb5 2023-05-29 aa o_hp, y + o_vp+font->ascent,
153 77968eb5 2023-05-29 aa (FcChar8 *)it->s, it->len);
154 15aae8ae 2023-05-28 aa
155 15aae8ae 2023-05-28 aa it->dirty = false;
156 15aae8ae 2023-05-28 aa }
157 15aae8ae 2023-05-28 aa
158 15aae8ae 2023-05-28 aa y += height;
159 15aae8ae 2023-05-28 aa it = it->next;
160 15aae8ae 2023-05-28 aa } while (it != first);
161 15aae8ae 2023-05-28 aa }
162 15aae8ae 2023-05-28 aa
163 2d5f9ee6 2023-05-29 aa /*
164 2d5f9ee6 2023-05-29 aa * freeitems -- recursively free the list of items. First call should be
165 2d5f9ee6 2023-05-29 aa * freeitems(NULL).
166 2d5f9ee6 2023-05-29 aa */
167 2d5f9ee6 2023-05-29 aa static void
168 2d5f9ee6 2023-05-29 aa freeitems(struct item *it)
169 2d5f9ee6 2023-05-29 aa {
170 2d5f9ee6 2023-05-29 aa if (!it)
171 2d5f9ee6 2023-05-29 aa it = first;
172 2d5f9ee6 2023-05-29 aa else if (it == first)
173 2d5f9ee6 2023-05-29 aa return;
174 2d5f9ee6 2023-05-29 aa
175 2d5f9ee6 2023-05-29 aa freeitems(it->next);
176 2d5f9ee6 2023-05-29 aa free(it);
177 2d5f9ee6 2023-05-29 aa }
178 2d5f9ee6 2023-05-29 aa
179 2d5f9ee6 2023-05-29 aa /* cleanup -- free allocated resources. */
180 2d5f9ee6 2023-05-29 aa static void
181 2d5f9ee6 2023-05-29 aa cleanup(void)
182 2d5f9ee6 2023-05-29 aa {
183 2d5f9ee6 2023-05-29 aa Colormap cmap = DefaultColormap(dpy, screen);
184 2d5f9ee6 2023-05-29 aa Visual *vis = DefaultVisual(dpy, screen);
185 2d5f9ee6 2023-05-29 aa
186 2d5f9ee6 2023-05-29 aa freeitems(NULL);
187 2d5f9ee6 2023-05-29 aa
188 2d5f9ee6 2023-05-29 aa XftFontClose(dpy, font);
189 2d5f9ee6 2023-05-29 aa
190 2d5f9ee6 2023-05-29 aa XFreeColors(dpy, cmap, pixels, PIXEL_N, 0);
191 2d5f9ee6 2023-05-29 aa XftColorFree(dpy, vis, cmap, &c_fg);
192 2d5f9ee6 2023-05-29 aa XftColorFree(dpy, vis, cmap, &c_sbg);
193 2d5f9ee6 2023-05-29 aa XftColorFree(dpy, vis, cmap, &c_sfg);
194 2d5f9ee6 2023-05-29 aa
195 2d5f9ee6 2023-05-29 aa XUngrabKeyboard(dpy, CurrentTime);
196 2d5f9ee6 2023-05-29 aa XUngrabPointer(dpy, CurrentTime);
197 2d5f9ee6 2023-05-29 aa
198 2d5f9ee6 2023-05-29 aa XCloseDisplay(dpy);
199 2d5f9ee6 2023-05-29 aa }
200 2d5f9ee6 2023-05-29 aa
201 6cdc5a3d 2023-05-28 aa /* succeed -- exit successfully. If print is true, also print selected item. */
202 6cdc5a3d 2023-05-28 aa static void
203 6cdc5a3d 2023-05-28 aa succeed(bool print)
204 6cdc5a3d 2023-05-28 aa {
205 6cdc5a3d 2023-05-28 aa if (print && selected)
206 6cdc5a3d 2023-05-28 aa puts(selected->s);
207 2d5f9ee6 2023-05-29 aa cleanup();
208 6cdc5a3d 2023-05-28 aa exit(0);
209 6cdc5a3d 2023-05-28 aa }
210 6cdc5a3d 2023-05-28 aa
211 6cdc5a3d 2023-05-28 aa /* scroll -- select the previous, or next item, depending on dir. */
212 6cdc5a3d 2023-05-28 aa static void
213 6cdc5a3d 2023-05-28 aa scroll(enum direction dir)
214 6cdc5a3d 2023-05-28 aa {
215 6cdc5a3d 2023-05-28 aa if (selected) {
216 6cdc5a3d 2023-05-28 aa selected->dirty = true;
217 6cdc5a3d 2023-05-28 aa selected = (dir == DIR_UP) ? selected->prev : selected->next;
218 6cdc5a3d 2023-05-28 aa } else
219 6cdc5a3d 2023-05-28 aa selected = (dir == DIR_UP) ? first->prev : first;
220 6cdc5a3d 2023-05-28 aa selected->dirty = true;
221 6cdc5a3d 2023-05-28 aa }
222 6cdc5a3d 2023-05-28 aa
223 843ffc79 2023-05-28 aa /*
224 843ffc79 2023-05-28 aa * keyselectd -- compare ks with keysyms stored in the items list, and mark
225 843ffc79 2023-05-28 aa * the first match as selected. Return true on match, and false otherwise.
226 843ffc79 2023-05-28 aa */
227 843ffc79 2023-05-28 aa static bool
228 843ffc79 2023-05-28 aa keyselect(KeySym ks)
229 843ffc79 2023-05-28 aa {
230 843ffc79 2023-05-28 aa struct item *it = first;
231 843ffc79 2023-05-28 aa KeySym k, dummy;
232 843ffc79 2023-05-28 aa
233 843ffc79 2023-05-28 aa XConvertCase(ks, &k, &dummy);
234 843ffc79 2023-05-28 aa
235 843ffc79 2023-05-28 aa do {
236 843ffc79 2023-05-28 aa size_t i;
237 843ffc79 2023-05-28 aa for (i = 0; i < it->nks; ++i)
238 843ffc79 2023-05-28 aa if (it->ks[i] == k) {
239 843ffc79 2023-05-28 aa selected->dirty = true;
240 843ffc79 2023-05-28 aa selected = it;
241 843ffc79 2023-05-28 aa selected->dirty = true;
242 843ffc79 2023-05-28 aa return true;
243 843ffc79 2023-05-28 aa }
244 843ffc79 2023-05-28 aa it = it->next;
245 843ffc79 2023-05-28 aa } while (it != first);
246 843ffc79 2023-05-28 aa
247 843ffc79 2023-05-28 aa return false;
248 843ffc79 2023-05-28 aa }
249 843ffc79 2023-05-28 aa
250 15aae8ae 2023-05-28 aa /* proc -- body of the main event-reading loop. */
251 15aae8ae 2023-05-28 aa static void
252 15aae8ae 2023-05-28 aa proc(void)
253 15aae8ae 2023-05-28 aa {
254 15aae8ae 2023-05-28 aa XKeyEvent ke;
255 15aae8ae 2023-05-28 aa KeySym ks;
256 15aae8ae 2023-05-28 aa char *dummy = "";
257 15aae8ae 2023-05-28 aa XEvent ev;
258 15aae8ae 2023-05-28 aa
259 6cdc5a3d 2023-05-28 aa static bool inwin = false;
260 6cdc5a3d 2023-05-28 aa
261 15aae8ae 2023-05-28 aa XNextEvent(dpy, &ev);
262 15aae8ae 2023-05-28 aa
263 15aae8ae 2023-05-28 aa switch (ev.type) {
264 6cdc5a3d 2023-05-28 aa case EnterNotify:
265 6cdc5a3d 2023-05-28 aa inwin = true;
266 6cdc5a3d 2023-05-28 aa /* FALLTHRU */
267 6cdc5a3d 2023-05-28 aa case MotionNotify:
268 843ffc79 2023-05-28 aa selpos(ev.xbutton.y);
269 6cdc5a3d 2023-05-28 aa /* FALLTHRU */
270 15aae8ae 2023-05-28 aa case Expose:
271 15aae8ae 2023-05-28 aa redraw();
272 15aae8ae 2023-05-28 aa break;
273 6cdc5a3d 2023-05-28 aa case LeaveNotify:
274 6cdc5a3d 2023-05-28 aa inwin = false;
275 6cdc5a3d 2023-05-28 aa break;
276 6cdc5a3d 2023-05-28 aa case ButtonPress:
277 6cdc5a3d 2023-05-28 aa if (ev.xbutton.button == Button4) {
278 6cdc5a3d 2023-05-28 aa scroll(DIR_UP);
279 6cdc5a3d 2023-05-28 aa redraw();
280 6cdc5a3d 2023-05-28 aa } else if (ev.xbutton.button == Button5) {
281 6cdc5a3d 2023-05-28 aa scroll(DIR_DOWN);
282 6cdc5a3d 2023-05-28 aa redraw();
283 6cdc5a3d 2023-05-28 aa } else if (inwin)
284 6cdc5a3d 2023-05-28 aa succeed(true);
285 6cdc5a3d 2023-05-28 aa else
286 6cdc5a3d 2023-05-28 aa succeed(false);
287 6cdc5a3d 2023-05-28 aa break;
288 15aae8ae 2023-05-28 aa case KeyPress:
289 15aae8ae 2023-05-28 aa ke = ev.xkey;
290 15aae8ae 2023-05-28 aa XLookupString(&ke, dummy, 0, &ks, NULL);
291 15aae8ae 2023-05-28 aa
292 15aae8ae 2023-05-28 aa if (ke.state & ControlMask) {
293 15aae8ae 2023-05-28 aa switch (ks) {
294 15aae8ae 2023-05-28 aa case XK_bracketleft:
295 15aae8ae 2023-05-28 aa case XK_C:
296 15aae8ae 2023-05-28 aa case XK_c:
297 15aae8ae 2023-05-28 aa ks = XK_Escape;
298 15aae8ae 2023-05-28 aa break;
299 15aae8ae 2023-05-28 aa case XK_M:
300 15aae8ae 2023-05-28 aa case XK_m:
301 15aae8ae 2023-05-28 aa case XK_J:
302 15aae8ae 2023-05-28 aa case XK_j:
303 15aae8ae 2023-05-28 aa ks = XK_Return;
304 15aae8ae 2023-05-28 aa break;
305 15aae8ae 2023-05-28 aa case XK_N:
306 15aae8ae 2023-05-28 aa case XK_n:
307 15aae8ae 2023-05-28 aa ks = XK_j;
308 15aae8ae 2023-05-28 aa return;
309 15aae8ae 2023-05-28 aa case XK_P:
310 15aae8ae 2023-05-28 aa case XK_p:
311 15aae8ae 2023-05-28 aa ks = XK_k;
312 15aae8ae 2023-05-28 aa return;
313 15aae8ae 2023-05-28 aa }
314 843ffc79 2023-05-28 aa } else if (keyselect(ks))
315 843ffc79 2023-05-28 aa succeed(true);
316 15aae8ae 2023-05-28 aa
317 15aae8ae 2023-05-28 aa switch (ks) {
318 15aae8ae 2023-05-28 aa case XK_j:
319 15aae8ae 2023-05-28 aa case XK_J:
320 15aae8ae 2023-05-28 aa case XK_Down:
321 6cdc5a3d 2023-05-28 aa scroll(DIR_DOWN);
322 15aae8ae 2023-05-28 aa redraw();
323 15aae8ae 2023-05-28 aa break;
324 15aae8ae 2023-05-28 aa case XK_k:
325 15aae8ae 2023-05-28 aa case XK_K:
326 15aae8ae 2023-05-28 aa case XK_Up:
327 6cdc5a3d 2023-05-28 aa scroll(DIR_UP);
328 15aae8ae 2023-05-28 aa redraw();
329 15aae8ae 2023-05-28 aa break;
330 15aae8ae 2023-05-28 aa case XK_Return:
331 6cdc5a3d 2023-05-28 aa succeed(true);
332 15aae8ae 2023-05-28 aa /* NOTREACHED */
333 15aae8ae 2023-05-28 aa case XK_Escape:
334 6cdc5a3d 2023-05-28 aa succeed(false);
335 15aae8ae 2023-05-28 aa /* NOTREACHED */
336 15aae8ae 2023-05-28 aa }
337 15aae8ae 2023-05-28 aa
338 15aae8ae 2023-05-28 aa break;
339 15aae8ae 2023-05-28 aa }
340 15aae8ae 2023-05-28 aa }
341 15aae8ae 2023-05-28 aa
342 6cdc5a3d 2023-05-28 aa /* grabptr -- try to grab pointer for a second */
343 6cdc5a3d 2023-05-28 aa static void
344 6cdc5a3d 2023-05-28 aa grabptr(void)
345 6cdc5a3d 2023-05-28 aa {
346 6cdc5a3d 2023-05-28 aa struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
347 6cdc5a3d 2023-05-28 aa int i;
348 6cdc5a3d 2023-05-28 aa
349 6cdc5a3d 2023-05-28 aa for (i = 0; i < 1000; ++i) {
350 6cdc5a3d 2023-05-28 aa if (XGrabPointer(dpy, RootWindow(dpy, screen), True,
351 6cdc5a3d 2023-05-28 aa ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None,
352 6cdc5a3d 2023-05-28 aa CurrentTime) == GrabSuccess)
353 6cdc5a3d 2023-05-28 aa return;
354 6cdc5a3d 2023-05-28 aa nanosleep(&ts, NULL);
355 6cdc5a3d 2023-05-28 aa }
356 6cdc5a3d 2023-05-28 aa die(1, "couldn't grab pointer");
357 6cdc5a3d 2023-05-28 aa }
358 6cdc5a3d 2023-05-28 aa
359 15aae8ae 2023-05-28 aa /* grabkb -- try to grab keyboard for a second */
360 15aae8ae 2023-05-28 aa static void
361 15aae8ae 2023-05-28 aa grabkb(void)
362 15aae8ae 2023-05-28 aa {
363 15aae8ae 2023-05-28 aa struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
364 15aae8ae 2023-05-28 aa int i;
365 15aae8ae 2023-05-28 aa
366 15aae8ae 2023-05-28 aa for (i = 0; i < 1000; ++i) {
367 15aae8ae 2023-05-28 aa if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True,
368 15aae8ae 2023-05-28 aa GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
369 15aae8ae 2023-05-28 aa return;
370 15aae8ae 2023-05-28 aa nanosleep(&ts, NULL);
371 15aae8ae 2023-05-28 aa }
372 15aae8ae 2023-05-28 aa die(1, "couldn't grab keyboard");
373 15aae8ae 2023-05-28 aa }
374 15aae8ae 2023-05-28 aa
375 6cdc5a3d 2023-05-28 aa /* setfocus -- try setting focus to the menu for a second */
376 6cdc5a3d 2023-05-28 aa static void
377 6cdc5a3d 2023-05-28 aa setfocus(void)
378 6cdc5a3d 2023-05-28 aa {
379 6cdc5a3d 2023-05-28 aa struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
380 6cdc5a3d 2023-05-28 aa Window focuswin;
381 6cdc5a3d 2023-05-28 aa int i, dummy;
382 6cdc5a3d 2023-05-28 aa
383 6cdc5a3d 2023-05-28 aa for (i = 0; i < 1000; ++i) {
384 6cdc5a3d 2023-05-28 aa XGetInputFocus(dpy, &focuswin, &dummy);
385 6cdc5a3d 2023-05-28 aa if (focuswin == win)
386 6cdc5a3d 2023-05-28 aa return;
387 6cdc5a3d 2023-05-28 aa XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
388 6cdc5a3d 2023-05-28 aa nanosleep(&ts, NULL);
389 6cdc5a3d 2023-05-28 aa }
390 6cdc5a3d 2023-05-28 aa die(1, "couldn't grab keyboard");
391 6cdc5a3d 2023-05-28 aa }
392 6cdc5a3d 2023-05-28 aa
393 77968eb5 2023-05-29 aa /* alloccol_xft -- safely allocate new Xft colour c from string s. */
394 b64495df 2023-05-29 aa static void
395 77968eb5 2023-05-29 aa alloccol_xft(char *s, XftColor *c)
396 77968eb5 2023-05-29 aa {
397 77968eb5 2023-05-29 aa if (!(XftColorAllocName(dpy, DefaultVisual(dpy, screen),
398 77968eb5 2023-05-29 aa DefaultColormap(dpy, screen), s, c)))
399 77968eb5 2023-05-29 aa die(1, "couldn't allocate Xft colour %s\n", s);
400 77968eb5 2023-05-29 aa }
401 77968eb5 2023-05-29 aa
402 77968eb5 2023-05-29 aa /* alloccol -- safely allocate new colour c from string s. */
403 77968eb5 2023-05-29 aa static void
404 b64495df 2023-05-29 aa alloccol(char *s, XColor *c)
405 b64495df 2023-05-29 aa {
406 b64495df 2023-05-29 aa XColor dummy;
407 b64495df 2023-05-29 aa if (!(XAllocNamedColor(dpy, DefaultColormap(dpy, screen), s, c, &dummy)))
408 b64495df 2023-05-29 aa die(1, "couldn't allocate colour %s\n", s);
409 b64495df 2023-05-29 aa }
410 b64495df 2023-05-29 aa
411 15aae8ae 2023-05-28 aa /*
412 15aae8ae 2023-05-28 aa * setupx -- create and map a window for n items; assign values to the X
413 15aae8ae 2023-05-28 aa * globals.
414 15aae8ae 2023-05-28 aa */
415 15aae8ae 2023-05-28 aa static void
416 15aae8ae 2023-05-28 aa setupx(int n)
417 15aae8ae 2023-05-28 aa {
418 15aae8ae 2023-05-28 aa struct item *it;
419 b64495df 2023-05-29 aa XColor col;
420 15aae8ae 2023-05-28 aa XClassHint ch = {PROGNAME, PROGNAME};
421 15aae8ae 2023-05-28 aa XSetWindowAttributes swa = {
422 15aae8ae 2023-05-28 aa .override_redirect = True,
423 15aae8ae 2023-05-28 aa .save_under = True,
424 6cdc5a3d 2023-05-28 aa .event_mask = ExposureMask | StructureNotifyMask |
425 6cdc5a3d 2023-05-28 aa KeyPressMask | ButtonPressMask | ButtonReleaseMask |
426 6cdc5a3d 2023-05-28 aa PointerMotionMask | LeaveWindowMask | EnterWindowMask,
427 15aae8ae 2023-05-28 aa };
428 15aae8ae 2023-05-28 aa
429 77968eb5 2023-05-29 aa if (!(font = XftFontOpenName(dpy, screen, o_font)))
430 15aae8ae 2023-05-28 aa die(1, "couldn't load font");
431 77968eb5 2023-05-29 aa height = font->height + o_vp;
432 15aae8ae 2023-05-28 aa
433 15aae8ae 2023-05-28 aa it = first;
434 15aae8ae 2023-05-28 aa do {
435 77968eb5 2023-05-29 aa XGlyphInfo gi;
436 77968eb5 2023-05-29 aa XftTextExtentsUtf8(dpy, font, (FcChar8 *)it->s, it->len, &gi);
437 77968eb5 2023-05-29 aa if (gi.xOff + o_hp*2 > width)
438 77968eb5 2023-05-29 aa width = gi.xOff + o_hp*2;
439 15aae8ae 2023-05-28 aa it = it->next;
440 15aae8ae 2023-05-28 aa } while (it != first);
441 15aae8ae 2023-05-28 aa
442 f0265770 2023-05-28 aa if (o_x + width > DisplayWidth(dpy, screen))
443 f0265770 2023-05-28 aa o_x = DisplayWidth(dpy, screen) - width;
444 f0265770 2023-05-28 aa if (o_y + height*n > DisplayHeight(dpy, screen))
445 f0265770 2023-05-28 aa o_y = DisplayHeight(dpy, screen) - height*n;
446 f0265770 2023-05-28 aa
447 b64495df 2023-05-29 aa alloccol(o_bg, &col);
448 2d5f9ee6 2023-05-29 aa pixels[PIXEL_BG] = swa.background_pixel = col.pixel;
449 2d5f9ee6 2023-05-29 aa alloccol(o_bc, &col);
450 2d5f9ee6 2023-05-29 aa pixels[PIXEL_BC] = swa.border_pixel = col.pixel;
451 f1adc4ca 2023-05-28 aa
452 15aae8ae 2023-05-28 aa win = XCreateWindow(dpy, RootWindow(dpy, screen), o_x, o_y,
453 15aae8ae 2023-05-28 aa width, n*height, o_bw, CopyFromParent, CopyFromParent,
454 f1adc4ca 2023-05-28 aa CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel |
455 f1adc4ca 2023-05-28 aa CWEventMask | CWSaveUnder, &swa);
456 15aae8ae 2023-05-28 aa XSetClassHint(dpy, win, &ch);
457 15aae8ae 2023-05-28 aa
458 77968eb5 2023-05-29 aa alloccol_xft(o_fg, &c_fg);
459 77968eb5 2023-05-29 aa alloccol_xft(o_sfg, &c_sfg);
460 77968eb5 2023-05-29 aa alloccol_xft(o_sbg, &c_sbg);
461 15aae8ae 2023-05-28 aa
462 15aae8ae 2023-05-28 aa grabkb();
463 6cdc5a3d 2023-05-28 aa grabptr();
464 15aae8ae 2023-05-28 aa
465 15aae8ae 2023-05-28 aa XMapRaised(dpy, win);
466 6cdc5a3d 2023-05-28 aa
467 6cdc5a3d 2023-05-28 aa setfocus();
468 15aae8ae 2023-05-28 aa }
469 15aae8ae 2023-05-28 aa
470 15aae8ae 2023-05-28 aa /*
471 15aae8ae 2023-05-28 aa * Create a new struct item, and insert it a after the it item. The rest of the
472 15aae8ae 2023-05-28 aa * arguments are values that the new item receives. On success return pointer
473 15aae8ae 2023-05-28 aa * to the new item. Return NULL otherwise.
474 15aae8ae 2023-05-28 aa */
475 15aae8ae 2023-05-28 aa static struct item *
476 843ffc79 2023-05-28 aa insitem(struct item *it, char *s) {
477 cb28fcb8 2023-05-26 aa struct item *new;
478 843ffc79 2023-05-28 aa char *p, *end;
479 843ffc79 2023-05-28 aa
480 cb28fcb8 2023-05-26 aa if (!(new = calloc(1, sizeof *new)))
481 cb28fcb8 2023-05-26 aa return NULL;
482 cb28fcb8 2023-05-26 aa if (it) {
483 cb28fcb8 2023-05-26 aa new->prev = it;
484 cb28fcb8 2023-05-26 aa new->next = it->next;
485 cb28fcb8 2023-05-26 aa it->next->prev = new;
486 cb28fcb8 2023-05-26 aa it->next = new;
487 cb28fcb8 2023-05-26 aa } else
488 cb28fcb8 2023-05-26 aa new->prev = new->next = new;
489 843ffc79 2023-05-28 aa new->nks = 0;
490 15aae8ae 2023-05-28 aa new->dirty = true;
491 843ffc79 2023-05-28 aa
492 843ffc79 2023-05-28 aa for (p = s; ; p = end) {
493 843ffc79 2023-05-28 aa size_t n;
494 843ffc79 2023-05-28 aa char c;
495 843ffc79 2023-05-28 aa KeySym ks;
496 843ffc79 2023-05-28 aa
497 843ffc79 2023-05-28 aa n = strspn(p, " ");
498 843ffc79 2023-05-28 aa p = p + n;
499 843ffc79 2023-05-28 aa if (!(n = strcspn(p, " \t"))) {
500 843ffc79 2023-05-28 aa p += strspn(p, " \t");
501 843ffc79 2023-05-28 aa break;
502 843ffc79 2023-05-28 aa }
503 843ffc79 2023-05-28 aa
504 843ffc79 2023-05-28 aa end = p + n;
505 843ffc79 2023-05-28 aa c = *end;
506 843ffc79 2023-05-28 aa *end = '\0';
507 843ffc79 2023-05-28 aa
508 843ffc79 2023-05-28 aa if ((ks = XStringToKeysym(p)) == NoSymbol)
509 843ffc79 2023-05-28 aa warn("no such keysym: %s\n", p);
510 843ffc79 2023-05-28 aa else if (new->nks >= MAXKS)
511 843ffc79 2023-05-28 aa warn("too many keysyms (%s)\n", p);
512 843ffc79 2023-05-28 aa else {
513 843ffc79 2023-05-28 aa KeySym k, dummy;
514 843ffc79 2023-05-28 aa XConvertCase(ks, &k, &dummy);
515 843ffc79 2023-05-28 aa new->ks[new->nks++] = k;
516 843ffc79 2023-05-28 aa }
517 843ffc79 2023-05-28 aa
518 843ffc79 2023-05-28 aa *end = c;
519 843ffc79 2023-05-28 aa }
520 843ffc79 2023-05-28 aa
521 843ffc79 2023-05-28 aa new->len = strlen(p);
522 843ffc79 2023-05-28 aa new->s = strdup(p);
523 843ffc79 2023-05-28 aa
524 cb28fcb8 2023-05-26 aa return new;
525 cb28fcb8 2023-05-26 aa }
526 cb28fcb8 2023-05-26 aa
527 15aae8ae 2023-05-28 aa /*
528 15aae8ae 2023-05-28 aa * mkitems -- create a list of items from stdin, and return pointer to the
529 15aae8ae 2023-05-28 aa * new list's first element. If np is not NULL, set its value to the number of
530 15aae8ae 2023-05-28 aa * elements in the list. Return NULL on error.
531 15aae8ae 2023-05-28 aa */
532 15aae8ae 2023-05-28 aa static struct item *
533 15aae8ae 2023-05-28 aa mkitems(int *np)
534 cb28fcb8 2023-05-26 aa {
535 15aae8ae 2023-05-28 aa struct item *last = NULL;
536 15aae8ae 2023-05-28 aa size_t n = 0;
537 15aae8ae 2023-05-28 aa char *line = NULL;
538 15aae8ae 2023-05-28 aa size_t linesize = 0;
539 15aae8ae 2023-05-28 aa ssize_t linelen;
540 cb28fcb8 2023-05-26 aa
541 15aae8ae 2023-05-28 aa while ((linelen = getline(&line, &linesize, stdin)) != -1) {
542 15aae8ae 2023-05-28 aa struct item *it;
543 f0265770 2023-05-28 aa if (line[linelen-1] == '\n')
544 f0265770 2023-05-28 aa line[--linelen] = '\0';
545 843ffc79 2023-05-28 aa if (!(it = insitem(last, line)))
546 15aae8ae 2023-05-28 aa die(1, "couldn't insert new item");
547 15aae8ae 2023-05-28 aa n++;
548 15aae8ae 2023-05-28 aa last = it;
549 15aae8ae 2023-05-28 aa }
550 15aae8ae 2023-05-28 aa free(line);
551 cb28fcb8 2023-05-26 aa
552 15aae8ae 2023-05-28 aa if (np)
553 15aae8ae 2023-05-28 aa *np = n;
554 cb28fcb8 2023-05-26 aa
555 15aae8ae 2023-05-28 aa return last ? last->next : NULL;
556 cb28fcb8 2023-05-26 aa }
557 cb28fcb8 2023-05-26 aa
558 15aae8ae 2023-05-28 aa /*
559 15aae8ae 2023-05-28 aa * {s,i}default -- return the {char *,int} value of the X default, or def
560 15aae8ae 2023-05-28 aa * otherwise.
561 15aae8ae 2023-05-28 aa */
562 15aae8ae 2023-05-28 aa static char *
563 f1adc4ca 2023-05-28 aa sdefault(const char *opt, const char *def)
564 7c7bbaee 2023-05-27 aa {
565 7c7bbaee 2023-05-27 aa char *val = XGetDefault(dpy, PROGNAME, opt);
566 f1adc4ca 2023-05-28 aa return val ? val : (char *)def;
567 7c7bbaee 2023-05-27 aa }
568 7c7bbaee 2023-05-27 aa
569 15aae8ae 2023-05-28 aa static int
570 f1adc4ca 2023-05-28 aa idefault(const char *opt, int def)
571 7c7bbaee 2023-05-27 aa {
572 7c7bbaee 2023-05-27 aa char *val = XGetDefault(dpy, PROGNAME, opt);
573 7c7bbaee 2023-05-27 aa return val ? atoi(val) : def;
574 7c7bbaee 2023-05-27 aa }
575 7c7bbaee 2023-05-27 aa
576 15aae8ae 2023-05-28 aa /* nextarg -- safely skip the current command-line option */
577 15aae8ae 2023-05-28 aa static void
578 15aae8ae 2023-05-28 aa nextarg(int *argcp, char **argvp[])
579 15aae8ae 2023-05-28 aa {
580 15aae8ae 2023-05-28 aa if (--*argcp <= 0)
581 15aae8ae 2023-05-28 aa usage();
582 15aae8ae 2023-05-28 aa ++*argvp;
583 15aae8ae 2023-05-28 aa }
584 15aae8ae 2023-05-28 aa
585 15aae8ae 2023-05-28 aa /*
586 15aae8ae 2023-05-28 aa * {s,i}arg -- return the {char *,int} argument of the current command-line
587 15aae8ae 2023-05-28 aa * option.
588 15aae8ae 2023-05-28 aa */
589 15aae8ae 2023-05-28 aa static char *
590 7c7bbaee 2023-05-27 aa sarg(int *argcp, char **argvp[])
591 7c7bbaee 2023-05-27 aa {
592 15aae8ae 2023-05-28 aa nextarg(argcp, argvp);
593 7c7bbaee 2023-05-27 aa return **argvp;
594 7c7bbaee 2023-05-27 aa }
595 7c7bbaee 2023-05-27 aa
596 15aae8ae 2023-05-28 aa static int
597 7c7bbaee 2023-05-27 aa iarg(int *argcp, char **argvp[])
598 7c7bbaee 2023-05-27 aa {
599 15aae8ae 2023-05-28 aa nextarg(argcp, argvp);
600 7c7bbaee 2023-05-27 aa return atoi(**argvp);
601 7c7bbaee 2023-05-27 aa }
602 7c7bbaee 2023-05-27 aa
603 15aae8ae 2023-05-28 aa /*
604 15aae8ae 2023-05-28 aa * xitems -- pop-up menu for X, constructed from stdin, and printing user choice
605 15aae8ae 2023-05-28 aa * to stdout.
606 15aae8ae 2023-05-28 aa */
607 7c7bbaee 2023-05-27 aa int
608 cb28fcb8 2023-05-26 aa main(int argc, char *argv[])
609 cb28fcb8 2023-05-26 aa {
610 15aae8ae 2023-05-28 aa int n = 0;
611 cb28fcb8 2023-05-26 aa
612 7c7bbaee 2023-05-27 aa for (--argc, ++argv; argc > 0; --argc, ++argv)
613 7c7bbaee 2023-05-27 aa if (argv[0][0] == '-')
614 7c7bbaee 2023-05-27 aa switch (argv[0][1]) {
615 7c7bbaee 2023-05-27 aa case 'b':
616 7c7bbaee 2023-05-27 aa switch (argv[0][2]) {
617 f1adc4ca 2023-05-28 aa case 'c': /* -bc */
618 f1adc4ca 2023-05-28 aa o_bc = sarg(&argc, &argv);
619 f1adc4ca 2023-05-28 aa break;
620 7c7bbaee 2023-05-27 aa case 'g': /* -bg */
621 15aae8ae 2023-05-28 aa o_bg = sarg(&argc, &argv);
622 7c7bbaee 2023-05-27 aa break;
623 7c7bbaee 2023-05-27 aa case 'w': /* -bw */
624 15aae8ae 2023-05-28 aa o_bw = iarg(&argc, &argv);
625 7c7bbaee 2023-05-27 aa break;
626 7c7bbaee 2023-05-27 aa default:
627 7c7bbaee 2023-05-27 aa usage();
628 7c7bbaee 2023-05-27 aa /* NOTREACHED */
629 7c7bbaee 2023-05-27 aa }
630 7c7bbaee 2023-05-27 aa break;
631 7c7bbaee 2023-05-27 aa case 'f':
632 7c7bbaee 2023-05-27 aa switch (argv[0][2]) {
633 7c7bbaee 2023-05-27 aa case 'g': /* -fg */
634 15aae8ae 2023-05-28 aa o_fg = sarg(&argc, &argv);
635 7c7bbaee 2023-05-27 aa break;
636 7c7bbaee 2023-05-27 aa case 'o': /* -font */
637 15aae8ae 2023-05-28 aa o_font = sarg(&argc, &argv);
638 7c7bbaee 2023-05-27 aa break;
639 7c7bbaee 2023-05-27 aa default:
640 7c7bbaee 2023-05-27 aa usage();
641 7c7bbaee 2023-05-27 aa /* NOTREACHED */
642 7c7bbaee 2023-05-27 aa }
643 7c7bbaee 2023-05-27 aa break;
644 15aae8ae 2023-05-28 aa case 'h': /* -hp */
645 15aae8ae 2023-05-28 aa o_hp = iarg(&argc, &argv);
646 15aae8ae 2023-05-28 aa break;
647 7c7bbaee 2023-05-27 aa case 's':
648 7c7bbaee 2023-05-27 aa switch (argv[0][2]) {
649 7c7bbaee 2023-05-27 aa case 'b': /* -sbg */
650 15aae8ae 2023-05-28 aa o_sbg = sarg(&argc, &argv);
651 7c7bbaee 2023-05-27 aa break;
652 7c7bbaee 2023-05-27 aa case 'f': /* -sfg */
653 15aae8ae 2023-05-28 aa o_sfg = sarg(&argc, &argv);
654 7c7bbaee 2023-05-27 aa break;
655 7c7bbaee 2023-05-27 aa default:
656 7c7bbaee 2023-05-27 aa usage();
657 7c7bbaee 2023-05-27 aa /* NOTREACHED */
658 7c7bbaee 2023-05-27 aa }
659 7c7bbaee 2023-05-27 aa break;
660 15aae8ae 2023-05-28 aa case 'v': /* -vp */
661 15aae8ae 2023-05-28 aa o_vp = iarg(&argc, &argv);
662 15aae8ae 2023-05-28 aa break;
663 7c7bbaee 2023-05-27 aa case 'x': /* -x */
664 15aae8ae 2023-05-28 aa o_x = iarg(&argc, &argv);
665 7c7bbaee 2023-05-27 aa break;
666 7c7bbaee 2023-05-27 aa case 'y': /* -y */
667 15aae8ae 2023-05-28 aa o_y = iarg(&argc, &argv);
668 7c7bbaee 2023-05-27 aa break;
669 7c7bbaee 2023-05-27 aa default:
670 7c7bbaee 2023-05-27 aa usage();
671 7c7bbaee 2023-05-27 aa /* NOTREACHED */
672 7c7bbaee 2023-05-27 aa }
673 7c7bbaee 2023-05-27 aa
674 7c7bbaee 2023-05-27 aa if (!(dpy = XOpenDisplay(NULL)))
675 15aae8ae 2023-05-28 aa die(1, "couldn't open display");
676 7c7bbaee 2023-05-27 aa screen = DefaultScreen(dpy);
677 7c7bbaee 2023-05-27 aa
678 15aae8ae 2023-05-28 aa if (!o_bg)
679 15aae8ae 2023-05-28 aa o_bg = sdefault("background", "white");
680 15aae8ae 2023-05-28 aa if (!o_fg)
681 15aae8ae 2023-05-28 aa o_fg = sdefault("foreground", "black");
682 15aae8ae 2023-05-28 aa if (!o_font)
683 77968eb5 2023-05-29 aa o_font = sdefault("font", "DejaVu Sans Mono-10");
684 15aae8ae 2023-05-28 aa if (!o_sbg)
685 15aae8ae 2023-05-28 aa o_sbg = sdefault("selectedBackground", "black");
686 15aae8ae 2023-05-28 aa if (!o_sfg)
687 15aae8ae 2023-05-28 aa o_sfg = sdefault("selectedForeground", "white");
688 f1adc4ca 2023-05-28 aa if (!o_bc)
689 f1adc4ca 2023-05-28 aa o_bc = sdefault("borderColour", "black");
690 15aae8ae 2023-05-28 aa if (o_bw == -1)
691 15aae8ae 2023-05-28 aa o_bw = idefault("borderWidth", 1);
692 15aae8ae 2023-05-28 aa if (o_hp == -1)
693 15aae8ae 2023-05-28 aa o_hp = idefault("horizontalPadding", 2);
694 15aae8ae 2023-05-28 aa if (o_vp == -1)
695 15aae8ae 2023-05-28 aa o_vp = idefault("verticalPadding", 1);
696 7c7bbaee 2023-05-27 aa
697 15aae8ae 2023-05-28 aa if (o_x == -1 || o_y == -1) {
698 7c7bbaee 2023-05-27 aa Window w;
699 7c7bbaee 2023-05-27 aa int i;
700 7c7bbaee 2023-05-27 aa unsigned int ui;
701 7c7bbaee 2023-05-27 aa int *xp, *yp;
702 7c7bbaee 2023-05-27 aa
703 15aae8ae 2023-05-28 aa xp = (o_x == -1) ? &o_x : &i;
704 15aae8ae 2023-05-28 aa yp = (o_y == -1) ? &o_y : &i;
705 7c7bbaee 2023-05-27 aa
706 7c7bbaee 2023-05-27 aa XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, xp, yp, &i,
707 7c7bbaee 2023-05-27 aa &i, &ui);
708 7c7bbaee 2023-05-27 aa }
709 7c7bbaee 2023-05-27 aa
710 15aae8ae 2023-05-28 aa if (!(first = selected = mkitems(&n))) {
711 15aae8ae 2023-05-28 aa if (n)
712 15aae8ae 2023-05-28 aa die(1, "coulnd't create items list");
713 15aae8ae 2023-05-28 aa else
714 15aae8ae 2023-05-28 aa exit(0);
715 cb28fcb8 2023-05-26 aa }
716 15aae8ae 2023-05-28 aa setupx(n);
717 cb28fcb8 2023-05-26 aa
718 15aae8ae 2023-05-28 aa for (;;)
719 15aae8ae 2023-05-28 aa proc();
720 cb28fcb8 2023-05-26 aa /* NOTREACHED */
721 cb28fcb8 2023-05-26 aa }