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