xref: /haiku/src/system/boot/loader/menu.cpp (revision b30304acc8c37e678a1bf66976d15bdab103f931)
1 /*
2  * Copyright 2003-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "menu.h"
8 #include "loader.h"
9 #include "RootFileSystem.h"
10 #include "load_driver_settings.h"
11 
12 #include <OS.h>
13 
14 #include <util/kernel_cpp.h>
15 #include <boot/menu.h>
16 #include <boot/stage2.h>
17 #include <boot/vfs.h>
18 #include <boot/platform.h>
19 #include <boot/stdio.h>
20 #include <safemode.h>
21 
22 #include <string.h>
23 
24 #define TRACE_MENU
25 #ifdef TRACE_MENU
26 #	define TRACE(x) dprintf x
27 #else
28 #	define TRACE(x) ;
29 #endif
30 
31 MenuItem::MenuItem(const char *label, Menu *subMenu)
32 	:
33 	fLabel(strdup(label)),
34 	fTarget(NULL),
35 	fIsMarked(false),
36 	fIsSelected(false),
37 	fIsEnabled(true),
38 	fType(MENU_ITEM_STANDARD),
39 	fMenu(NULL),
40 	fSubMenu(subMenu),
41 	fData(NULL),
42 	fHelpText(NULL)
43 {
44 	if (subMenu != NULL)
45 		subMenu->fSuperItem = this;
46 }
47 
48 
49 MenuItem::~MenuItem()
50 {
51 	if (fSubMenu != NULL)
52 		fSubMenu->fSuperItem = NULL;
53 
54 	free(const_cast<char *>(fLabel));
55 }
56 
57 
58 void
59 MenuItem::SetTarget(menu_item_hook target)
60 {
61 	fTarget = target;
62 }
63 
64 
65 /**	Marks or unmarks a menu item. A marked menu item usually gets a visual
66  *	clue like a checkmark that distinguishes it from others.
67  *	For menus of type CHOICE_MENU, there can only be one marked item - the
68  *	chosen one.
69  */
70 
71 void
72 MenuItem::SetMarked(bool marked)
73 {
74 	if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) {
75 		// always set choice text of parent if we were marked
76 		fMenu->SetChoiceText(Label());
77 	}
78 
79 	if (fIsMarked == marked)
80 		return;
81 
82 	if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) {
83 		// unmark previous item
84 		MenuItem *markedItem = fMenu->FindMarked();
85 		if (markedItem != NULL)
86 			markedItem->SetMarked(false);
87 	}
88 
89 	fIsMarked = marked;
90 
91 	if (fMenu != NULL)
92 		fMenu->Draw(this);
93 }
94 
95 
96 void
97 MenuItem::Select(bool selected)
98 {
99 	if (fIsSelected == selected)
100 		return;
101 
102 	if (selected && fMenu != NULL) {
103 		// unselect previous item
104 		MenuItem *selectedItem = fMenu->FindSelected();
105 		if (selectedItem != NULL)
106 			selectedItem->Select(false);
107 	}
108 
109 	fIsSelected = selected;
110 
111 	if (fMenu != NULL)
112 		fMenu->Draw(this);
113 }
114 
115 
116 void
117 MenuItem::SetType(menu_item_type type)
118 {
119 	fType = type;
120 }
121 
122 
123 void
124 MenuItem::SetEnabled(bool enabled)
125 {
126 	if (fIsEnabled == enabled)
127 		return;
128 
129 	fIsEnabled = enabled;
130 
131 	if (fMenu != NULL)
132 		fMenu->Draw(this);
133 }
134 
135 
136 void
137 MenuItem::SetData(const void *data)
138 {
139 	fData = data;
140 }
141 
142 
143 /** This sets a help text that is shown when the item is
144  *	selected.
145  *	Note, unlike the label, the string is not copied, it's
146  *	just referenced and has to stay valid as long as the
147  *	item's menu is being used.
148  */
149 
150 void
151 MenuItem::SetHelpText(const char *text)
152 {
153 	fHelpText = text;
154 }
155 
156 
157 void
158 MenuItem::SetMenu(Menu *menu)
159 {
160 	fMenu = menu;
161 }
162 
163 
164 //	#pragma mark -
165 
166 
167 Menu::Menu(menu_type type, const char *title)
168 	:
169 	fTitle(title),
170 	fChoiceText(NULL),
171 	fCount(0),
172 	fIsHidden(true),
173 	fType(type),
174 	fSuperItem(NULL)
175 {
176 }
177 
178 
179 Menu::~Menu()
180 {
181 	// take all remaining items with us
182 
183 	MenuItem *item;
184 	while ((item = fItems.Head()) != NULL) {
185 		fItems.Remove(item);
186 		delete item;
187 	}
188 }
189 
190 
191 MenuItem *
192 Menu::ItemAt(int32 index)
193 {
194 	if (index < 0 || index >= fCount)
195 		return NULL;
196 
197 	MenuItemIterator iterator = ItemIterator();
198 	MenuItem *item;
199 
200 	while ((item = iterator.Next()) != NULL) {
201 		if (index-- == 0)
202 			return item;
203 	}
204 
205 	return NULL;
206 }
207 
208 
209 int32
210 Menu::IndexOf(MenuItem *searchedItem)
211 {
212 	MenuItemIterator iterator = ItemIterator();
213 	MenuItem *item;
214 	int32 index = 0;
215 
216 	while ((item = iterator.Next()) != NULL) {
217 		if (item == searchedItem)
218 			return index;
219 
220 		index++;
221 	}
222 
223 	return -1;
224 }
225 
226 
227 int32
228 Menu::CountItems() const
229 {
230 	return fCount;
231 }
232 
233 
234 MenuItem *
235 Menu::FindItem(const char *label)
236 {
237 	MenuItemIterator iterator = ItemIterator();
238 	MenuItem *item;
239 
240 	while ((item = iterator.Next()) != NULL) {
241 		if (item->Label() != NULL && !strcmp(item->Label(), label))
242 			return item;
243 	}
244 
245 	return NULL;
246 }
247 
248 
249 MenuItem *
250 Menu::FindMarked()
251 {
252 	MenuItemIterator iterator = ItemIterator();
253 	MenuItem *item;
254 
255 	while ((item = iterator.Next()) != NULL) {
256 		if (item->IsMarked())
257 			return item;
258 	}
259 
260 	return NULL;
261 }
262 
263 
264 MenuItem *
265 Menu::FindSelected(int32 *_index)
266 {
267 	MenuItemIterator iterator = ItemIterator();
268 	MenuItem *item;
269 	int32 index = 0;
270 
271 	while ((item = iterator.Next()) != NULL) {
272 		if (item->IsSelected()) {
273 			if (_index != NULL)
274 				*_index = index;
275 			return item;
276 		}
277 
278 		index++;
279 	}
280 
281 	return NULL;
282 }
283 
284 
285 void
286 Menu::AddItem(MenuItem *item)
287 {
288 	item->fMenu = this;
289 	fItems.Add(item);
290 	fCount++;
291 }
292 
293 
294 status_t
295 Menu::AddSeparatorItem()
296 {
297 	MenuItem *item = new(nothrow) MenuItem();
298 	if (item == NULL)
299 		return B_NO_MEMORY;
300 
301 	item->SetType(MENU_ITEM_SEPARATOR);
302 
303 	AddItem(item);
304 	return B_OK;
305 }
306 
307 
308 MenuItem *
309 Menu::RemoveItemAt(int32 index)
310 {
311 	if (index < 0 || index >= fCount)
312 		return NULL;
313 
314 	MenuItemIterator iterator = ItemIterator();
315 	MenuItem *item;
316 
317 	while ((item = iterator.Next()) != NULL) {
318 		if (index-- == 0) {
319 			RemoveItem(item);
320 			return item;
321 		}
322 	}
323 
324 	return NULL;
325 }
326 
327 
328 void
329 Menu::RemoveItem(MenuItem *item)
330 {
331 	item->fMenu = NULL;
332 	fItems.Remove(item);
333 	fCount--;
334 }
335 
336 
337 void
338 Menu::Draw(MenuItem *item)
339 {
340 	if (!IsHidden())
341 		platform_update_menu_item(this, item);
342 }
343 
344 
345 void
346 Menu::Run()
347 {
348 	platform_run_menu(this);
349 }
350 
351 
352 //	#pragma mark -
353 
354 
355 static bool
356 user_menu_boot_volume(Menu *menu, MenuItem *item)
357 {
358 	Menu *super = menu->Supermenu();
359 	if (super == NULL) {
360 		// huh?
361 		return true;
362 	}
363 
364 	MenuItem *bootItem = super->ItemAt(super->CountItems() - 1);
365 	bootItem->SetEnabled(true);
366 	bootItem->Select(true);
367 	bootItem->SetData(item->Data());
368 
369 	gKernelArgs.boot_volume.SetBool(BOOT_VOLUME_USER_SELECTED, true);
370 	return true;
371 }
372 
373 
374 static Menu *
375 add_boot_volume_menu(Directory *bootVolume)
376 {
377 	Menu *menu = new(nothrow) Menu(CHOICE_MENU, "Select Boot Volume");
378 	MenuItem *item;
379 	void *cookie;
380 	int32 count = 0;
381 
382 	if (gRoot->Open(&cookie, O_RDONLY) == B_OK) {
383 		Directory *volume;
384 		while (gRoot->GetNextNode(cookie, (Node **)&volume) == B_OK) {
385 			// only list bootable volumes
386 			if (!is_bootable(volume))
387 				continue;
388 
389 			char name[B_FILE_NAME_LENGTH];
390 			if (volume->GetName(name, sizeof(name)) == B_OK) {
391 				menu->AddItem(item = new(nothrow) MenuItem(name));
392 				item->SetTarget(user_menu_boot_volume);
393 				item->SetData(volume);
394 
395 				if (volume == bootVolume) {
396 					item->SetMarked(true);
397 					item->Select(true);
398 				}
399 
400 				count++;
401 			}
402 		}
403 		gRoot->Close(cookie);
404 	}
405 
406 	if (count == 0) {
407 		// no boot volume found yet
408 		menu->AddItem(item = new(nothrow) MenuItem("<No boot volume found>"));
409 		item->SetType(MENU_ITEM_NO_CHOICE);
410 		item->SetEnabled(false);
411 	}
412 
413 	menu->AddSeparatorItem();
414 
415 	menu->AddItem(item = new(nothrow) MenuItem("Rescan volumes"));
416 	item->SetHelpText("Please insert a Haiku CD-ROM or attach a USB disk - "
417 		"depending on your system, you can then boot from them.");
418 	item->SetType(MENU_ITEM_NO_CHOICE);
419 	if (count == 0)
420 		item->Select(true);
421 
422 	menu->AddItem(item = new(nothrow) MenuItem("Return to main menu"));
423 	item->SetType(MENU_ITEM_NO_CHOICE);
424 
425 	if (gKernelArgs.boot_volume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false))
426 		menu->SetChoiceText("CD-ROM or hard drive");
427 
428 	return menu;
429 }
430 
431 
432 static Menu *
433 add_safe_mode_menu()
434 {
435 	Menu *safeMenu = new(nothrow) Menu(SAFE_MODE_MENU, "Safe Mode Options");
436 	MenuItem *item;
437 
438 	safeMenu->AddItem(item = new(nothrow) MenuItem("Safe mode"));
439 	item->SetData(B_SAFEMODE_SAFE_MODE);
440 	item->SetType(MENU_ITEM_MARKABLE);
441 	item->SetHelpText("Puts the system into safe mode. This can be enabled independently "
442 		"from the other options.");
443 
444 	safeMenu->AddItem(item = new(nothrow) MenuItem("Disable user add-ons"));
445 	item->SetData(B_SAFEMODE_DISABLE_USER_ADD_ONS);
446 	item->SetType(MENU_ITEM_MARKABLE);
447 	item->SetHelpText("Prevent all user installed add-ons to be loaded. Only the add-ons "
448 		"in the system directory will be used.");
449 
450 	safeMenu->AddItem(item = new(nothrow) MenuItem("Disable IDE DMA"));
451 	item->SetData(B_SAFEMODE_DISABLE_IDE_DMA);
452 	item->SetType(MENU_ITEM_MARKABLE);
453 
454 	platform_add_menus(safeMenu);
455 
456 	safeMenu->AddItem(item = new(nothrow) MenuItem("Enable on screen debug output"));
457 	item->SetData("debug_screen");
458 	item->SetType(MENU_ITEM_MARKABLE);
459 
460 	safeMenu->AddSeparatorItem();
461 	safeMenu->AddItem(item = new(nothrow) MenuItem("Return to main menu"));
462 
463 	return safeMenu;
464 }
465 
466 
467 static void
468 apply_safe_mode_options(Menu *menu)
469 {
470 	MenuItemIterator iterator = menu->ItemIterator();
471 	MenuItem *item;
472 	char buffer[2048];
473 	int32 pos = 0;
474 
475 	buffer[0] = '\0';
476 
477 	while ((item = iterator.Next()) != NULL) {
478 		if (item->Type() == MENU_ITEM_SEPARATOR || !item->IsMarked()
479 			|| item->Data() == NULL || (uint32)pos > sizeof(buffer))
480 			continue;
481 
482 		pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s true\n",
483 			(const char *)item->Data());
484 	}
485 
486 	add_safe_mode_settings(buffer);
487 }
488 
489 
490 static bool
491 user_menu_reboot(Menu *menu, MenuItem *item)
492 {
493 	platform_exit();
494 	return true;
495 }
496 
497 
498 status_t
499 user_menu(Directory **_bootVolume)
500 {
501 	Menu *menu = new(nothrow) Menu(MAIN_MENU);
502 	Menu *safeModeMenu = NULL;
503 	MenuItem *item;
504 
505 	TRACE(("user_menu: enter\n"));
506 
507 	// Add boot volume
508 	menu->AddItem(item = new(nothrow) MenuItem("Select boot volume",
509 		add_boot_volume_menu(*_bootVolume)));
510 
511 	// Add safe mode
512 	menu->AddItem(item = new(nothrow) MenuItem("Select safe mode options",
513 		safeModeMenu = add_safe_mode_menu()));
514 
515 	// Add platform dependent menus
516 	platform_add_menus(menu);
517 
518 	menu->AddSeparatorItem();
519 	if (*_bootVolume == NULL) {
520 		menu->AddItem(item = new(nothrow) MenuItem("Reboot"));
521 		item->SetTarget(user_menu_reboot);
522 	}
523 
524 	menu->AddItem(item = new(nothrow) MenuItem("Continue booting"));
525 	if (*_bootVolume == NULL) {
526 		item->SetEnabled(false);
527 		menu->ItemAt(0)->Select(true);
528 	}
529 
530 	menu->Run();
531 
532 	// See if a new boot device has been selected, and propagate that back
533 	if (item->Data() != NULL)
534 		*_bootVolume = (Directory *)item->Data();
535 
536 	apply_safe_mode_options(safeModeMenu);
537 	delete menu;
538 
539 	TRACE(("user_menu: leave\n"));
540 
541 	return B_OK;
542 }
543 
544