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