tests: add simple write tests
[pandora-misc.git] / op_runfbapp.c
1 /*
2  * Copyright (c) 2010,2012 GraÅžvydas Ignotas
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the organization nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <pthread.h>
31 #include <X11/Xlib.h>
32 #include <X11/Xutil.h>
33 #include <X11/XKBlib.h>
34 #include <X11/XF86keysym.h>
35
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/wait.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41 #include <sys/ioctl.h>
42 #include <termios.h>
43 #include <signal.h>
44 #include <linux/kd.h>
45 #include <linux/fb.h>
46
47 #define __user
48 #include "omapfb.h"
49
50 #define PFX "op_runfbapp: "
51
52 static struct termios g_kbd_termios_saved;
53 static int g_kbdfd = -1;
54 static int g_have_x;
55 static pid_t g_child_pid;
56 static pthread_cond_t g_start_cond = PTHREAD_COND_INITIALIZER;
57 static pthread_mutex_t g_start_mutex = PTHREAD_MUTEX_INITIALIZER;
58
59 static void signal_main_thread(void)
60 {
61         pthread_mutex_lock(&g_start_mutex);
62         pthread_cond_signal(&g_start_cond);
63         pthread_mutex_unlock(&g_start_mutex);
64 }
65
66 static Cursor transparent_cursor(Display *display, Window win)
67 {
68         Cursor cursor;
69         Pixmap pix;
70         XColor dummy;
71         char d = 0;
72
73         memset(&dummy, 0, sizeof(dummy));
74         pix = XCreateBitmapFromData(display, win, &d, 1, 1);
75         cursor = XCreatePixmapCursor(display, pix, pix,
76                         &dummy, &dummy, 0, 0);
77         XFreePixmap(display, pix);
78         return cursor;
79 }
80
81 static void x11h_wait_vmstate(Display *display)
82 {
83         Atom wm_state = XInternAtom(display, "WM_STATE", False);
84         XEvent evt;
85         int i;
86
87         for (i = 0; i < 20; i++) {
88                 while (XPending(display)) {
89                         XNextEvent(display, &evt);
90                         if (evt.type == PropertyNotify && evt.xproperty.atom == wm_state)
91                                 return;
92                 }
93                 usleep(200000);
94         }
95
96         fprintf(stderr, "timeout waiting for wm_state change\n");
97 }
98
99 static struct omapfb_plane_info desktop_pi, app_pi[2];
100 static struct fb_var_screeninfo desktop_si, app_si[2];
101 static int desktop_saved;
102
103 static int save_fb_state(int index, struct omapfb_plane_info *pi,
104                          struct fb_var_screeninfo *si)
105 {
106         int retval = -1;
107         char buf[16];
108         int fd = -1;
109         int ret;
110
111         snprintf(buf, sizeof(buf), "/dev/fb%d", index);
112         fd = open(buf, O_RDWR);
113         if (fd == -1) {
114                 fprintf(stderr, PFX);
115                 perror(buf);
116                 goto out;
117         }
118
119         ret = ioctl(fd, OMAPFB_QUERY_PLANE, pi);
120         if (ret != 0) {
121                 perror(PFX "QUERY_PLANE");
122                 goto out;
123         }
124
125         ret = ioctl(fd, FBIOGET_VSCREENINFO, si);
126         if (ret == -1) {
127                 perror(PFX "FBIOGET_VSCREENINFO");
128                 goto out;
129         }
130
131         retval = 0;
132 out:
133         if (fd != -1)
134                 close(fd);
135         return retval;
136 }
137
138 static int load_fb_state(int index, struct omapfb_plane_info *pi,
139                          struct fb_var_screeninfo *si)
140 {
141         struct omapfb_plane_info tmp_pi;
142         int retval = -1;
143         char buf[16];
144         int fd = -1;
145         int ret;
146
147         snprintf(buf, sizeof(buf), "/dev/fb%d", index);
148         fd = open(buf, O_RDWR);
149         if (fd == -1) {
150                 fprintf(stderr, PFX);
151                 perror(buf);
152                 goto out;
153         }
154
155         // resolution can sometimes only be restored if position is right,
156         // so it's a bit more tricky..
157         ret = ioctl(fd, OMAPFB_QUERY_PLANE, &tmp_pi);
158         if (ret != 0) {
159                 perror(PFX "QUERY_PLANE");
160                 goto out;
161         }
162
163         tmp_pi.enabled = 0;
164         tmp_pi.pos_x = 0;
165         tmp_pi.pos_y = 0;
166         ret = ioctl(fd, OMAPFB_SETUP_PLANE, &tmp_pi);
167         if (ret != 0) {
168                 perror(PFX "SETUP_PLANE");
169                 goto out;
170         }
171
172         ret = ioctl(fd, FBIOPUT_VSCREENINFO, si);
173         if (ret == -1) {
174                 perror(PFX "FBIOPUT_VSCREENINFO");
175                 goto out;
176         }
177
178         ret = ioctl(fd, OMAPFB_SETUP_PLANE, pi);
179         if (ret != 0) {
180                 perror(PFX "SETUP_PLANE(2)");
181                 goto out;
182         }
183
184         retval = 0;
185 out:
186         if (fd != -1)
187                 close(fd);
188         return retval;
189 }
190
191 // prepare OMAP layers for desktop
192 static int minimize_omap_layers(void)
193 {
194         struct omapfb_plane_info pi_set;
195         int retval = -1;
196         int fd = -1;
197         int i, ret;
198
199         if (!desktop_saved) {
200                 fprintf(stderr, PFX "desktop not saved\n");
201                 goto out;
202         }
203
204         for (i = 0; i < 2; i++) {
205                 ret = save_fb_state(i, &app_pi[i], &app_si[i]);
206                 if (ret != 0) {
207                         fprintf(stderr, PFX "layer %d state save failed\n", i);
208                         goto out;
209                 }
210         }
211
212         retval = 0; // no restoring needed
213
214         if (memcmp(&app_pi[0], &desktop_pi, sizeof(desktop_pi)) != 0) {
215                 ret = load_fb_state(0, &desktop_pi, &desktop_si);
216                 if (ret != 0) {
217                         fprintf(stderr, PFX "desktop restore failed\n");
218                         // but continue, I guess?
219                 }
220                 retval = 1;
221         }
222
223         if (app_pi[1].enabled) {
224                 pi_set = app_pi[1];
225                 pi_set.enabled = 0;
226
227                 fd = open("/dev/fb1", O_RDWR);
228                 ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi_set);
229                 if (ret != 0) {
230                         perror(PFX "SETUP_PLANE");
231                 }
232                 retval = 1;
233         }
234
235 out:
236         if (fd != -1)
237                 close(fd);
238         return retval;
239 }
240
241 static void restore_omap_layers(void)
242 {
243         int i, ret;
244
245         for (i = 0; i < 2; i++) {
246                 ret = load_fb_state(i, &app_pi[i], &app_si[i]);
247                 if (ret != 0) {
248                         fprintf(stderr, PFX "layer %d state load failed\n", i);
249                         // .. but try to continue ..
250                 }
251         }
252 }
253
254 static int x11h_minimize(Display *display, Window window)
255 {
256         XSetWindowAttributes attributes;
257         int screen = DefaultScreen(display);
258         int display_width, display_height;
259         XWMHints wm_hints;
260         XEvent evt;
261
262         XWithdrawWindow(display, window, screen);
263
264         attributes.override_redirect = False;
265         XChangeWindowAttributes(display, window,
266                 CWOverrideRedirect, &attributes);
267
268         wm_hints.flags = StateHint;
269         wm_hints.initial_state = IconicState;
270         XSetWMHints(display, window, &wm_hints);
271
272         XMapWindow(display, window);
273
274         while (XNextEvent(display, &evt) == 0)
275         {
276                 // printf("m event %d\n", evt.type);
277                 switch (evt.type)
278                 {
279                         case FocusIn:
280                                 goto out;
281                         default:
282                                 break;
283                 }
284         }
285
286 out:
287         XWithdrawWindow(display, window, screen);
288
289         // must wait for some magic vmstate property change before setting override_redirect
290         x11h_wait_vmstate(display);
291
292         attributes.override_redirect = True;
293         XChangeWindowAttributes(display, window,
294                 CWOverrideRedirect, &attributes);
295
296         // fixup window after resize on override_redirect loss
297         display_width = DisplayWidth(display, screen);
298         display_height = DisplayHeight(display, screen);
299         XMoveResizeWindow(display, window, 0, 0, display_width, display_height);
300
301         XMapWindow(display, window);
302         XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
303         XkbSetDetectableAutoRepeat(display, 1, NULL);
304
305         // we don't know when event dispatch will be called, so sync now
306         XSync(display, False);
307
308         return 0;
309 }
310
311 static void *x11_handler(void *arg)
312 {
313         unsigned int display_width, display_height;
314         XSetWindowAttributes attributes;
315         const char *window_title = arg;
316         Window win;
317         XEvent evt;
318         Display *display;
319         int screen;
320         int ret;
321
322         display = XOpenDisplay(NULL);
323         if (display == NULL)
324         {
325                 fprintf(stderr, PFX "(not hiding X): Can't open display: %s\n",
326                         XDisplayName(NULL));
327                 signal_main_thread();
328                 return NULL;
329         }
330         g_have_x = 1;
331
332         screen = DefaultScreen(display);
333
334         display_width = DisplayWidth(display, screen);
335         display_height = DisplayHeight(display, screen);
336
337         win = XCreateSimpleWindow(display,
338                         RootWindow(display, screen),
339                         0, 0, display_width, display_height, 0,
340                         BlackPixel(display, screen),
341                         BlackPixel(display, screen));
342
343         attributes.override_redirect = True;
344         attributes.cursor = transparent_cursor(display, win);
345         XChangeWindowAttributes(display, win, CWOverrideRedirect | CWCursor, &attributes);
346
347         XStoreName(display, win, window_title);
348         XSelectInput(display, win, ExposureMask | FocusChangeMask
349                 | KeyPressMask | KeyReleaseMask | PropertyChangeMask);
350         XMapWindow(display, win);
351         XGrabKeyboard(display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
352         XkbSetDetectableAutoRepeat(display, 1, NULL);
353         XSync(display, False);
354
355         while (1)
356         {
357                 XNextEvent(display, &evt);
358
359                 switch (evt.type) {
360                 case Expose:
361                         signal_main_thread();
362                         while (XCheckTypedEvent(display, Expose, &evt))
363                                 ;
364                         break;
365
366                 case KeyPress:
367                         if (XLookupKeysym(&evt.xkey, 0) == XF86XK_MenuKB
368                             && g_child_pid != 0) {
369                                 kill(g_child_pid, SIGSTOP);
370
371                                 ret = minimize_omap_layers();
372                                 if (ret >= 0) {
373                                         fflush(stdout);
374                                         fflush(stderr);
375                                         x11h_minimize(display, win);
376                                 }
377                                 if (ret > 0) {
378                                         restore_omap_layers();
379                                         fflush(stdout);
380                                         fflush(stderr);
381                                 }
382
383                                 kill(g_child_pid, SIGCONT);
384                         }
385                         break;
386                 }
387         }
388
389         return NULL;
390 }
391
392 static void hidecon_start(void)
393 {
394         struct termios kbd_termios;
395         int mode;
396
397         g_kbdfd = open("/dev/tty", O_RDWR);
398         if (g_kbdfd == -1) {
399                 perror(PFX "open /dev/tty");
400                 return;
401         }
402
403         if (ioctl(g_kbdfd, KDGETMODE, &mode) == -1) {
404                 perror(PFX "(not hiding FB): KDGETMODE");
405                 goto fail;
406         }
407
408         if (tcgetattr(g_kbdfd, &kbd_termios) == -1) {
409                 perror(PFX "tcgetattr");
410                 goto fail;
411         }
412
413         g_kbd_termios_saved = kbd_termios;
414         kbd_termios.c_lflag &= ~(ICANON | ECHO); // | ISIG);
415         kbd_termios.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON);
416         kbd_termios.c_cc[VMIN] = 0;
417         kbd_termios.c_cc[VTIME] = 0;
418
419         if (tcsetattr(g_kbdfd, TCSAFLUSH, &kbd_termios) == -1) {
420                 perror(PFX "tcsetattr");
421                 goto fail;
422         }
423
424         if (ioctl(g_kbdfd, KDSETMODE, KD_GRAPHICS) == -1) {
425                 perror(PFX "KDSETMODE KD_GRAPHICS");
426                 tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved);
427                 goto fail;
428         }
429
430         return;
431
432 fail:
433         close(g_kbdfd);
434         g_kbdfd = -1;
435 }
436
437 static void hidecon_end(void)
438 {
439         if (g_kbdfd < 0)
440                 return;
441
442         if (ioctl(g_kbdfd, KDSETMODE, KD_TEXT) == -1)
443                 perror(PFX "KDSETMODE KD_TEXT");
444
445         if (tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved) == -1)
446                 perror(PFX "tcsetattr");
447
448         close(g_kbdfd);
449         g_kbdfd = -1;
450 }
451
452 static void do_exec(char * const argv[])
453 {
454         int ret, status;
455         pid_t pid;
456
457         pid = fork();
458         if (pid == -1) {
459                 perror(PFX "fork");
460                 return;
461         }
462
463         if (pid == 0) {
464                 /* child */
465                 execvp(argv[0], argv);
466                 perror(PFX "execvp");
467                 exit(1);
468         }
469
470         g_child_pid = pid;
471
472         ret = waitpid(pid, &status, 0);
473         if (ret < 0)
474                 perror(PFX "waitpid");
475 }
476
477 int main(int argc, char *argv[])
478 {
479         pthread_t tid;
480         char *name;
481         int ret;
482
483         if (argc < 2) {
484                 printf("usage:\n%s <app> [arg]..\n", argv[0]);
485                 return 1;
486         }
487
488         pthread_mutex_lock(&g_start_mutex);
489
490         name = strrchr(argv[1], '/');
491         if (name == NULL)
492                 name = argv[1];
493         else
494                 name++;
495
496         ret = pthread_create(&tid, NULL, x11_handler, name);
497         if (ret != 0) {
498                 fprintf(stderr, PFX "pthread_create: %d\n", ret);
499                 return 1;
500         }
501         pthread_detach(tid);
502
503         // assume desktop is in good state..
504         ret = save_fb_state(0, &desktop_pi, &desktop_si);
505         if (ret == 0)
506                 desktop_saved = 1;
507         else
508                 fprintf(stderr, PFX "initial desktop state save failed\n");
509
510         pthread_cond_wait(&g_start_cond, &g_start_mutex);
511         pthread_mutex_unlock(&g_start_mutex);
512
513         if (!g_have_x)
514                 hidecon_start();
515
516         do_exec(argv + 1);
517
518         if (!g_have_x)
519                 hidecon_end();
520
521         /* XXX: maybe stop the X thread nicely? */
522
523         return 0;
524 }
525