1 /* 2 * Copyright 2001-2008, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Rafael Romo 7 * Stefano Ceccherini (burton666@libero.it) 8 * Andrew Bachmann 9 * Thomas Kurschel 10 * Axel Dörfler, axeld@pinc-software.de 11 * Stephan Aßmus <superstippi@gmx.de> 12 * Alexandre Deckner, alex@zappotek.com 13 */ 14 15 16 #include "AlertWindow.h" 17 #include "Constants.h" 18 #include "RefreshWindow.h" 19 #include "MonitorView.h" 20 #include "ScreenSettings.h" 21 #include "ScreenWindow.h" 22 #include "Utility.h" 23 24 /* Note, this headers defines a *private* interface to the Radeon accelerant. 25 * It's a solution that works with the current BeOS interface that Haiku 26 * adopted. 27 * However, it's not a nice and clean solution. Don't use this header in any 28 * application if you can avoid it. No other driver is using this, or should 29 * be using this. 30 * It will be replaced as soon as we introduce an updated accelerant interface 31 * which may even happen before R1 hits the streets. 32 */ 33 #include "multimon.h" // the usual: DANGER WILL, ROBINSON! 34 35 #include <Alert.h> 36 #include <Application.h> 37 #include <Box.h> 38 #include <Button.h> 39 #include <Directory.h> 40 #include <File.h> 41 #include <FindDirectory.h> 42 #include <InterfaceDefs.h> 43 #include <MenuBar.h> 44 #include <MenuItem.h> 45 #include <MenuField.h> 46 #include <Messenger.h> 47 #include <Path.h> 48 #include <PopUpMenu.h> 49 #include <Screen.h> 50 #include <String.h> 51 #include <Roster.h> 52 #include <Window.h> 53 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 58 59 const char* kBackgroundsSignature = "application/x-vnd.haiku-backgrounds"; 60 61 // list of officially supported colour spaces 62 static const struct { 63 color_space space; 64 int32 bits_per_pixel; 65 const char* label; 66 } kColorSpaces[] = { 67 { B_CMAP8, 8, "8 Bits/Pixel, 256 Colors" }, 68 { B_RGB15, 15, "15 Bits/Pixel, 32768 Colors" }, 69 { B_RGB16, 16, "16 Bits/Pixel, 65536 Colors" }, 70 { B_RGB32, 32, "32 Bits/Pixel, 16 Million Colors" } 71 }; 72 static const int32 kColorSpaceCount = sizeof(kColorSpaces) / sizeof(kColorSpaces[0]); 73 74 // list of standard refresh rates 75 static const int32 kRefreshRates[] = { 60, 70, 72, 75, 80, 85, 95, 100 }; 76 static const int32 kRefreshRateCount = sizeof(kRefreshRates) / sizeof(kRefreshRates[0]); 77 78 // list of combine modes 79 static const struct { 80 combine_mode mode; 81 const char *name; 82 } kCombineModes[] = { 83 { kCombineDisable, "disable" }, 84 { kCombineHorizontally, "horizontally" }, 85 { kCombineVertically, "vertically" } 86 }; 87 static const int32 kCombineModeCount = sizeof(kCombineModes) / sizeof(kCombineModes[0]); 88 89 enum { 90 SHOW_COMBINE_FIELD = 0x01, 91 SHOW_SWAP_FIELD = 0x02, 92 SHOW_LAPTOP_PANEL_FIELD = 0x04, 93 SHOW_TV_STANDARD_FIELD = 0x08, 94 }; 95 96 97 static BString 98 tv_standard_to_string(uint32 mode) 99 { 100 switch (mode) { 101 case 0: return "disabled"; 102 case 1: return "NTSC"; 103 case 2: return "NTSC Japan"; 104 case 3: return "PAL BDGHI"; 105 case 4: return "PAL M"; 106 case 5: return "PAL N"; 107 case 6: return "SECAM"; 108 case 101: return "NTSC 443"; 109 case 102: return "PAL 60"; 110 case 103: return "PAL NC"; 111 default: 112 { 113 BString name; 114 name << "??? (" << mode << ")"; 115 116 return name; 117 } 118 } 119 } 120 121 122 static void 123 resolution_to_string(screen_mode& mode, BString &string) 124 { 125 string << mode.width << " x " << mode.height; 126 } 127 128 129 static void 130 refresh_rate_to_string(float refresh, BString &string, 131 bool appendUnit = true, bool alwaysWithFraction = false) 132 { 133 snprintf(string.LockBuffer(32), 32, "%.*g", refresh >= 100.0 ? 4 : 3, refresh); 134 string.UnlockBuffer(); 135 136 if (appendUnit) 137 string << " Hz"; 138 } 139 140 141 static const char* 142 screen_errors(status_t status) 143 { 144 switch (status) { 145 case B_ENTRY_NOT_FOUND: 146 return "Unknown Mode"; 147 // TODO: add more? 148 149 default: 150 return strerror(status); 151 } 152 } 153 154 155 static float 156 max_label_width(BMenuField* control, float widestLabel) 157 { 158 float labelWidth = control->StringWidth(control->Label()); 159 if (widestLabel < labelWidth) 160 return labelWidth; 161 return widestLabel; 162 } 163 164 165 static BRect 166 stack_and_align_menu_fields(const BList& menuFields) 167 { 168 float widestLabel = 0.0; 169 int32 count = menuFields.CountItems(); 170 for (int32 i = 0; i < count; i++) { 171 BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i); 172 widestLabel = max_label_width(menuField, widestLabel); 173 } 174 175 // add some room (but only if there is text at all) 176 if (widestLabel > 0.0) 177 widestLabel += 5.0; 178 179 // make all controls the same width 180 float widestField = 0.0f; 181 for (int32 i = 0; i < count; i++) { 182 BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i); 183 if (widestField == 0.0f) { 184 widestField = menuField->StringWidth("9999 x 9999 WWW") 185 + widestLabel; 186 } 187 menuField->SetAlignment(B_ALIGN_RIGHT); 188 menuField->SetDivider(widestLabel); 189 menuField->ResizeToPreferred(); 190 widestField = max_c(menuField->Bounds().Width(), widestField); 191 } 192 193 // layout controls under each other, resize all to size 194 // of largest of them (they could still have different 195 // heights though) 196 BMenuField* topMenuField = (BMenuField*)menuFields.FirstItem(); 197 BPoint leftTop = topMenuField->Frame().LeftTop(); 198 BRect frame = topMenuField->Frame(); 199 200 for (int32 i = 0; i < count; i++) { 201 BMenuField* menuField = (BMenuField*)menuFields.ItemAtFast(i); 202 menuField->MoveTo(leftTop); 203 float height = menuField->Bounds().Height(); 204 menuField->ResizeTo(widestField, height); 205 frame = frame | menuField->Frame(); 206 leftTop.y += height + 5.0; 207 } 208 209 return frame; 210 } 211 212 213 // #pragma mark - 214 215 216 ScreenWindow::ScreenWindow(ScreenSettings *settings) 217 : BWindow(settings->WindowFrame(), "Screen", B_TITLED_WINDOW, 218 B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES), 219 fIsVesa(false), 220 fVesaApplied(false), 221 fScreenMode(this), 222 fTempScreenMode(this), 223 fModified(false) 224 { 225 BScreen screen(this); 226 227 accelerant_device_info info; 228 if (screen.GetDeviceInfo(&info) == B_OK 229 && !strcasecmp(info.chipset, "VESA")) 230 fIsVesa = true; 231 232 UpdateOriginal(); 233 fActive = fSelected = fOriginal; 234 235 BView *view = new BView(Bounds(), "ScreenView", B_FOLLOW_ALL, B_WILL_DRAW); 236 view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 237 AddChild(view); 238 239 fSettings = settings; 240 241 // we need the "Current Workspace" first to get its height 242 243 BPopUpMenu *popUpMenu = new BPopUpMenu("Current Workspace", true, true); 244 fAllWorkspacesItem = new BMenuItem("All Workspaces", 245 new BMessage(WORKSPACE_CHECK_MSG)); 246 popUpMenu->AddItem(fAllWorkspacesItem); 247 BMenuItem *item = new BMenuItem("Current Workspace", 248 new BMessage(WORKSPACE_CHECK_MSG)); 249 250 // TODO: since per workspace settings is unimplemented (Ticket #693) 251 // we force the menu to "All Workspaces" for now 252 //if (_IsVesa()) { 253 fAllWorkspacesItem->SetMarked(true); 254 item->SetEnabled(false); 255 //} else 256 // item->SetMarked(true); 257 258 popUpMenu->AddItem(item); 259 260 BMenuField* workspaceMenuField = new BMenuField(BRect(0, 0, 100, 15), 261 "WorkspaceMenu", NULL, popUpMenu, true); 262 workspaceMenuField->ResizeToPreferred(); 263 264 // box on the left with workspace count and monitor view 265 266 popUpMenu = new BPopUpMenu("", true, true); 267 fWorkspaceCountField = new BMenuField(BRect(0.0, 0.0, 50.0, 15.0), 268 "WorkspaceCountMenu", "Workspace count:", popUpMenu, true); 269 float labelWidth = fWorkspaceCountField->StringWidth( 270 fWorkspaceCountField->Label()) + 5.0; 271 fWorkspaceCountField->SetDivider(labelWidth); 272 273 fScreenBox = new BBox(BRect(0.0, 0.0, 100.0, 100.0), "left box"); 274 fScreenBox->AddChild(fWorkspaceCountField); 275 276 for (int32 count = 1; count <= 32; count++) { 277 BString workspaceCount; 278 workspaceCount << count; 279 280 BMessage *message = new BMessage(POP_WORKSPACE_CHANGED_MSG); 281 message->AddInt32("workspace count", count); 282 283 popUpMenu->AddItem(new BMenuItem(workspaceCount.String(), 284 message)); 285 } 286 287 item = popUpMenu->ItemAt(count_workspaces() - 1); 288 if (item != NULL) 289 item->SetMarked(true); 290 291 fMonitorView = new MonitorView(BRect(0.0, 0.0, 80.0, 80.0), "monitor", 292 screen.Frame().Width() + 1, screen.Frame().Height() + 1); 293 fScreenBox->AddChild(fMonitorView); 294 295 view->AddChild(fScreenBox); 296 297 // box on the right with screen resolution, etc. 298 299 fControlsBox = new BBox(BRect(0.0, 0.0, 100.0, 100.0)); 300 fControlsBox->SetLabel(workspaceMenuField); 301 302 fResolutionMenu = new BPopUpMenu("resolution", true, true); 303 304 uint16 previousWidth = 0, previousHeight = 0; 305 for (int32 i = 0; i < fScreenMode.CountModes(); i++) { 306 screen_mode mode = fScreenMode.ModeAt(i); 307 308 if (mode.width == previousWidth && mode.height == previousHeight) 309 continue; 310 311 previousWidth = mode.width; 312 previousHeight = mode.height; 313 314 BMessage *message = new BMessage(POP_RESOLUTION_MSG); 315 message->AddInt32("width", mode.width); 316 message->AddInt32("height", mode.height); 317 318 BString name; 319 name << mode.width << " x " << mode.height; 320 321 fResolutionMenu->AddItem(new BMenuItem(name.String(), message)); 322 } 323 324 BRect rect(0.0, 0.0, 200.0, 15.0); 325 // fResolutionField needs to be at the correct 326 // left-top offset, because all other menu fields 327 // will be layouted relative to it 328 fResolutionField = new BMenuField(rect.OffsetToCopy(10.0, 30.0), 329 "ResolutionMenu", "Resolution:", fResolutionMenu, false); 330 fControlsBox->AddChild(fResolutionField); 331 332 fColorsMenu = new BPopUpMenu("colors", true, true); 333 334 for (int32 i = 0; i < kColorSpaceCount; i++) { 335 BMessage *message = new BMessage(POP_COLORS_MSG); 336 message->AddInt32("bits_per_pixel", kColorSpaces[i].bits_per_pixel); 337 message->AddInt32("space", kColorSpaces[i].space); 338 339 fColorsMenu->AddItem(new BMenuItem(kColorSpaces[i].label, message)); 340 } 341 342 rect.OffsetTo(B_ORIGIN); 343 344 fColorsField = new BMenuField(rect, "ColorsMenu", "Colors:", fColorsMenu, 345 false); 346 fControlsBox->AddChild(fColorsField); 347 348 fRefreshMenu = new BPopUpMenu("refresh rate", true, true); 349 350 BMessage *message; 351 352 float min, max; 353 if (fScreenMode.GetRefreshLimits(fActive, min, max) && min == max) { 354 // TODO: investigate, doesn't work for VESA at least 355 356 // This is a special case for drivers that only support a single 357 // frequency, like the VESA driver 358 BString name; 359 name << min << " Hz"; 360 361 message = new BMessage(POP_REFRESH_MSG); 362 message->AddFloat("refresh", min); 363 364 fRefreshMenu->AddItem(item = new BMenuItem(name.String(), message)); 365 item->SetEnabled(false); 366 } else { 367 for (int32 i = 0; i < kRefreshRateCount; ++i) { 368 BString name; 369 name << kRefreshRates[i] << " Hz"; 370 371 message = new BMessage(POP_REFRESH_MSG); 372 message->AddFloat("refresh", kRefreshRates[i]); 373 374 fRefreshMenu->AddItem(new BMenuItem(name.String(), message)); 375 } 376 377 message = new BMessage(POP_OTHER_REFRESH_MSG); 378 379 fOtherRefresh = new BMenuItem("Other" B_UTF8_ELLIPSIS, message); 380 fRefreshMenu->AddItem(fOtherRefresh); 381 } 382 383 fRefreshField = new BMenuField(rect, "RefreshMenu", "Refresh Rate:", 384 fRefreshMenu, false); 385 if (_IsVesa()) 386 fRefreshField->Hide(); 387 fControlsBox->AddChild(fRefreshField); 388 389 view->AddChild(fControlsBox); 390 391 uint32 controlsFlags = 0; 392 393 // enlarged area for multi-monitor settings 394 { 395 bool dummy; 396 uint32 dummy32; 397 bool multiMonSupport; 398 bool useLaptopPanelSupport; 399 bool tvStandardSupport; 400 401 multiMonSupport = TestMultiMonSupport(&screen) == B_OK; 402 useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK; 403 tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK; 404 if (multiMonSupport) { 405 controlsFlags |= SHOW_COMBINE_FIELD; 406 controlsFlags |= SHOW_SWAP_FIELD; 407 } 408 if (useLaptopPanelSupport) 409 controlsFlags |= SHOW_LAPTOP_PANEL_FIELD; 410 411 // even if there is no support, we still create all controls 412 // to make sure we don't access NULL pointers later on 413 414 fCombineMenu = new BPopUpMenu("CombineDisplays", true, true); 415 416 for (int32 i = 0; i < kCombineModeCount; i++) { 417 message = new BMessage(POP_COMBINE_DISPLAYS_MSG); 418 message->AddInt32("mode", kCombineModes[i].mode); 419 420 fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name, 421 message)); 422 } 423 424 fCombineField = new BMenuField(rect, "CombineMenu", 425 "Combine Displays:", fCombineMenu, false); 426 fControlsBox->AddChild(fCombineField); 427 428 if (!multiMonSupport) 429 fCombineField->Hide(); 430 431 fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays", true, true); 432 433 // !order is important - we rely that boolean value == idx 434 message = new BMessage(POP_SWAP_DISPLAYS_MSG); 435 message->AddBool("swap", false); 436 fSwapDisplaysMenu->AddItem(new BMenuItem("no", message)); 437 438 message = new BMessage(POP_SWAP_DISPLAYS_MSG); 439 message->AddBool("swap", true); 440 fSwapDisplaysMenu->AddItem(new BMenuItem("yes", message)); 441 442 fSwapDisplaysField = new BMenuField(rect, "SwapMenu", "Swap Displays:", 443 fSwapDisplaysMenu, false); 444 445 fControlsBox->AddChild(fSwapDisplaysField); 446 if (!multiMonSupport) 447 fSwapDisplaysField->Hide(); 448 449 fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel", true, true); 450 451 // !order is important - we rely that boolean value == idx 452 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG); 453 message->AddBool("use", false); 454 fUseLaptopPanelMenu->AddItem(new BMenuItem("if needed", message)); 455 456 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG); 457 message->AddBool("use", true); 458 fUseLaptopPanelMenu->AddItem(new BMenuItem("always", message)); 459 460 fUseLaptopPanelField = new BMenuField(rect, "UseLaptopPanel", 461 "Use Laptop Panel:", fUseLaptopPanelMenu, false); 462 463 fControlsBox->AddChild(fUseLaptopPanelField); 464 if (!useLaptopPanelSupport) 465 fUseLaptopPanelField->Hide(); 466 467 fTVStandardMenu = new BPopUpMenu("TVStandard", true, true); 468 469 // arbitrary limit 470 uint32 i; 471 for (i = 0; i < 100; ++i) { 472 uint32 mode; 473 if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK) 474 break; 475 476 BString name = tv_standard_to_string(mode); 477 478 message = new BMessage(POP_TV_STANDARD_MSG); 479 message->AddInt32("tv_standard", mode); 480 481 fTVStandardMenu->AddItem(new BMenuItem(name.String(), message)); 482 } 483 484 fTVStandardField = new BMenuField(rect, "tv standard", "Video Format:", 485 fTVStandardMenu, false); 486 fTVStandardField->SetAlignment(B_ALIGN_RIGHT); 487 488 if (!tvStandardSupport || i == 0) { 489 fTVStandardField->Hide(); 490 } else { 491 controlsFlags |= SHOW_TV_STANDARD_FIELD; 492 } 493 494 fControlsBox->AddChild(fTVStandardField); 495 } 496 497 BRect buttonRect(0.0, 0.0, 30.0, 10.0); 498 fBackgroundsButton = new BButton(buttonRect, "BackgroundsButton", 499 "Set Background"B_UTF8_ELLIPSIS, 500 new BMessage(BUTTON_LAUNCH_BACKGROUNDS_MSG)); 501 fBackgroundsButton->SetFontSize(be_plain_font->Size() * 0.9); 502 fScreenBox->AddChild(fBackgroundsButton); 503 504 // TODO: we don't support getting the screen's preferred settings 505 /* fDefaultsButton = new BButton(buttonRect, "DefaultsButton", "Defaults", 506 new BMessage(BUTTON_DEFAULTS_MSG)); 507 view->AddChild(fDefaultsButton); */ 508 509 fRevertButton = new BButton(buttonRect, "RevertButton", "Revert", 510 new BMessage(BUTTON_REVERT_MSG)); 511 fRevertButton->SetEnabled(false); 512 view->AddChild(fRevertButton); 513 514 fApplyButton = new BButton(buttonRect, "ApplyButton", "Apply", 515 new BMessage(BUTTON_APPLY_MSG)); 516 fApplyButton->SetEnabled(false); 517 fControlsBox->AddChild(fApplyButton); 518 519 UpdateControls(); 520 521 LayoutControls(controlsFlags); 522 } 523 524 525 ScreenWindow::~ScreenWindow() 526 { 527 delete fSettings; 528 } 529 530 531 bool 532 ScreenWindow::QuitRequested() 533 { 534 fSettings->SetWindowFrame(Frame()); 535 if (fVesaApplied) { 536 status_t status = _WriteVesaModeFile(fSelected); 537 if (status < B_OK) { 538 BString warning = "Could not write VESA mode settings file:\n\t"; 539 warning << strerror(status); 540 (new BAlert("VesaAlert", warning.String(), "Okay", NULL, NULL, 541 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 542 543 } 544 } 545 546 be_app->PostMessage(B_QUIT_REQUESTED); 547 548 return BWindow::QuitRequested(); 549 } 550 551 552 /** update resolution list according to combine mode 553 * (some resolution may not be combinable due to memory restrictions) 554 */ 555 556 void 557 ScreenWindow::CheckResolutionMenu() 558 { 559 for (int32 i = 0; i < fResolutionMenu->CountItems(); i++) 560 fResolutionMenu->ItemAt(i)->SetEnabled(false); 561 562 for (int32 i = 0; i < fScreenMode.CountModes(); i++) { 563 screen_mode mode = fScreenMode.ModeAt(i); 564 if (mode.combine != fSelected.combine) 565 continue; 566 567 BString name; 568 name << mode.width << " x " << mode.height; 569 570 BMenuItem *item = fResolutionMenu->FindItem(name.String()); 571 if (item != NULL) 572 item->SetEnabled(true); 573 } 574 } 575 576 577 /** update color and refresh options according to current mode 578 * (a color space is made active if there is any mode with 579 * given resolution and this colour space; same applies for 580 * refresh rate, though "Other…" is always possible) 581 */ 582 583 void 584 ScreenWindow::CheckColorMenu() 585 { 586 for (int32 i = 0; i < kColorSpaceCount; i++) { 587 bool supported = false; 588 589 for (int32 j = 0; j < fScreenMode.CountModes(); j++) { 590 screen_mode mode = fScreenMode.ModeAt(j); 591 592 if (fSelected.width == mode.width 593 && fSelected.height == mode.height 594 && kColorSpaces[i].space == mode.space 595 && fSelected.combine == mode.combine) { 596 supported = true; 597 break; 598 } 599 } 600 601 BMenuItem* item = fColorsMenu->ItemAt(i); 602 if (item) 603 item->SetEnabled(supported); 604 } 605 } 606 607 608 /** Enable/disable refresh options according to current mode. */ 609 610 void 611 ScreenWindow::CheckRefreshMenu() 612 { 613 float min, max; 614 if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max) 615 return; 616 617 for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) { 618 BMenuItem* item = fRefreshMenu->ItemAt(i); 619 BMessage* message = item->Message(); 620 float refresh; 621 if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK) 622 item->SetEnabled(refresh >= min && refresh <= max); 623 } 624 } 625 626 627 /** Activate appropriate menu item according to selected refresh rate */ 628 629 void 630 ScreenWindow::UpdateRefreshControl() 631 { 632 BString string; 633 refresh_rate_to_string(fSelected.refresh, string); 634 635 BMenuItem* item = fRefreshMenu->FindItem(string.String()); 636 if (item) { 637 if (!item->IsMarked()) 638 item->SetMarked(true); 639 640 // "Other…" items only contains a refresh rate when active 641 fOtherRefresh->SetLabel("Other…"); 642 return; 643 } 644 645 // this is a non-standard refresh rate 646 647 fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh); 648 fOtherRefresh->SetMarked(true); 649 650 fRefreshMenu->Superitem()->SetLabel(string.String()); 651 652 string.Append("/Other" B_UTF8_ELLIPSIS); 653 fOtherRefresh->SetLabel(string.String()); 654 } 655 656 657 void 658 ScreenWindow::UpdateMonitorView() 659 { 660 BMessage updateMessage(UPDATE_DESKTOP_MSG); 661 updateMessage.AddInt32("width", fSelected.width); 662 updateMessage.AddInt32("height", fSelected.height); 663 664 PostMessage(&updateMessage, fMonitorView); 665 } 666 667 668 void 669 ScreenWindow::UpdateControls() 670 { 671 BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays); 672 if (item && !item->IsMarked()) 673 item->SetMarked(true); 674 675 item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel); 676 if (item && !item->IsMarked()) 677 item->SetMarked(true); 678 679 for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) { 680 item = fTVStandardMenu->ItemAt(i); 681 682 uint32 tvStandard; 683 item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard); 684 if (tvStandard == fSelected.tv_standard) { 685 if (!item->IsMarked()) 686 item->SetMarked(true); 687 break; 688 } 689 } 690 691 CheckResolutionMenu(); 692 CheckColorMenu(); 693 CheckRefreshMenu(); 694 695 BString string; 696 resolution_to_string(fSelected, string); 697 item = fResolutionMenu->FindItem(string.String()); 698 699 if (item != NULL) { 700 if (!item->IsMarked()) 701 item->SetMarked(true); 702 } else { 703 // this is bad luck - if mode has been set via screen references, 704 // this case cannot occur; there are three possible solutions: 705 // 1. add a new resolution to list 706 // - we had to remove it as soon as a "valid" one is selected 707 // - we don't know which frequencies/bit depths are supported 708 // - as long as we haven't the GMT formula to create 709 // parameters for any resolution given, we cannot 710 // really set current mode - it's just not in the list 711 // 2. choose nearest resolution 712 // - probably a good idea, but implies coding and testing 713 // 3. choose lowest resolution 714 // - do you really think we are so lazy? yes, we are 715 item = fResolutionMenu->ItemAt(0); 716 if (item) 717 item->SetMarked(true); 718 719 // okay - at least we set menu label to active resolution 720 fResolutionMenu->Superitem()->SetLabel(string.String()); 721 } 722 723 // mark active combine mode 724 for (int32 i = 0; i < kCombineModeCount; i++) { 725 if (kCombineModes[i].mode == fSelected.combine) { 726 item = fCombineMenu->ItemAt(i); 727 if (item && !item->IsMarked()) 728 item->SetMarked(true); 729 break; 730 } 731 } 732 733 item = fColorsMenu->ItemAt(0); 734 735 for (int32 i = kColorSpaceCount; i-- > 0;) { 736 if (kColorSpaces[i].space == fSelected.space) { 737 item = fColorsMenu->ItemAt(i); 738 break; 739 } 740 } 741 742 if (item && !item->IsMarked()) 743 item->SetMarked(true); 744 745 string.Truncate(0); 746 string << fSelected.BitsPerPixel() << " Bits/Pixel"; 747 if (string != fColorsMenu->Superitem()->Label()) 748 fColorsMenu->Superitem()->SetLabel(string.String()); 749 750 UpdateMonitorView(); 751 UpdateRefreshControl(); 752 753 CheckApplyEnabled(); 754 } 755 756 757 /*! Reflect active mode in chosen settings */ 758 void 759 ScreenWindow::UpdateActiveMode() 760 { 761 // Usually, this function gets called after a mode 762 // has been set manually; still, as the graphics driver 763 // is free to fiddle with mode passed, we better ask 764 // what kind of mode we actually got 765 if (!fVesaApplied) 766 fScreenMode.Get(fActive); 767 fSelected = fActive; 768 769 UpdateControls(); 770 } 771 772 773 void 774 ScreenWindow::ScreenChanged(BRect frame, color_space mode) 775 { 776 // move window on screen, if necessary 777 if (frame.right <= Frame().right 778 && frame.bottom <= Frame().bottom) { 779 MoveTo((frame.Width() - Frame().Width()) / 2, 780 (frame.Height() - Frame().Height()) / 2); 781 } 782 } 783 784 785 void 786 ScreenWindow::WorkspaceActivated(int32 workspace, bool state) 787 { 788 if (!_IsVesa()) { 789 fScreenMode.GetOriginalMode(fOriginal, workspace); 790 UpdateActiveMode(); 791 } 792 793 BMessage message(UPDATE_DESKTOP_COLOR_MSG); 794 PostMessage(&message, fMonitorView); 795 } 796 797 798 void 799 ScreenWindow::MessageReceived(BMessage* message) 800 { 801 switch (message->what) { 802 case WORKSPACE_CHECK_MSG: 803 CheckApplyEnabled(); 804 break; 805 806 case POP_WORKSPACE_CHANGED_MSG: 807 { 808 // update checkpoint state 809 int32 index; 810 if (message->FindInt32("index", &index) == B_OK) { 811 set_workspace_count(index + 1); 812 CheckApplyEnabled(); 813 } 814 break; 815 } 816 817 case POP_RESOLUTION_MSG: 818 { 819 message->FindInt32("width", &fSelected.width); 820 message->FindInt32("height", &fSelected.height); 821 822 CheckColorMenu(); 823 CheckRefreshMenu(); 824 825 UpdateMonitorView(); 826 UpdateRefreshControl(); 827 828 CheckApplyEnabled(); 829 break; 830 } 831 832 case POP_COLORS_MSG: 833 { 834 message->FindInt32("space", (int32 *)&fSelected.space); 835 836 BString string; 837 string << fSelected.BitsPerPixel() << " Bits/Pixel"; 838 fColorsMenu->Superitem()->SetLabel(string.String()); 839 840 CheckApplyEnabled(); 841 break; 842 } 843 844 case POP_REFRESH_MSG: 845 { 846 message->FindFloat("refresh", &fSelected.refresh); 847 fOtherRefresh->SetLabel("Other" B_UTF8_ELLIPSIS); 848 // revert "Other…" label - it might have had a refresh rate prefix 849 850 CheckApplyEnabled(); 851 break; 852 } 853 854 case POP_OTHER_REFRESH_MSG: 855 { 856 // make sure menu shows something useful 857 UpdateRefreshControl(); 858 859 float min = 0, max = 999; 860 fScreenMode.GetRefreshLimits(fSelected, min, max); 861 if (min < gMinRefresh) 862 min = gMinRefresh; 863 if (max > gMaxRefresh) 864 max = gMaxRefresh; 865 866 RefreshWindow *fRefreshWindow = new RefreshWindow( 867 fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh, min, max); 868 fRefreshWindow->Show(); 869 break; 870 } 871 872 case SET_CUSTOM_REFRESH_MSG: 873 { 874 // user pressed "done" in "Other…" refresh dialog; 875 // select the refresh rate chosen 876 message->FindFloat("refresh", &fSelected.refresh); 877 878 UpdateRefreshControl(); 879 CheckApplyEnabled(); 880 break; 881 } 882 883 case POP_COMBINE_DISPLAYS_MSG: 884 { 885 // new combine mode has bee chosen 886 int32 mode; 887 if (message->FindInt32("mode", &mode) == B_OK) 888 fSelected.combine = (combine_mode)mode; 889 890 CheckResolutionMenu(); 891 CheckApplyEnabled(); 892 break; 893 } 894 895 case POP_SWAP_DISPLAYS_MSG: 896 message->FindBool("swap", &fSelected.swap_displays); 897 CheckApplyEnabled(); 898 break; 899 900 case POP_USE_LAPTOP_PANEL_MSG: 901 message->FindBool("use", &fSelected.use_laptop_panel); 902 CheckApplyEnabled(); 903 break; 904 905 case POP_TV_STANDARD_MSG: 906 message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard); 907 CheckApplyEnabled(); 908 break; 909 910 case BUTTON_LAUNCH_BACKGROUNDS_MSG: 911 if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) { 912 app_info info; 913 be_roster->GetAppInfo(kBackgroundsSignature, &info); 914 be_roster->ActivateApp(info.team); 915 } 916 break; 917 918 case BUTTON_DEFAULTS_MSG: 919 { 920 // TODO: get preferred settings of screen 921 fSelected.width = 640; 922 fSelected.height = 480; 923 fSelected.space = B_CMAP8; 924 fSelected.refresh = 60.0; 925 fSelected.combine = kCombineDisable; 926 fSelected.swap_displays = false; 927 fSelected.use_laptop_panel = false; 928 fSelected.tv_standard = 0; 929 930 BMenuItem *item; 931 item = fWorkspaceCountField->Menu()->ItemAt(3); 932 if (item != NULL) 933 item->SetMarked(true); 934 935 UpdateControls(); 936 break; 937 } 938 939 case BUTTON_UNDO_MSG: 940 fTempScreenMode.Revert(); 941 UpdateActiveMode(); 942 break; 943 944 case BUTTON_REVERT_MSG: 945 { 946 fModified = false; 947 fVesaApplied = false; 948 BMenuItem *item; 949 item = fWorkspaceCountField->Menu()->ItemAt(fOriginalWorkspaceCount - 1); 950 if (item != NULL) 951 item->SetMarked(true); 952 953 // ScreenMode::Revert() assumes that we first set the correct number 954 // of workspaces 955 956 if (_IsVesa()) { 957 set_workspace_count(fOriginalWorkspaceCount); 958 fActive = fOriginal; 959 fSelected = fOriginal; 960 UpdateControls(); 961 } else { 962 set_workspace_count(fOriginalWorkspaceCount); 963 fScreenMode.Revert(); 964 UpdateActiveMode(); 965 } 966 break; 967 } 968 969 case BUTTON_APPLY_MSG: 970 Apply(); 971 break; 972 973 case MAKE_INITIAL_MSG: 974 // user pressed "keep" in confirmation dialog 975 fModified = true; 976 UpdateActiveMode(); 977 break; 978 979 default: 980 BWindow::MessageReceived(message); 981 break; 982 } 983 } 984 985 986 status_t 987 ScreenWindow::_WriteVesaModeFile(const screen_mode& mode) const 988 { 989 BPath path; 990 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 991 if (status < B_OK) 992 return status; 993 994 path.Append("kernel/drivers"); 995 status = create_directory(path.Path(), 0755); 996 if (status < B_OK) 997 return status; 998 999 path.Append("vesa"); 1000 BFile file; 1001 status = file.SetTo(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE); 1002 if (status < B_OK) 1003 return status; 1004 1005 char buffer[256]; 1006 snprintf(buffer, sizeof(buffer), "mode %ld %ld %ld\n", 1007 mode.width, mode.height, mode.BitsPerPixel()); 1008 1009 ssize_t bytesWritten = file.Write(buffer, strlen(buffer)); 1010 if (bytesWritten < B_OK) 1011 return bytesWritten; 1012 1013 return B_OK; 1014 } 1015 1016 1017 status_t 1018 ScreenWindow::_ReadVesaModeFile(screen_mode& mode) const 1019 { 1020 BPath path; 1021 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 1022 if (status < B_OK) 1023 return status; 1024 1025 path.Append("kernel/drivers/vesa"); 1026 BFile file; 1027 status = file.SetTo(path.Path(), B_READ_ONLY); 1028 if (status < B_OK) 1029 return status; 1030 1031 char buffer[256]; 1032 1033 ssize_t bytesRead = file.Read(buffer, sizeof(buffer) - 1); 1034 if (bytesRead < B_OK) { 1035 return bytesRead; 1036 } else { 1037 buffer[bytesRead] = '\0'; 1038 } 1039 1040 char ignore[256]; 1041 // if the file is malformed, sscanf shouldn't crash 1042 // on reading a big string since we don't even read 1043 // as much from the file 1044 uint32 bitDepth = 0; 1045 1046 if (sscanf(buffer, "%s %ld %ld %ld", ignore, 1047 &mode.width, &mode.height, &bitDepth) == 4) { 1048 1049 //TODO: check for valid width and height values 1050 1051 switch (bitDepth) { 1052 case 32: 1053 mode.space = B_RGB32; 1054 break; 1055 case 24: 1056 mode.space = B_RGB24; 1057 break; 1058 case 16: 1059 mode.space = B_RGB16; 1060 break; 1061 case 15: 1062 mode.space = B_RGB15; 1063 break; 1064 case 8: 1065 mode.space = B_CMAP8; 1066 break; 1067 default: 1068 // invalid value, we force it to B_RGB16 just in case 1069 mode.space = B_RGB16; 1070 return B_ERROR; 1071 } 1072 return B_OK; 1073 } else { 1074 return B_ERROR; 1075 } 1076 } 1077 1078 1079 void 1080 ScreenWindow::CheckApplyEnabled() 1081 { 1082 fApplyButton->SetEnabled(fSelected != fActive); 1083 fRevertButton->SetEnabled(count_workspaces() != fOriginalWorkspaceCount || fSelected != fOriginal); 1084 } 1085 1086 1087 void 1088 ScreenWindow::UpdateOriginal() 1089 { 1090 fOriginalWorkspaceCount = count_workspaces(); 1091 fScreenMode.Get(fOriginal); 1092 1093 // If we are in vesa we overwrite fOriginal's resolution and bitdepth 1094 // with those found the vesa settings file. (if the file exists) 1095 if (_IsVesa()) 1096 _ReadVesaModeFile(fOriginal); 1097 1098 fScreenMode.UpdateOriginalModes(); 1099 } 1100 1101 1102 void 1103 ScreenWindow::Apply() 1104 { 1105 if (_IsVesa()) { 1106 (new BAlert("VesaAlert", 1107 "Haiku is using your video card in safe mode (VESA)." 1108 " Your settings will be applied on next startup.\n", "Okay", NULL, NULL, B_WIDTH_AS_USUAL, 1109 B_INFO_ALERT))->Go(NULL); 1110 1111 fVesaApplied = true; 1112 fActive = fSelected; 1113 UpdateControls(); 1114 return; 1115 } 1116 1117 // make checkpoint, so we can undo these changes 1118 fTempScreenMode.UpdateOriginalModes(); 1119 status_t status = fScreenMode.Set(fSelected); 1120 if (status == B_OK) { 1121 // use the mode that has eventually been set and 1122 // thus we know to be working; it can differ from 1123 // the mode selected by user due to hardware limitation 1124 display_mode newMode; 1125 BScreen screen(this); 1126 screen.GetMode(&newMode); 1127 1128 if (fAllWorkspacesItem->IsMarked()) { 1129 int32 originatingWorkspace = current_workspace(); 1130 for (int32 i = 0; i < count_workspaces(); i++) { 1131 if (i != originatingWorkspace) 1132 screen.SetMode(i, &newMode, true); 1133 } 1134 } 1135 1136 fActive = fSelected; 1137 1138 // TODO: only show alert when this is an unknown mode 1139 BWindow* window = new AlertWindow(this); 1140 window->Show(); 1141 } else { 1142 char message[256]; 1143 snprintf(message, sizeof(message), 1144 "The screen mode could not be set:\n\t%s\n", screen_errors(status)); 1145 BAlert* alert = new BAlert("Screen Alert", message, "Okay", NULL, NULL, 1146 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1147 alert->Go(); 1148 } 1149 } 1150 1151 1152 void 1153 ScreenWindow::LayoutControls(uint32 flags) 1154 { 1155 // layout the screen box and its controls 1156 fWorkspaceCountField->ResizeToPreferred(); 1157 1158 float monitorViewHeight = fMonitorView->Bounds().Height(); 1159 float workspaceFieldHeight = fWorkspaceCountField->Bounds().Height(); 1160 float backgroundsButtonHeight = fBackgroundsButton->Bounds().Height(); 1161 1162 float screenBoxWidth = fWorkspaceCountField->Bounds().Width() + 20.0; 1163 float screenBoxHeight = monitorViewHeight + 5.0 1164 + workspaceFieldHeight + 5.0 1165 + backgroundsButtonHeight 1166 + 20.0; 1167 1168 #ifdef __HAIKU__ 1169 fScreenBox->MoveTo(10.0, 10.0 + fControlsBox->TopBorderOffset()); 1170 #else 1171 fScreenBox->MoveTo(10.0, 10.0 + 3); 1172 #endif 1173 fScreenBox->ResizeTo(screenBoxWidth, screenBoxHeight); 1174 1175 float leftOffset = 10.0; 1176 float topOffset = 10.0; 1177 fMonitorView->MoveTo(leftOffset, topOffset); 1178 fMonitorView->ResizeTo(screenBoxWidth - 20.0, monitorViewHeight); 1179 fMonitorView->SetResizingMode(B_FOLLOW_ALL); 1180 topOffset += monitorViewHeight + 5.0; 1181 1182 fWorkspaceCountField->MoveTo(leftOffset, topOffset); 1183 fWorkspaceCountField->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 1184 topOffset += workspaceFieldHeight + 5.0; 1185 1186 fBackgroundsButton->MoveTo(leftOffset, topOffset); 1187 fBackgroundsButton->ResizeTo(screenBoxWidth - 20.0, backgroundsButtonHeight); 1188 fBackgroundsButton->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 1189 1190 fControlsBox->MoveTo(fScreenBox->Frame().right + 10.0, 10.0); 1191 1192 // layout the right side 1193 fApplyButton->ResizeToPreferred(); 1194 BRect controlsRect = LayoutMenuFields(flags); 1195 controlsRect.InsetBy(-10.0, -10.0); 1196 controlsRect.bottom += 8 + fApplyButton->Bounds().Height(); 1197 // adjust size of controls box and move aligned buttons along 1198 float xDiff = controlsRect.right - fControlsBox->Bounds().right; 1199 float yDiff = controlsRect.bottom - fControlsBox->Bounds().bottom; 1200 if (yDiff < 0.0) { 1201 // don't shrink vertically 1202 yDiff = 0.0; 1203 } 1204 1205 fControlsBox->ResizeBy(xDiff, yDiff); 1206 1207 // align bottom of boxen 1208 float boxBottomDiff = fControlsBox->Frame().bottom - fScreenBox->Frame().bottom; 1209 if (boxBottomDiff > 0) 1210 fScreenBox->ResizeBy(0.0, boxBottomDiff); 1211 else 1212 fControlsBox->ResizeBy(0.0, -boxBottomDiff); 1213 1214 BRect boxFrame = fScreenBox->Frame() | fControlsBox->Frame(); 1215 1216 // layout rest of buttons 1217 // TODO: we don't support getting the screen's preferred settings 1218 // fDefaultsButton->ResizeToPreferred(); 1219 // fDefaultsButton->MoveTo(boxFrame.left, boxFrame.bottom + 8); 1220 1221 fRevertButton->ResizeToPreferred(); 1222 fRevertButton->MoveTo(boxFrame.left, boxFrame.bottom + 8); 1223 // fRevertButton->MoveTo(fDefaultsButton->Frame().right + 10, 1224 // fDefaultsButton->Frame().top); 1225 1226 // Apply button was already resized above 1227 float resolutionFieldRight = fResolutionField->Frame().right; 1228 fApplyButton->MoveTo(resolutionFieldRight - fApplyButton->Bounds().Width(), 1229 fControlsBox->Bounds().bottom - fApplyButton->Bounds().Height() - 10); 1230 1231 ResizeTo(boxFrame.right + 10, fRevertButton->Frame().bottom + 10); 1232 } 1233 1234 1235 BRect 1236 ScreenWindow::LayoutMenuFields(uint32 flags, bool sideBySide) 1237 { 1238 BList menuFields; 1239 menuFields.AddItem((void*)fResolutionField); 1240 menuFields.AddItem((void*)fColorsField); 1241 menuFields.AddItem((void*)fRefreshField); 1242 1243 BRect frame; 1244 1245 if (sideBySide) 1246 frame = stack_and_align_menu_fields(menuFields); 1247 1248 if (sideBySide) 1249 menuFields.MakeEmpty(); 1250 1251 if (flags & SHOW_COMBINE_FIELD) 1252 menuFields.AddItem((void*)fCombineField); 1253 if (flags & SHOW_SWAP_FIELD) 1254 menuFields.AddItem((void*)fSwapDisplaysField); 1255 if (flags & SHOW_LAPTOP_PANEL_FIELD) 1256 menuFields.AddItem((void*)fUseLaptopPanelField); 1257 if (flags & SHOW_TV_STANDARD_FIELD) 1258 menuFields.AddItem((void*)fTVStandardField); 1259 1260 if (sideBySide) { 1261 if (menuFields.CountItems() > 0) { 1262 ((BMenuField*)menuFields.FirstItem())->MoveTo(frame.right + 8.0, frame.top); 1263 frame = frame | stack_and_align_menu_fields(menuFields); 1264 } 1265 } else { 1266 frame = stack_and_align_menu_fields(menuFields); 1267 } 1268 1269 return frame; 1270 } 1271 1272