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