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