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 // This is a special case for drivers that only support a single 355 // frequency, like the VESA driver 356 BString name; 357 name << min << " Hz"; 358 359 message = new BMessage(POP_REFRESH_MSG); 360 message->AddFloat("refresh", min); 361 362 fRefreshMenu->AddItem(item = new BMenuItem(name.String(), message)); 363 item->SetEnabled(false); 364 } else { 365 for (int32 i = 0; i < kRefreshRateCount; ++i) { 366 BString name; 367 name << kRefreshRates[i] << " Hz"; 368 369 message = new BMessage(POP_REFRESH_MSG); 370 message->AddFloat("refresh", kRefreshRates[i]); 371 372 fRefreshMenu->AddItem(new BMenuItem(name.String(), message)); 373 } 374 375 message = new BMessage(POP_OTHER_REFRESH_MSG); 376 377 fOtherRefresh = new BMenuItem("Other" B_UTF8_ELLIPSIS, message); 378 fRefreshMenu->AddItem(fOtherRefresh); 379 } 380 381 fRefreshField = new BMenuField(rect, "RefreshMenu", "Refresh Rate:", 382 fRefreshMenu, false); 383 if (_IsVesa()) 384 fRefreshField->Hide(); 385 fControlsBox->AddChild(fRefreshField); 386 387 view->AddChild(fControlsBox); 388 389 uint32 controlsFlags = 0; 390 391 // enlarged area for multi-monitor settings 392 { 393 bool dummy; 394 uint32 dummy32; 395 bool multiMonSupport; 396 bool useLaptopPanelSupport; 397 bool tvStandardSupport; 398 399 multiMonSupport = TestMultiMonSupport(&screen) == B_OK; 400 useLaptopPanelSupport = GetUseLaptopPanel(&screen, &dummy) == B_OK; 401 tvStandardSupport = GetTVStandard(&screen, &dummy32) == B_OK; 402 if (multiMonSupport) { 403 controlsFlags |= SHOW_COMBINE_FIELD; 404 controlsFlags |= SHOW_SWAP_FIELD; 405 } 406 if (useLaptopPanelSupport) 407 controlsFlags |= SHOW_LAPTOP_PANEL_FIELD; 408 409 // even if there is no support, we still create all controls 410 // to make sure we don't access NULL pointers later on 411 412 fCombineMenu = new BPopUpMenu("CombineDisplays", true, true); 413 414 for (int32 i = 0; i < kCombineModeCount; i++) { 415 message = new BMessage(POP_COMBINE_DISPLAYS_MSG); 416 message->AddInt32("mode", kCombineModes[i].mode); 417 418 fCombineMenu->AddItem(new BMenuItem(kCombineModes[i].name, 419 message)); 420 } 421 422 fCombineField = new BMenuField(rect, "CombineMenu", 423 "Combine Displays:", fCombineMenu, false); 424 fControlsBox->AddChild(fCombineField); 425 426 if (!multiMonSupport) 427 fCombineField->Hide(); 428 429 fSwapDisplaysMenu = new BPopUpMenu("SwapDisplays", true, true); 430 431 // !order is important - we rely that boolean value == idx 432 message = new BMessage(POP_SWAP_DISPLAYS_MSG); 433 message->AddBool("swap", false); 434 fSwapDisplaysMenu->AddItem(new BMenuItem("no", message)); 435 436 message = new BMessage(POP_SWAP_DISPLAYS_MSG); 437 message->AddBool("swap", true); 438 fSwapDisplaysMenu->AddItem(new BMenuItem("yes", message)); 439 440 fSwapDisplaysField = new BMenuField(rect, "SwapMenu", "Swap Displays:", 441 fSwapDisplaysMenu, false); 442 443 fControlsBox->AddChild(fSwapDisplaysField); 444 if (!multiMonSupport) 445 fSwapDisplaysField->Hide(); 446 447 fUseLaptopPanelMenu = new BPopUpMenu("UseLaptopPanel", true, true); 448 449 // !order is important - we rely that boolean value == idx 450 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG); 451 message->AddBool("use", false); 452 fUseLaptopPanelMenu->AddItem(new BMenuItem("if needed", message)); 453 454 message = new BMessage(POP_USE_LAPTOP_PANEL_MSG); 455 message->AddBool("use", true); 456 fUseLaptopPanelMenu->AddItem(new BMenuItem("always", message)); 457 458 fUseLaptopPanelField = new BMenuField(rect, "UseLaptopPanel", 459 "Use Laptop Panel:", fUseLaptopPanelMenu, false); 460 461 fControlsBox->AddChild(fUseLaptopPanelField); 462 if (!useLaptopPanelSupport) 463 fUseLaptopPanelField->Hide(); 464 465 fTVStandardMenu = new BPopUpMenu("TVStandard", true, true); 466 467 // arbitrary limit 468 uint32 i; 469 for (i = 0; i < 100; ++i) { 470 uint32 mode; 471 if (GetNthSupportedTVStandard(&screen, i, &mode) != B_OK) 472 break; 473 474 BString name = tv_standard_to_string(mode); 475 476 message = new BMessage(POP_TV_STANDARD_MSG); 477 message->AddInt32("tv_standard", mode); 478 479 fTVStandardMenu->AddItem(new BMenuItem(name.String(), message)); 480 } 481 482 fTVStandardField = new BMenuField(rect, "tv standard", "Video Format:", 483 fTVStandardMenu, false); 484 fTVStandardField->SetAlignment(B_ALIGN_RIGHT); 485 486 if (!tvStandardSupport || i == 0) { 487 fTVStandardField->Hide(); 488 } else { 489 controlsFlags |= SHOW_TV_STANDARD_FIELD; 490 } 491 492 fControlsBox->AddChild(fTVStandardField); 493 } 494 495 BRect buttonRect(0.0, 0.0, 30.0, 10.0); 496 fBackgroundsButton = new BButton(buttonRect, "BackgroundsButton", 497 "Set Background"B_UTF8_ELLIPSIS, 498 new BMessage(BUTTON_LAUNCH_BACKGROUNDS_MSG)); 499 fBackgroundsButton->SetFontSize(be_plain_font->Size() * 0.9); 500 fScreenBox->AddChild(fBackgroundsButton); 501 502 // TODO: we don't support getting the screen's preferred settings 503 /* fDefaultsButton = new BButton(buttonRect, "DefaultsButton", "Defaults", 504 new BMessage(BUTTON_DEFAULTS_MSG)); 505 view->AddChild(fDefaultsButton); */ 506 507 fRevertButton = new BButton(buttonRect, "RevertButton", "Revert", 508 new BMessage(BUTTON_REVERT_MSG)); 509 fRevertButton->SetEnabled(false); 510 view->AddChild(fRevertButton); 511 512 fApplyButton = new BButton(buttonRect, "ApplyButton", "Apply", 513 new BMessage(BUTTON_APPLY_MSG)); 514 fApplyButton->SetEnabled(false); 515 fControlsBox->AddChild(fApplyButton); 516 517 _UpdateControls(); 518 519 _LayoutControls(controlsFlags); 520 } 521 522 523 ScreenWindow::~ScreenWindow() 524 { 525 delete fSettings; 526 } 527 528 529 bool 530 ScreenWindow::QuitRequested() 531 { 532 fSettings->SetWindowFrame(Frame()); 533 if (fVesaApplied) { 534 status_t status = _WriteVesaModeFile(fSelected); 535 if (status < B_OK) { 536 BString warning = "Could not write VESA mode settings file:\n\t"; 537 warning << strerror(status); 538 (new BAlert("VesaAlert", warning.String(), "Okay", NULL, NULL, 539 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 540 541 } 542 } 543 544 be_app->PostMessage(B_QUIT_REQUESTED); 545 546 return BWindow::QuitRequested(); 547 } 548 549 550 /*! Update resolution list according to combine mode 551 (some resolution may not be combinable due to memory restrictions) 552 */ 553 void 554 ScreenWindow::_CheckResolutionMenu() 555 { 556 for (int32 i = 0; i < fResolutionMenu->CountItems(); i++) 557 fResolutionMenu->ItemAt(i)->SetEnabled(false); 558 559 for (int32 i = 0; i < fScreenMode.CountModes(); i++) { 560 screen_mode mode = fScreenMode.ModeAt(i); 561 if (mode.combine != fSelected.combine) 562 continue; 563 564 BString name; 565 name << mode.width << " x " << mode.height; 566 567 BMenuItem *item = fResolutionMenu->FindItem(name.String()); 568 if (item != NULL) 569 item->SetEnabled(true); 570 } 571 } 572 573 574 /*! Update color and refresh options according to current mode 575 (a color space is made active if there is any mode with 576 given resolution and this colour space; same applies for 577 refresh rate, though "Other…" is always possible) 578 */ 579 void 580 ScreenWindow::_CheckColorMenu() 581 { 582 for (int32 i = 0; i < kColorSpaceCount; i++) { 583 bool supported = false; 584 585 for (int32 j = 0; j < fScreenMode.CountModes(); j++) { 586 screen_mode mode = fScreenMode.ModeAt(j); 587 588 if (fSelected.width == mode.width 589 && fSelected.height == mode.height 590 && (kColorSpaces[i].space == mode.space 591 // advertize 24 bit mode as 32 bit to avoid confusion 592 || (kColorSpaces[i].space == B_RGB32 593 && mode.space == B_RGB24)) 594 && fSelected.combine == mode.combine) { 595 supported = true; 596 break; 597 } 598 } 599 600 BMenuItem* item = fColorsMenu->ItemAt(i); 601 if (item) 602 item->SetEnabled(supported); 603 } 604 } 605 606 607 /*! Enable/disable refresh options according to current mode. */ 608 void 609 ScreenWindow::_CheckRefreshMenu() 610 { 611 float min, max; 612 if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max) 613 return; 614 615 for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) { 616 BMenuItem* item = fRefreshMenu->ItemAt(i); 617 BMessage* message = item->Message(); 618 float refresh; 619 if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK) 620 item->SetEnabled(refresh >= min && refresh <= max); 621 } 622 } 623 624 625 /*! Activate appropriate menu item according to selected refresh rate */ 626 void 627 ScreenWindow::_UpdateRefreshControl() 628 { 629 BString string; 630 refresh_rate_to_string(fSelected.refresh, string); 631 632 BMenuItem* item = fRefreshMenu->FindItem(string.String()); 633 if (item) { 634 if (!item->IsMarked()) 635 item->SetMarked(true); 636 637 // "Other…" items only contains a refresh rate when active 638 fOtherRefresh->SetLabel("Other" B_UTF8_ELLIPSIS); 639 return; 640 } 641 642 // this is a non-standard refresh rate 643 644 fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh); 645 fOtherRefresh->SetMarked(true); 646 647 fRefreshMenu->Superitem()->SetLabel(string.String()); 648 649 string.Append("/Other" B_UTF8_ELLIPSIS); 650 fOtherRefresh->SetLabel(string.String()); 651 } 652 653 654 void 655 ScreenWindow::_UpdateMonitorView() 656 { 657 BMessage updateMessage(UPDATE_DESKTOP_MSG); 658 updateMessage.AddInt32("width", fSelected.width); 659 updateMessage.AddInt32("height", fSelected.height); 660 661 PostMessage(&updateMessage, fMonitorView); 662 } 663 664 665 void 666 ScreenWindow::_UpdateControls() 667 { 668 BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays); 669 if (item && !item->IsMarked()) 670 item->SetMarked(true); 671 672 item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel); 673 if (item && !item->IsMarked()) 674 item->SetMarked(true); 675 676 for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) { 677 item = fTVStandardMenu->ItemAt(i); 678 679 uint32 tvStandard; 680 item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard); 681 if (tvStandard == fSelected.tv_standard) { 682 if (!item->IsMarked()) 683 item->SetMarked(true); 684 break; 685 } 686 } 687 688 _CheckResolutionMenu(); 689 _CheckColorMenu(); 690 _CheckRefreshMenu(); 691 692 BString string; 693 resolution_to_string(fSelected, string); 694 item = fResolutionMenu->FindItem(string.String()); 695 696 if (item != NULL) { 697 if (!item->IsMarked()) 698 item->SetMarked(true); 699 } else { 700 // this is bad luck - if mode has been set via screen references, 701 // this case cannot occur; there are three possible solutions: 702 // 1. add a new resolution to list 703 // - we had to remove it as soon as a "valid" one is selected 704 // - we don't know which frequencies/bit depths are supported 705 // - as long as we haven't the GMT formula to create 706 // parameters for any resolution given, we cannot 707 // really set current mode - it's just not in the list 708 // 2. choose nearest resolution 709 // - probably a good idea, but implies coding and testing 710 // 3. choose lowest resolution 711 // - do you really think we are so lazy? yes, we are 712 item = fResolutionMenu->ItemAt(0); 713 if (item) 714 item->SetMarked(true); 715 716 // okay - at least we set menu label to active resolution 717 fResolutionMenu->Superitem()->SetLabel(string.String()); 718 } 719 720 // mark active combine mode 721 for (int32 i = 0; i < kCombineModeCount; i++) { 722 if (kCombineModes[i].mode == fSelected.combine) { 723 item = fCombineMenu->ItemAt(i); 724 if (item && !item->IsMarked()) 725 item->SetMarked(true); 726 break; 727 } 728 } 729 730 item = fColorsMenu->ItemAt(0); 731 732 for (int32 i = kColorSpaceCount; i-- > 0;) { 733 if (kColorSpaces[i].space == fSelected.space 734 || (kColorSpaces[i].space == B_RGB32 735 && fSelected.space == B_RGB24)) { 736 item = fColorsMenu->ItemAt(i); 737 break; 738 } 739 } 740 741 if (item && !item->IsMarked()) 742 item->SetMarked(true); 743 744 string.Truncate(0); 745 uint32 bitsPerPixel = fSelected.BitsPerPixel(); 746 // advertize 24 bit mode as 32 bit to avoid confusion 747 if (bitsPerPixel == 24) 748 bitsPerPixel = 32; 749 750 string << bitsPerPixel << " Bits/Pixel"; 751 if (string != fColorsMenu->Superitem()->Label()) 752 fColorsMenu->Superitem()->SetLabel(string.String()); 753 754 _UpdateMonitorView(); 755 _UpdateRefreshControl(); 756 757 _CheckApplyEnabled(); 758 } 759 760 761 /*! Reflect active mode in chosen settings */ 762 void 763 ScreenWindow::_UpdateActiveMode() 764 { 765 // Usually, this function gets called after a mode 766 // has been set manually; still, as the graphics driver 767 // is free to fiddle with mode passed, we better ask 768 // what kind of mode we actually got 769 if (!fVesaApplied) 770 fScreenMode.Get(fActive); 771 fSelected = fActive; 772 773 _UpdateControls(); 774 } 775 776 777 void 778 ScreenWindow::ScreenChanged(BRect frame, color_space mode) 779 { 780 // move window on screen, if necessary 781 if (frame.right <= Frame().right 782 && frame.bottom <= Frame().bottom) { 783 MoveTo((frame.Width() - Frame().Width()) / 2, 784 (frame.Height() - Frame().Height()) / 2); 785 } 786 } 787 788 789 void 790 ScreenWindow::WorkspaceActivated(int32 workspace, bool state) 791 { 792 if (!_IsVesa()) { 793 fScreenMode.GetOriginalMode(fOriginal, workspace); 794 _UpdateActiveMode(); 795 } 796 797 BMessage message(UPDATE_DESKTOP_COLOR_MSG); 798 PostMessage(&message, fMonitorView); 799 } 800 801 802 void 803 ScreenWindow::MessageReceived(BMessage* message) 804 { 805 switch (message->what) { 806 case WORKSPACE_CHECK_MSG: 807 _CheckApplyEnabled(); 808 break; 809 810 case POP_WORKSPACE_CHANGED_MSG: 811 { 812 // update checkpoint state 813 int32 index; 814 if (message->FindInt32("index", &index) == B_OK) { 815 set_workspace_count(index + 1); 816 _CheckApplyEnabled(); 817 } 818 break; 819 } 820 821 case POP_RESOLUTION_MSG: 822 { 823 message->FindInt32("width", &fSelected.width); 824 message->FindInt32("height", &fSelected.height); 825 826 _CheckColorMenu(); 827 _CheckRefreshMenu(); 828 829 _UpdateMonitorView(); 830 _UpdateRefreshControl(); 831 832 _CheckApplyEnabled(); 833 break; 834 } 835 836 case POP_COLORS_MSG: 837 { 838 message->FindInt32("space", (int32 *)&fSelected.space); 839 840 BString string; 841 string << fSelected.BitsPerPixel() << " Bits/Pixel"; 842 fColorsMenu->Superitem()->SetLabel(string.String()); 843 844 _CheckApplyEnabled(); 845 break; 846 } 847 848 case POP_REFRESH_MSG: 849 { 850 message->FindFloat("refresh", &fSelected.refresh); 851 fOtherRefresh->SetLabel("Other" B_UTF8_ELLIPSIS); 852 // revert "Other…" label - it might have had a refresh rate prefix 853 854 _CheckApplyEnabled(); 855 break; 856 } 857 858 case POP_OTHER_REFRESH_MSG: 859 { 860 // make sure menu shows something useful 861 _UpdateRefreshControl(); 862 863 float min = 0, max = 999; 864 fScreenMode.GetRefreshLimits(fSelected, min, max); 865 if (min < gMinRefresh) 866 min = gMinRefresh; 867 if (max > gMaxRefresh) 868 max = gMaxRefresh; 869 870 RefreshWindow *fRefreshWindow = new RefreshWindow( 871 fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh, min, max); 872 fRefreshWindow->Show(); 873 break; 874 } 875 876 case SET_CUSTOM_REFRESH_MSG: 877 { 878 // user pressed "done" in "Other…" refresh dialog; 879 // select the refresh rate chosen 880 message->FindFloat("refresh", &fSelected.refresh); 881 882 _UpdateRefreshControl(); 883 _CheckApplyEnabled(); 884 break; 885 } 886 887 case POP_COMBINE_DISPLAYS_MSG: 888 { 889 // new combine mode has bee chosen 890 int32 mode; 891 if (message->FindInt32("mode", &mode) == B_OK) 892 fSelected.combine = (combine_mode)mode; 893 894 _CheckResolutionMenu(); 895 _CheckApplyEnabled(); 896 break; 897 } 898 899 case POP_SWAP_DISPLAYS_MSG: 900 message->FindBool("swap", &fSelected.swap_displays); 901 _CheckApplyEnabled(); 902 break; 903 904 case POP_USE_LAPTOP_PANEL_MSG: 905 message->FindBool("use", &fSelected.use_laptop_panel); 906 _CheckApplyEnabled(); 907 break; 908 909 case POP_TV_STANDARD_MSG: 910 message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard); 911 _CheckApplyEnabled(); 912 break; 913 914 case BUTTON_LAUNCH_BACKGROUNDS_MSG: 915 if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) { 916 app_info info; 917 be_roster->GetAppInfo(kBackgroundsSignature, &info); 918 be_roster->ActivateApp(info.team); 919 } 920 break; 921 922 case BUTTON_DEFAULTS_MSG: 923 { 924 // TODO: get preferred settings of screen 925 fSelected.width = 640; 926 fSelected.height = 480; 927 fSelected.space = B_CMAP8; 928 fSelected.refresh = 60.0; 929 fSelected.combine = kCombineDisable; 930 fSelected.swap_displays = false; 931 fSelected.use_laptop_panel = false; 932 fSelected.tv_standard = 0; 933 934 BMenuItem *item; 935 item = fWorkspaceCountField->Menu()->ItemAt(3); 936 if (item != NULL) 937 item->SetMarked(true); 938 939 _UpdateControls(); 940 break; 941 } 942 943 case BUTTON_UNDO_MSG: 944 fTempScreenMode.Revert(); 945 _UpdateActiveMode(); 946 break; 947 948 case BUTTON_REVERT_MSG: 949 { 950 fModified = false; 951 fVesaApplied = false; 952 BMenuItem *item; 953 item = fWorkspaceCountField->Menu()->ItemAt(fOriginalWorkspaceCount - 1); 954 if (item != NULL) 955 item->SetMarked(true); 956 957 // ScreenMode::Revert() assumes that we first set the correct number 958 // of workspaces 959 960 if (_IsVesa()) { 961 set_workspace_count(fOriginalWorkspaceCount); 962 fActive = fOriginal; 963 fSelected = fOriginal; 964 _UpdateControls(); 965 } else { 966 set_workspace_count(fOriginalWorkspaceCount); 967 fScreenMode.Revert(); 968 _UpdateActiveMode(); 969 } 970 break; 971 } 972 973 case BUTTON_APPLY_MSG: 974 _Apply(); 975 break; 976 977 case MAKE_INITIAL_MSG: 978 // user pressed "keep" in confirmation dialog 979 fModified = true; 980 _UpdateActiveMode(); 981 break; 982 983 default: 984 BWindow::MessageReceived(message); 985 break; 986 } 987 } 988 989 990 status_t 991 ScreenWindow::_WriteVesaModeFile(const screen_mode& mode) const 992 { 993 BPath path; 994 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 995 if (status < B_OK) 996 return status; 997 998 path.Append("kernel/drivers"); 999 status = create_directory(path.Path(), 0755); 1000 if (status < B_OK) 1001 return status; 1002 1003 path.Append("vesa"); 1004 BFile file; 1005 status = file.SetTo(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE); 1006 if (status < B_OK) 1007 return status; 1008 1009 char buffer[256]; 1010 snprintf(buffer, sizeof(buffer), "mode %ld %ld %ld\n", 1011 mode.width, mode.height, mode.BitsPerPixel()); 1012 1013 ssize_t bytesWritten = file.Write(buffer, strlen(buffer)); 1014 if (bytesWritten < B_OK) 1015 return bytesWritten; 1016 1017 return B_OK; 1018 } 1019 1020 1021 status_t 1022 ScreenWindow::_ReadVesaModeFile(screen_mode& mode) const 1023 { 1024 BPath path; 1025 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 1026 if (status < B_OK) 1027 return status; 1028 1029 path.Append("kernel/drivers/vesa"); 1030 BFile file; 1031 status = file.SetTo(path.Path(), B_READ_ONLY); 1032 if (status < B_OK) 1033 return status; 1034 1035 char buffer[256]; 1036 1037 ssize_t bytesRead = file.Read(buffer, sizeof(buffer) - 1); 1038 if (bytesRead < B_OK) { 1039 return bytesRead; 1040 } else { 1041 buffer[bytesRead] = '\0'; 1042 } 1043 1044 char ignore[256]; 1045 // if the file is malformed, sscanf shouldn't crash 1046 // on reading a big string since we don't even read 1047 // as much from the file 1048 uint32 bitDepth = 0; 1049 1050 if (sscanf(buffer, "%s %ld %ld %ld", ignore, &mode.width, &mode.height, 1051 &bitDepth) != 4) { 1052 return B_ERROR; 1053 } 1054 1055 // TODO: check for valid width and height values 1056 1057 switch (bitDepth) { 1058 case 32: 1059 mode.space = B_RGB32; 1060 break; 1061 case 24: 1062 mode.space = B_RGB24; 1063 break; 1064 case 16: 1065 mode.space = B_RGB16; 1066 break; 1067 case 15: 1068 mode.space = B_RGB15; 1069 break; 1070 case 8: 1071 mode.space = B_CMAP8; 1072 break; 1073 default: 1074 // invalid value, we force it to B_RGB16 just in case 1075 mode.space = B_RGB16; 1076 return B_ERROR; 1077 } 1078 1079 return B_OK; 1080 } 1081 1082 1083 void 1084 ScreenWindow::_CheckApplyEnabled() 1085 { 1086 fApplyButton->SetEnabled(fSelected != fActive); 1087 fRevertButton->SetEnabled(count_workspaces() != fOriginalWorkspaceCount 1088 || fSelected != fOriginal); 1089 } 1090 1091 1092 void 1093 ScreenWindow::_UpdateOriginal() 1094 { 1095 fOriginalWorkspaceCount = count_workspaces(); 1096 fScreenMode.Get(fOriginal); 1097 1098 // If we are in vesa we overwrite fOriginal's resolution and bitdepth 1099 // with those found the vesa settings file. (if the file exists) 1100 if (_IsVesa()) 1101 _ReadVesaModeFile(fOriginal); 1102 1103 fScreenMode.UpdateOriginalModes(); 1104 } 1105 1106 1107 void 1108 ScreenWindow::_Apply() 1109 { 1110 if (_IsVesa()) { 1111 (new BAlert("VesaAlert", 1112 "Haiku is using your video card in compatibility mode (VESA)." 1113 " Your settings will be applied on next startup.\n", "Okay", NULL, NULL, B_WIDTH_AS_USUAL, 1114 B_INFO_ALERT))->Go(NULL); 1115 1116 fVesaApplied = true; 1117 fActive = fSelected; 1118 _UpdateControls(); 1119 return; 1120 } 1121 1122 // make checkpoint, so we can undo these changes 1123 fTempScreenMode.UpdateOriginalModes(); 1124 status_t status = fScreenMode.Set(fSelected); 1125 if (status == B_OK) { 1126 // use the mode that has eventually been set and 1127 // thus we know to be working; it can differ from 1128 // the mode selected by user due to hardware limitation 1129 display_mode newMode; 1130 BScreen screen(this); 1131 screen.GetMode(&newMode); 1132 1133 if (fAllWorkspacesItem->IsMarked()) { 1134 int32 originatingWorkspace = current_workspace(); 1135 for (int32 i = 0; i < count_workspaces(); i++) { 1136 if (i != originatingWorkspace) 1137 screen.SetMode(i, &newMode, true); 1138 } 1139 } 1140 1141 fActive = fSelected; 1142 1143 // TODO: only show alert when this is an unknown mode 1144 BWindow* window = new AlertWindow(this); 1145 window->Show(); 1146 } else { 1147 char message[256]; 1148 snprintf(message, sizeof(message), 1149 "The screen mode could not be set:\n\t%s\n", screen_errors(status)); 1150 BAlert* alert = new BAlert("Screen Alert", message, "Okay", NULL, NULL, 1151 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1152 alert->Go(); 1153 } 1154 } 1155 1156 1157 void 1158 ScreenWindow::_LayoutControls(uint32 flags) 1159 { 1160 // layout the screen box and its controls 1161 fWorkspaceCountField->ResizeToPreferred(); 1162 1163 float monitorViewHeight = fMonitorView->Bounds().Height(); 1164 float workspaceFieldHeight = fWorkspaceCountField->Bounds().Height(); 1165 float backgroundsButtonHeight = fBackgroundsButton->Bounds().Height(); 1166 1167 float screenBoxWidth = fWorkspaceCountField->Bounds().Width() + 20.0; 1168 float screenBoxHeight = monitorViewHeight + 5.0 + workspaceFieldHeight + 5.0 1169 + backgroundsButtonHeight + 20.0; 1170 1171 #ifdef __HAIKU__ 1172 fScreenBox->MoveTo(10.0, 10.0 + fControlsBox->TopBorderOffset()); 1173 #else 1174 fScreenBox->MoveTo(10.0, 10.0 + 3); 1175 #endif 1176 fScreenBox->ResizeTo(screenBoxWidth, screenBoxHeight); 1177 1178 float leftOffset = 10.0; 1179 float topOffset = 10.0; 1180 fMonitorView->MoveTo(leftOffset, topOffset); 1181 fMonitorView->ResizeTo(screenBoxWidth - 20.0, monitorViewHeight); 1182 fMonitorView->SetResizingMode(B_FOLLOW_ALL); 1183 topOffset += monitorViewHeight + 5.0; 1184 1185 fWorkspaceCountField->MoveTo(leftOffset, topOffset); 1186 fWorkspaceCountField->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 1187 topOffset += workspaceFieldHeight + 5.0; 1188 1189 fBackgroundsButton->MoveTo(leftOffset, topOffset); 1190 fBackgroundsButton->ResizeTo(screenBoxWidth - 20.0, backgroundsButtonHeight); 1191 fBackgroundsButton->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 1192 1193 fControlsBox->MoveTo(fScreenBox->Frame().right + 10.0, 10.0); 1194 1195 // layout the right side 1196 fApplyButton->ResizeToPreferred(); 1197 BRect controlsRect = _LayoutMenuFields(flags); 1198 controlsRect.InsetBy(-10.0, -10.0); 1199 controlsRect.bottom += 8 + fApplyButton->Bounds().Height(); 1200 // adjust size of controls box and move aligned buttons along 1201 float xDiff = controlsRect.right - fControlsBox->Bounds().right; 1202 float yDiff = controlsRect.bottom - fControlsBox->Bounds().bottom; 1203 if (yDiff < 0.0) { 1204 // don't shrink vertically 1205 yDiff = 0.0; 1206 } 1207 1208 fControlsBox->ResizeBy(xDiff, yDiff); 1209 1210 // align bottom of boxen 1211 float boxBottomDiff = fControlsBox->Frame().bottom - fScreenBox->Frame().bottom; 1212 if (boxBottomDiff > 0) 1213 fScreenBox->ResizeBy(0.0, boxBottomDiff); 1214 else 1215 fControlsBox->ResizeBy(0.0, -boxBottomDiff); 1216 1217 BRect boxFrame = fScreenBox->Frame() | fControlsBox->Frame(); 1218 1219 // layout rest of buttons 1220 // TODO: we don't support getting the screen's preferred settings 1221 // fDefaultsButton->ResizeToPreferred(); 1222 // fDefaultsButton->MoveTo(boxFrame.left, boxFrame.bottom + 8); 1223 1224 fRevertButton->ResizeToPreferred(); 1225 fRevertButton->MoveTo(boxFrame.left, boxFrame.bottom + 8); 1226 // fRevertButton->MoveTo(fDefaultsButton->Frame().right + 10, 1227 // fDefaultsButton->Frame().top); 1228 1229 // Apply button was already resized above 1230 float resolutionFieldRight = fResolutionField->Frame().right; 1231 fApplyButton->MoveTo(resolutionFieldRight - fApplyButton->Bounds().Width(), 1232 fControlsBox->Bounds().bottom - fApplyButton->Bounds().Height() - 10); 1233 1234 ResizeTo(boxFrame.right + 10, fRevertButton->Frame().bottom + 10); 1235 } 1236 1237 1238 BRect 1239 ScreenWindow::_LayoutMenuFields(uint32 flags, bool sideBySide) 1240 { 1241 BList menuFields; 1242 menuFields.AddItem((void*)fResolutionField); 1243 menuFields.AddItem((void*)fColorsField); 1244 menuFields.AddItem((void*)fRefreshField); 1245 1246 BRect frame; 1247 1248 if (sideBySide) 1249 frame = stack_and_align_menu_fields(menuFields); 1250 1251 if (sideBySide) 1252 menuFields.MakeEmpty(); 1253 1254 if (flags & SHOW_COMBINE_FIELD) 1255 menuFields.AddItem((void*)fCombineField); 1256 if (flags & SHOW_SWAP_FIELD) 1257 menuFields.AddItem((void*)fSwapDisplaysField); 1258 if (flags & SHOW_LAPTOP_PANEL_FIELD) 1259 menuFields.AddItem((void*)fUseLaptopPanelField); 1260 if (flags & SHOW_TV_STANDARD_FIELD) 1261 menuFields.AddItem((void*)fTVStandardField); 1262 1263 if (sideBySide) { 1264 if (menuFields.CountItems() > 0) { 1265 ((BMenuField*)menuFields.FirstItem())->MoveTo(frame.right + 8.0, frame.top); 1266 frame = frame | stack_and_align_menu_fields(menuFields); 1267 } 1268 } else { 1269 frame = stack_and_align_menu_fields(menuFields); 1270 } 1271 1272 return frame; 1273 } 1274 1275