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