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