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