/* * Copyright 2005-2011, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de */ #include "ScreenMode.h" #include #include #include #include #include #include #include /* Note, this headers defines a *private* interface to the Radeon accelerant. * It's a solution that works with the current BeOS interface that Haiku * adopted. * However, it's not a nice and clean solution. Don't use this header in any * application if you can avoid it. No other driver is using this, or should * be using this. * It will be replaced as soon as we introduce an updated accelerant interface * which may even happen before R1 hits the streets. */ #include "multimon.h" // the usual: DANGER WILL, ROBINSON! // Vendors.h is generated using these commands (plus a bit of manual editing): /* * wget https://uefi.org/uefi-pnp-export -O Vendors.h.tmp * if [ $? -eq 0 ]; then * sed -E -e 's:::g' -e 's::{ ":g' -e 's:[[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}::g' -e 's: *(<\/td>):\", \":g' -e 's: *<\/td>.<\/tr>:\" },:g' -e 's/"(.*?)", "(.*?)"/\"\2\", \"\1\"/' -e 's/&/\&/' -e "s/'/'/" Vendors.h.tmp | grep -v '<' | sort > Vendors.h * fi * rm Vendors.h.tmp */ struct pnp_id { const char* id; const char* manufacturer; bool operator==(const pnp_id& a) const { return ::strcasecmp(a.id, id) == 0; }; bool operator()(const pnp_id& a, const pnp_id& b) const { return ::strcasecmp(a.id, b.id) < 0; }; }; static const struct pnp_id kPNPIDs[] = { #include "Vendors.h" }; static combine_mode get_combine_mode(display_mode& mode) { if ((mode.flags & B_SCROLL) == 0) return kCombineDisable; if (mode.virtual_width == mode.timing.h_display * 2) return kCombineHorizontally; if (mode.virtual_height == mode.timing.v_display * 2) return kCombineVertically; return kCombineDisable; } static float get_refresh_rate(display_mode& mode) { // we have to be catious as refresh rate cannot be controlled directly, // so it suffers under rounding errors and hardware restrictions return rint(10 * float(mode.timing.pixel_clock * 1000) / float(mode.timing.h_total * mode.timing.v_total)) / 10.0; } /*! Helper to sort modes by resolution */ static int compare_mode(const void* _mode1, const void* _mode2) { display_mode *mode1 = (display_mode *)_mode1; display_mode *mode2 = (display_mode *)_mode2; combine_mode combine1, combine2; uint16 width1, width2, height1, height2; combine1 = get_combine_mode(*mode1); combine2 = get_combine_mode(*mode2); width1 = mode1->virtual_width; height1 = mode1->virtual_height; width2 = mode2->virtual_width; height2 = mode2->virtual_height; if (combine1 == kCombineHorizontally) width1 /= 2; if (combine1 == kCombineVertically) height1 /= 2; if (combine2 == kCombineHorizontally) width2 /= 2; if (combine2 == kCombineVertically) height2 /= 2; if (width1 != width2) return width1 - width2; if (height1 != height2) return height1 - height2; return (int)(10 * get_refresh_rate(*mode1) - 10 * get_refresh_rate(*mode2)); } // #pragma mark - int32 screen_mode::BitsPerPixel() const { switch (space) { case B_RGB32: return 32; case B_RGB24: return 24; case B_RGB16: return 16; case B_RGB15: return 15; case B_CMAP8: return 8; default: return 0; } } bool screen_mode::operator==(const screen_mode &other) const { return !(*this != other); } bool screen_mode::operator!=(const screen_mode &other) const { return width != other.width || height != other.height || space != other.space || refresh != other.refresh || combine != other.combine || swap_displays != other.swap_displays || use_laptop_panel != other.use_laptop_panel || tv_standard != other.tv_standard; } void screen_mode::SetTo(display_mode& mode) { width = mode.virtual_width; height = mode.virtual_height; space = (color_space)mode.space; combine = get_combine_mode(mode); refresh = get_refresh_rate(mode); if (combine == kCombineHorizontally) width /= 2; else if (combine == kCombineVertically) height /= 2; swap_displays = false; use_laptop_panel = false; tv_standard = 0; } // #pragma mark - ScreenMode::ScreenMode(BWindow* window) : fWindow(window), fUpdatedModes(false) { BScreen screen(window); if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) { // sort modes by resolution and refresh to make // the resolution and refresh menu look nicer qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode); } else { fModeList = NULL; fModeCount = 0; } } ScreenMode::~ScreenMode() { free(fModeList); } status_t ScreenMode::Set(const screen_mode& mode, int32 workspace) { if (!fUpdatedModes) UpdateOriginalModes(); BScreen screen(fWindow); if (workspace == ~0) workspace = current_workspace(); // TODO: our app_server doesn't fully support workspaces, yet SetSwapDisplays(&screen, mode.swap_displays); SetUseLaptopPanel(&screen, mode.use_laptop_panel); SetTVStandard(&screen, mode.tv_standard); display_mode displayMode; if (!_GetDisplayMode(mode, displayMode)) return B_ENTRY_NOT_FOUND; return screen.SetMode(workspace, &displayMode, true); } status_t ScreenMode::Get(screen_mode& mode, int32 workspace) const { display_mode displayMode; BScreen screen(fWindow); if (workspace == ~0) workspace = current_workspace(); if (screen.GetMode(workspace, &displayMode) != B_OK) return B_ERROR; mode.SetTo(displayMode); // TODO: our app_server doesn't fully support workspaces, yet if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK) mode.swap_displays = false; if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK) mode.use_laptop_panel = false; if (GetTVStandard(&screen, &mode.tv_standard) != B_OK) mode.tv_standard = 0; return B_OK; } status_t ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const { if (workspace == ~0) workspace = current_workspace(); // TODO this should use kMaxWorkspaces else if (workspace > 31) return B_BAD_INDEX; mode = fOriginal[workspace]; return B_OK; } status_t ScreenMode::Set(const display_mode& mode, int32 workspace) { if (!fUpdatedModes) UpdateOriginalModes(); BScreen screen(fWindow); if (workspace == ~0) workspace = current_workspace(); // BScreen::SetMode() needs a non-const display_mode display_mode nonConstMode; memcpy(&nonConstMode, &mode, sizeof(display_mode)); return screen.SetMode(workspace, &nonConstMode, true); } status_t ScreenMode::Get(display_mode& mode, int32 workspace) const { BScreen screen(fWindow); if (workspace == ~0) workspace = current_workspace(); return screen.GetMode(workspace, &mode); } /*! This method assumes that you already reverted to the correct number of workspaces. */ status_t ScreenMode::Revert() { if (!fUpdatedModes) return B_ERROR; status_t result = B_OK; screen_mode current; for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { if (Get(current, workspace) == B_OK && fOriginal[workspace] == current) continue; BScreen screen(fWindow); // TODO: our app_server doesn't fully support workspaces, yet if (workspace == current_workspace()) { SetSwapDisplays(&screen, fOriginal[workspace].swap_displays); SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel); SetTVStandard(&screen, fOriginal[workspace].tv_standard); } result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace], true); if (result != B_OK) break; } return result; } void ScreenMode::UpdateOriginalModes() { BScreen screen(fWindow); for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace]) == B_OK) { Get(fOriginal[workspace], workspace); fUpdatedModes = true; } } } bool ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space) { return true; } status_t ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max) { uint32 minClock, maxClock; display_mode displayMode; if (!_GetDisplayMode(mode, displayMode)) return B_ERROR; BScreen screen(fWindow); if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK) return B_ERROR; uint32 total = displayMode.timing.h_total * displayMode.timing.v_total; min = minClock * 1000.0 / total; max = maxClock * 1000.0 / total; return B_OK; } const char* ScreenMode::GetManufacturerFromID(const char* id) const { // We assume the array is sorted const size_t numElements = B_COUNT_OF(kPNPIDs); const struct pnp_id key = { id, "dummy" }; const pnp_id* lastElement = kPNPIDs + numElements; const pnp_id* element = std::find(kPNPIDs, lastElement, key); if (element == lastElement) { // can't find the vendor code return NULL; } return element->manufacturer; } status_t ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches) { BScreen screen(fWindow); status_t status = screen.GetMonitorInfo(&info); if (status != B_OK) return status; if (_diagonalInches != NULL) { *_diagonalInches = round(sqrt(info.width * info.width + info.height * info.height) / 0.254) / 10.0; } // Some older CRT monitors do not contain the monitor range information // (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max // horizontal/vertical frequencies being zero. In this case, set the // vertical frequency range to 60..85 Hz. if (info.min_vertical_frequency == 0) { info.min_vertical_frequency = 60; info.max_vertical_frequency = 85; } char vendor[4]; strlcpy(vendor, info.vendor, sizeof(vendor)); const char* vendorString = GetManufacturerFromID(vendor); if (vendorString != NULL) strlcpy(info.vendor, vendorString, sizeof(info.vendor)); // Remove extraneous vendor strings and whitespace BString name(info.name); name.IReplaceAll(info.vendor, ""); name.Trim(); strcpy(info.name, name.String()); return B_OK; } status_t ScreenMode::GetDeviceInfo(accelerant_device_info& info) { BScreen screen(fWindow); return screen.GetDeviceInfo(&info); } screen_mode ScreenMode::ModeAt(int32 index) { if (index < 0) index = 0; else if (index >= (int32)fModeCount) index = fModeCount - 1; screen_mode mode; mode.SetTo(fModeList[index]); return mode; } const display_mode& ScreenMode::DisplayModeAt(int32 index) { if (index < 0) index = 0; else if (index >= (int32)fModeCount) index = fModeCount - 1; return fModeList[index]; } int32 ScreenMode::CountModes() { return fModeCount; } /*! Searches for a similar mode in the reported mode list, and if that does not find a matching mode, it will compute the mode manually using the GTF. */ bool ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode) { uint16 virtualWidth, virtualHeight; int32 bestIndex = -1; float bestDiff = 999; virtualWidth = mode.combine == kCombineHorizontally ? mode.width * 2 : mode.width; virtualHeight = mode.combine == kCombineVertically ? mode.height * 2 : mode.height; // try to find mode in list provided by driver for (uint32 i = 0; i < fModeCount; i++) { if (fModeList[i].virtual_width != virtualWidth || fModeList[i].virtual_height != virtualHeight || (color_space)fModeList[i].space != mode.space) continue; // Accept the mode if the computed refresh rate of the mode is within // 0.6 percent of the refresh rate specified by the caller. Note that // refresh rates computed from mode parameters is not exact; especially // some of the older modes such as 640x480, 800x600, and 1024x768. // The tolerance of 0.6% was obtained by examining the various possible // modes. float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh); if (refreshDiff < 0.006 * mode.refresh) { // Accept this mode. displayMode = fModeList[i]; displayMode.h_display_start = 0; displayMode.v_display_start = 0; // Since the computed refresh rate of the selected mode might differ // from selected refresh rate by a few tenths (e.g. 60.2 instead of // 60.0), tweak the pixel clock so the the refresh rate of the mode // matches the selected refresh rate. displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total * displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5); return true; } // Mode not acceptable. if (refreshDiff < bestDiff) { bestDiff = refreshDiff; bestIndex = i; } } // we didn't find the exact mode, but something very similar? if (bestIndex == -1) return false; displayMode = fModeList[bestIndex]; displayMode.h_display_start = 0; displayMode.v_display_start = 0; // For the mode selected by the width, height, and refresh rate, compute // the video timing parameters for the mode by using the VESA Generalized // Timing Formula (GTF). compute_display_timing(mode.width, mode.height, mode.refresh, false, &displayMode.timing); return true; }