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