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