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