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