Fix check for null initrd and boot args
[petitboot] / petitboot.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <signal.h>
5 #include <unistd.h>
6 #include <syscall.h>
7 #include <assert.h>
8 #include <fcntl.h>
9 #include <sys/ioctl.h>
10
11 #include <linux/input.h>
12
13 #undef _USE_X11
14
15 #include <libtwin/twin.h>
16 #include <libtwin/twin_linux_mouse.h>
17 #include <libtwin/twin_linux_js.h>
18 #include <libtwin/twin_png.h>
19 #include <libtwin/twin_jpeg.h>
20
21 #include "petitboot.h"
22 #include "petitboot-paths.h"
23
24 #ifdef _USE_X11
25 #include <libtwin/twin_x11.h>
26 static twin_x11_t *pboot_x11;
27 #else
28 #include <libtwin/twin_fbdev.h>
29 static twin_fbdev_t *pboot_fbdev;
30 #endif
31
32 static twin_screen_t *pboot_screen;
33
34 #define PBOOT_INITIAL_MESSAGE           \
35         "keys: 0=safe 1=720p 2=1080i 3=1080p del=GameOS"
36
37 #define PBOOT_LEFT_PANE_SIZE            160
38 #define PBOOT_LEFT_PANE_COLOR           0x80000000
39 #define PBOOT_LEFT_LINE_COLOR           0xff000000
40
41 #define PBOOT_LEFT_FOCUS_WIDTH          80
42 #define PBOOT_LEFT_FOCUS_HEIGHT         80
43 #define PBOOT_LEFT_FOCUS_XOFF           40
44 #define PBOOT_LEFT_FOCUS_YOFF           40
45 #define PBOOT_LEFT_FOCUS_XRAD           (6 * TWIN_FIXED_ONE)
46 #define PBOOT_LEFT_FOCUS_YRAD           (6 * TWIN_FIXED_ONE)
47
48 #define PBOOT_RIGHT_FOCUS_XOFF          20
49 #define PBOOT_RIGHT_FOCUS_YOFF          60
50 #define PBOOT_RIGHT_FOCUS_HEIGHT        80
51 #define PBOOT_RIGHT_FOCUS_XRAD          (6 * TWIN_FIXED_ONE)
52 #define PBOOT_RIGHT_FOCUS_YRAD          (6 * TWIN_FIXED_ONE)
53
54 #define PBOOT_LEFT_ICON_WIDTH           64
55 #define PBOOT_LEFT_ICON_HEIGHT          64
56 #define PBOOT_LEFT_ICON_XOFF            50
57 #define PBOOT_LEFT_ICON_YOFF            50
58 #define PBOOT_LEFT_ICON_STRIDE          100
59
60 #define PBOOT_RIGHT_OPTION_LMARGIN      30
61 #define PBOOT_RIGHT_OPTION_RMARGIN      30
62 #define PBOOT_RIGHT_OPTION_TMARGIN      70
63 #define PBOOT_RIGHT_OPTION_HEIGHT       64
64 #define PBOOT_RIGHT_OPTION_STRIDE       100
65 #define PBOOT_RIGHT_TITLE_TEXT_SIZE     (30 * TWIN_FIXED_ONE)
66 #define PBOOT_RIGHT_SUBTITLE_TEXT_SIZE  (18 * TWIN_FIXED_ONE)
67 #define PBOOT_RIGHT_TITLE_XOFFSET       80
68 #define PBOOT_RIGHT_TITLE_YOFFSET       30
69 #define PBOOT_RIGHT_SUBTITLE_XOFFSET    100
70 #define PBOOT_RIGHT_SUBTITLE_YOFFSET    50
71 #define PBOOT_RIGHT_BADGE_XOFFSET       2
72 #define PBOOT_RIGHT_BADGE_YOFFSET       0
73
74
75 #define PBOOT_RIGHT_TITLE_COLOR         0xff000000
76 #define PBOOT_RIGHT_SUBTITLE_COLOR      0xff400000
77
78 #define PBOOT_FOCUS_COLOR               0x10404040
79
80 #define PBOOT_STATUS_PANE_COLOR         0x60606060
81 #define PBOOT_STATUS_PANE_HEIGHT        20
82 #define PBOOT_STATUS_PANE_XYMARGIN      20
83 #define PBOOT_STATUS_TEXT_MARGIN        10
84 #define PBOOT_STATUS_TEXT_SIZE          (16 * TWIN_FIXED_ONE)
85 #define PBOOT_STATUS_TEXT_COLOR         0xff000000
86
87 typedef struct _pboot_option pboot_option_t;
88 typedef struct _pboot_device pboot_device_t;
89
90 struct _pboot_option
91 {
92         char            *title;
93         char            *subtitle;
94         twin_pixmap_t   *badge;
95         twin_pixmap_t   *cache;
96         twin_rect_t     box;
97         void            *data;
98 };
99
100 struct _pboot_device
101 {
102         char                    *id;
103         twin_pixmap_t           *badge;
104         twin_rect_t             box;
105         int                     option_count;
106         pboot_option_t          options[PBOOT_MAX_OPTION];
107 };
108
109 static twin_pixmap_t    *pboot_cursor;
110 static int              pboot_cursor_hx;
111 static int              pboot_cursor_hy;
112
113 static pboot_device_t   *pboot_devices[PBOOT_MAX_DEV];
114 static int              pboot_dev_count;
115 static int              pboot_dev_sel = -1;
116 static int              pboot_focus_lpane = 1;
117
118 typedef struct _pboot_lpane {
119         twin_window_t   *window;
120         twin_rect_t     focus_box;
121         int             focus_start;
122         int             focus_target;
123         int             focus_curindex;
124         int             mouse_target;
125 } pboot_lpane_t;
126
127 typedef struct _pboot_rpane {
128         twin_window_t   *window;
129         twin_rect_t     focus_box;
130         int             focus_start;
131         int             focus_target;
132         int             focus_curindex;
133         int             mouse_target;
134 } pboot_rpane_t;
135
136 typedef struct _pboot_spane {
137         twin_window_t   *window;
138         char            *text;
139 } pboot_spane_t;
140
141 static pboot_lpane_t    *pboot_lpane;
142 static pboot_rpane_t    *pboot_rpane;
143 static pboot_spane_t    *pboot_spane;
144
145 /* control to keyboard mappings for the sixaxis controller */
146 uint8_t sixaxis_map[] = {
147         0,              /*   0  Select          */
148         0,              /*   1  L3              */
149         0,              /*   2  R3              */
150         0,              /*   3  Start           */
151         KEY_UP,         /*   4  Dpad Up         */
152         KEY_RIGHT,      /*   5  Dpad Right      */
153         KEY_DOWN,       /*   6  Dpad Down       */
154         KEY_LEFT,       /*   7  Dpad Left       */
155         0,              /*   8  L2              */
156         0,              /*   9  R2              */
157         0,              /*  10  L1              */
158         0,              /*  11  R1              */
159         0,              /*  12  Triangle        */
160         KEY_ENTER,      /*  13  Circle          */
161         0,              /*  14  Cross           */
162         KEY_DELETE,     /*  15  Square          */
163         0,              /*  16  PS Button       */
164         0,              /*  17  nothing      */
165         0,              /*  18  nothing      */
166 };
167
168
169 static int pboot_vmode_change = -1;
170
171 /* XXX move to twin */
172 static inline twin_bool_t twin_rect_intersect(twin_rect_t r1,
173                                               twin_rect_t r2)
174 {
175         return !(r1.left > r2.right ||
176                  r1.right < r2.left ||
177                  r1.top > r2.bottom ||
178                  r1.bottom < r2.top);
179 }
180
181 static void pboot_draw_option_cache(pboot_device_t *dev, pboot_option_t *opt,
182                                     int index)
183 {
184         twin_pixmap_t   *px;
185         twin_path_t     *path;
186         twin_fixed_t    tx, ty;
187
188         /* Create pixmap */
189         px = twin_pixmap_create(TWIN_ARGB32, opt->box.right - opt->box.left,
190                                  opt->box.bottom - opt->box.top);
191         assert(px);
192         opt->cache = px;
193
194         /* Fill background */
195         twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height);
196
197         /* Allocate a path for drawing */
198         path = twin_path_create();
199         assert(path);
200
201 #if 0
202         /* TEST - Bounding rectangle */
203         twin_path_rectangle(path, 0, 0,
204                             twin_int_to_fixed(px->width),
205                             twin_int_to_fixed(px->height));
206         twin_paint_path(px, PBOOT_RIGHT_TITLE_COLOR, path);
207         twin_path_empty(path);
208         twin_fill(px, 0x00000000, TWIN_SOURCE, 2, 2,
209                   px->width - 3, px->height - 3);
210 #endif
211
212         /* Draw texts */
213         twin_path_set_font_size(path, PBOOT_RIGHT_TITLE_TEXT_SIZE);
214         twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
215         tx = twin_int_to_fixed(PBOOT_RIGHT_TITLE_XOFFSET);
216         ty = twin_int_to_fixed(PBOOT_RIGHT_TITLE_YOFFSET);
217         twin_path_move (path, tx, ty);
218         twin_path_utf8 (path, opt->title);
219         twin_paint_path (px, PBOOT_RIGHT_TITLE_COLOR, path);
220         twin_path_empty (path);
221
222         if (opt->subtitle) {
223                 twin_path_set_font_size(path, PBOOT_RIGHT_SUBTITLE_TEXT_SIZE);
224                 twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
225                 tx = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_XOFFSET);
226                 ty = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_YOFFSET);
227                 twin_path_move (path, tx, ty);
228                 twin_path_utf8 (path, opt->subtitle);
229                 twin_paint_path (px, PBOOT_RIGHT_SUBTITLE_COLOR, path);
230                 twin_path_empty (path);
231         }
232
233         if (opt->badge) {
234                 twin_operand_t  src;
235
236                 src.source_kind = TWIN_PIXMAP;
237                 src.u.pixmap = opt->badge;
238
239                 twin_composite(px, PBOOT_RIGHT_BADGE_XOFFSET,
240                                PBOOT_RIGHT_BADGE_YOFFSET,
241                                &src, 0, 0, NULL, 0, 0, TWIN_OVER,
242                                opt->badge->width, opt->badge->height);
243         }
244
245
246         /* Destroy path */
247         twin_path_destroy(path);
248 }
249
250 static void pboot_rpane_draw(twin_window_t *window)
251 {
252         twin_pixmap_t   *px = window->pixmap;
253         pboot_rpane_t   *rpane = window->client_data;
254         pboot_device_t  *dev;
255         twin_path_t     *path;
256         twin_fixed_t    x, y, w, h;
257         int             i;
258
259         /* Fill background */
260         twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height);
261
262         /* Nothing to draw, return */
263         if (pboot_dev_sel < 0)
264                 return;
265
266         /* Create a path for use later */
267         path = twin_path_create();
268         assert(path);
269
270         /* Draw focus box */
271         if (rpane->focus_curindex >= 0 &&
272             twin_rect_intersect(rpane->focus_box, px->clip)) {
273                 x = twin_int_to_fixed(rpane->focus_box.left + 2);
274                 y = twin_int_to_fixed(rpane->focus_box.top + 2);
275                 w = twin_int_to_fixed(rpane->focus_box.right -
276                                       rpane->focus_box.left - 4);
277                 h = twin_int_to_fixed(rpane->focus_box.bottom -
278                                       rpane->focus_box.top - 4);
279                 twin_path_rounded_rectangle(path, x, y, w, h,
280                                             PBOOT_RIGHT_FOCUS_XRAD,
281                                             PBOOT_RIGHT_FOCUS_YRAD);
282                 if (!pboot_focus_lpane)
283                         twin_paint_path(px, PBOOT_FOCUS_COLOR, path);
284                 else
285                         twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path,
286                                           4 * TWIN_FIXED_ONE);
287         }
288
289         /* Get device and iterate through options */
290         dev = pboot_devices[pboot_dev_sel];
291         for (i = 0; i < dev->option_count; i++) {
292                 pboot_option_t  *opt = &dev->options[i];
293                 twin_operand_t  src;
294
295                 if (opt->title == NULL)
296                         continue;
297                 if (!twin_rect_intersect(opt->box, px->clip))
298                         continue;
299                 if (opt->cache == NULL)
300                         pboot_draw_option_cache(dev, opt, i);
301
302                 src.source_kind = TWIN_PIXMAP;
303                 src.u.pixmap = opt->cache;
304
305                 twin_composite(px, opt->box.left, opt->box.top,
306                                &src, 0, 0, NULL, 0, 0, TWIN_OVER,
307                                opt->box.right - opt->box.left,
308                                opt->box.bottom - opt->box.top);
309         }
310
311         /* Destroy path */
312         twin_path_destroy(path);
313 }
314
315 static twin_time_t pboot_rfocus_timeout (twin_time_t now, void *closure)
316 {
317         int dir = 1, dist, pos;
318         const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 1, 2, 2, 3 };
319
320         dist = abs(pboot_rpane->focus_target - pboot_rpane->focus_start);
321         dir = dist > 5 ? 5 : dist;
322         pos = pboot_rpane->focus_target - (int)pboot_rpane->focus_box.top;
323         if (pos == 0) {
324                 return -1;
325         }
326         if (pos < 0) {
327                 dir = -dir;
328                 pos = -pos;
329         }
330         twin_window_damage(pboot_rpane->window,
331                            pboot_rpane->focus_box.left,
332                            pboot_rpane->focus_box.top,
333                            pboot_rpane->focus_box.right,
334                            pboot_rpane->focus_box.bottom);
335
336         pboot_rpane->focus_box.top += dir;
337         pboot_rpane->focus_box.bottom += dir;
338
339         twin_window_damage(pboot_rpane->window,
340                            pboot_rpane->focus_box.left,
341                            pboot_rpane->focus_box.top,
342                            pboot_rpane->focus_box.right,
343                            pboot_rpane->focus_box.bottom);
344
345         twin_window_queue_paint(pboot_rpane->window);
346
347         return accel[(pos * 10) / dist];
348 }
349
350 static void pboot_set_rfocus(int index)
351 {
352         pboot_device_t  *dev;
353
354         if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count)
355                 return;
356         dev = pboot_devices[pboot_dev_sel];
357         if (index < 0 || index >= dev->option_count)
358                 return;
359
360         pboot_rpane->focus_start = pboot_rpane->focus_box.top;
361         pboot_rpane->focus_target = PBOOT_RIGHT_FOCUS_YOFF +
362                 PBOOT_RIGHT_OPTION_STRIDE * index;
363         pboot_rpane->focus_curindex = index;
364
365         twin_set_timeout(pboot_rfocus_timeout, 0, NULL);
366 }
367
368 static void pboot_select_rpane(void)
369 {
370         if (pboot_focus_lpane == 0)
371                 return;
372         pboot_focus_lpane = 0;
373
374         twin_screen_set_active(pboot_screen, pboot_rpane->window->pixmap);
375
376         twin_window_damage(pboot_lpane->window,
377                            pboot_lpane->focus_box.left,
378                            pboot_lpane->focus_box.top,
379                            pboot_lpane->focus_box.right,
380                            pboot_lpane->focus_box.bottom);
381
382         twin_window_damage(pboot_rpane->window,
383                            pboot_rpane->focus_box.left,
384                            pboot_rpane->focus_box.top,
385                            pboot_rpane->focus_box.right,
386                            pboot_rpane->focus_box.bottom);
387
388         twin_window_queue_paint(pboot_lpane->window);
389         twin_window_queue_paint(pboot_rpane->window);
390
391         pboot_set_rfocus(0);
392 }
393
394 static void pboot_select_lpane(void)
395 {
396         if (pboot_focus_lpane == 1)
397                 return;
398         pboot_focus_lpane = 1;
399
400         twin_screen_set_active(pboot_screen, pboot_lpane->window->pixmap);
401
402         twin_window_damage(pboot_lpane->window,
403                            pboot_lpane->focus_box.left,
404                            pboot_lpane->focus_box.top,
405                            pboot_lpane->focus_box.right,
406                            pboot_lpane->focus_box.bottom);
407
408         twin_window_damage(pboot_rpane->window,
409                            pboot_rpane->focus_box.left,
410                            pboot_rpane->focus_box.top,
411                            pboot_rpane->focus_box.right,
412                            pboot_rpane->focus_box.bottom);
413
414         twin_window_queue_paint(pboot_lpane->window);
415         twin_window_queue_paint(pboot_rpane->window);
416 }
417
418 static void pboot_rpane_mousetrack(twin_coord_t x, twin_coord_t y)
419 {
420         pboot_device_t  *dev;
421         pboot_option_t  *opt;
422         int             candidate = -1;
423
424         if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count)
425                 return;
426         dev = pboot_devices[pboot_dev_sel];
427
428         if (y < PBOOT_RIGHT_OPTION_TMARGIN)
429                 goto miss;
430         candidate = (y - PBOOT_RIGHT_OPTION_TMARGIN) /
431                 PBOOT_RIGHT_OPTION_STRIDE;
432         if (candidate >= dev->option_count) {
433                 candidate = -1;
434                 goto miss;
435         }
436         if (candidate == pboot_rpane->mouse_target)
437                 return;
438         opt = &dev->options[candidate];
439         if (x < opt->box.left || x > opt->box.right ||
440             y < opt->box.top || y > opt->box.bottom) {
441                 candidate = -1;
442                 goto miss;
443         }
444
445         /* Ok, so now, we know the mouse hit an icon that wasn't the same
446          * as the previous one, we trigger a focus change
447          */
448         pboot_set_rfocus(candidate);
449
450  miss:
451         pboot_rpane->mouse_target = candidate;
452 }
453
454 static void pboot_choose_option(void)
455 {
456         pboot_device_t *dev = pboot_devices[pboot_dev_sel];
457         pboot_option_t *opt = &dev->options[pboot_rpane->focus_curindex];
458
459         LOG("Selected device %s\n", opt->title);
460
461         /* Give user feedback, make sure errors and panics will be seen */
462         pboot_exec_option(opt->data);
463 }
464
465 static twin_bool_t pboot_rpane_event (twin_window_t         *window,
466                                       twin_event_t          *event)
467 {
468         /* filter out all mouse events */
469         switch(event->kind) {
470         case TwinEventEnter:
471         case TwinEventMotion:
472         case TwinEventLeave:
473                 pboot_select_rpane();
474                 pboot_rpane_mousetrack(event->u.pointer.x, event->u.pointer.y);
475                 return TWIN_TRUE;
476         case TwinEventButtonDown:
477                 pboot_select_rpane();
478                 pboot_rpane_mousetrack(event->u.pointer.x, event->u.pointer.y);
479                 pboot_choose_option();
480         case TwinEventButtonUp:
481                 return TWIN_TRUE;
482         case TwinEventKeyDown:
483                 switch(event->u.key.key) {
484                 case KEY_UP:
485                         pboot_set_rfocus(pboot_rpane->focus_curindex - 1);
486                         return TWIN_TRUE;
487                 case KEY_DOWN:
488                         pboot_set_rfocus(pboot_rpane->focus_curindex + 1);
489                         return TWIN_TRUE;
490                 case KEY_LEFT:
491                         pboot_select_lpane();
492                         return TWIN_TRUE;
493                 case KEY_ENTER:
494                         pboot_choose_option();
495                 default:
496                         break;
497                 }
498                 break;
499         default:
500                 break;
501         }
502         return TWIN_FALSE;
503 }
504
505
506 int pboot_add_option(int devindex, const char *title,
507                      const char *subtitle, twin_pixmap_t *badge, void *data)
508 {
509         pboot_device_t  *dev;
510         pboot_option_t  *opt;
511         twin_coord_t    width;
512         int             index;
513
514         if (devindex < 0 || devindex >= pboot_dev_count)
515                 return -1;
516         dev = pboot_devices[devindex];
517
518         if (dev->option_count >= PBOOT_MAX_OPTION)
519                 return -1;
520         index = dev->option_count++;
521         opt = &dev->options[index];
522
523         opt->title = malloc(strlen(title) + 1);
524         strcpy(opt->title, title);
525
526         if (subtitle) {
527                 opt->subtitle = malloc(strlen(subtitle) + 1);
528                 strcpy(opt->subtitle, subtitle);
529         } else
530                 opt->subtitle = NULL;
531
532         opt->badge = badge;
533         opt->cache = NULL;
534
535         width = pboot_rpane->window->pixmap->width -
536                 (PBOOT_RIGHT_OPTION_LMARGIN + PBOOT_RIGHT_OPTION_RMARGIN);
537
538         opt->box.left = PBOOT_RIGHT_OPTION_LMARGIN;
539         opt->box.right = opt->box.left + width;
540         opt->box.top = PBOOT_RIGHT_OPTION_TMARGIN +
541                 index * PBOOT_RIGHT_OPTION_STRIDE;
542         opt->box.bottom = opt->box.top + PBOOT_RIGHT_OPTION_HEIGHT;
543
544         opt->data = data;
545         return index;
546 }
547
548
549 static void pboot_set_device_select(int sel, int force)
550 {
551         LOG("%s: %d -> %d\n", __FUNCTION__, pboot_dev_sel, sel);
552         if (!force && sel == pboot_dev_sel)
553                 return;
554         if (sel >= pboot_dev_count)
555                 return;
556         pboot_dev_sel = sel;
557         if (force) {
558                 pboot_lpane->focus_curindex = sel;
559                 if (sel < 0)
560                         pboot_lpane->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT;
561                 else
562                         pboot_lpane->focus_target = PBOOT_LEFT_FOCUS_YOFF +
563                                 PBOOT_LEFT_ICON_STRIDE * sel;
564                 pboot_rpane->focus_box.bottom = pboot_lpane->focus_target;
565                 pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top +
566                         PBOOT_RIGHT_FOCUS_HEIGHT;
567                 twin_window_damage(pboot_lpane->window,
568                                    0, 0,
569                                    pboot_lpane->window->pixmap->width,
570                                    pboot_lpane->window->pixmap->height);
571                 twin_window_queue_paint(pboot_lpane->window);
572         }
573         pboot_rpane->focus_curindex = -1;
574         pboot_rpane->mouse_target = -1;
575         pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT;
576         pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top +
577                 PBOOT_RIGHT_FOCUS_HEIGHT;
578         twin_window_damage(pboot_rpane->window, 0, 0,
579                            pboot_rpane->window->pixmap->width,
580                            pboot_rpane->window->pixmap->height);
581         twin_window_queue_paint(pboot_rpane->window);
582 }
583
584 static void pboot_create_rpane(void)
585 {
586         pboot_rpane = calloc(1, sizeof(pboot_rpane_t));
587         assert(pboot_rpane);
588
589         pboot_rpane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
590                                                  TwinWindowPlain,
591                                                  PBOOT_LEFT_PANE_SIZE, 0,
592                                                  pboot_screen->width -
593                                                    PBOOT_LEFT_PANE_SIZE,
594                                                  pboot_screen->height);
595         assert(pboot_rpane->window);
596
597         pboot_rpane->window->draw = pboot_rpane_draw;
598         pboot_rpane->window->event = pboot_rpane_event;
599         pboot_rpane->window->client_data = pboot_rpane;
600
601         pboot_rpane->focus_curindex = -1;
602         pboot_rpane->focus_box.left = PBOOT_RIGHT_FOCUS_XOFF;
603         pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT;
604         pboot_rpane->focus_box.right = pboot_rpane->window->pixmap->width -
605                 2 * PBOOT_RIGHT_FOCUS_XOFF;
606         pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top +
607                 PBOOT_RIGHT_FOCUS_HEIGHT;
608         pboot_rpane->mouse_target = -1;
609         twin_window_show(pboot_rpane->window);
610         twin_window_queue_paint(pboot_rpane->window);
611 }
612
613
614 static twin_time_t pboot_lfocus_timeout (twin_time_t now, void *closure)
615 {
616         int dir = 1, dist, pos;
617         const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 1, 2, 2, 3 };
618
619         dist = abs(pboot_lpane->focus_target - pboot_lpane->focus_start);
620         dir = dist > 2 ? 2 : dist;
621         pos = pboot_lpane->focus_target - (int)pboot_lpane->focus_box.top;
622         if (pos == 0) {
623                 pboot_set_device_select(pboot_lpane->focus_curindex, 0);
624                 return -1;
625         }
626         if (pos < 0) {
627                 dir = -1;
628                 pos = -pos;
629         }
630         twin_window_damage(pboot_lpane->window,
631                            pboot_lpane->focus_box.left,
632                            pboot_lpane->focus_box.top,
633                            pboot_lpane->focus_box.right,
634                            pboot_lpane->focus_box.bottom);
635
636         pboot_lpane->focus_box.top += dir;
637         pboot_lpane->focus_box.bottom += dir;
638
639         twin_window_damage(pboot_lpane->window,
640                            pboot_lpane->focus_box.left,
641                            pboot_lpane->focus_box.top,
642                            pboot_lpane->focus_box.right,
643                            pboot_lpane->focus_box.bottom);
644
645         twin_window_queue_paint(pboot_lpane->window);
646
647         return accel[(pos * 10) / dist];
648 }
649
650 static void pboot_set_lfocus(int index)
651 {
652         if (index >= pboot_dev_count)
653                 return;
654
655         pboot_lpane->focus_start = pboot_lpane->focus_box.top;
656
657         if (index < 0)
658                 pboot_lpane->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT;
659         else
660                 pboot_lpane->focus_target = PBOOT_LEFT_FOCUS_YOFF +
661                         PBOOT_LEFT_ICON_STRIDE * index;
662
663         pboot_lpane->focus_curindex = index;
664
665         twin_set_timeout(pboot_lfocus_timeout, 0, NULL);
666 }
667
668 static void pboot_lpane_mousetrack(twin_coord_t x, twin_coord_t y)
669 {
670         int candidate = -1;
671         twin_coord_t icon_top;
672
673         if (x < PBOOT_LEFT_ICON_XOFF ||
674             x > (PBOOT_LEFT_ICON_XOFF + PBOOT_LEFT_ICON_WIDTH))
675                 goto miss;
676         if (y < PBOOT_LEFT_ICON_YOFF)
677                 goto miss;
678         candidate = (y - PBOOT_LEFT_ICON_YOFF) / PBOOT_LEFT_ICON_STRIDE;
679         if (candidate >= pboot_dev_count) {
680                 candidate = -1;
681                 goto miss;
682         }
683         if (candidate == pboot_lpane->mouse_target)
684                 return;
685         icon_top = PBOOT_LEFT_ICON_YOFF +
686                 candidate * PBOOT_LEFT_ICON_STRIDE;
687         if (y > (icon_top + PBOOT_LEFT_ICON_HEIGHT)) {
688                 candidate = -1;
689                 goto miss;
690         }
691
692         /* Ok, so now, we know the mouse hit an icon that wasn't the same
693          * as the previous one, we trigger a focus change
694          */
695         pboot_set_lfocus(candidate);
696
697  miss:
698         pboot_lpane->mouse_target = candidate;
699 }
700
701 static twin_bool_t pboot_lpane_event (twin_window_t         *window,
702                                       twin_event_t          *event)
703 {
704         /* filter out all mouse events */
705         switch(event->kind) {
706         case TwinEventEnter:
707         case TwinEventMotion:
708         case TwinEventLeave:
709                 pboot_select_lpane();
710                 pboot_lpane_mousetrack(event->u.pointer.x, event->u.pointer.y);
711                 return TWIN_TRUE;
712         case TwinEventButtonDown:
713         case TwinEventButtonUp:
714                 return TWIN_TRUE;
715         case TwinEventKeyDown:
716                 switch(event->u.key.key) {
717                 case KEY_UP:
718                         if (pboot_lpane->focus_curindex > 0)
719                                 pboot_set_lfocus(
720                                         pboot_lpane->focus_curindex - 1);
721                         return TWIN_TRUE;
722                 case KEY_DOWN:
723                         pboot_set_lfocus(pboot_lpane->focus_curindex + 1);
724                         return TWIN_TRUE;
725                 case KEY_RIGHT:
726                         pboot_select_rpane();
727                         return TWIN_TRUE;
728                 default:
729                         break;
730                 }
731                 break;
732         default:
733                 break;
734         }
735         return TWIN_FALSE;
736 }
737
738 static void pboot_quit(void)
739 {
740         kill(0, SIGINT);
741 }
742
743 twin_bool_t pboot_event_filter(twin_screen_t        *screen,
744                                twin_event_t         *event)
745 {
746         switch(event->kind) {
747         case TwinEventEnter:
748         case TwinEventMotion:
749         case TwinEventLeave:
750         case TwinEventButtonDown:
751         case TwinEventButtonUp:
752                 if (pboot_cursor != NULL)
753                         twin_screen_set_cursor(pboot_screen, pboot_cursor,
754                                                pboot_cursor_hx,
755                                                pboot_cursor_hy);
756                 break;
757         case TwinEventJoyButton:
758                 /* map joystick events into key events */
759                 if (event->u.js.control >= sizeof(sixaxis_map))
760                         break;
761
762                 event->u.key.key = sixaxis_map[event->u.js.control];
763                 if (event->u.js.value == 0) {
764                         event->kind = TwinEventKeyUp;
765                         break;
766                 } else {
767                         event->kind = TwinEventKeyDown;
768                 }
769
770                 /* fall through.. */
771         case TwinEventKeyDown:
772                 switch(event->u.key.key) {
773                 /* Gross hack for video modes, need something better ! */
774                 case KEY_0:
775                         pboot_vmode_change = 0; /* auto */
776                         pboot_quit();
777                         return TWIN_TRUE;
778                 case KEY_1:
779                         pboot_vmode_change = 3; /* 720p */
780                         pboot_quit();
781                         return TWIN_TRUE;
782                 case KEY_2:
783                         pboot_vmode_change = 4; /* 1080i */
784                         pboot_quit();
785                         return TWIN_TRUE;
786                 case KEY_3:
787                         pboot_vmode_change = 5; /* 1080p */
788                         pboot_quit();
789                         return TWIN_TRUE;
790
791                 /* Another gross hack for booting back to gameos */
792                 case KEY_BACKSPACE:
793                 case KEY_DELETE:
794                         system("boot-game-os");
795                         pboot_quit();
796                 }
797         case TwinEventKeyUp:
798                 twin_screen_set_cursor(pboot_screen, NULL, 0, 0);
799                 break;
800         default:
801                 break;
802         }
803         return TWIN_FALSE;
804 }
805
806 static void pboot_lpane_draw(twin_window_t *window)
807 {
808         twin_pixmap_t   *px = window->pixmap;
809         pboot_lpane_t   *lpane = window->client_data;
810         twin_path_t     *path;
811         twin_fixed_t    x, y, w, h;
812         int             i;
813
814         /* Fill background */
815         twin_fill(px, PBOOT_LEFT_PANE_COLOR, TWIN_SOURCE,
816                   0, 0, px->width, px->height);
817
818         /* Create a path for use later */
819         path = twin_path_create();
820         assert(path);
821
822         /* Draw right line if needed */
823         if (px->clip.right > (PBOOT_LEFT_PANE_SIZE - 4)) {
824                 x = twin_int_to_fixed(PBOOT_LEFT_PANE_SIZE - 4);
825                 y = twin_int_to_fixed(px->height);
826                 twin_path_rectangle(path, x, 0, 0x40000, y);
827                 twin_paint_path(px, PBOOT_LEFT_LINE_COLOR, path);
828                 twin_path_empty(path);
829         }
830
831         /* Draw focus box */
832         if (lpane->focus_curindex >= 0 &&
833             twin_rect_intersect(lpane->focus_box, px->clip)) {
834                 x = twin_int_to_fixed(lpane->focus_box.left + 2);
835                 y = twin_int_to_fixed(lpane->focus_box.top + 2);
836                 w = twin_int_to_fixed(lpane->focus_box.right -
837                                       lpane->focus_box.left - 4);
838                 h = twin_int_to_fixed(lpane->focus_box.bottom -
839                                       lpane->focus_box.top - 4);
840                 twin_path_rounded_rectangle(path, x, y, w, h,
841                                             PBOOT_LEFT_FOCUS_XRAD,
842                                             PBOOT_LEFT_FOCUS_YRAD);
843                 if (pboot_focus_lpane)
844                         twin_paint_path(px, PBOOT_FOCUS_COLOR, path);
845                 else
846                         twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path,
847                                           4 * TWIN_FIXED_ONE);
848         }
849
850         /* Draw icons */
851         for (i = 0; i < pboot_dev_count; i++) {
852                 pboot_device_t  *dev = pboot_devices[i];
853                 twin_operand_t  src;
854
855                 if (!twin_rect_intersect(dev->box, px->clip))
856                         continue;
857
858                 src.source_kind = TWIN_PIXMAP;
859                 src.u.pixmap = dev->badge;
860
861                 twin_composite(px, dev->box.left, dev->box.top,
862                                &src, 0, 0, NULL, 0, 0, TWIN_OVER,
863                                dev->box.right - dev->box.left,
864                                dev->box.bottom - dev->box.top);
865
866         }
867
868         /* Destroy path */
869         twin_path_destroy(path);
870 }
871
872 static void pboot_create_lpane(void)
873 {
874         pboot_lpane = calloc(1, sizeof(pboot_lpane_t));
875         assert(pboot_lpane);
876
877         pboot_lpane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
878                                                  TwinWindowPlain,
879                                                  0, 0, PBOOT_LEFT_PANE_SIZE,
880                                                  pboot_screen->height);
881         assert(pboot_lpane->window);
882
883         pboot_lpane->window->draw = pboot_lpane_draw;
884         pboot_lpane->window->event = pboot_lpane_event;
885         pboot_lpane->window->client_data = pboot_lpane;
886         pboot_lpane->focus_curindex = -1;
887         pboot_lpane->focus_box.left = PBOOT_LEFT_FOCUS_XOFF;
888         pboot_lpane->focus_box.top = -2*PBOOT_LEFT_FOCUS_HEIGHT;
889         pboot_lpane->focus_box.right = pboot_lpane->focus_box.left +
890                 PBOOT_LEFT_FOCUS_WIDTH;
891         pboot_lpane->focus_box.bottom = pboot_lpane->focus_box.top +
892                 PBOOT_LEFT_FOCUS_HEIGHT;
893         pboot_lpane->mouse_target = -1;
894         twin_window_show(pboot_lpane->window);
895         twin_window_queue_paint(pboot_lpane->window);
896 }
897
898 static void pboot_spane_draw(twin_window_t *window)
899 {
900         twin_pixmap_t   *px = window->pixmap;
901         pboot_spane_t   *spane = window->client_data;
902         twin_path_t     *path;
903         twin_fixed_t    tx, ty;
904
905         /* Fill background */
906         twin_fill(px, PBOOT_STATUS_PANE_COLOR, TWIN_SOURCE,
907                   0, 0, px->width, px->height);
908
909         path = twin_path_create();
910         assert(path);
911
912         twin_path_set_font_size(path, PBOOT_STATUS_TEXT_SIZE);
913         twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
914         tx = twin_int_to_fixed(PBOOT_STATUS_TEXT_MARGIN);
915         ty = twin_int_to_fixed(PBOOT_STATUS_PANE_HEIGHT - 2);
916         twin_path_move (path, tx, ty);
917         twin_path_utf8 (path, spane->text);
918         twin_paint_path (px, PBOOT_STATUS_TEXT_COLOR, path);
919
920         twin_path_destroy(path);
921 }
922
923 void pboot_message(const char *message)
924 {
925         if (pboot_spane->text)
926                 free(pboot_spane->text);
927         pboot_spane->text = strdup(message);
928         twin_window_damage(pboot_spane->window,
929                            0, 0,
930                            pboot_spane->window->pixmap->width,
931                            pboot_spane->window->pixmap->height);
932         twin_window_queue_paint(pboot_spane->window);
933 }
934
935 static void pboot_create_spane(void)
936 {
937         pboot_spane = calloc(1, sizeof(pboot_spane_t));
938         assert(pboot_spane);
939
940         pboot_spane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
941                                                  TwinWindowPlain,
942                                                  PBOOT_LEFT_PANE_SIZE +
943                                                   PBOOT_STATUS_PANE_XYMARGIN,
944                                                  pboot_screen->height - 
945                                                   PBOOT_STATUS_PANE_HEIGHT,
946                                                  pboot_screen->width -
947                                                   PBOOT_LEFT_PANE_SIZE -
948                                                   2*PBOOT_STATUS_PANE_XYMARGIN,
949                                                  PBOOT_STATUS_PANE_HEIGHT);
950         assert(pboot_spane->window);
951
952         pboot_spane->window->draw = pboot_spane_draw;
953         pboot_spane->window->client_data = pboot_spane;
954         pboot_spane->text = strdup(PBOOT_INITIAL_MESSAGE);
955         twin_window_show(pboot_spane->window);
956         twin_window_queue_paint(pboot_spane->window);
957 }
958
959 int pboot_add_device(const char *dev_id, const char *name,
960                 twin_pixmap_t *pixmap)
961 {
962         int             index;
963         pboot_device_t  *dev;
964
965         if (pboot_dev_count >= PBOOT_MAX_DEV)
966                 return -1;
967
968         index = pboot_dev_count++;
969
970         dev = malloc(sizeof(*dev));
971         memset(dev, 0, sizeof(*dev));
972         dev->id = malloc(strlen(dev_id) + 1);
973         strcpy(dev->id, dev_id);
974         dev->badge = pixmap;
975         dev->box.left = PBOOT_LEFT_ICON_XOFF;
976         dev->box.right = dev->box.left + PBOOT_LEFT_ICON_WIDTH;
977         dev->box.top = PBOOT_LEFT_ICON_YOFF +
978                 PBOOT_LEFT_ICON_STRIDE * index;
979         dev->box.bottom = dev->box.top + PBOOT_LEFT_ICON_HEIGHT;
980
981         pboot_devices[index] = dev;
982
983         twin_window_damage(pboot_lpane->window,
984                            dev->box.left, dev->box.top,
985                            dev->box.right, dev->box.bottom);
986         twin_window_queue_paint(pboot_lpane->window);
987
988         return index;
989 }
990
991 int pboot_remove_device(const char *dev_id)
992 {
993         pboot_device_t  *dev = NULL;
994         int             i, newsel = pboot_dev_sel;
995
996         /* find the matching device */
997         for (i = 0; i < pboot_dev_count; i++) {
998                 if (!strcmp(pboot_devices[i]->id, dev_id)) {
999                         dev = pboot_devices[i];
1000                         break;
1001                 }
1002         }
1003
1004         if (!dev)
1005                 return TWIN_FALSE;
1006
1007         memmove(pboot_devices + i, pboot_devices + i + 1,
1008                         sizeof(*pboot_devices) * (pboot_dev_count + i - 1));
1009         pboot_devices[--pboot_dev_count] = NULL;
1010
1011         /* select the newly-focussed device */
1012         if (pboot_dev_sel > i)
1013                 newsel = pboot_dev_sel - 1;
1014         else if (pboot_dev_sel == i && i >= pboot_dev_count)
1015                         newsel = pboot_dev_count - 1;
1016         pboot_set_device_select(newsel, 1);
1017
1018         /* todo: free device & options */
1019
1020         return TWIN_TRUE;
1021 }
1022
1023 static void pboot_make_background(void)
1024 {
1025         twin_pixmap_t   *filepic, *scaledpic;
1026         const char      *background_path;
1027
1028         /* Set background pixmap */
1029         LOG("loading background...");
1030         background_path = artwork_pathname("background.jpg");
1031         filepic = twin_jpeg_to_pixmap(background_path, TWIN_ARGB32);
1032         LOG("%s\n", filepic ? "ok" : "failed");
1033
1034         if (filepic == NULL)
1035                 return;
1036
1037         if (pboot_screen->height == filepic->height &&
1038             pboot_screen->width == filepic->width)
1039                 scaledpic = filepic;
1040         else {
1041                 twin_fixed_t    sx, sy;
1042                 twin_operand_t  srcop;
1043
1044                 scaledpic = twin_pixmap_create(TWIN_ARGB32,
1045                                                pboot_screen->width,
1046                                                pboot_screen->height);
1047                 if (scaledpic == NULL) {
1048                         twin_pixmap_destroy(filepic);
1049                         return;
1050                 }
1051                 sx = twin_fixed_div(twin_int_to_fixed(filepic->width),
1052                                     twin_int_to_fixed(pboot_screen->width));
1053                 sy = twin_fixed_div(twin_int_to_fixed(filepic->height),
1054                                     twin_int_to_fixed(pboot_screen->height));
1055                 
1056                 twin_matrix_scale(&filepic->transform, sx, sy);
1057                 srcop.source_kind = TWIN_PIXMAP;
1058                 srcop.u.pixmap = filepic;
1059                 twin_composite(scaledpic, 0, 0, &srcop, 0, 0,
1060                                NULL, 0, 0, TWIN_SOURCE,
1061                                pboot_screen->width, pboot_screen->height);
1062                 twin_pixmap_destroy(filepic);
1063                                
1064         }
1065         twin_screen_set_background(pboot_screen, scaledpic);
1066 }
1067
1068 #define PS3FB_IOCTL_SETMODE          _IOW('r',  1, int)
1069 #define PS3FB_IOCTL_GETMODE          _IOR('r',  2, int)
1070
1071 static void exitfunc(void)
1072 {
1073 #ifndef _USE_X11
1074         if (pboot_fbdev)
1075                 twin_fbdev_destroy(pboot_fbdev);
1076         pboot_fbdev = NULL;
1077         if (pboot_vmode_change != -1) {
1078                 int fd = open("/dev/fb0", O_RDWR);
1079                 if (fd >= 0)
1080                         ioctl(fd, PS3FB_IOCTL_SETMODE,
1081                               (unsigned long)&pboot_vmode_change);
1082                 close(fd);
1083         }
1084 #endif
1085 }
1086
1087 static void sigint(int sig)
1088 {
1089         exitfunc();
1090         syscall(__NR_exit);
1091 }
1092
1093 static void usage(const char *progname)
1094 {
1095         fprintf(stderr, "Usage: %s [-u] [-h]\n", progname);
1096 }
1097
1098 int main(int argc, char **argv)
1099 {
1100         int c;
1101         int udev_trigger = 0;
1102
1103         for (;;) {
1104                 c = getopt(argc, argv, "u::h");
1105                 if (c == -1)
1106                         break;
1107
1108                 switch (c) {
1109                 case 'u':
1110                         udev_trigger = 1;
1111                         break;
1112                 case 'h':
1113                         usage(argv[0]);
1114                         return EXIT_SUCCESS;
1115                 default:
1116                         fprintf(stderr, "Unknown option '%c'\n", c);
1117                         usage(argv[0]);
1118                         return EXIT_FAILURE;
1119                 }
1120         }
1121
1122         atexit(exitfunc);
1123         signal(SIGINT, sigint);
1124
1125 #ifdef _USE_X11
1126         pboot_x11 = twin_x11_create(XOpenDisplay(0), 1024, 768);
1127         if (pboot_x11 == NULL) {
1128                 perror("failed to create x11 screen !\n");
1129                 return 1;
1130         }
1131         pboot_screen = pboot_x11->screen;
1132 #else
1133         /* Create screen and mouse drivers */
1134         pboot_fbdev = twin_fbdev_create(-1, SIGUSR1);
1135         if (pboot_fbdev == NULL) {
1136                 perror("failed to create fbdev screen !\n");
1137                 return 1;
1138         }
1139         pboot_screen = pboot_fbdev->screen;
1140         twin_linux_mouse_create(NULL, pboot_screen);
1141         twin_linux_js_create(pboot_screen);
1142
1143         if (pboot_fbdev != NULL) {
1144                 char *cursor_path = artwork_pathname("cursor.gz");
1145                 pboot_cursor = twin_load_X_cursor(cursor_path, 2,
1146                                                   &pboot_cursor_hx,
1147                                                   &pboot_cursor_hy);
1148                 if (pboot_cursor == NULL)
1149                         pboot_cursor =
1150                                 twin_get_default_cursor(&pboot_cursor_hx,
1151                                                         &pboot_cursor_hy);
1152         }
1153 #endif
1154
1155         /* Set background pixmap */
1156         pboot_make_background();
1157
1158         /* Init more stuffs */
1159         pboot_create_lpane();
1160         pboot_create_rpane();
1161         pboot_create_spane();
1162
1163         if (!pboot_start_device_discovery(udev_trigger)) {
1164                 LOG("Couldn't start device discovery!\n");
1165                 return 1;
1166         }
1167
1168         pboot_set_lfocus(0);
1169         twin_screen_set_active(pboot_screen, pboot_lpane->window->pixmap);
1170         pboot_screen->event_filter = pboot_event_filter;
1171
1172         /* Console switch */
1173 #ifndef _USE_X11
1174         if (pboot_fbdev)
1175                 twin_fbdev_activate(pboot_fbdev);
1176 #endif
1177
1178         /* Process events */
1179         twin_dispatch ();
1180
1181         return 0;
1182 }