initial dmenu / dinput separation
[dmenu.git] / dinput.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <locale.h>
4 #include <stdarg.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <strings.h>
9 #include <unistd.h>
10 #include <X11/keysym.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xutil.h>
13 #ifdef XINERAMA
14 #include <X11/extensions/Xinerama.h>
15 #endif
16
17 /* macros */
18 #define CLEANMASK(mask)         (mask & ~(numlockmask | LockMask))
19 #define INRECT(X,Y,RX,RY,RW,RH) ((X) >= (RX) && (X) < (RX) + (RW) && (Y) >= (RY) && (Y) < (RY) + (RH))
20 #define MIN(a, b)               ((a) < (b) ? (a) : (b))
21 #define MAX(a, b)               ((a) > (b) ? (a) : (b))
22 #define IS_UTF8_1ST_CHAR(c)     ((((c) & 0xc0) == 0xc0) || !((c) & 0x80))
23
24 /* forward declarations */
25 static void cleanup(void);
26 static void drawcursor(void);
27 static void drawinput(void);
28 static void eprint(const char *errstr, ...);
29 static Bool grabkeyboard(void);
30 static void kpress(XKeyEvent * e);
31 static void run(void);
32 static void setup(Bool topbar);
33
34 #include "config.h"
35
36 /* variables */
37 static char *prompt = NULL;
38 static char text[4096];
39 static int promptw = 0;
40 static int ret = 0;
41 static int screen;
42 static unsigned int mw, mh;
43 static unsigned int cursor = 0;
44 static unsigned int numlockmask = 0;
45 static Bool running = True;
46 static Display *dpy;
47 static Window parent, win;
48
49 #include "draw.c"
50
51 void
52 cleanup(void) {
53         dccleanup();
54         XDestroyWindow(dpy, win);
55         XUngrabKeyboard(dpy, CurrentTime);
56 }
57
58 void
59 drawcursor(void) {
60         XRectangle r = { dc.x, dc.y + 2, 1, dc.font.height - 2 };
61
62         r.x += textnw(text, cursor) + dc.font.height / 2;
63
64         XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
65         XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
66 }
67
68 void
69 drawinput(void)
70 {
71         dc.x = 0;
72         dc.y = 0;
73         dc.w = mw;
74         dc.h = mh;
75         drawtext(NULL, dc.norm);
76         /* print prompt? */
77         if(prompt) {
78                 dc.w = promptw;
79                 drawtext(prompt, dc.sel);
80                 dc.x += dc.w;
81         }
82         dc.w = mw - dc.x;
83         drawtext(*text ? text : NULL, dc.norm);
84         drawcursor();
85         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
86         XFlush(dpy);
87 }
88
89 void
90 eprint(const char *errstr, ...) {
91         va_list ap;
92
93         va_start(ap, errstr);
94         vfprintf(stderr, errstr, ap);
95         va_end(ap);
96         exit(EXIT_FAILURE);
97 }
98
99 Bool
100 grabkeyboard(void) {
101         unsigned int len;
102
103         for(len = 1000; len; len--) {
104                 if(XGrabKeyboard(dpy, parent, True, GrabModeAsync, GrabModeAsync, CurrentTime)
105                 == GrabSuccess)
106                         break;
107                 usleep(1000);
108         }
109         return len > 0;
110 }
111
112 void
113 kpress(XKeyEvent * e) {
114         char buf[sizeof text];
115         int num;
116         unsigned int i, len;
117         KeySym ksym;
118
119         len = strlen(text);
120         num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
121         if(ksym == XK_KP_Enter)
122                 ksym = XK_Return;
123         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
124                 ksym = (ksym - XK_KP_0) + XK_0;
125         else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
126         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
127         || IsPrivateKeypadKey(ksym))
128                 return;
129         /* first check if a control mask is omitted */
130         if(e->state & ControlMask) {
131                 switch(tolower(ksym)) {
132                 default:
133                         return;
134                 case XK_a:
135                         ksym = XK_Home;
136                         break;
137                 case XK_b:
138                         ksym = XK_Left;
139                         break;
140                 case XK_c:
141                         ksym = XK_Escape;
142                         break;
143                 case XK_e:
144                         ksym = XK_End;
145                         break;
146                 case XK_f:
147                         ksym = XK_Right;
148                         break;
149                 case XK_h:
150                         ksym = XK_BackSpace;
151                         break;
152                 case XK_j:
153                         ksym = XK_Return;
154                         break;
155                 case XK_k:
156                         text[cursor] = '\0';
157                         break;
158                 case XK_u:
159                         memmove(text, text + cursor, sizeof text - cursor + 1);
160                         cursor = 0;
161                         break;
162                 case XK_w:
163                         if(cursor > 0) {
164                                 i = cursor;
165                                 while(i-- > 0 && text[i] == ' ');
166                                 while(i-- > 0 && text[i] != ' ');
167                                 memmove(text + i + 1, text + cursor, sizeof text - cursor + 1);
168                                 cursor = i + 1;
169                         }
170                         break;
171                 case XK_y:
172                         {
173                                 FILE *fp;
174                                 char *s;
175                                 if(!(fp = popen("sselp", "r")))
176                                         eprint("dinput: cannot popen sselp\n");
177                                 s = fgets(buf, sizeof buf, fp);
178                                 pclose(fp);
179                                 if(s == NULL)
180                                         return;
181                         }
182                         num = strlen(buf);
183                         if(num && buf[num-1] == '\n')
184                                 buf[--num] = '\0';
185                         break;
186                 }
187         }
188         switch(ksym) {
189         default:
190                 num = MIN(num, sizeof text - cursor);
191                 if(num && !iscntrl((int) buf[0])) {
192                         memmove(text + cursor + num, text + cursor, sizeof text - cursor - num);
193                         memcpy(text + cursor, buf, num);
194                         cursor += num;
195                 }
196                 break;
197         case XK_BackSpace:
198                 if(cursor == 0)
199                         return;
200                 for(i = 1; cursor - i > 0 && !IS_UTF8_1ST_CHAR(text[cursor - i]); i++);
201                 memmove(text + cursor - i, text + cursor, sizeof text - cursor + i);
202                 cursor -= i;
203                 break;
204         case XK_Delete:
205                 if(cursor == len)
206                         return;
207                 for(i = 1; cursor + i < len && !IS_UTF8_1ST_CHAR(text[cursor + i]); i++);
208                 memmove(text + cursor, text + cursor + i, sizeof text - cursor);
209                 break;
210         case XK_End:
211                 cursor = len;
212                 break;
213         case XK_Escape:
214                 ret = 1;
215                 running = False;
216                 return;
217         case XK_Home:
218                 cursor = 0;
219                 break;
220         case XK_Left:
221                 if(cursor == 0)
222                         return;
223                 while(cursor-- > 0 && !IS_UTF8_1ST_CHAR(text[cursor]));
224                 break;
225         case XK_Return:
226                 fprintf(stdout, "%s", text);
227                 fflush(stdout);
228                 running = False;
229                 return;
230         case XK_Right:
231                 if(cursor == len)
232                         return;
233                 while(cursor++ < len && !IS_UTF8_1ST_CHAR(text[cursor]));
234                 break;
235         }
236         drawinput();
237 }
238
239 void
240 run(void) {
241         XEvent ev;
242
243         /* main event loop */
244         while(running && !XNextEvent(dpy, &ev))
245                 switch (ev.type) {
246                 case KeyPress:
247                         kpress(&ev.xkey);
248                         break;
249                 case Expose:
250                         if(ev.xexpose.count == 0)
251                                 drawinput();
252                         break;
253                 case VisibilityNotify:
254                         if (ev.xvisibility.state != VisibilityUnobscured)
255                                 XRaiseWindow(dpy, win);
256                         break;
257                 }
258 }
259
260 void
261 setup(Bool topbar) {
262         int i, j, x, y;
263 #if XINERAMA
264         int n;
265         XineramaScreenInfo *info = NULL;
266 #endif
267         XModifierKeymap *modmap;
268         XSetWindowAttributes wa;
269         XWindowAttributes pwa;
270
271         /* init modifier map */
272         modmap = XGetModifierMapping(dpy);
273         for(i = 0; i < 8; i++)
274                 for(j = 0; j < modmap->max_keypermod; j++) {
275                         if(modmap->modifiermap[i * modmap->max_keypermod + j]
276                         == XKeysymToKeycode(dpy, XK_Num_Lock))
277                                 numlockmask = (1 << i);
278                 }
279         XFreeModifiermap(modmap);
280
281         /* style */
282         dc.norm[ColBG] = getcolor(normbgcolor);
283         dc.norm[ColFG] = getcolor(normfgcolor);
284         dc.sel[ColBG] = getcolor(selbgcolor);
285         dc.sel[ColFG] = getcolor(selfgcolor);
286         initfont(font);
287
288         /* menu window */
289         wa.override_redirect = True;
290         wa.background_pixmap = ParentRelative;
291         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask | VisibilityChangeMask;
292
293         /* menu window geometry */
294         mh = (dc.font.height + 2);
295 #if XINERAMA
296         if(parent == RootWindow(dpy, screen) && XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) {
297                 i = 0;
298                 if(n > 1) {
299                         int di;
300                         unsigned int dui;
301                         Window dummy;
302                         if(XQueryPointer(dpy, parent, &dummy, &dummy, &x, &y, &di, &di, &dui))
303                                 for(i = 0; i < n; i++)
304                                         if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
305                                                 break;
306                 }
307                 x = info[i].x_org;
308                 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh;
309                 mw = info[i].width;
310                 XFree(info);
311         }
312         else
313 #endif
314         {
315                 XGetWindowAttributes(dpy, parent, &pwa);
316                 x = 0;
317                 y = topbar ? 0 : pwa.height - mh;
318                 mw = pwa.width;
319         }
320
321         win = XCreateWindow(dpy, parent, x, y, mw, mh, 0,
322                         DefaultDepth(dpy, screen), CopyFromParent,
323                         DefaultVisual(dpy, screen),
324                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
325
326         /* pixmap */
327         dcsetup();
328         if(prompt)
329                 promptw = MIN(textw(prompt), mw / 5);
330         cursor = strlen(text);
331         XMapRaised(dpy, win);
332 }
333
334 int
335 main(int argc, char *argv[]) {
336         unsigned int i;
337         Bool topbar = True;
338
339         /* command line args */
340         for(i = 1; i < argc; i++)
341                 if(!strcmp(argv[i], "-b"))
342                         topbar = False;
343                 else if(!strcmp(argv[i], "-e")) {
344                         if(++i < argc) parent = atoi(argv[i]);
345                 }
346                 else if(!strcmp(argv[i], "-fn")) {
347                         if(++i < argc) font = argv[i];
348                 }
349                 else if(!strcmp(argv[i], "-nb")) {
350                         if(++i < argc) normbgcolor = argv[i];
351                 }
352                 else if(!strcmp(argv[i], "-nf")) {
353                         if(++i < argc) normfgcolor = argv[i];
354                 }
355                 else if(!strcmp(argv[i], "-p")) {
356                         if(++i < argc) prompt = argv[i];
357                 }
358                 else if(!strcmp(argv[i], "-sb")) {
359                         if(++i < argc) selbgcolor = argv[i];
360                 }
361                 else if(!strcmp(argv[i], "-sf")) {
362                         if(++i < argc) selfgcolor = argv[i];
363                 }
364                 else if(!strcmp(argv[i], "-v"))
365                         eprint("dinput-"VERSION", © 2006-2010 dinput engineers, see LICENSE for details\n");
366                 else if(!*text)
367                         strncpy(text, argv[i], sizeof text);
368                 else
369                         eprint("usage: dinput [-b] [-e <xid>] [-fn <font>] [-nb <color>] [-nf <color>]\n"
370                                "              [-p <prompt>] [-sb <color>] [-sf <color>] [-v] [<text>]\n");
371         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
372                 fprintf(stderr, "dinput: warning: no locale support\n");
373         if(!(dpy = XOpenDisplay(NULL)))
374                 eprint("dinput: cannot open display\n");
375         screen = DefaultScreen(dpy);
376         if(!parent)
377                 parent = RootWindow(dpy, screen);
378
379         running = grabkeyboard();
380         setup(topbar);
381         drawinput();
382         XSync(dpy, False);
383         run();
384         cleanup();
385         XCloseDisplay(dpy);
386         return ret;
387 }