An optical flow driven virtual keyboard.
[vkeyb] / src / main.cc
1 /* 
2 vkeyb - camera motion detection virtual keyboard
3 Copyright (C) 2012 Eleni Maria Stea <elene.mst@gmail.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <assert.h>
20 #include <pthread.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include <X11/Xlib.h>
27 #include <GL/gl.h>
28 #include <GL/glx.h>
29 #include <GL/glut.h>
30
31 #include <opencv2/opencv.hpp>
32
33 #include <imago2.h>
34 #include "vkeyb.h"
35 #include "motion.h"
36
37 int init(void);
38 void shutdown(void);
39 int create_window(int xsz, int ysz);
40 void display(void);
41 void show_frame(float frm_width);
42 int handle_event(XEvent *xev);
43 void reshape(int w, int h);
44 void keyb(int key, int pressed);
45 void send_key(KeySym key);
46 void motion(int x, int y);
47 void cam_motion(double orient);
48 void button(int x, int y, int bn, int state);
49 void activate(int enter);
50
51 Display *dpy;           /* X11 display structure (x server connection) */
52 Window win;                     /* X window */
53 GLXContext ctx;         /* OpenGL context */
54
55 unsigned int frm_tex;
56 bool tex_created;
57
58 double size = 0.1;
59 VKeyb *vkeyb;
60
61 int must_redraw;
62
63 static double orient = 0.0;
64
65
66 int main (int argc, char** argv)
67 {
68         glutInit(&argc, argv);
69
70         if(init() == -1) {
71                 return 1;
72         }
73         atexit(shutdown);
74
75         glEnable(GL_CULL_FACE);
76
77         for(;;) {
78                 fd_set fdset;
79
80                 FD_ZERO(&fdset);
81
82                 //retreive the X server socket
83                 int xsock = ConnectionNumber(dpy);
84                 FD_SET(xsock, &fdset);
85                 FD_SET(pipefd[0], &fdset);
86
87                 int maxfd = (xsock > pipefd[0] ? xsock : pipefd[0]) + 1;
88                 select(maxfd, &fdset, 0, 0, 0);
89
90                 if(FD_ISSET(pipefd[0], &fdset)) {
91                         // process all pending events ...
92                         while(XPending(dpy)) {
93                                 XEvent xev;
94                                 XNextEvent(dpy, &xev);
95                                 handle_event(&xev);
96                         }
97                 }
98
99                 if(FD_ISSET(pipefd[0], &fdset)) {
100                         if(read(pipefd[0], &orient, sizeof orient) < (int)sizeof orient) {
101                                 fprintf(stderr, "read from pipe failed\n");
102                         }
103                         else {
104                                 glBindTexture(GL_TEXTURE_2D, frm_tex);
105                                 if(!tex_created) {
106                                         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frm.cols, frm.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, frm.data);
107                                         tex_created = true;
108                                 }
109                                 else {
110                                         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frm.cols, frm.rows, GL_BGR, GL_UNSIGNED_BYTE, frm.data);
111                                 }
112
113                                 cam_motion(orient);
114                                 must_redraw = true;
115                         }
116                 }
117
118                 // ... and then do a single redisplay if needed
119                 if(must_redraw) {
120                         display();
121                 }
122
123         }
124
125         shutdown();
126         return 0;
127 }
128
129 int init(void)
130 {
131         Screen *scr;
132         int width, height;
133
134         if(!(dpy = XOpenDisplay(0))) {
135                 fprintf(stderr, "failed to connect to the X server\n");
136                 return -1;
137         }
138         scr = ScreenOfDisplay(dpy, DefaultScreen(dpy));
139
140         width = WidthOfScreen(scr);
141         height = width / 16;
142
143         if(create_window(width, height) == -1) {
144                 XCloseDisplay(dpy);
145                 return -1;
146         }
147         XMoveWindow(dpy, win, 0, HeightOfScreen(scr) - height);
148
149         try {
150                 vkeyb = new VKeyb;
151         }
152         catch(...) {
153                 fprintf(stderr, "failed to initialize virtual keyboard\n");
154                 return -1;
155         }
156
157         // register a passive grab
158         Window root = RootWindow(dpy, DefaultScreen(dpy));
159         XGrabKey(dpy, XKeysymToKeycode(dpy, 'e'), ControlMask, root, False, GrabModeAsync, GrabModeAsync);
160
161         // create texture
162         glGenTextures(1, &frm_tex);
163         glBindTexture(GL_TEXTURE_2D, frm_tex);
164         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
165         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
166         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
167         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
168
169         // start the capturing thread
170         if(!start_capture())
171                 return -1;
172
173         return 0;
174 }
175
176 void shutdown(void)
177 {
178         glXMakeCurrent(dpy, None, 0);
179         glXDestroyContext(dpy, ctx);
180         XDestroyWindow(dpy, win);
181         XCloseDisplay(dpy);
182 }
183
184 int create_window(int xsz, int ysz)
185 {
186         int scr;
187         Window root;
188         XSetWindowAttributes xattr;
189         XVisualInfo *vis;
190         unsigned int attr_valid;
191         long evmask;
192
193         int glattr[] = {
194                 GLX_RGBA,
195                 GLX_RED_SIZE, 8,
196                 GLX_GREEN_SIZE, 8,
197                 GLX_BLUE_SIZE, 8,
198                 GLX_DEPTH_SIZE, 24,
199                 GLX_DOUBLEBUFFER,
200                 None
201         };
202
203         scr = DefaultScreen(dpy);
204         root = RootWindow(dpy, scr);
205
206         if(!(vis = glXChooseVisual(dpy, scr, glattr))) {
207                 fprintf(stderr, "failed to find a suitable visual\n");
208                 return -1;
209         }
210
211         if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
212                 fprintf(stderr, "failed to create OpenGL context\n");
213                 XFree(vis);
214                 return -1;
215         }
216
217         xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr);
218         xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone);
219         xattr.override_redirect = True;
220         attr_valid = CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect;
221
222         if(!(win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput,
223                                         vis->visual, attr_valid, &xattr))) {
224                 fprintf(stderr, "failed to create X window\n");
225                 glXDestroyContext(dpy, ctx);
226                 XFree(vis);
227                 return -1;
228         }
229         XFree(vis);
230
231         evmask = StructureNotifyMask | VisibilityChangeMask | KeyPressMask | PointerMotionMask |
232                 ButtonPressMask | ButtonReleaseMask | ExposureMask | EnterWindowMask | LeaveWindowMask;
233         XSelectInput(dpy, win, evmask);
234
235         XMapWindow(dpy, win);
236
237         glXMakeCurrent(dpy, win, ctx);
238         return 0;
239 }
240
241
242 void display(void)
243 {
244         glClearColor(1, 0, 0, 0);
245         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
246         glMatrixMode(GL_MODELVIEW);
247         glLoadIdentity();
248
249         vkeyb->show();
250         show_frame(0.1);
251
252         glRasterPos2i(-1, -1);
253
254         char buf[64];
255         snprintf(buf, sizeof buf, "%f %s", orient, orient > 0 ? "->" : orient < 0 ? "<-" : " ");
256         char *ptr = buf;
257         while(*ptr) {
258                 glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *ptr++);
259         }
260
261         glXSwapBuffers(dpy, win);
262
263         must_redraw = 0;
264         assert(glGetError() == GL_NO_ERROR);
265 }
266
267 void show_frame(float frm_width)
268 {
269         frm_width *= 2;
270
271         glEnable(GL_TEXTURE_2D);
272         glBindTexture(GL_TEXTURE_2D, frm_tex);
273
274         glBegin(GL_QUADS);
275         glColor3f(1.0, 1.0, 1.0);
276         glTexCoord2f(0, 1); glVertex2f(-1, -1);
277         glTexCoord2f(1, 1); glVertex2f(frm_width - 1, -1);
278         glTexCoord2f(1, 0); glVertex2f(frm_width - 1, 1);
279         glTexCoord2f(0, 0); glVertex2f(-1, 1);
280         glEnd();
281
282         glDisable(GL_TEXTURE_2D);
283 }
284
285
286 int handle_event(XEvent *xev)
287 {
288         static int mapped;
289         KeySym sym;
290
291         switch(xev->type) {
292         case ConfigureNotify:
293                 reshape(xev->xconfigure.width, xev->xconfigure.height);
294                 break;
295
296         case MapNotify:
297                 mapped = 1;
298                 break;
299         case UnmapNotify:
300                 mapped = 0;
301                 break;
302
303         case Expose:
304                 if(mapped && xev->xexpose.count == 0) {
305                         display();
306                 }
307                 break;
308
309         case KeyPress:
310                 sym = XLookupKeysym(&xev->xkey, 0);
311                 keyb(sym, 1);
312                 break;
313
314         case MotionNotify:
315                 motion(xev->xmotion.x, xev->xmotion.y);
316                 break;
317
318         case ButtonPress:
319                 button(xev->xbutton.x, xev->xbutton.y, xev->xbutton.button, 1);
320                 break;
321         case ButtonRelease:
322                 button(xev->xbutton.x, xev->xbutton.y, xev->xbutton.button, 0);
323                 break;
324
325         case EnterNotify:
326                 activate(1);
327                 break;
328         case LeaveNotify:
329                 activate(0);
330                 break;
331
332         default:
333                 break;
334         }
335
336         return 0;
337 }
338
339
340 void reshape(int w, int h)
341 {
342         glViewport(0, 0, w, h);
343 }
344
345 void keyb(int key, int pressed)
346 {
347         if(!pressed) {
348                 return;
349         }
350
351         switch (key) {
352         case XK_Escape:
353                 exit(0);
354
355         case 'e':
356                 printf("sending key: %c\n", (char)vkeyb->active_key());
357                 send_key(vkeyb->active_key());
358                 break;
359
360         }
361 }
362
363 void send_key(KeySym key)
364 {
365         Window win;
366         XEvent ev;
367         int junk;
368
369         XGetInputFocus(dpy, &win, &junk);
370
371         memset(&ev, 0, sizeof ev);
372         ev.type = KeyPress;
373         ev.xkey.window = win;
374         ev.xkey.keycode = XKeysymToKeycode(dpy, key);
375         ev.xkey.state = 0;
376         ev.xkey.time = CurrentTime;
377
378         XSendEvent(dpy, InputFocus, False, NoEventMask, &ev);
379 }
380
381 static int prev_x = -1;
382
383 void motion(int x, int y)
384 {
385         if(prev_x == -1) {
386                 prev_x = x;
387         }
388
389         vkeyb->move((x - prev_x) / 25.0);
390         prev_x = x;
391
392         must_redraw = 1;
393 }
394
395 void cam_motion(double orient)
396 {
397         if(orient > 0) {
398                 vkeyb->move(1);
399         }
400
401         if(orient < 0) {
402                 vkeyb->move(-1);
403         }
404
405         must_redraw = 1;
406 }
407
408 void button(int x, int y, int bn, int state)
409 {
410         if(bn == 3 && state) {
411                 exit(0);
412         }
413 }
414
415
416 void activate(int enter)
417 {
418         prev_x = -1;
419 }