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