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 break; 406 case 'CMO\0': 407 strcpy(info.vendor, "Chi Mei Optoelectronics"); 408 break; 409 case 'COR\0': 410 strcpy(info.vendor, "CPT"); 411 break; 412 case 'CPL\0': 413 strcpy(info.vendor, "ALFA"); 414 break; 415 case 'CPQ\0': 416 strcpy(info.vendor, "Compaq"); 417 break; 418 case 'DEL\0': 419 strcpy(info.vendor, "Dell"); 420 break; 421 case 'DPC\0': 422 strcpy(info.vendor, "Delta Electronics"); 423 break; 424 case 'DWE\0': 425 strcpy(info.vendor, "Daewoo"); 426 break; 427 case 'ECS\0': 428 strcpy(info.vendor, "Elitegroup"); 429 break; 430 case 'ELS\0': 431 strcpy(info.vendor, "ELSA"); 432 break; 433 case 'EMA\0': 434 strcpy(info.vendor, "eMachines"); 435 break; 436 case 'EIZ\0': 437 case 'ENC\0': 438 strcpy(info.vendor, "Eizo"); 439 break; 440 case 'EPI\0': 441 strcpy(info.vendor, "Envision"); 442 break; 443 case 'FCM\0': 444 strcpy(info.vendor, "Funai Electronics"); 445 break; 446 case 'FUS\0': 447 strcpy(info.vendor, "Fujitsu-Siemens"); 448 break; 449 case 'GSM\0': 450 strcpy(info.vendor, "LG"); 451 break; 452 case 'GWY\0': 453 strcpy(info.vendor, "Gateway"); 454 break; 455 case 'HIQ\0': 456 case 'HEI\0': 457 strcpy(info.vendor, "Hyundai"); 458 break; 459 case 'HIT\0': 460 case 'HTC\0': 461 strcpy(info.vendor, "Hitachi"); 462 break; 463 case 'HSL\0': 464 strcpy(info.vendor, "Hansol"); 465 break; 466 case 'HWP\0': 467 strcpy(info.vendor, "Hewlett Packard"); 468 break; 469 case 'ICL\0': 470 strcpy(info.vendor, "Fujitsu"); 471 break; 472 case 'IVM\0': 473 strcpy(info.vendor, "Iiyama"); 474 break; 475 case 'LEN\0': 476 strcpy(info.vendor, "Lenovo"); 477 break; 478 case 'LGD\0': 479 strcpy(info.vendor, "LG Display"); 480 break; 481 case 'LPL\0': 482 strcpy(info.vendor, "LG Philips"); 483 break; 484 case 'LTN\0': 485 strcpy(info.vendor, "Lite-On"); 486 break; 487 case 'MAX\0': 488 strcpy(info.vendor, "Maxdata"); 489 break; 490 case 'MED\0': 491 strcpy(info.vendor, "Medion"); 492 break; 493 case 'MEI\0': 494 strcpy(info.vendor, "Panasonic"); 495 break; 496 case 'MEL\0': 497 strcpy(info.vendor, "Mitsubishi"); 498 break; 499 case 'MIR\0': 500 strcpy(info.vendor, "miro"); 501 break; 502 case 'MTC\0': 503 strcpy(info.vendor, "Mitac"); 504 break; 505 case 'NAN\0': 506 strcpy(info.vendor, "Nanao"); 507 break; 508 case 'NOK\0': 509 strcpy(info.vendor, "Nokia"); 510 break; 511 case 'OQI\0': 512 strcpy(info.vendor, "Optiquest"); 513 break; 514 case 'PHL\0': 515 strcpy(info.vendor, "Philips"); 516 break; 517 case 'PTS\0': 518 strcpy(info.vendor, "Proview"); 519 break; 520 case 'QDS\0': 521 strcpy(info.vendor, "Quanta Display"); 522 break; 523 case 'REL\0': 524 strcpy(info.vendor, "Relisys"); 525 break; 526 case 'SAM\0': 527 strcpy(info.vendor, "Samsung"); 528 break; 529 case 'SDI\0': 530 strcpy(info.vendor, "Samtron"); 531 break; 532 case 'SEC\0': 533 strcpy(info.vendor, "Seiko Epson"); 534 break; 535 case 'SHP\0': 536 strcpy(info.vendor, "Sharp"); 537 break; 538 case 'SNI\0': 539 strcpy(info.vendor, "Siemens"); 540 break; 541 case 'SNY\0': 542 strcpy(info.vendor, "Sony"); 543 break; 544 case 'SPT\0': 545 strcpy(info.vendor, "Sceptre"); 546 break; 547 case 'SRC\0': 548 strcpy(info.vendor, "Shamrock"); 549 break; 550 case 'SUN\0': 551 strcpy(info.vendor, "Sun Microsystems"); 552 break; 553 case 'TAT\0': 554 strcpy(info.vendor, "Tatung"); 555 break; 556 case 'TOS\0': 557 case 'TSB\0': 558 strcpy(info.vendor, "Toshiba"); 559 break; 560 case 'UNK\0': 561 strcpy(info.vendor, "(unknown)"); 562 break; 563 case 'UNM\0': 564 strcpy(info.vendor, "Unisys"); 565 break; 566 case 'VES\0': 567 strcpy(info.vendor, "Vestel"); 568 break; 569 case 'VIZ\0': 570 strcpy(info.vendor, "Vizio"); 571 break; 572 case 'VSC\0': 573 strcpy(info.vendor, "ViewSonic"); 574 break; 575 case 'ZCM\0': 576 strcpy(info.vendor, "Zenith"); 577 break; 578 } 579 580 // Remove extraneous vendor strings and whitespace 581 582 BString name(info.name); 583 name.IReplaceAll(info.vendor, ""); 584 name.Trim(); 585 586 strcpy(info.name, name.String()); 587 588 return B_OK; 589 } 590 591 592 status_t 593 ScreenMode::GetDeviceInfo(accelerant_device_info& info) 594 { 595 BScreen screen(fWindow); 596 return screen.GetDeviceInfo(&info); 597 } 598 599 600 screen_mode 601 ScreenMode::ModeAt(int32 index) 602 { 603 if (index < 0) 604 index = 0; 605 else if (index >= (int32)fModeCount) 606 index = fModeCount - 1; 607 608 screen_mode mode; 609 mode.SetTo(fModeList[index]); 610 611 return mode; 612 } 613 614 615 const display_mode& 616 ScreenMode::DisplayModeAt(int32 index) 617 { 618 if (index < 0) 619 index = 0; 620 else if (index >= (int32)fModeCount) 621 index = fModeCount - 1; 622 623 return fModeList[index]; 624 } 625 626 627 int32 628 ScreenMode::CountModes() 629 { 630 return fModeCount; 631 } 632 633 634 /*! Searches for a similar mode in the reported mode list, and if that does not 635 find a matching mode, it will compute the mode manually using the GTF. 636 */ 637 bool 638 ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode) 639 { 640 uint16 virtualWidth, virtualHeight; 641 int32 bestIndex = -1; 642 float bestDiff = 999; 643 644 virtualWidth = mode.combine == kCombineHorizontally 645 ? mode.width * 2 : mode.width; 646 virtualHeight = mode.combine == kCombineVertically 647 ? mode.height * 2 : mode.height; 648 649 // try to find mode in list provided by driver 650 for (uint32 i = 0; i < fModeCount; i++) { 651 if (fModeList[i].virtual_width != virtualWidth 652 || fModeList[i].virtual_height != virtualHeight 653 || (color_space)fModeList[i].space != mode.space) 654 continue; 655 656 // Accept the mode if the computed refresh rate of the mode is within 657 // 0.6 percent of the refresh rate specified by the caller. Note that 658 // refresh rates computed from mode parameters is not exact; especially 659 // some of the older modes such as 640x480, 800x600, and 1024x768. 660 // The tolerance of 0.6% was obtained by examining the various possible 661 // modes. 662 663 float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh); 664 if (refreshDiff < 0.006 * mode.refresh) { 665 // Accept this mode. 666 displayMode = fModeList[i]; 667 displayMode.h_display_start = 0; 668 displayMode.v_display_start = 0; 669 670 // Since the computed refresh rate of the selected mode might differ 671 // from selected refresh rate by a few tenths (e.g. 60.2 instead of 672 // 60.0), tweak the pixel clock so the the refresh rate of the mode 673 // matches the selected refresh rate. 674 675 displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total 676 * displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5); 677 return true; 678 } 679 680 // Mode not acceptable. 681 682 if (refreshDiff < bestDiff) { 683 bestDiff = refreshDiff; 684 bestIndex = i; 685 } 686 } 687 688 // we didn't find the exact mode, but something very similar? 689 if (bestIndex == -1) 690 return false; 691 692 displayMode = fModeList[bestIndex]; 693 displayMode.h_display_start = 0; 694 displayMode.v_display_start = 0; 695 696 // For the mode selected by the width, height, and refresh rate, compute 697 // the video timing parameters for the mode by using the VESA Generalized 698 // Timing Formula (GTF). 699 compute_display_timing(mode.width, mode.height, mode.refresh, false, 700 &displayMode.timing); 701 702 return true; 703 } 704