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