1 /* 2 * Copyright 2005-2011, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 */ 8 9 10 #include "ScreenMode.h" 11 12 #include <stdlib.h> 13 #include <stdio.h> 14 #include <string.h> 15 16 #include <InterfaceDefs.h> 17 #include <String.h> 18 19 #include <compute_display_timing.h> 20 21 22 /* Note, this headers defines a *private* interface to the Radeon accelerant. 23 * It's a solution that works with the current BeOS interface that Haiku 24 * adopted. 25 * However, it's not a nice and clean solution. Don't use this header in any 26 * application if you can avoid it. No other driver is using this, or should 27 * be using this. 28 * It will be replaced as soon as we introduce an updated accelerant interface 29 * which may even happen before R1 hits the streets. 30 */ 31 32 #include "multimon.h" // the usual: DANGER WILL, ROBINSON! 33 34 35 static combine_mode 36 get_combine_mode(display_mode& mode) 37 { 38 if ((mode.flags & B_SCROLL) == 0) 39 return kCombineDisable; 40 41 if (mode.virtual_width == mode.timing.h_display * 2) 42 return kCombineHorizontally; 43 44 if (mode.virtual_height == mode.timing.v_display * 2) 45 return kCombineVertically; 46 47 return kCombineDisable; 48 } 49 50 51 static float 52 get_refresh_rate(display_mode& mode) 53 { 54 // we have to be catious as refresh rate cannot be controlled directly, 55 // so it suffers under rounding errors and hardware restrictions 56 return rint(10 * float(mode.timing.pixel_clock * 1000) 57 / float(mode.timing.h_total * mode.timing.v_total)) / 10.0; 58 } 59 60 61 /*! Helper to sort modes by resolution */ 62 static int 63 compare_mode(const void* _mode1, const void* _mode2) 64 { 65 display_mode *mode1 = (display_mode *)_mode1; 66 display_mode *mode2 = (display_mode *)_mode2; 67 combine_mode combine1, combine2; 68 uint16 width1, width2, height1, height2; 69 70 combine1 = get_combine_mode(*mode1); 71 combine2 = get_combine_mode(*mode2); 72 73 width1 = mode1->virtual_width; 74 height1 = mode1->virtual_height; 75 width2 = mode2->virtual_width; 76 height2 = mode2->virtual_height; 77 78 if (combine1 == kCombineHorizontally) 79 width1 /= 2; 80 if (combine1 == kCombineVertically) 81 height1 /= 2; 82 if (combine2 == kCombineHorizontally) 83 width2 /= 2; 84 if (combine2 == kCombineVertically) 85 height2 /= 2; 86 87 if (width1 != width2) 88 return width1 - width2; 89 90 if (height1 != height2) 91 return height1 - height2; 92 93 return (int)(10 * get_refresh_rate(*mode1) 94 - 10 * get_refresh_rate(*mode2)); 95 } 96 97 98 // #pragma mark - 99 100 101 int32 102 screen_mode::BitsPerPixel() const 103 { 104 switch (space) { 105 case B_RGB32: return 32; 106 case B_RGB24: return 24; 107 case B_RGB16: return 16; 108 case B_RGB15: return 15; 109 case B_CMAP8: return 8; 110 default: return 0; 111 } 112 } 113 114 115 bool 116 screen_mode::operator==(const screen_mode &other) const 117 { 118 return !(*this != other); 119 } 120 121 122 bool 123 screen_mode::operator!=(const screen_mode &other) const 124 { 125 return width != other.width || height != other.height 126 || space != other.space || refresh != other.refresh 127 || combine != other.combine 128 || swap_displays != other.swap_displays 129 || use_laptop_panel != other.use_laptop_panel 130 || tv_standard != other.tv_standard; 131 } 132 133 134 void 135 screen_mode::SetTo(display_mode& mode) 136 { 137 width = mode.virtual_width; 138 height = mode.virtual_height; 139 space = (color_space)mode.space; 140 combine = get_combine_mode(mode); 141 refresh = get_refresh_rate(mode); 142 143 if (combine == kCombineHorizontally) 144 width /= 2; 145 else if (combine == kCombineVertically) 146 height /= 2; 147 148 swap_displays = false; 149 use_laptop_panel = false; 150 tv_standard = 0; 151 } 152 153 154 // #pragma mark - 155 156 157 ScreenMode::ScreenMode(BWindow* window) 158 : 159 fWindow(window), 160 fUpdatedModes(false) 161 { 162 BScreen screen(window); 163 if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) { 164 // sort modes by resolution and refresh to make 165 // the resolution and refresh menu look nicer 166 qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode); 167 } else { 168 fModeList = NULL; 169 fModeCount = 0; 170 } 171 } 172 173 174 ScreenMode::~ScreenMode() 175 { 176 free(fModeList); 177 } 178 179 180 status_t 181 ScreenMode::Set(const screen_mode& mode, int32 workspace) 182 { 183 if (!fUpdatedModes) 184 UpdateOriginalModes(); 185 186 BScreen screen(fWindow); 187 188 if (workspace == ~0) 189 workspace = current_workspace(); 190 191 // TODO: our app_server doesn't fully support workspaces, yet 192 SetSwapDisplays(&screen, mode.swap_displays); 193 SetUseLaptopPanel(&screen, mode.use_laptop_panel); 194 SetTVStandard(&screen, mode.tv_standard); 195 196 display_mode displayMode; 197 if (!_GetDisplayMode(mode, displayMode)) 198 return B_ENTRY_NOT_FOUND; 199 200 return screen.SetMode(workspace, &displayMode, true); 201 } 202 203 204 status_t 205 ScreenMode::Get(screen_mode& mode, int32 workspace) const 206 { 207 display_mode displayMode; 208 BScreen screen(fWindow); 209 210 if (workspace == ~0) 211 workspace = current_workspace(); 212 213 if (screen.GetMode(workspace, &displayMode) != B_OK) 214 return B_ERROR; 215 216 mode.SetTo(displayMode); 217 218 // TODO: our app_server doesn't fully support workspaces, yet 219 if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK) 220 mode.swap_displays = false; 221 if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK) 222 mode.use_laptop_panel = false; 223 if (GetTVStandard(&screen, &mode.tv_standard) != B_OK) 224 mode.tv_standard = 0; 225 226 return B_OK; 227 } 228 229 230 status_t 231 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const 232 { 233 if (workspace == ~0) 234 workspace = current_workspace(); 235 // TODO this should use kMaxWorkspaces 236 else if (workspace > 31) 237 return B_BAD_INDEX; 238 239 mode = fOriginal[workspace]; 240 241 return B_OK; 242 } 243 244 245 status_t 246 ScreenMode::Set(const display_mode& mode, int32 workspace) 247 { 248 if (!fUpdatedModes) 249 UpdateOriginalModes(); 250 251 BScreen screen(fWindow); 252 253 if (workspace == ~0) 254 workspace = current_workspace(); 255 256 // BScreen::SetMode() needs a non-const display_mode 257 display_mode nonConstMode; 258 memcpy(&nonConstMode, &mode, sizeof(display_mode)); 259 return screen.SetMode(workspace, &nonConstMode, true); 260 } 261 262 263 status_t 264 ScreenMode::Get(display_mode& mode, int32 workspace) const 265 { 266 BScreen screen(fWindow); 267 268 if (workspace == ~0) 269 workspace = current_workspace(); 270 271 return screen.GetMode(workspace, &mode); 272 } 273 274 275 /*! This method assumes that you already reverted to the correct number 276 of workspaces. 277 */ 278 status_t 279 ScreenMode::Revert() 280 { 281 if (!fUpdatedModes) 282 return B_ERROR; 283 284 status_t result = B_OK; 285 screen_mode current; 286 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { 287 if (Get(current, workspace) == B_OK && fOriginal[workspace] == current) 288 continue; 289 290 BScreen screen(fWindow); 291 292 // TODO: our app_server doesn't fully support workspaces, yet 293 if (workspace == current_workspace()) { 294 SetSwapDisplays(&screen, fOriginal[workspace].swap_displays); 295 SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel); 296 SetTVStandard(&screen, fOriginal[workspace].tv_standard); 297 } 298 299 result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace], 300 true); 301 if (result != B_OK) 302 break; 303 } 304 305 return result; 306 } 307 308 309 void 310 ScreenMode::UpdateOriginalModes() 311 { 312 BScreen screen(fWindow); 313 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { 314 if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace]) 315 == B_OK) { 316 Get(fOriginal[workspace], workspace); 317 fUpdatedModes = true; 318 } 319 } 320 } 321 322 323 bool 324 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space) 325 { 326 return true; 327 } 328 329 330 status_t 331 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max) 332 { 333 uint32 minClock, maxClock; 334 display_mode displayMode; 335 if (!_GetDisplayMode(mode, displayMode)) 336 return B_ERROR; 337 338 BScreen screen(fWindow); 339 if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK) 340 return B_ERROR; 341 342 uint32 total = displayMode.timing.h_total * displayMode.timing.v_total; 343 min = minClock * 1000.0 / total; 344 max = maxClock * 1000.0 / total; 345 346 return B_OK; 347 } 348 349 350 status_t 351 ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches) 352 { 353 BScreen screen(fWindow); 354 status_t status = screen.GetMonitorInfo(&info); 355 if (status != B_OK) 356 return status; 357 358 if (_diagonalInches != NULL) { 359 *_diagonalInches = round(sqrt(info.width * info.width 360 + info.height * info.height) / 0.254) / 10.0; 361 } 362 363 // Some older CRT monitors do not contain the monitor range information 364 // (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max 365 // horizontal/vertical frequencies being zero. In this case, set the 366 // vertical frequency range to 60..85 Hz. 367 if (info.min_vertical_frequency == 0) { 368 info.min_vertical_frequency = 60; 369 info.max_vertical_frequency = 85; 370 } 371 372 // TODO: If the names aren't sound, we could see if we find/create a 373 // database for the entries with user presentable names; they are fine 374 // for the models I could test with so far. 375 376 uint32 id = (info.vendor[0] << 24) | (info.vendor[1] << 16) 377 | (info.vendor[2] << 8) | (info.vendor[3]); 378 379 switch (id) { 380 case 'ACI\0': 381 strcpy(info.vendor, "Ancor Communications"); 382 break; 383 case 'ADI\0': 384 strcpy(info.vendor, "ADI MicroScan"); 385 break; 386 case 'AAC\0': 387 case 'ACR\0': 388 case 'API\0': 389 strcpy(info.vendor, "Acer"); 390 break; 391 case 'ACT\0': 392 strcpy(info.vendor, "Targa"); 393 break; 394 case 'APP\0': 395 strcpy(info.vendor, "Apple"); 396 break; 397 case 'AUO\0': 398 strcpy(info.vendor, "AU Optronics"); 399 break; 400 case 'BNQ\0': 401 strcpy(info.vendor, "BenQ"); 402 break; 403 case 'CMN\0': 404 strcpy(info.vendor, "Chimei Innolux"); 405 case 'CPL\0': 406 strcpy(info.vendor, "ALFA"); 407 break; 408 case 'CPQ\0': 409 strcpy(info.vendor, "Compaq"); 410 break; 411 case 'DEL\0': 412 strcpy(info.vendor, "Dell"); 413 break; 414 case 'DPC\0': 415 strcpy(info.vendor, "Delta Electronics"); 416 break; 417 case 'DWE\0': 418 strcpy(info.vendor, "Daewoo"); 419 break; 420 case 'ECS\0': 421 strcpy(info.vendor, "Elitegroup"); 422 break; 423 case 'ELS\0': 424 strcpy(info.vendor, "ELSA"); 425 break; 426 case 'EMA\0': 427 strcpy(info.vendor, "eMachines"); 428 break; 429 case 'EIZ\0': 430 case 'ENC\0': 431 strcpy(info.vendor, "Eizo"); 432 break; 433 case 'EPI\0': 434 strcpy(info.vendor, "Envision"); 435 break; 436 case 'FCM\0': 437 strcpy(info.vendor, "Funai Electronics"); 438 break; 439 case 'FUS\0': 440 strcpy(info.vendor, "Fujitsu-Siemens"); 441 break; 442 case 'GSM\0': 443 strcpy(info.vendor, "LG"); 444 break; 445 case 'GWY\0': 446 strcpy(info.vendor, "Gateway"); 447 break; 448 case 'HIQ\0': 449 case 'HEI\0': 450 strcpy(info.vendor, "Hyundai"); 451 break; 452 case 'HIT\0': 453 case 'HTC\0': 454 strcpy(info.vendor, "Hitachi"); 455 break; 456 case 'HSL\0': 457 strcpy(info.vendor, "Hansol"); 458 break; 459 case 'HWP\0': 460 strcpy(info.vendor, "Hewlett Packard"); 461 break; 462 case 'ICL\0': 463 strcpy(info.vendor, "Fujitsu"); 464 break; 465 case 'IVM\0': 466 strcpy(info.vendor, "Iiyama"); 467 break; 468 case 'LEN\0': 469 strcpy(info.vendor, "Lenovo"); 470 break; 471 case 'LPL\0': 472 strcpy(info.vendor, "LG Phillips"); 473 break; 474 case 'LTN\0': 475 strcpy(info.vendor, "Lite-On"); 476 break; 477 case 'MAX\0': 478 strcpy(info.vendor, "Maxdata"); 479 break; 480 case 'MED\0': 481 strcpy(info.vendor, "Medion"); 482 break; 483 case 'MEI\0': 484 strcpy(info.vendor, "Panasonic"); 485 break; 486 case 'MEL\0': 487 strcpy(info.vendor, "Mitsubishi"); 488 break; 489 case 'MIR\0': 490 strcpy(info.vendor, "miro"); 491 break; 492 case 'MTC\0': 493 strcpy(info.vendor, "Mitac"); 494 break; 495 case 'NAN\0': 496 strcpy(info.vendor, "Nanao"); 497 break; 498 case 'NOK\0': 499 strcpy(info.vendor, "Nokia"); 500 break; 501 case 'OQI\0': 502 strcpy(info.vendor, "Optiquest"); 503 break; 504 case 'PHL\0': 505 strcpy(info.vendor, "Philips"); 506 break; 507 case 'PTS\0': 508 strcpy(info.vendor, "Proview"); 509 break; 510 case 'QDS\0': 511 strcpy(info.vendor, "Quanta Display"); 512 break; 513 case 'REL\0': 514 strcpy(info.vendor, "Relisys"); 515 break; 516 case 'SAM\0': 517 strcpy(info.vendor, "Samsung"); 518 break; 519 case 'SDI\0': 520 strcpy(info.vendor, "Samtron"); 521 break; 522 case 'SEC\0': 523 strcpy(info.vendor, "Seiko Epson"); 524 break; 525 case 'SHP\0': 526 strcpy(info.vendor, "Sharp"); 527 break; 528 case 'SNI\0': 529 strcpy(info.vendor, "Siemens"); 530 break; 531 case 'SNY\0': 532 strcpy(info.vendor, "Sony"); 533 break; 534 case 'SPT\0': 535 strcpy(info.vendor, "Sceptre"); 536 break; 537 case 'SRC\0': 538 strcpy(info.vendor, "Shamrock"); 539 break; 540 case 'SUN\0': 541 strcpy(info.vendor, "Sun Microsystems"); 542 break; 543 case 'TAT\0': 544 strcpy(info.vendor, "Tatung"); 545 break; 546 case 'TOS\0': 547 case 'TSB\0': 548 strcpy(info.vendor, "Toshiba"); 549 break; 550 case 'UNK\0': 551 strcpy(info.vendor, "(unknown)"); 552 break; 553 case 'UNM\0': 554 strcpy(info.vendor, "Unisys"); 555 break; 556 case 'VES\0': 557 strcpy(info.vendor, "Vestel"); 558 break; 559 case 'VIZ\0': 560 strcpy(info.vendor, "Vizio"); 561 break; 562 case 'VSC\0': 563 strcpy(info.vendor, "ViewSonic"); 564 break; 565 case 'ZCM\0': 566 strcpy(info.vendor, "Zenith"); 567 break; 568 } 569 570 // Remove extraneous vendor strings and whitespace 571 572 BString name(info.name); 573 name.IReplaceAll(info.vendor, ""); 574 name.Trim(); 575 576 strcpy(info.name, name.String()); 577 578 return B_OK; 579 } 580 581 582 status_t 583 ScreenMode::GetDeviceInfo(accelerant_device_info& info) 584 { 585 BScreen screen(fWindow); 586 return screen.GetDeviceInfo(&info); 587 } 588 589 590 screen_mode 591 ScreenMode::ModeAt(int32 index) 592 { 593 if (index < 0) 594 index = 0; 595 else if (index >= (int32)fModeCount) 596 index = fModeCount - 1; 597 598 screen_mode mode; 599 mode.SetTo(fModeList[index]); 600 601 return mode; 602 } 603 604 605 const display_mode& 606 ScreenMode::DisplayModeAt(int32 index) 607 { 608 if (index < 0) 609 index = 0; 610 else if (index >= (int32)fModeCount) 611 index = fModeCount - 1; 612 613 return fModeList[index]; 614 } 615 616 617 int32 618 ScreenMode::CountModes() 619 { 620 return fModeCount; 621 } 622 623 624 /*! Searches for a similar mode in the reported mode list, and if that does not 625 find a matching mode, it will compute the mode manually using the GTF. 626 */ 627 bool 628 ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode) 629 { 630 uint16 virtualWidth, virtualHeight; 631 int32 bestIndex = -1; 632 float bestDiff = 999; 633 634 virtualWidth = mode.combine == kCombineHorizontally 635 ? mode.width * 2 : mode.width; 636 virtualHeight = mode.combine == kCombineVertically 637 ? mode.height * 2 : mode.height; 638 639 // try to find mode in list provided by driver 640 for (uint32 i = 0; i < fModeCount; i++) { 641 if (fModeList[i].virtual_width != virtualWidth 642 || fModeList[i].virtual_height != virtualHeight 643 || (color_space)fModeList[i].space != mode.space) 644 continue; 645 646 // Accept the mode if the computed refresh rate of the mode is within 647 // 0.6 percent of the refresh rate specified by the caller. Note that 648 // refresh rates computed from mode parameters is not exact; especially 649 // some of the older modes such as 640x480, 800x600, and 1024x768. 650 // The tolerance of 0.6% was obtained by examining the various possible 651 // modes. 652 653 float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh); 654 if (refreshDiff < 0.006 * mode.refresh) { 655 // Accept this mode. 656 displayMode = fModeList[i]; 657 displayMode.h_display_start = 0; 658 displayMode.v_display_start = 0; 659 660 // Since the computed refresh rate of the selected mode might differ 661 // from selected refresh rate by a few tenths (e.g. 60.2 instead of 662 // 60.0), tweak the pixel clock so the the refresh rate of the mode 663 // matches the selected refresh rate. 664 665 displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total 666 * displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5); 667 return true; 668 } 669 670 // Mode not acceptable. 671 672 if (refreshDiff < bestDiff) { 673 bestDiff = refreshDiff; 674 bestIndex = i; 675 } 676 } 677 678 // we didn't find the exact mode, but something very similar? 679 if (bestIndex == -1) 680 return false; 681 682 displayMode = fModeList[bestIndex]; 683 displayMode.h_display_start = 0; 684 displayMode.v_display_start = 0; 685 686 // For the mode selected by the width, height, and refresh rate, compute 687 // the video timing parameters for the mode by using the VESA Generalized 688 // Timing Formula (GTF). 689 compute_display_timing(mode.width, mode.height, mode.refresh, false, 690 &displayMode.timing); 691 692 return true; 693 } 694