]> git.ozlabs.org Git - petitboot/blob - petitboot.c
236d8e7d448877ee6568279ac5b1ebca1911208d
[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
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)
525 {
526         LOG("%s: %d -> %d\n", __FUNCTION__, pboot_dev_sel, sel);
527         if (sel == pboot_dev_sel || sel >= pboot_dev_count)
528                 return;
529         pboot_dev_sel = sel;
530         pboot_rpane->focus_curindex = -1;
531         pboot_rpane->mouse_target = -1;
532         pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT;
533         pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top +
534                 PBOOT_RIGHT_FOCUS_HEIGHT;
535         twin_window_damage(pboot_rpane->window, 0, 0,
536                            pboot_rpane->window->pixmap->width,
537                            pboot_rpane->window->pixmap->height);
538         twin_window_queue_paint(pboot_rpane->window);
539 }
540
541 static void pboot_create_rpane(void)
542 {
543         pboot_rpane = calloc(1, sizeof(pboot_rpane_t));
544         assert(pboot_rpane);
545
546         pboot_rpane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
547                                                  TwinWindowPlain,
548                                                  PBOOT_LEFT_PANE_SIZE, 0,
549                                                  pboot_screen->width -
550                                                    PBOOT_LEFT_PANE_SIZE,
551                                                  pboot_screen->height);
552         assert(pboot_rpane->window);
553
554         pboot_rpane->window->draw = pboot_rpane_draw;
555         pboot_rpane->window->event = pboot_rpane_event;
556         pboot_rpane->window->client_data = pboot_rpane;
557
558         pboot_rpane->focus_curindex = -1;
559         pboot_rpane->focus_box.left = PBOOT_RIGHT_FOCUS_XOFF;
560         pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT;
561         pboot_rpane->focus_box.right = pboot_rpane->window->pixmap->width -
562                 2 * PBOOT_RIGHT_FOCUS_XOFF;
563         pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top +
564                 PBOOT_RIGHT_FOCUS_HEIGHT;
565         pboot_rpane->mouse_target = -1;
566         twin_window_show(pboot_rpane->window);
567         twin_window_queue_paint(pboot_rpane->window);
568 }
569
570
571 static twin_time_t pboot_lfocus_timeout (twin_time_t now, void *closure)
572 {
573         int dir = 1, dist, pos;
574         const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 1, 2, 2, 3 };
575
576         dist = abs(pboot_lpane->focus_target - pboot_lpane->focus_start);
577         pos = pboot_lpane->focus_target - (int)pboot_lpane->focus_box.top;
578         if (pos == 0) {
579                 pboot_set_device_select(pboot_lpane->focus_curindex);
580                 return -1;
581         }
582         if (pos < 0) {
583                 dir = -1;
584                 pos = -pos;
585         }
586         twin_window_damage(pboot_lpane->window,
587                            pboot_lpane->focus_box.left,
588                            pboot_lpane->focus_box.top,
589                            pboot_lpane->focus_box.right,
590                            pboot_lpane->focus_box.bottom);
591
592         pboot_lpane->focus_box.top += dir;
593         pboot_lpane->focus_box.bottom += dir;
594
595         twin_window_damage(pboot_lpane->window,
596                            pboot_lpane->focus_box.left,
597                            pboot_lpane->focus_box.top,
598                            pboot_lpane->focus_box.right,
599                            pboot_lpane->focus_box.bottom);
600
601         twin_window_queue_paint(pboot_lpane->window);
602
603         return accel[(pos * 10) / dist];
604 }
605
606 static void pboot_set_lfocus(int index)
607 {
608         if (index >= pboot_dev_count)
609                 return;
610
611         pboot_lpane->focus_start = pboot_lpane->focus_box.top;
612
613         if (index < 0)
614                 pboot_lpane->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT;
615         else
616                 pboot_lpane->focus_target = PBOOT_LEFT_FOCUS_YOFF +
617                         PBOOT_LEFT_ICON_STRIDE * index;
618
619         pboot_lpane->focus_curindex = index;
620
621         twin_set_timeout(pboot_lfocus_timeout, 0, NULL);
622 }
623
624 static void pboot_lpane_mousetrack(twin_coord_t x, twin_coord_t y)
625 {
626         int candidate = -1;
627         twin_coord_t icon_top;
628
629         if (x < PBOOT_LEFT_ICON_XOFF ||
630             x > (PBOOT_LEFT_ICON_XOFF + PBOOT_LEFT_ICON_WIDTH))
631                 goto miss;
632         if (y < PBOOT_LEFT_ICON_YOFF)
633                 goto miss;
634         candidate = (y - PBOOT_LEFT_ICON_YOFF) / PBOOT_LEFT_ICON_STRIDE;
635         if (candidate >= pboot_dev_count) {
636                 candidate = -1;
637                 goto miss;
638         }
639         if (candidate == pboot_lpane->mouse_target)
640                 return;
641         icon_top = PBOOT_LEFT_ICON_YOFF +
642                 candidate * PBOOT_LEFT_ICON_STRIDE;
643         if (y > (icon_top + PBOOT_LEFT_ICON_HEIGHT)) {
644                 candidate = -1;
645                 goto miss;
646         }
647
648         /* Ok, so now, we know the mouse hit an icon that wasn't the same
649          * as the previous one, we trigger a focus change
650          */
651         pboot_set_lfocus(candidate);
652
653  miss:
654         pboot_lpane->mouse_target = candidate;
655 }
656
657 static twin_bool_t pboot_lpane_event (twin_window_t         *window,
658                                       twin_event_t          *event)
659 {
660         /* filter out all mouse events */
661         switch(event->kind) {
662         case TwinEventEnter:
663         case TwinEventMotion:
664         case TwinEventLeave:
665                 pboot_select_lpane();
666                 pboot_lpane_mousetrack(event->u.pointer.x, event->u.pointer.y);
667                 return TWIN_TRUE;
668         case TwinEventButtonDown:
669         case TwinEventButtonUp:
670                 return TWIN_TRUE;
671         case TwinEventKeyDown:
672                 switch(event->u.key.key) {
673                 case KEY_UP:
674                         if (pboot_lpane->focus_curindex > 0)
675                                 pboot_set_lfocus(
676                                         pboot_lpane->focus_curindex - 1);
677                         return TWIN_TRUE;
678                 case KEY_DOWN:
679                         pboot_set_lfocus(pboot_lpane->focus_curindex + 1);
680                         return TWIN_TRUE;
681                 case KEY_RIGHT:
682                         pboot_select_rpane();
683                         return TWIN_TRUE;
684                 default:
685                         break;
686                 }
687                 break;
688         default:
689                 break;
690         }
691         return TWIN_FALSE;
692 }
693
694 static void pboot_quit(void)
695 {
696         kill(0, SIGINT);
697 }
698
699 twin_bool_t pboot_event_filter(twin_screen_t        *screen,
700                                twin_event_t         *event)
701 {
702         switch(event->kind) {
703         case TwinEventEnter:
704         case TwinEventMotion:
705         case TwinEventLeave:
706         case TwinEventButtonDown:
707         case TwinEventButtonUp:
708                 if (pboot_cursor != NULL)
709                         twin_screen_set_cursor(pboot_screen, pboot_cursor,
710                                                pboot_cursor_hx,
711                                                pboot_cursor_hy);
712                 break;
713         case TwinEventKeyDown:
714                 /* Gross hack for video modes, need something better ! */
715                 switch(event->u.key.key) {
716                 case KEY_0:
717                         pboot_vmode_change = 0; /* auto */
718                         pboot_quit();
719                         return TWIN_TRUE;
720                 case KEY_1:
721                         pboot_vmode_change = 3; /* 720p */
722                         pboot_quit();
723                         return TWIN_TRUE;
724                 case KEY_2:
725                         pboot_vmode_change = 4; /* 1080i */
726                         pboot_quit();
727                         return TWIN_TRUE;
728                 case KEY_3:
729                         pboot_vmode_change = 5; /* 1080p */
730                         pboot_quit();
731                         return TWIN_TRUE;
732                 }
733         case TwinEventKeyUp:
734                 twin_screen_set_cursor(pboot_screen, NULL, 0, 0);
735                 break;
736         default:
737                 break;
738         }
739         return TWIN_FALSE;
740 }
741
742 static void pboot_lpane_draw(twin_window_t *window)
743 {
744         twin_pixmap_t   *px = window->pixmap;
745         pboot_lpane_t   *lpane = window->client_data;
746         twin_path_t     *path;
747         twin_fixed_t    x, y, w, h;
748         int             i;
749
750         /* Fill background */
751         twin_fill(px, PBOOT_LEFT_PANE_COLOR, TWIN_SOURCE,
752                   0, 0, px->width, px->height);
753
754         /* Create a path for use later */
755         path = twin_path_create();
756         assert(path);
757
758         /* Draw right line if needed */
759         if (px->clip.right > (PBOOT_LEFT_PANE_SIZE - 4)) {
760                 x = twin_int_to_fixed(PBOOT_LEFT_PANE_SIZE - 4);
761                 y = twin_int_to_fixed(px->height);
762                 twin_path_rectangle(path, x, 0, 0x40000, y);
763                 twin_paint_path(px, PBOOT_LEFT_LINE_COLOR, path);
764                 twin_path_empty(path);
765         }
766
767         /* Draw focus box */
768         if (lpane->focus_curindex >= 0 &&
769             twin_rect_intersect(lpane->focus_box, px->clip)) {
770                 x = twin_int_to_fixed(lpane->focus_box.left + 2);
771                 y = twin_int_to_fixed(lpane->focus_box.top + 2);
772                 w = twin_int_to_fixed(lpane->focus_box.right -
773                                       lpane->focus_box.left - 4);
774                 h = twin_int_to_fixed(lpane->focus_box.bottom -
775                                       lpane->focus_box.top - 4);
776                 twin_path_rounded_rectangle(path, x, y, w, h,
777                                             PBOOT_LEFT_FOCUS_XRAD,
778                                             PBOOT_LEFT_FOCUS_YRAD);
779                 if (pboot_focus_lpane)
780                         twin_paint_path(px, PBOOT_FOCUS_COLOR, path);
781                 else
782                         twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path,
783                                           4 * TWIN_FIXED_ONE);
784         }
785
786         /* Draw icons */
787         for (i = 0; i < pboot_dev_count; i++) {
788                 pboot_device_t  *dev = pboot_devices[i];
789                 twin_operand_t  src;
790
791                 if (!twin_rect_intersect(dev->box, px->clip))
792                         continue;
793
794                 src.source_kind = TWIN_PIXMAP;
795                 src.u.pixmap = dev->badge;
796
797                 twin_composite(px, dev->box.left, dev->box.top,
798                                &src, 0, 0, NULL, 0, 0, TWIN_OVER,
799                                dev->box.right - dev->box.left,
800                                dev->box.bottom - dev->box.top);
801
802         }
803
804         /* Destroy path */
805         twin_path_destroy(path);
806 }
807
808 static void pboot_create_lpane(void)
809 {
810         pboot_lpane = calloc(1, sizeof(pboot_lpane_t));
811         assert(pboot_lpane);
812
813         pboot_lpane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
814                                                  TwinWindowPlain,
815                                                  0, 0, PBOOT_LEFT_PANE_SIZE,
816                                                  pboot_screen->height);
817         assert(pboot_lpane->window);
818
819         pboot_lpane->window->draw = pboot_lpane_draw;
820         pboot_lpane->window->event = pboot_lpane_event;
821         pboot_lpane->window->client_data = pboot_lpane;
822         pboot_lpane->focus_curindex = -1;
823         pboot_lpane->focus_box.left = PBOOT_LEFT_FOCUS_XOFF;
824         pboot_lpane->focus_box.top = -2*PBOOT_LEFT_FOCUS_HEIGHT;
825         pboot_lpane->focus_box.right = pboot_lpane->focus_box.left +
826                 PBOOT_LEFT_FOCUS_WIDTH;
827         pboot_lpane->focus_box.bottom = pboot_lpane->focus_box.top +
828                 PBOOT_LEFT_FOCUS_HEIGHT;
829         pboot_lpane->mouse_target = -1;
830         twin_window_show(pboot_lpane->window);
831         twin_window_queue_paint(pboot_lpane->window);
832 }
833
834 static void pboot_spane_draw(twin_window_t *window)
835 {
836         twin_pixmap_t   *px = window->pixmap;
837         pboot_spane_t   *spane = window->client_data;
838         twin_path_t     *path;
839         twin_fixed_t    tx, ty;
840
841         /* Fill background */
842         twin_fill(px, PBOOT_STATUS_PANE_COLOR, TWIN_SOURCE,
843                   0, 0, px->width, px->height);
844
845         path = twin_path_create();
846         assert(path);
847
848         twin_path_set_font_size(path, PBOOT_STATUS_TEXT_SIZE);
849         twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
850         tx = twin_int_to_fixed(PBOOT_STATUS_TEXT_MARGIN);
851         ty = twin_int_to_fixed(PBOOT_STATUS_PANE_HEIGHT - 2);
852         twin_path_move (path, tx, ty);
853         twin_path_utf8 (path, spane->text);
854         twin_paint_path (px, PBOOT_STATUS_TEXT_COLOR, path);
855
856         twin_path_destroy(path);
857 }
858
859 void pboot_message(const char *message)
860 {
861         if (pboot_spane->text)
862                 free(pboot_spane->text);
863         pboot_spane->text = strdup(message);
864         twin_window_damage(pboot_spane->window,
865                            0, 0,
866                            pboot_spane->window->pixmap->width,
867                            pboot_spane->window->pixmap->height);
868         twin_window_queue_paint(pboot_spane->window);
869 }
870
871 static void pboot_create_spane(void)
872 {
873         pboot_spane = calloc(1, sizeof(pboot_spane_t));
874         assert(pboot_spane);
875
876         pboot_spane->window = twin_window_create(pboot_screen, TWIN_ARGB32,
877                                                  TwinWindowPlain,
878                                                  PBOOT_LEFT_PANE_SIZE +
879                                                   PBOOT_STATUS_PANE_XYMARGIN,
880                                                  pboot_screen->height - 
881                                                   PBOOT_STATUS_PANE_HEIGHT,
882                                                  pboot_screen->width -
883                                                   PBOOT_LEFT_PANE_SIZE -
884                                                   2*PBOOT_STATUS_PANE_XYMARGIN,
885                                                  PBOOT_STATUS_PANE_HEIGHT);
886         assert(pboot_spane->window);
887
888         pboot_spane->window->draw = pboot_spane_draw;
889         pboot_spane->window->client_data = pboot_spane;
890         pboot_spane->text = strdup(PBOOT_INITIAL_MESSAGE);
891         twin_window_show(pboot_spane->window);
892         twin_window_queue_paint(pboot_spane->window);
893 }
894
895 int pboot_add_device(const char *dev_id, const char *name,
896                 twin_pixmap_t *pixmap)
897 {
898         int             index;
899         pboot_device_t  *dev;
900
901         if (pboot_dev_count >= PBOOT_MAX_DEV)
902                 return -1;
903
904         index = pboot_dev_count++;
905
906         dev = malloc(sizeof(*dev));
907         memset(dev, 0, sizeof(*dev));
908         dev->id = malloc(strlen(dev_id) + 1);
909         strcpy(dev->id, dev_id);
910         dev->badge = pixmap;
911         dev->box.left = PBOOT_LEFT_ICON_XOFF;
912         dev->box.right = dev->box.left + PBOOT_LEFT_ICON_WIDTH;
913         dev->box.top = PBOOT_LEFT_ICON_YOFF +
914                 PBOOT_LEFT_ICON_STRIDE * index;
915         dev->box.bottom = dev->box.top + PBOOT_LEFT_ICON_HEIGHT;
916
917         pboot_devices[index] = dev;
918
919         twin_window_damage(pboot_lpane->window,
920                            dev->box.left, dev->box.top,
921                            dev->box.right, dev->box.bottom);
922         twin_window_queue_paint(pboot_lpane->window);
923
924         return index;
925 }
926
927 int pboot_remove_device(const char *dev_id)
928 {
929         int             i, new_dev_index;
930         pboot_device_t  *dev = NULL;
931
932         /* find the matching device */
933         for (i = 0; i < pboot_dev_count; i++) {
934                 if (!strcmp(pboot_devices[i]->id, dev_id)) {
935                         dev = pboot_devices[i];
936                         break;
937                 }
938         }
939
940         if (!dev)
941                 return TWIN_FALSE;
942
943         /* select the newly-focussed device */
944         if (i == pboot_dev_count - 1)
945                 new_dev_index = i - 1;
946         else
947                 new_dev_index = i + 1;
948
949         memmove(pboot_devices + i, pboot_devices + i + 1,
950                         sizeof(*pboot_devices) * (pboot_dev_count + i - 1));
951
952         pboot_devices[--pboot_dev_count] = NULL;
953
954         pboot_set_device_select(new_dev_index);
955         twin_window_damage(pboot_lpane->window,
956                            dev->box.left, dev->box.top,
957                            dev->box.right, dev->box.bottom);
958         twin_window_queue_paint(pboot_lpane->window);
959
960         /* todo: free device & options */
961
962         return TWIN_TRUE;
963 }
964
965 static void pboot_make_background(void)
966 {
967         twin_pixmap_t   *filepic, *scaledpic;
968         const char      *background_path;
969
970         /* Set background pixmap */
971         LOG("loading background...");
972         background_path = artwork_pathname("background.jpg");
973         filepic = twin_jpeg_to_pixmap(background_path, TWIN_ARGB32);
974         LOG("%s\n", filepic ? "ok" : "failed");
975
976         if (filepic == NULL)
977                 return;
978
979         if (pboot_screen->height == filepic->height &&
980             pboot_screen->width == filepic->width)
981                 scaledpic = filepic;
982         else {
983                 twin_fixed_t    sx, sy;
984                 twin_operand_t  srcop;
985
986                 scaledpic = twin_pixmap_create(TWIN_ARGB32,
987                                                pboot_screen->width,
988                                                pboot_screen->height);
989                 if (scaledpic == NULL) {
990                         twin_pixmap_destroy(filepic);
991                         return;
992                 }
993                 sx = twin_fixed_div(twin_int_to_fixed(filepic->width),
994                                     twin_int_to_fixed(pboot_screen->width));
995                 sy = twin_fixed_div(twin_int_to_fixed(filepic->height),
996                                     twin_int_to_fixed(pboot_screen->height));
997                 
998                 twin_matrix_scale(&filepic->transform, sx, sy);
999                 srcop.source_kind = TWIN_PIXMAP;
1000                 srcop.u.pixmap = filepic;
1001                 twin_composite(scaledpic, 0, 0, &srcop, 0, 0,
1002                                NULL, 0, 0, TWIN_SOURCE,
1003                                pboot_screen->width, pboot_screen->height);
1004                 twin_pixmap_destroy(filepic);
1005                                
1006         }
1007         twin_screen_set_background(pboot_screen, scaledpic);
1008 }
1009
1010 #define PS3FB_IOCTL_SETMODE          _IOW('r',  1, int)
1011 #define PS3FB_IOCTL_GETMODE          _IOR('r',  2, int)
1012
1013 static void exitfunc(void)
1014 {
1015 #ifndef _USE_X11
1016         if (pboot_fbdev)
1017                 twin_fbdev_destroy(pboot_fbdev);
1018         pboot_fbdev = NULL;
1019         if (pboot_vmode_change != -1) {
1020                 int fd = open("/dev/fb0", O_RDWR);
1021                 if (fd >= 0)
1022                         ioctl(fd, PS3FB_IOCTL_SETMODE,
1023                               (unsigned long)&pboot_vmode_change);
1024                 close(fd);
1025         }
1026 #endif
1027 }
1028
1029 static void sigint(int sig)
1030 {
1031         exitfunc();
1032         syscall(__NR_exit);
1033 }
1034
1035 static void usage(const char *progname)
1036 {
1037         fprintf(stderr, "Usage: %s [-u] [-h]\n", progname);
1038 }
1039
1040 int main(int argc, char **argv)
1041 {
1042         int c;
1043         int udev_trigger = 0;
1044
1045         for (;;) {
1046                 c = getopt(argc, argv, "u::h");
1047                 if (c == -1)
1048                         break;
1049
1050                 switch (c) {
1051                 case 'u':
1052                         udev_trigger = 1;
1053                         break;
1054                 case 'h':
1055                         usage(argv[0]);
1056                         return EXIT_SUCCESS;
1057                 default:
1058                         fprintf(stderr, "Unknown option '%c'\n", c);
1059                         usage(argv[0]);
1060                         return EXIT_FAILURE;
1061                 }
1062         }
1063
1064         atexit(exitfunc);
1065         signal(SIGINT, sigint);
1066
1067 #ifdef _USE_X11
1068         pboot_x11 = twin_x11_create(XOpenDisplay(0), 1024, 768);
1069         if (pboot_x11 == NULL) {
1070                 perror("failed to create x11 screen !\n");
1071                 return 1;
1072         }
1073         pboot_screen = pboot_x11->screen;
1074 #else
1075         /* Create screen and mouse drivers */
1076         pboot_fbdev = twin_fbdev_create(-1, SIGUSR1);
1077         if (pboot_fbdev == NULL) {
1078                 perror("failed to create fbdev screen !\n");
1079                 return 1;
1080         }
1081         pboot_screen = pboot_fbdev->screen;
1082         twin_linux_mouse_create(NULL, pboot_screen);
1083
1084         if (pboot_fbdev != NULL) {
1085                 char *cursor_path = artwork_pathname("cursor.gz");
1086                 pboot_cursor = twin_load_X_cursor(cursor_path, 2,
1087                                                   &pboot_cursor_hx,
1088                                                   &pboot_cursor_hy);
1089                 if (pboot_cursor == NULL)
1090                         pboot_cursor =
1091                                 twin_get_default_cursor(&pboot_cursor_hx,
1092                                                         &pboot_cursor_hy);
1093         }
1094 #endif
1095
1096         /* Set background pixmap */
1097         pboot_make_background();
1098
1099         /* Init more stuffs */
1100         pboot_create_lpane();
1101         pboot_create_rpane();
1102         pboot_create_spane();
1103
1104         if (!pboot_start_device_discovery(udev_trigger)) {
1105                 LOG("Couldn't start device discovery!\n");
1106                 return 1;
1107         }
1108
1109         pboot_set_lfocus(0);
1110         twin_screen_set_active(pboot_screen, pboot_lpane->window->pixmap);
1111         pboot_screen->event_filter = pboot_event_filter;
1112
1113         /* Console switch */
1114 #ifndef _USE_X11
1115         if (pboot_fbdev)
1116                 twin_fbdev_activate(pboot_fbdev);
1117 #endif
1118
1119         /* Process events */
1120         twin_dispatch ();
1121
1122         return 0;
1123 }