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