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