ui/ncurses: Add device selector to boot editor
[petitboot] / ui / ncurses / nc-boot-editor.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #include "config.h"
20
21 #define _GNU_SOURCE
22
23 #include <assert.h>
24 #include <string.h>
25
26 #include "log/log.h"
27 #include "talloc/talloc.h"
28 #include "nc-boot-editor.h"
29 #include "nc-widgets.h"
30
31 struct boot_editor {
32         struct nc_scr           scr;
33         struct cui              *cui;
34         void                    *data;
35         struct pmenu_item       *item;
36         void                    (*on_exit)(struct cui *cui,
37                                         struct pmenu_item *item,
38                                         struct pb_boot_data *bd);
39
40         int                     label_x;
41         int                     field_x;
42
43         struct nc_widgetset     *widgetset;
44         struct {
45                 struct nc_widget_label          *device_l;
46                 struct nc_widget_select         *device_f;
47                 struct nc_widget_label          *image_l;
48                 struct nc_widget_textbox        *image_f;
49                 struct nc_widget_label          *initrd_l;
50                 struct nc_widget_textbox        *initrd_f;
51                 struct nc_widget_label          *dtb_l;
52                 struct nc_widget_textbox        *dtb_f;
53                 struct nc_widget_label          *args_l;
54                 struct nc_widget_textbox        *args_f;
55                 struct nc_widget_button         *ok_b;
56                 struct nc_widget_button         *cancel_b;
57         } widgets;
58
59         const char              *selected_device;
60 };
61
62 static struct boot_editor *boot_editor_from_scr(struct nc_scr *scr)
63 {
64         struct boot_editor *boot_editor;
65
66         assert(scr->sig == pb_boot_editor_sig);
67         boot_editor = (struct boot_editor *)
68                 ((char *)scr - (size_t)&((struct boot_editor *)0)->scr);
69         assert(boot_editor->scr.sig == pb_boot_editor_sig);
70         return boot_editor;
71 }
72
73 static struct boot_editor *boot_editor_from_arg(void *arg)
74 {
75         struct boot_editor *boot_editor = arg;
76
77         assert(boot_editor->scr.sig == pb_boot_editor_sig);
78         return boot_editor;
79 }
80
81 static int boot_editor_post(struct nc_scr *scr)
82 {
83         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
84
85         widgetset_post(boot_editor->widgetset);
86         nc_scr_frame_draw(scr);
87         redrawwin(boot_editor->scr.main_ncw);
88         wrefresh(boot_editor->scr.main_ncw);
89
90         return 0;
91 }
92
93 static int boot_editor_unpost(struct nc_scr *scr)
94 {
95         widgetset_unpost(boot_editor_from_scr(scr)->widgetset);
96         return 0;
97 }
98
99 struct nc_scr *boot_editor_scr(struct boot_editor *boot_editor)
100 {
101         return &boot_editor->scr;
102 }
103
104 static void boot_editor_resize(struct nc_scr *scr)
105 {
106         /* FIXME: forms can't be resized, need to recreate here */
107         boot_editor_unpost(scr);
108         boot_editor_post(scr);
109 }
110
111 static char *conditional_prefix(struct pb_boot_data *ctx,
112                 const char *prefix, const char *value)
113 {
114         const char *sep;
115
116         if (!value || !*value)
117                 return NULL;
118
119         sep = NULL;
120         if (!prefix)
121                 prefix = "";
122         else if (prefix[strlen(prefix)] != '/')
123                 sep = "/";
124
125         return talloc_asprintf(ctx, "%s%s%s", prefix, sep, value);
126 }
127
128 static struct pb_boot_data *boot_editor_prepare_data(
129                 struct boot_editor *boot_editor)
130 {
131         struct pb_boot_data *bd;
132         char *s, *prefix;
133         int idx;
134
135         bd = talloc(boot_editor, struct pb_boot_data);
136
137         if (!bd)
138                 return NULL;
139
140         idx = widget_select_get_value(boot_editor->widgets.device_f);
141         if (idx == -1 || (unsigned int)idx >
142                         boot_editor->cui->sysinfo->n_blockdevs)
143                 prefix = NULL;
144         else
145                 prefix = boot_editor->cui->sysinfo->blockdevs[idx]->mountpoint;
146
147         s = widget_textbox_get_value(boot_editor->widgets.image_f);
148         bd->image = conditional_prefix(bd, prefix, s);
149
150         s = widget_textbox_get_value(boot_editor->widgets.initrd_f);
151         bd->initrd = conditional_prefix(bd, prefix, s);
152
153         s = widget_textbox_get_value(boot_editor->widgets.dtb_f);
154         bd->dtb = conditional_prefix(bd, prefix, s);
155
156         s = widget_textbox_get_value(boot_editor->widgets.args_f);
157         bd->args = *s ? talloc_strdup(bd, s) : NULL;
158
159         return bd;
160 }
161
162 /**
163  * boot_editor_process_key - Process a user keystroke.
164  *
165  * Called from the cui via the scr:process_key method.
166  */
167
168 static void boot_editor_process_key(struct nc_scr *scr, int key)
169 {
170         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
171         bool handled;
172
173         handled = widgetset_process_key(boot_editor->widgetset, key);
174         if (handled) {
175                 wrefresh(boot_editor->scr.main_ncw);
176                 return;
177         }
178
179         switch (key) {
180         case 'x':
181         case 27: /* ESC */
182                 boot_editor->on_exit(boot_editor->cui, NULL, NULL);
183                 nc_flush_keys();
184         }
185 }
186
187 /**
188  * boot_editor_destructor - The talloc destructor for a boot_editor.
189  */
190
191 static int boot_editor_destructor(void *arg)
192 {
193         struct boot_editor *boot_editor = boot_editor_from_arg(arg);
194         boot_editor->scr.sig = pb_removed_sig;
195         return 0;
196 }
197
198 static void ok_click(void *arg)
199 {
200         struct boot_editor *boot_editor = arg;
201         struct pb_boot_data *bd;
202
203         bd = boot_editor_prepare_data(boot_editor);
204         boot_editor->on_exit(boot_editor->cui, boot_editor->item, bd);
205 }
206
207 static void cancel_click(void *arg)
208 {
209         struct boot_editor *boot_editor = arg;
210         boot_editor->on_exit(boot_editor->cui, NULL, NULL);
211 }
212
213 static int layout_pair(struct boot_editor *boot_editor, int y,
214                 struct nc_widget_label *label,
215                 struct nc_widget_textbox *field)
216 {
217         struct nc_widget *label_w = widget_label_base(label);
218         struct nc_widget *field_w = widget_textbox_base(field);
219         widget_move(label_w, y, boot_editor->label_x);
220         widget_move(field_w, y, boot_editor->field_x);
221         return max(widget_height(label_w), widget_height(field_w));
222 }
223
224 static void boot_editor_layout_widgets(struct boot_editor *boot_editor)
225 {
226         struct nc_widget *wf, *wl;
227         int y = 1;
228
229         wl = widget_label_base(boot_editor->widgets.device_l);
230         wf = widget_select_base(boot_editor->widgets.device_f);
231         widget_move(wl, y, boot_editor->label_x);
232         widget_move(wf, y, boot_editor->field_x);
233
234         y += widget_height(wf) + 1;
235
236
237         y += layout_pair(boot_editor, y, boot_editor->widgets.image_l,
238                                          boot_editor->widgets.image_f);
239
240         y += layout_pair(boot_editor, y, boot_editor->widgets.initrd_l,
241                                          boot_editor->widgets.initrd_f);
242
243         y += layout_pair(boot_editor, y, boot_editor->widgets.dtb_l,
244                                          boot_editor->widgets.dtb_f);
245
246         y += layout_pair(boot_editor, y, boot_editor->widgets.args_l,
247                                          boot_editor->widgets.args_f);
248
249
250         y++;
251         widget_move(widget_button_base(boot_editor->widgets.ok_b), y, 9);
252         widget_move(widget_button_base(boot_editor->widgets.cancel_b), y, 19);
253 }
254
255 static void boot_editor_device_select_change(void *arg, int idx)
256 {
257         struct boot_editor *boot_editor = arg;
258         if (idx == -1)
259                 boot_editor->selected_device = NULL;
260         else
261                 boot_editor->selected_device =
262                         boot_editor->cui->sysinfo->blockdevs[idx]->name;
263 }
264
265 static void boot_editor_populate_device_select(struct boot_editor *boot_editor,
266                 const struct system_info *sysinfo)
267 {
268         struct nc_widget_select *select = boot_editor->widgets.device_f;
269         unsigned int i;
270         bool selected;
271
272         widget_select_drop_options(select);
273
274         for (i = 0; i < sysinfo->n_blockdevs; i++) {
275                 struct blockdev_info *bd_info = sysinfo->blockdevs[i];
276                 const char *name;
277
278                 name = talloc_asprintf(boot_editor, "%s [%s]",
279                                 bd_info->name, bd_info->uuid);
280                 selected = boot_editor->selected_device &&
281                                 !strcmp(bd_info->name,
282                                                 boot_editor->selected_device);
283
284                 widget_select_add_option(boot_editor->widgets.device_f,
285                                 i, name, selected);
286         }
287
288         /* If we're editing an existing option, the paths will be fully-
289          * resolved. In this case, we want the manual device pre-selected.
290          * However, we only do this if the widget hasn't been manually
291          * changed. */
292         selected = boot_editor->item && !boot_editor->selected_device;
293
294         widget_select_add_option(boot_editor->widgets.device_f,
295                         -1, "Specify paths/URLs manually", selected);
296 }
297
298 struct boot_editor *boot_editor_init(struct cui *cui,
299                 struct pmenu_item *item,
300                 const struct system_info *sysinfo,
301                 void (*on_exit)(struct cui *cui,
302                                 struct pmenu_item *item,
303                                 struct pb_boot_data *bd))
304 {
305         char *image, *initrd, *dtb, *args;
306         struct boot_editor *boot_editor;
307         struct nc_widgetset *set;
308         int field_size;
309
310         (void)sysinfo;
311
312         boot_editor = talloc_zero(cui, struct boot_editor);
313
314         if (!boot_editor)
315                 return NULL;
316
317         talloc_set_destructor(boot_editor, boot_editor_destructor);
318         boot_editor->cui = cui;
319         boot_editor->item = item;
320         boot_editor->on_exit = on_exit;
321
322         boot_editor->label_x = 1;
323         boot_editor->field_x = 9;
324
325         nc_scr_init(&boot_editor->scr, pb_boot_editor_sig, 0,
326                         cui, boot_editor_process_key,
327                 boot_editor_post, boot_editor_unpost, boot_editor_resize);
328
329         boot_editor->scr.frame.ltitle = talloc_strdup(boot_editor,
330                         "Petitboot Option Editor");
331         boot_editor->scr.frame.rtitle = NULL;
332         boot_editor->scr.frame.help = talloc_strdup(boot_editor,
333                         "Enter=accept");
334
335         if (item) {
336                 struct pb_boot_data *bd = cod_from_item(item)->bd;
337                 image = bd->image;
338                 initrd = bd->initrd;
339                 dtb = bd->dtb;
340                 args = bd->args;
341         } else {
342                 image = initrd = dtb = args = "";
343         }
344
345         field_size = COLS - 1 - boot_editor->field_x;
346
347         boot_editor->widgetset = set = widgetset_create(boot_editor,
348                         boot_editor->scr.main_ncw,
349                         boot_editor->scr.sub_ncw);
350
351         boot_editor->widgets.device_l = widget_new_label(set, 0, 0, "device:");
352         boot_editor->widgets.device_f = widget_new_select(set, 0, 0,
353                                                 field_size);
354         widget_select_on_change(boot_editor->widgets.device_f,
355                         boot_editor_device_select_change, boot_editor);
356
357         boot_editor_populate_device_select(boot_editor, sysinfo);
358
359         boot_editor->widgets.image_l = widget_new_label(set, 0, 0, "image:");
360         boot_editor->widgets.image_f = widget_new_textbox(set, 0, 0,
361                                                 field_size, image);
362
363         boot_editor->widgets.initrd_l = widget_new_label(set, 0, 0, "initrd:");
364         boot_editor->widgets.initrd_f = widget_new_textbox(set, 0, 0,
365                                                 field_size, initrd);
366
367         boot_editor->widgets.dtb_l = widget_new_label(set, 0, 0, "dtb:");
368         boot_editor->widgets.dtb_f = widget_new_textbox(set, 0, 0,
369                                                 field_size, dtb);
370
371         boot_editor->widgets.args_l = widget_new_label(set, 0, 0, "args:");
372         boot_editor->widgets.args_f = widget_new_textbox(set, 0, 0,
373                                         field_size, args);
374
375         boot_editor->widgets.ok_b = widget_new_button(set, 0, 0, 6,
376                                         "OK", ok_click, boot_editor);
377         boot_editor->widgets.cancel_b = widget_new_button(set, 0, 0, 6,
378                                         "Cancel", cancel_click, boot_editor);
379
380         boot_editor_layout_widgets(boot_editor);
381
382         return boot_editor;
383 }