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