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