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, 10.0) 509 .SetInsets(10, 10, 10, 10) 510 .AddGroup(B_HORIZONTAL, 10.0) 511 .AddGroup(B_VERTICAL) 512 .AddStrut(floor(controlsBox->TopBorderOffset() / 16) - 1) 513 .Add(screenBox) 514 .End() 515 .Add(controlsBox) 516 .End() 517 .AddGroup(B_HORIZONTAL, 10.0) 518 .Add(fRevertButton) 519 .AddGlue(); 520 521 _UpdateControls(); 522 _UpdateMonitor(); 523 } 524 525 526 ScreenWindow::~ScreenWindow() 527 { 528 delete fSettings; 529 } 530 531 532 bool 533 ScreenWindow::QuitRequested() 534 { 535 fSettings->SetWindowFrame(Frame()); 536 537 // Write mode of workspace 0 (the boot workspace) to the vesa settings file 538 screen_mode vesaMode; 539 if (fBootWorkspaceApplied && fScreenMode.Get(vesaMode, 0) == B_OK) { 540 status_t status = _WriteVesaModeFile(vesaMode); 541 if (status < B_OK) { 542 BString warning = B_TRANSLATE("Could not write VESA mode settings" 543 " file:\n\t"); 544 warning << strerror(status); 545 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 546 warning.String(), B_TRANSLATE("OK"), NULL, 547 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 548 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 549 alert->Go(); 550 } 551 } 552 553 be_app->PostMessage(B_QUIT_REQUESTED); 554 555 return BWindow::QuitRequested(); 556 } 557 558 559 /*! Update resolution list according to combine mode 560 (some resolutions may not be combinable due to memory restrictions). 561 */ 562 void 563 ScreenWindow::_CheckResolutionMenu() 564 { 565 for (int32 i = 0; i < fResolutionMenu->CountItems(); i++) 566 fResolutionMenu->ItemAt(i)->SetEnabled(false); 567 568 for (int32 i = 0; i < fScreenMode.CountModes(); i++) { 569 screen_mode mode = fScreenMode.ModeAt(i); 570 if (mode.combine != fSelected.combine) 571 continue; 572 573 BString name; 574 name << mode.width << " x " << mode.height; 575 576 BMenuItem *item = fResolutionMenu->FindItem(name.String()); 577 if (item != NULL) 578 item->SetEnabled(true); 579 } 580 } 581 582 583 /*! Update color and refresh options according to current mode 584 (a color space is made active if there is any mode with 585 given resolution and this colour space; same applies for 586 refresh rate, though "Other…" is always possible) 587 */ 588 void 589 ScreenWindow::_CheckColorMenu() 590 { 591 int32 supportsAnything = false; 592 int32 index = 0; 593 594 for (int32 i = 0; i < kColorSpaceCount; i++) { 595 if ((fSupportedColorSpaces & (1 << i)) == 0) 596 continue; 597 598 bool supported = false; 599 600 for (int32 j = 0; j < fScreenMode.CountModes(); j++) { 601 screen_mode mode = fScreenMode.ModeAt(j); 602 603 if (fSelected.width == mode.width 604 && fSelected.height == mode.height 605 && kColorSpaces[i].space == mode.space 606 && fSelected.combine == mode.combine) { 607 supportsAnything = true; 608 supported = true; 609 break; 610 } 611 } 612 613 BMenuItem* item = fColorsMenu->ItemAt(index++); 614 if (item) 615 item->SetEnabled(supported); 616 } 617 618 fColorsField->SetEnabled(supportsAnything); 619 620 if (!supportsAnything) 621 return; 622 623 // Make sure a valid item is selected 624 625 BMenuItem* item = fColorsMenu->FindMarked(); 626 bool changed = false; 627 628 if (item != fUserSelectedColorSpace) { 629 if (fUserSelectedColorSpace != NULL 630 && fUserSelectedColorSpace->IsEnabled()) { 631 fUserSelectedColorSpace->SetMarked(true); 632 item = fUserSelectedColorSpace; 633 changed = true; 634 } 635 } 636 if (item != NULL && !item->IsEnabled()) { 637 // find the next best item 638 int32 index = fColorsMenu->IndexOf(item); 639 bool found = false; 640 641 for (int32 i = index + 1; i < fColorsMenu->CountItems(); i++) { 642 item = fColorsMenu->ItemAt(i); 643 if (item->IsEnabled()) { 644 found = true; 645 break; 646 } 647 } 648 if (!found) { 649 // search backwards as well 650 for (int32 i = index - 1; i >= 0; i--) { 651 item = fColorsMenu->ItemAt(i); 652 if (item->IsEnabled()) 653 break; 654 } 655 } 656 657 item->SetMarked(true); 658 changed = true; 659 } 660 661 if (changed) { 662 // Update selected space 663 664 BMessage* message = item->Message(); 665 int32 space; 666 if (message->FindInt32("space", &space) == B_OK) { 667 fSelected.space = (color_space)space; 668 _UpdateColorLabel(); 669 } 670 } 671 } 672 673 674 /*! Enable/disable refresh options according to current mode. */ 675 void 676 ScreenWindow::_CheckRefreshMenu() 677 { 678 float min, max; 679 if (fScreenMode.GetRefreshLimits(fSelected, min, max) != B_OK || min == max) 680 return; 681 682 for (int32 i = fRefreshMenu->CountItems(); i-- > 0;) { 683 BMenuItem* item = fRefreshMenu->ItemAt(i); 684 BMessage* message = item->Message(); 685 float refresh; 686 if (message != NULL && message->FindFloat("refresh", &refresh) == B_OK) 687 item->SetEnabled(refresh >= min && refresh <= max); 688 } 689 } 690 691 692 /*! Activate appropriate menu item according to selected refresh rate */ 693 void 694 ScreenWindow::_UpdateRefreshControl() 695 { 696 for (int32 i = 0; i < fRefreshMenu->CountItems(); i++) { 697 BMenuItem* item = fRefreshMenu->ItemAt(i); 698 if (item->Message()->FindFloat("refresh") == fSelected.refresh) { 699 item->SetMarked(true); 700 // "Other" items only contains a refresh rate when active 701 fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS)); 702 return; 703 } 704 } 705 706 // this is a non-standard refresh rate 707 if (fOtherRefresh != NULL) { 708 fOtherRefresh->Message()->ReplaceFloat("refresh", fSelected.refresh); 709 fOtherRefresh->SetMarked(true); 710 711 BString string; 712 refresh_rate_to_string(fSelected.refresh, string); 713 fRefreshMenu->Superitem()->SetLabel(string.String()); 714 715 string.Append(B_TRANSLATE("/other" B_UTF8_ELLIPSIS)); 716 fOtherRefresh->SetLabel(string.String()); 717 } 718 } 719 720 721 void 722 ScreenWindow::_UpdateMonitorView() 723 { 724 BMessage updateMessage(UPDATE_DESKTOP_MSG); 725 updateMessage.AddInt32("width", fSelected.width); 726 updateMessage.AddInt32("height", fSelected.height); 727 728 PostMessage(&updateMessage, fMonitorView); 729 } 730 731 732 void 733 ScreenWindow::_UpdateControls() 734 { 735 _UpdateWorkspaceButtons(); 736 737 BMenuItem* item = fSwapDisplaysMenu->ItemAt((int32)fSelected.swap_displays); 738 if (item != NULL && !item->IsMarked()) 739 item->SetMarked(true); 740 741 item = fUseLaptopPanelMenu->ItemAt((int32)fSelected.use_laptop_panel); 742 if (item != NULL && !item->IsMarked()) 743 item->SetMarked(true); 744 745 for (int32 i = 0; i < fTVStandardMenu->CountItems(); i++) { 746 item = fTVStandardMenu->ItemAt(i); 747 748 uint32 tvStandard; 749 item->Message()->FindInt32("tv_standard", (int32 *)&tvStandard); 750 if (tvStandard == fSelected.tv_standard) { 751 if (!item->IsMarked()) 752 item->SetMarked(true); 753 break; 754 } 755 } 756 757 _CheckResolutionMenu(); 758 _CheckColorMenu(); 759 _CheckRefreshMenu(); 760 761 BString string; 762 resolution_to_string(fSelected, string); 763 item = fResolutionMenu->FindItem(string.String()); 764 765 if (item != NULL) { 766 if (!item->IsMarked()) 767 item->SetMarked(true); 768 } else { 769 // this is bad luck - if mode has been set via screen references, 770 // this case cannot occur; there are three possible solutions: 771 // 1. add a new resolution to list 772 // - we had to remove it as soon as a "valid" one is selected 773 // - we don't know which frequencies/bit depths are supported 774 // - as long as we haven't the GMT formula to create 775 // parameters for any resolution given, we cannot 776 // really set current mode - it's just not in the list 777 // 2. choose nearest resolution 778 // - probably a good idea, but implies coding and testing 779 // 3. choose lowest resolution 780 // - do you really think we are so lazy? yes, we are 781 item = fResolutionMenu->ItemAt(0); 782 if (item) 783 item->SetMarked(true); 784 785 // okay - at least we set menu label to active resolution 786 fResolutionMenu->Superitem()->SetLabel(string.String()); 787 } 788 789 // mark active combine mode 790 for (int32 i = 0; i < kCombineModeCount; i++) { 791 if (kCombineModes[i].mode == fSelected.combine) { 792 item = fCombineMenu->ItemAt(i); 793 if (item != NULL && !item->IsMarked()) 794 item->SetMarked(true); 795 break; 796 } 797 } 798 799 item = fColorsMenu->ItemAt(0); 800 801 for (int32 i = 0, index = 0; i < kColorSpaceCount; i++) { 802 if ((fSupportedColorSpaces & (1 << i)) == 0) 803 continue; 804 805 if (kColorSpaces[i].space == fSelected.space) { 806 item = fColorsMenu->ItemAt(index); 807 break; 808 } 809 810 index++; 811 } 812 813 if (item != NULL && !item->IsMarked()) 814 item->SetMarked(true); 815 816 _UpdateColorLabel(); 817 _UpdateMonitorView(); 818 _UpdateRefreshControl(); 819 820 _CheckApplyEnabled(); 821 } 822 823 824 /*! Reflect active mode in chosen settings */ 825 void 826 ScreenWindow::_UpdateActiveMode() 827 { 828 _UpdateActiveMode(current_workspace()); 829 } 830 831 832 void 833 ScreenWindow::_UpdateActiveMode(int32 workspace) 834 { 835 // Usually, this function gets called after a mode 836 // has been set manually; still, as the graphics driver 837 // is free to fiddle with mode passed, we better ask 838 // what kind of mode we actually got 839 if (fScreenMode.Get(fActive, workspace) == B_OK) { 840 fSelected = fActive; 841 842 _UpdateMonitor(); 843 _BuildSupportedColorSpaces(); 844 _UpdateControls(); 845 } 846 } 847 848 849 void 850 ScreenWindow::_UpdateWorkspaceButtons() 851 { 852 uint32 columns; 853 uint32 rows; 854 BPrivate::get_workspaces_layout(&columns, &rows); 855 856 char text[32]; 857 snprintf(text, sizeof(text), "%" B_PRId32, columns); 858 fColumnsControl->SetText(text); 859 860 snprintf(text, sizeof(text), "%" B_PRId32, rows); 861 fRowsControl->SetText(text); 862 863 _GetColumnRowButton(true, false)->SetEnabled(columns != 1 && rows != 32); 864 _GetColumnRowButton(true, true)->SetEnabled((columns + 1) * rows < 32); 865 _GetColumnRowButton(false, false)->SetEnabled(rows != 1 && columns != 32); 866 _GetColumnRowButton(false, true)->SetEnabled(columns * (rows + 1) < 32); 867 } 868 869 870 void 871 ScreenWindow::ScreenChanged(BRect frame, color_space mode) 872 { 873 // move window on screen, if necessary 874 if (frame.right <= Frame().right 875 && frame.bottom <= Frame().bottom) { 876 MoveTo((frame.Width() - Frame().Width()) / 2, 877 (frame.Height() - Frame().Height()) / 2); 878 } 879 } 880 881 882 void 883 ScreenWindow::WorkspaceActivated(int32 workspace, bool state) 884 { 885 if (fScreenMode.GetOriginalMode(fOriginal, workspace) == B_OK) { 886 _UpdateActiveMode(workspace); 887 888 BMessage message(UPDATE_DESKTOP_COLOR_MSG); 889 PostMessage(&message, fMonitorView); 890 } 891 } 892 893 894 void 895 ScreenWindow::MessageReceived(BMessage* message) 896 { 897 switch (message->what) { 898 case WORKSPACE_CHECK_MSG: 899 _CheckApplyEnabled(); 900 break; 901 902 case kMsgWorkspaceLayoutChanged: 903 { 904 int32 deltaX = 0; 905 int32 deltaY = 0; 906 message->FindInt32("delta_x", &deltaX); 907 message->FindInt32("delta_y", &deltaY); 908 909 if (deltaX == 0 && deltaY == 0) 910 break; 911 912 uint32 newColumns; 913 uint32 newRows; 914 BPrivate::get_workspaces_layout(&newColumns, &newRows); 915 916 newColumns += deltaX; 917 newRows += deltaY; 918 BPrivate::set_workspaces_layout(newColumns, newRows); 919 920 _UpdateWorkspaceButtons(); 921 _CheckApplyEnabled(); 922 break; 923 } 924 925 case kMsgWorkspaceColumnsChanged: 926 { 927 uint32 newColumns = strtoul(fColumnsControl->Text(), NULL, 10); 928 929 uint32 rows; 930 BPrivate::get_workspaces_layout(NULL, &rows); 931 BPrivate::set_workspaces_layout(newColumns, rows); 932 933 _UpdateWorkspaceButtons(); 934 _CheckApplyEnabled(); 935 break; 936 } 937 938 case kMsgWorkspaceRowsChanged: 939 { 940 uint32 newRows = strtoul(fRowsControl->Text(), NULL, 10); 941 942 uint32 columns; 943 BPrivate::get_workspaces_layout(&columns, NULL); 944 BPrivate::set_workspaces_layout(columns, newRows); 945 946 _UpdateWorkspaceButtons(); 947 _CheckApplyEnabled(); 948 break; 949 } 950 951 case POP_RESOLUTION_MSG: 952 { 953 message->FindInt32("width", &fSelected.width); 954 message->FindInt32("height", &fSelected.height); 955 956 _CheckColorMenu(); 957 _CheckRefreshMenu(); 958 959 _UpdateMonitorView(); 960 _UpdateRefreshControl(); 961 962 _CheckApplyEnabled(); 963 break; 964 } 965 966 case POP_COLORS_MSG: 967 { 968 int32 space; 969 if (message->FindInt32("space", &space) != B_OK) 970 break; 971 972 int32 index; 973 if (message->FindInt32("index", &index) == B_OK 974 && fColorsMenu->ItemAt(index) != NULL) 975 fUserSelectedColorSpace = fColorsMenu->ItemAt(index); 976 977 fSelected.space = (color_space)space; 978 _UpdateColorLabel(); 979 980 _CheckApplyEnabled(); 981 break; 982 } 983 984 case POP_REFRESH_MSG: 985 { 986 message->FindFloat("refresh", &fSelected.refresh); 987 fOtherRefresh->SetLabel(B_TRANSLATE("Other" B_UTF8_ELLIPSIS)); 988 // revert "Other…" label - it might have a refresh rate prefix 989 990 _CheckApplyEnabled(); 991 break; 992 } 993 994 case POP_OTHER_REFRESH_MSG: 995 { 996 // make sure menu shows something useful 997 _UpdateRefreshControl(); 998 999 float min = 0, max = 999; 1000 fScreenMode.GetRefreshLimits(fSelected, min, max); 1001 if (min < gMinRefresh) 1002 min = gMinRefresh; 1003 if (max > gMaxRefresh) 1004 max = gMaxRefresh; 1005 1006 monitor_info info; 1007 if (fScreenMode.GetMonitorInfo(info) == B_OK) { 1008 min = max_c(info.min_vertical_frequency, min); 1009 max = min_c(info.max_vertical_frequency, max); 1010 } 1011 1012 RefreshWindow *fRefreshWindow = new RefreshWindow( 1013 fRefreshField->ConvertToScreen(B_ORIGIN), fSelected.refresh, 1014 min, max); 1015 fRefreshWindow->Show(); 1016 break; 1017 } 1018 1019 case SET_CUSTOM_REFRESH_MSG: 1020 { 1021 // user pressed "done" in "Other…" refresh dialog; 1022 // select the refresh rate chosen 1023 message->FindFloat("refresh", &fSelected.refresh); 1024 1025 _UpdateRefreshControl(); 1026 _CheckApplyEnabled(); 1027 break; 1028 } 1029 1030 case POP_COMBINE_DISPLAYS_MSG: 1031 { 1032 // new combine mode has bee chosen 1033 int32 mode; 1034 if (message->FindInt32("mode", &mode) == B_OK) 1035 fSelected.combine = (combine_mode)mode; 1036 1037 _CheckResolutionMenu(); 1038 _CheckApplyEnabled(); 1039 break; 1040 } 1041 1042 case POP_SWAP_DISPLAYS_MSG: 1043 message->FindBool("swap", &fSelected.swap_displays); 1044 _CheckApplyEnabled(); 1045 break; 1046 1047 case POP_USE_LAPTOP_PANEL_MSG: 1048 message->FindBool("use", &fSelected.use_laptop_panel); 1049 _CheckApplyEnabled(); 1050 break; 1051 1052 case POP_TV_STANDARD_MSG: 1053 message->FindInt32("tv_standard", (int32 *)&fSelected.tv_standard); 1054 _CheckApplyEnabled(); 1055 break; 1056 1057 case BUTTON_LAUNCH_BACKGROUNDS_MSG: 1058 if (be_roster->Launch(kBackgroundsSignature) == B_ALREADY_RUNNING) { 1059 app_info info; 1060 be_roster->GetAppInfo(kBackgroundsSignature, &info); 1061 be_roster->ActivateApp(info.team); 1062 } 1063 break; 1064 1065 case BUTTON_DEFAULTS_MSG: 1066 { 1067 // TODO: get preferred settings of screen 1068 fSelected.width = 640; 1069 fSelected.height = 480; 1070 fSelected.space = B_CMAP8; 1071 fSelected.refresh = 60.0; 1072 fSelected.combine = kCombineDisable; 1073 fSelected.swap_displays = false; 1074 fSelected.use_laptop_panel = false; 1075 fSelected.tv_standard = 0; 1076 1077 // TODO: workspace defaults 1078 1079 _UpdateControls(); 1080 break; 1081 } 1082 1083 case BUTTON_UNDO_MSG: 1084 fUndoScreenMode.Revert(); 1085 _UpdateActiveMode(); 1086 break; 1087 1088 case BUTTON_REVERT_MSG: 1089 { 1090 fModified = false; 1091 fBootWorkspaceApplied = false; 1092 1093 // ScreenMode::Revert() assumes that we first set the correct 1094 // number of workspaces 1095 1096 BPrivate::set_workspaces_layout(fOriginalWorkspacesColumns, 1097 fOriginalWorkspacesRows); 1098 _UpdateWorkspaceButtons(); 1099 1100 fScreenMode.Revert(); 1101 _UpdateActiveMode(); 1102 break; 1103 } 1104 1105 case BUTTON_APPLY_MSG: 1106 _Apply(); 1107 break; 1108 1109 case MAKE_INITIAL_MSG: 1110 // user pressed "keep" in confirmation dialog 1111 fModified = true; 1112 _UpdateActiveMode(); 1113 break; 1114 1115 default: 1116 BWindow::MessageReceived(message); 1117 break; 1118 } 1119 } 1120 1121 1122 status_t 1123 ScreenWindow::_WriteVesaModeFile(const screen_mode& mode) const 1124 { 1125 BPath path; 1126 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 1127 if (status < B_OK) 1128 return status; 1129 1130 path.Append("kernel/drivers"); 1131 status = create_directory(path.Path(), 0755); 1132 if (status < B_OK) 1133 return status; 1134 1135 path.Append("vesa"); 1136 BFile file; 1137 status = file.SetTo(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE); 1138 if (status < B_OK) 1139 return status; 1140 1141 char buffer[256]; 1142 snprintf(buffer, sizeof(buffer), "mode %" B_PRId32 " %" B_PRId32 " %" 1143 B_PRId32 "\n", mode.width, mode.height, mode.BitsPerPixel()); 1144 1145 ssize_t bytesWritten = file.Write(buffer, strlen(buffer)); 1146 if (bytesWritten < B_OK) 1147 return bytesWritten; 1148 1149 return B_OK; 1150 } 1151 1152 1153 BButton* 1154 ScreenWindow::_CreateColumnRowButton(bool columns, bool plus) 1155 { 1156 BMessage* message = new BMessage(kMsgWorkspaceLayoutChanged); 1157 message->AddInt32("delta_x", columns ? (plus ? 1 : -1) : 0); 1158 message->AddInt32("delta_y", !columns ? (plus ? 1 : -1) : 0); 1159 1160 BButton* button = new BButton(plus ? "+" : "-", message); 1161 button->SetFontSize(be_plain_font->Size() * 0.9); 1162 1163 BSize size = button->MinSize(); 1164 size.width = button->StringWidth("+") + 16; 1165 button->SetExplicitMinSize(size); 1166 button->SetExplicitMaxSize(size); 1167 1168 fWorkspacesButtons[(columns ? 0 : 2) + (plus ? 1 : 0)] = button; 1169 return button; 1170 } 1171 1172 1173 BButton* 1174 ScreenWindow::_GetColumnRowButton(bool columns, bool plus) 1175 { 1176 return fWorkspacesButtons[(columns ? 0 : 2) + (plus ? 1 : 0)]; 1177 } 1178 1179 1180 void 1181 ScreenWindow::_BuildSupportedColorSpaces() 1182 { 1183 fSupportedColorSpaces = 0; 1184 1185 for (int32 i = 0; i < kColorSpaceCount; i++) { 1186 for (int32 j = 0; j < fScreenMode.CountModes(); j++) { 1187 if (fScreenMode.ModeAt(j).space == kColorSpaces[i].space) { 1188 fSupportedColorSpaces |= 1 << i; 1189 break; 1190 } 1191 } 1192 } 1193 } 1194 1195 1196 void 1197 ScreenWindow::_CheckApplyEnabled() 1198 { 1199 fApplyButton->SetEnabled(fSelected != fActive 1200 || fAllWorkspacesItem->IsMarked()); 1201 1202 uint32 columns; 1203 uint32 rows; 1204 BPrivate::get_workspaces_layout(&columns, &rows); 1205 1206 fRevertButton->SetEnabled(columns != fOriginalWorkspacesColumns 1207 || rows != fOriginalWorkspacesRows 1208 || fSelected != fOriginal); 1209 } 1210 1211 1212 void 1213 ScreenWindow::_UpdateOriginal() 1214 { 1215 BPrivate::get_workspaces_layout(&fOriginalWorkspacesColumns, 1216 &fOriginalWorkspacesRows); 1217 1218 fScreenMode.Get(fOriginal); 1219 fScreenMode.UpdateOriginalModes(); 1220 } 1221 1222 1223 void 1224 ScreenWindow::_UpdateMonitor() 1225 { 1226 monitor_info info; 1227 float diagonalInches; 1228 status_t status = fScreenMode.GetMonitorInfo(info, &diagonalInches); 1229 if (status == B_OK) { 1230 char text[512]; 1231 snprintf(text, sizeof(text), "%s%s%s %g\"", info.vendor, 1232 info.name[0] ? " " : "", info.name, diagonalInches); 1233 1234 fMonitorInfo->SetText(text); 1235 1236 if (fMonitorInfo->IsHidden()) 1237 fMonitorInfo->Show(); 1238 } else { 1239 if (!fMonitorInfo->IsHidden()) 1240 fMonitorInfo->Hide(); 1241 } 1242 1243 char text[512]; 1244 size_t length = 0; 1245 text[0] = 0; 1246 1247 if (status == B_OK) { 1248 if (info.min_horizontal_frequency != 0 1249 && info.min_vertical_frequency != 0 1250 && info.max_pixel_clock != 0) { 1251 length = snprintf(text, sizeof(text), 1252 B_TRANSLATE("Horizonal frequency:\t%lu - %lu kHz\n" 1253 "Vertical frequency:\t%lu - %lu Hz\n\n" 1254 "Maximum pixel clock:\t%g MHz"), 1255 info.min_horizontal_frequency, info.max_horizontal_frequency, 1256 info.min_vertical_frequency, info.max_vertical_frequency, 1257 info.max_pixel_clock / 1000.0); 1258 } 1259 if (info.serial_number[0] && length < sizeof(text)) { 1260 length += snprintf(text + length, sizeof(text) - length, 1261 B_TRANSLATE("%sSerial no.: %s"), length ? "\n\n" : "", 1262 info.serial_number); 1263 if (info.produced.week != 0 && info.produced.year != 0 1264 && length < sizeof(text)) { 1265 length += snprintf(text + length, sizeof(text) - length, 1266 " (%u/%u)", info.produced.week, info.produced.year); 1267 } 1268 } 1269 } 1270 1271 // Add info about the graphics device 1272 1273 accelerant_device_info deviceInfo; 1274 if (fScreenMode.GetDeviceInfo(deviceInfo) == B_OK 1275 && length < sizeof(text)) { 1276 if (deviceInfo.name[0] && deviceInfo.chipset[0]) { 1277 length += snprintf(text + length, sizeof(text) - length, 1278 "%s%s (%s)", length != 0 ? "\n\n" : "", deviceInfo.name, 1279 deviceInfo.chipset); 1280 } else if (deviceInfo.name[0] || deviceInfo.chipset[0]) { 1281 length += snprintf(text + length, sizeof(text) - length, 1282 "%s%s", length != 0 ? "\n\n" : "", deviceInfo.name[0] 1283 ? deviceInfo.name : deviceInfo.chipset); 1284 } 1285 } 1286 1287 if (text[0]) 1288 fMonitorView->SetToolTip(text); 1289 } 1290 1291 1292 void 1293 ScreenWindow::_UpdateColorLabel() 1294 { 1295 BString string; 1296 string << fSelected.BitsPerPixel() << " " << B_TRANSLATE("bits/pixel"); 1297 fColorsMenu->Superitem()->SetLabel(string.String()); 1298 } 1299 1300 1301 void 1302 ScreenWindow::_Apply() 1303 { 1304 // make checkpoint, so we can undo these changes 1305 fUndoScreenMode.UpdateOriginalModes(); 1306 1307 status_t status = fScreenMode.Set(fSelected); 1308 if (status == B_OK) { 1309 // use the mode that has eventually been set and 1310 // thus we know to be working; it can differ from 1311 // the mode selected by user due to hardware limitation 1312 display_mode newMode; 1313 BScreen screen(this); 1314 screen.GetMode(&newMode); 1315 1316 if (fAllWorkspacesItem->IsMarked()) { 1317 int32 originatingWorkspace = current_workspace(); 1318 for (int32 i = 0; i < count_workspaces(); i++) { 1319 if (i != originatingWorkspace) 1320 screen.SetMode(i, &newMode, true); 1321 } 1322 fBootWorkspaceApplied = true; 1323 } else { 1324 if (current_workspace() == 0) 1325 fBootWorkspaceApplied = true; 1326 } 1327 1328 fActive = fSelected; 1329 1330 // TODO: only show alert when this is an unknown mode 1331 BWindow* window = new AlertWindow(this); 1332 window->Show(); 1333 } else { 1334 char message[256]; 1335 snprintf(message, sizeof(message), 1336 B_TRANSLATE("The screen mode could not be set:\n\t%s\n"), 1337 screen_errors(status)); 1338 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), message, 1339 B_TRANSLATE("OK"), NULL, NULL, 1340 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1341 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1342 alert->Go(); 1343 } 1344 } 1345