| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/display/chromeos/display_configurator.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/sys_info.h" |
| #include "base/time/time.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/display/types/chromeos/display_mode.h" |
| #include "ui/display/types/chromeos/display_snapshot.h" |
| #include "ui/display/types/chromeos/native_display_delegate.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| typedef std::vector<const DisplayMode*> DisplayModeList; |
| |
| // The delay to perform configuration after RRNotify. See the comment |
| // in |Dispatch()|. |
| const int kConfigureDelayMs = 500; |
| |
| // Returns a string describing |state|. |
| std::string DisplayPowerStateToString(chromeos::DisplayPowerState state) { |
| switch (state) { |
| case chromeos::DISPLAY_POWER_ALL_ON: |
| return "ALL_ON"; |
| case chromeos::DISPLAY_POWER_ALL_OFF: |
| return "ALL_OFF"; |
| case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: |
| return "INTERNAL_OFF_EXTERNAL_ON"; |
| case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: |
| return "INTERNAL_ON_EXTERNAL_OFF"; |
| default: |
| return "unknown (" + base::IntToString(state) + ")"; |
| } |
| } |
| |
| // Returns a string describing |state|. |
| std::string DisplayStateToString(MultipleDisplayState state) { |
| switch (state) { |
| case MULTIPLE_DISPLAY_STATE_INVALID: |
| return "INVALID"; |
| case MULTIPLE_DISPLAY_STATE_HEADLESS: |
| return "HEADLESS"; |
| case MULTIPLE_DISPLAY_STATE_SINGLE: |
| return "SINGLE"; |
| case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR: |
| return "DUAL_MIRROR"; |
| case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED: |
| return "DUAL_EXTENDED"; |
| } |
| NOTREACHED() << "Unknown state " << state; |
| return "INVALID"; |
| } |
| |
| // Returns the number of displays in |displays| that should be turned on, per |
| // |state|. If |display_power| is non-NULL, it is updated to contain the |
| // on/off state of each corresponding entry in |displays|. |
| int GetDisplayPower( |
| const std::vector<DisplayConfigurator::DisplayState>& display_states, |
| chromeos::DisplayPowerState state, |
| std::vector<bool>* display_power) { |
| int num_on_displays = 0; |
| if (display_power) |
| display_power->resize(display_states.size()); |
| |
| for (size_t i = 0; i < display_states.size(); ++i) { |
| bool internal = |
| display_states[i].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL; |
| bool on = |
| state == chromeos::DISPLAY_POWER_ALL_ON || |
| (state == chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && |
| !internal) || |
| (state == chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); |
| if (display_power) |
| (*display_power)[i] = on; |
| if (on) |
| num_on_displays++; |
| } |
| return num_on_displays; |
| } |
| |
| } // namespace |
| |
| DisplayConfigurator::CoordinateTransformation::CoordinateTransformation() |
| : x_scale(1.0), |
| x_offset(0.0), |
| y_scale(1.0), |
| y_offset(0.0) {} |
| |
| DisplayConfigurator::DisplayState::DisplayState() |
| : display(NULL), |
| touch_device_id(0), |
| selected_mode(NULL), |
| mirror_mode(NULL) {} |
| |
| bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() { |
| if (configurator_->configure_timer_.get() && |
| configurator_->configure_timer_->IsRunning()) { |
| configurator_->configure_timer_.reset(); |
| configurator_->ConfigureDisplays(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // static |
| const DisplayMode* DisplayConfigurator::FindDisplayModeMatchingSize( |
| const DisplaySnapshot& display, |
| const gfx::Size& size) { |
| const DisplayMode* best_mode = NULL; |
| for (DisplayModeList::const_iterator it = display.modes().begin(); |
| it != display.modes().end(); |
| ++it) { |
| const DisplayMode* mode = *it; |
| |
| if (mode->size() != size) |
| continue; |
| |
| if (!best_mode) { |
| best_mode = mode; |
| continue; |
| } |
| |
| if (mode->is_interlaced()) { |
| if (!best_mode->is_interlaced()) |
| continue; |
| } else { |
| // Reset the best rate if the non interlaced is |
| // found the first time. |
| if (best_mode->is_interlaced()) { |
| best_mode = mode; |
| continue; |
| } |
| } |
| if (mode->refresh_rate() < best_mode->refresh_rate()) |
| continue; |
| |
| best_mode = mode; |
| } |
| |
| return best_mode; |
| } |
| |
| DisplayConfigurator::DisplayConfigurator() |
| : state_controller_(NULL), |
| mirroring_controller_(NULL), |
| is_panel_fitting_enabled_(false), |
| configure_display_(base::SysInfo::IsRunningOnChromeOS()), |
| display_state_(MULTIPLE_DISPLAY_STATE_INVALID), |
| power_state_(chromeos::DISPLAY_POWER_ALL_ON), |
| next_display_protection_client_id_(1) {} |
| |
| DisplayConfigurator::~DisplayConfigurator() { |
| if (native_display_delegate_) |
| native_display_delegate_->RemoveObserver(this); |
| } |
| |
| void DisplayConfigurator::SetDelegatesForTesting( |
| scoped_ptr<NativeDisplayDelegate> display_delegate, |
| scoped_ptr<TouchscreenDelegate> touchscreen_delegate) { |
| DCHECK(!native_display_delegate_); |
| DCHECK(!touchscreen_delegate_); |
| |
| InitializeDelegates(display_delegate.Pass(), touchscreen_delegate.Pass()); |
| configure_display_ = true; |
| } |
| |
| void DisplayConfigurator::SetInitialDisplayPower( |
| chromeos::DisplayPowerState power_state) { |
| DCHECK_EQ(display_state_, MULTIPLE_DISPLAY_STATE_INVALID); |
| power_state_ = power_state; |
| } |
| |
| void DisplayConfigurator::Init(bool is_panel_fitting_enabled) { |
| is_panel_fitting_enabled_ = is_panel_fitting_enabled; |
| if (!configure_display_) |
| return; |
| |
| PlatformInitialize(); |
| } |
| |
| void DisplayConfigurator::InitializeDelegates( |
| scoped_ptr<NativeDisplayDelegate> display_delegate, |
| scoped_ptr<TouchscreenDelegate> touchscreen_delegate) { |
| if (!native_display_delegate_ && !touchscreen_delegate_) { |
| native_display_delegate_ = display_delegate.Pass(); |
| touchscreen_delegate_ = touchscreen_delegate.Pass(); |
| |
| native_display_delegate_->AddObserver(this); |
| } |
| } |
| |
| void DisplayConfigurator::ForceInitialConfigure( |
| uint32_t background_color_argb) { |
| if (!configure_display_) |
| return; |
| |
| native_display_delegate_->GrabServer(); |
| native_display_delegate_->Initialize(); |
| |
| UpdateCachedDisplays(); |
| if (cached_displays_.size() > 1 && background_color_argb) |
| native_display_delegate_->SetBackgroundColor(background_color_argb); |
| const MultipleDisplayState new_state = ChooseDisplayState(power_state_); |
| const bool success = |
| EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_); |
| |
| // Force the DPMS on chrome startup as the driver doesn't always detect |
| // that all displays are on when signing out. |
| native_display_delegate_->ForceDPMSOn(); |
| native_display_delegate_->UngrabServer(); |
| NotifyObservers(success, new_state); |
| } |
| |
| bool DisplayConfigurator::ApplyProtections(const ContentProtections& requests) { |
| for (DisplayStateList::const_iterator it = cached_displays_.begin(); |
| it != cached_displays_.end(); |
| ++it) { |
| uint32_t all_desired = 0; |
| ContentProtections::const_iterator request_it = |
| requests.find(it->display->display_id()); |
| if (request_it != requests.end()) |
| all_desired = request_it->second; |
| switch (it->display->type()) { |
| case DISPLAY_CONNECTION_TYPE_UNKNOWN: |
| return false; |
| // DisplayPort, DVI, and HDMI all support HDCP. |
| case DISPLAY_CONNECTION_TYPE_DISPLAYPORT: |
| case DISPLAY_CONNECTION_TYPE_DVI: |
| case DISPLAY_CONNECTION_TYPE_HDMI: { |
| HDCPState new_desired_state = |
| (all_desired & CONTENT_PROTECTION_METHOD_HDCP) ? |
| HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED; |
| if (!native_display_delegate_->SetHDCPState(*it->display, |
| new_desired_state)) |
| return false; |
| break; |
| } |
| case DISPLAY_CONNECTION_TYPE_INTERNAL: |
| case DISPLAY_CONNECTION_TYPE_VGA: |
| case DISPLAY_CONNECTION_TYPE_NETWORK: |
| // No protections for these types. Do nothing. |
| break; |
| case DISPLAY_CONNECTION_TYPE_NONE: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| DisplayConfigurator::ContentProtectionClientId |
| DisplayConfigurator::RegisterContentProtectionClient() { |
| if (!configure_display_) |
| return kInvalidClientId; |
| |
| return next_display_protection_client_id_++; |
| } |
| |
| void DisplayConfigurator::UnregisterContentProtectionClient( |
| ContentProtectionClientId client_id) { |
| client_protection_requests_.erase(client_id); |
| |
| ContentProtections protections; |
| for (ProtectionRequests::const_iterator it = |
| client_protection_requests_.begin(); |
| it != client_protection_requests_.end(); |
| ++it) { |
| for (ContentProtections::const_iterator it2 = it->second.begin(); |
| it2 != it->second.end(); |
| ++it2) { |
| protections[it2->first] |= it2->second; |
| } |
| } |
| |
| ApplyProtections(protections); |
| } |
| |
| bool DisplayConfigurator::QueryContentProtectionStatus( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| uint32_t* link_mask, |
| uint32_t* protection_mask) { |
| if (!configure_display_) |
| return false; |
| |
| uint32_t enabled = 0; |
| uint32_t unfulfilled = 0; |
| *link_mask = 0; |
| for (DisplayStateList::const_iterator it = cached_displays_.begin(); |
| it != cached_displays_.end(); |
| ++it) { |
| if (it->display->display_id() != display_id) |
| continue; |
| *link_mask |= it->display->type(); |
| switch (it->display->type()) { |
| case DISPLAY_CONNECTION_TYPE_UNKNOWN: |
| return false; |
| // DisplayPort, DVI, and HDMI all support HDCP. |
| case DISPLAY_CONNECTION_TYPE_DISPLAYPORT: |
| case DISPLAY_CONNECTION_TYPE_DVI: |
| case DISPLAY_CONNECTION_TYPE_HDMI: { |
| HDCPState state; |
| if (!native_display_delegate_->GetHDCPState(*it->display, &state)) |
| return false; |
| if (state == HDCP_STATE_ENABLED) |
| enabled |= CONTENT_PROTECTION_METHOD_HDCP; |
| else |
| unfulfilled |= CONTENT_PROTECTION_METHOD_HDCP; |
| break; |
| } |
| case DISPLAY_CONNECTION_TYPE_INTERNAL: |
| case DISPLAY_CONNECTION_TYPE_VGA: |
| case DISPLAY_CONNECTION_TYPE_NETWORK: |
| // No protections for these types. Do nothing. |
| break; |
| case DISPLAY_CONNECTION_TYPE_NONE: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // Don't reveal protections requested by other clients. |
| ProtectionRequests::iterator it = client_protection_requests_.find(client_id); |
| if (it != client_protection_requests_.end()) { |
| uint32_t requested_mask = 0; |
| if (it->second.find(display_id) != it->second.end()) |
| requested_mask = it->second[display_id]; |
| *protection_mask = enabled & ~unfulfilled & requested_mask; |
| } else { |
| *protection_mask = 0; |
| } |
| return true; |
| } |
| |
| bool DisplayConfigurator::EnableContentProtection( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| uint32_t desired_method_mask) { |
| if (!configure_display_) |
| return false; |
| |
| ContentProtections protections; |
| for (ProtectionRequests::const_iterator it = |
| client_protection_requests_.begin(); |
| it != client_protection_requests_.end(); |
| ++it) { |
| for (ContentProtections::const_iterator it2 = it->second.begin(); |
| it2 != it->second.end(); |
| ++it2) { |
| if (it->first == client_id && it2->first == display_id) |
| continue; |
| protections[it2->first] |= it2->second; |
| } |
| } |
| protections[display_id] |= desired_method_mask; |
| |
| if (!ApplyProtections(protections)) |
| return false; |
| |
| if (desired_method_mask == CONTENT_PROTECTION_METHOD_NONE) { |
| if (client_protection_requests_.find(client_id) != |
| client_protection_requests_.end()) { |
| client_protection_requests_[client_id].erase(display_id); |
| if (client_protection_requests_[client_id].size() == 0) |
| client_protection_requests_.erase(client_id); |
| } |
| } else { |
| client_protection_requests_[client_id][display_id] = desired_method_mask; |
| } |
| |
| return true; |
| } |
| |
| std::vector<ui::ColorCalibrationProfile> |
| DisplayConfigurator::GetAvailableColorCalibrationProfiles(int64_t display_id) { |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableDisplayColorCalibration)) { |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| if (cached_displays_[i].display && |
| cached_displays_[i].display->display_id() == display_id) { |
| return native_display_delegate_->GetAvailableColorCalibrationProfiles( |
| *cached_displays_[i].display); |
| } |
| } |
| } |
| |
| return std::vector<ui::ColorCalibrationProfile>(); |
| } |
| |
| bool DisplayConfigurator::SetColorCalibrationProfile( |
| int64_t display_id, |
| ui::ColorCalibrationProfile new_profile) { |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| if (cached_displays_[i].display && |
| cached_displays_[i].display->display_id() == display_id) { |
| return native_display_delegate_->SetColorCalibrationProfile( |
| *cached_displays_[i].display, new_profile); |
| } |
| } |
| |
| return false; |
| } |
| |
| void DisplayConfigurator::PrepareForExit() { |
| configure_display_ = false; |
| } |
| |
| bool DisplayConfigurator::SetDisplayPower( |
| chromeos::DisplayPowerState power_state, |
| int flags) { |
| if (!configure_display_) |
| return false; |
| |
| VLOG(1) << "SetDisplayPower: power_state=" |
| << DisplayPowerStateToString(power_state) << " flags=" << flags |
| << ", configure timer=" |
| << ((configure_timer_.get() && configure_timer_->IsRunning()) ? |
| "Running" : "Stopped"); |
| if (power_state == power_state_ && !(flags & kSetDisplayPowerForceProbe)) |
| return true; |
| |
| native_display_delegate_->GrabServer(); |
| UpdateCachedDisplays(); |
| |
| const MultipleDisplayState new_state = ChooseDisplayState(power_state); |
| bool attempted_change = false; |
| bool success = false; |
| |
| bool only_if_single_internal_display = |
| flags & kSetDisplayPowerOnlyIfSingleInternalDisplay; |
| bool single_internal_display = |
| cached_displays_.size() == 1 && |
| cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL; |
| if (single_internal_display || !only_if_single_internal_display) { |
| success = EnterStateOrFallBackToSoftwareMirroring(new_state, power_state); |
| attempted_change = true; |
| |
| // Force the DPMS on since the driver doesn't always detect that it |
| // should turn on. This is needed when coming back from idle suspend. |
| if (success && power_state != chromeos::DISPLAY_POWER_ALL_OFF) |
| native_display_delegate_->ForceDPMSOn(); |
| } |
| |
| native_display_delegate_->UngrabServer(); |
| if (attempted_change) |
| NotifyObservers(success, new_state); |
| return true; |
| } |
| |
| bool DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) { |
| if (!configure_display_) |
| return false; |
| |
| VLOG(1) << "SetDisplayMode: state=" << DisplayStateToString(new_state); |
| if (display_state_ == new_state) { |
| // Cancel software mirroring if the state is moving from |
| // MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED to |
| // MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED. |
| if (mirroring_controller_ && |
| new_state == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED) |
| mirroring_controller_->SetSoftwareMirroring(false); |
| NotifyObservers(true, new_state); |
| return true; |
| } |
| |
| native_display_delegate_->GrabServer(); |
| UpdateCachedDisplays(); |
| const bool success = |
| EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_); |
| native_display_delegate_->UngrabServer(); |
| |
| NotifyObservers(success, new_state); |
| return success; |
| } |
| |
| void DisplayConfigurator::OnConfigurationChanged() { |
| // Configure displays with |kConfigureDelayMs| delay, |
| // so that time-consuming ConfigureDisplays() won't be called multiple times. |
| if (configure_timer_.get()) { |
| configure_timer_->Reset(); |
| } else { |
| configure_timer_.reset(new base::OneShotTimer<DisplayConfigurator>()); |
| configure_timer_->Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kConfigureDelayMs), |
| this, |
| &DisplayConfigurator::ConfigureDisplays); |
| } |
| } |
| |
| void DisplayConfigurator::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void DisplayConfigurator::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void DisplayConfigurator::SuspendDisplays() { |
| // If the display is off due to user inactivity and there's only a single |
| // internal display connected, switch to the all-on state before |
| // suspending. This shouldn't be very noticeable to the user since the |
| // backlight is off at this point, and doing this lets us resume directly |
| // into the "on" state, which greatly reduces resume times. |
| if (power_state_ == chromeos::DISPLAY_POWER_ALL_OFF) { |
| SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON, |
| kSetDisplayPowerOnlyIfSingleInternalDisplay); |
| |
| // We need to make sure that the monitor configuration we just did actually |
| // completes before we return, because otherwise the X message could be |
| // racing with the HandleSuspendReadiness message. |
| native_display_delegate_->SyncWithServer(); |
| } |
| } |
| |
| void DisplayConfigurator::ResumeDisplays() { |
| // Force probing to ensure that we pick up any changes that were made |
| // while the system was suspended. |
| SetDisplayPower(power_state_, kSetDisplayPowerForceProbe); |
| } |
| |
| void DisplayConfigurator::UpdateCachedDisplays() { |
| std::vector<DisplaySnapshot*> snapshots = |
| native_display_delegate_->GetDisplays(); |
| |
| cached_displays_.clear(); |
| for (size_t i = 0; i < snapshots.size(); ++i) { |
| DisplayState display_state; |
| display_state.display = snapshots[i]; |
| cached_displays_.push_back(display_state); |
| } |
| |
| touchscreen_delegate_->AssociateTouchscreens(&cached_displays_); |
| |
| // Set |selected_mode| fields. |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* display_state = &cached_displays_[i]; |
| if (display_state->display->has_proper_display_id()) { |
| gfx::Size size; |
| if (state_controller_ && |
| state_controller_->GetResolutionForDisplayId( |
| display_state->display->display_id(), &size)) { |
| display_state->selected_mode = |
| FindDisplayModeMatchingSize(*display_state->display, size); |
| } |
| } |
| // Fall back to native mode. |
| if (!display_state->selected_mode) |
| display_state->selected_mode = display_state->display->native_mode(); |
| } |
| |
| // Set |mirror_mode| fields. |
| if (cached_displays_.size() == 2) { |
| bool one_is_internal = |
| cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL; |
| bool two_is_internal = |
| cached_displays_[1].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL; |
| int internal_displays = |
| (one_is_internal ? 1 : 0) + (two_is_internal ? 1 : 0); |
| DCHECK_LT(internal_displays, 2); |
| LOG_IF(WARNING, internal_displays == 2) |
| << "Two internal displays detected."; |
| |
| bool can_mirror = false; |
| for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) { |
| // Try preserving external display's aspect ratio on the first attempt. |
| // If that fails, fall back to the highest matching resolution. |
| bool preserve_aspect = attempt == 0; |
| |
| if (internal_displays == 1) { |
| if (one_is_internal) { |
| can_mirror = FindMirrorMode(&cached_displays_[0], |
| &cached_displays_[1], |
| is_panel_fitting_enabled_, |
| preserve_aspect); |
| } else { |
| DCHECK(two_is_internal); |
| can_mirror = FindMirrorMode(&cached_displays_[1], |
| &cached_displays_[0], |
| is_panel_fitting_enabled_, |
| preserve_aspect); |
| } |
| } else { // if (internal_displays == 0) |
| // No panel fitting for external displays, so fall back to exact match. |
| can_mirror = FindMirrorMode( |
| &cached_displays_[0], &cached_displays_[1], false, preserve_aspect); |
| if (!can_mirror && preserve_aspect) { |
| // FindMirrorMode() will try to preserve aspect ratio of what it |
| // thinks is external display, so if it didn't succeed with one, maybe |
| // it will succeed with the other. This way we will have the correct |
| // aspect ratio on at least one of them. |
| can_mirror = FindMirrorMode(&cached_displays_[1], |
| &cached_displays_[0], |
| false, |
| preserve_aspect); |
| } |
| } |
| } |
| } |
| } |
| |
| bool DisplayConfigurator::FindMirrorMode(DisplayState* internal_display, |
| DisplayState* external_display, |
| bool try_panel_fitting, |
| bool preserve_aspect) { |
| const DisplayMode* internal_native_info = |
| internal_display->display->native_mode(); |
| const DisplayMode* external_native_info = |
| external_display->display->native_mode(); |
| if (!internal_native_info || !external_native_info) |
| return false; |
| |
| // Check if some external display resolution can be mirrored on internal. |
| // Prefer the modes in the order they're present in DisplaySnapshot, assuming |
| // this is the order in which they look better on the monitor. |
| for (DisplayModeList::const_iterator external_it = |
| external_display->display->modes().begin(); |
| external_it != external_display->display->modes().end(); |
| ++external_it) { |
| const DisplayMode& external_info = **external_it; |
| bool is_native_aspect_ratio = |
| external_native_info->size().width() * external_info.size().height() == |
| external_native_info->size().height() * external_info.size().width(); |
| if (preserve_aspect && !is_native_aspect_ratio) |
| continue; // Allow only aspect ratio preserving modes for mirroring. |
| |
| // Try finding an exact match. |
| for (DisplayModeList::const_iterator internal_it = |
| internal_display->display->modes().begin(); |
| internal_it != internal_display->display->modes().end(); |
| ++internal_it) { |
| const DisplayMode& internal_info = **internal_it; |
| if (internal_info.size().width() == external_info.size().width() && |
| internal_info.size().height() == external_info.size().height() && |
| internal_info.is_interlaced() == external_info.is_interlaced()) { |
| internal_display->mirror_mode = *internal_it; |
| external_display->mirror_mode = *external_it; |
| return true; // Mirror mode found. |
| } |
| } |
| |
| // Try to create a matching internal display mode by panel fitting. |
| if (try_panel_fitting) { |
| // We can downscale by 1.125, and upscale indefinitely. Downscaling looks |
| // ugly, so, can fit == can upscale. Also, internal panels don't support |
| // fitting interlaced modes. |
| bool can_fit = internal_native_info->size().width() >= |
| external_info.size().width() && |
| internal_native_info->size().height() >= |
| external_info.size().height() && |
| !external_info.is_interlaced(); |
| if (can_fit) { |
| native_display_delegate_->AddMode(*internal_display->display, |
| *external_it); |
| internal_display->display->add_mode(*external_it); |
| internal_display->mirror_mode = *external_it; |
| external_display->mirror_mode = *external_it; |
| return true; // Mirror mode created. |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void DisplayConfigurator::ConfigureDisplays() { |
| configure_timer_.reset(); |
| |
| if (!configure_display_) |
| return; |
| |
| native_display_delegate_->GrabServer(); |
| UpdateCachedDisplays(); |
| const MultipleDisplayState new_state = ChooseDisplayState(power_state_); |
| const bool success = |
| EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_); |
| native_display_delegate_->UngrabServer(); |
| |
| NotifyObservers(success, new_state); |
| } |
| |
| void DisplayConfigurator::NotifyObservers( |
| bool success, |
| MultipleDisplayState attempted_state) { |
| if (success) { |
| FOR_EACH_OBSERVER( |
| Observer, observers_, OnDisplayModeChanged(cached_displays_)); |
| } else { |
| FOR_EACH_OBSERVER( |
| Observer, observers_, OnDisplayModeChangeFailed(attempted_state)); |
| } |
| } |
| |
| bool DisplayConfigurator::EnterStateOrFallBackToSoftwareMirroring( |
| MultipleDisplayState display_state, |
| chromeos::DisplayPowerState power_state) { |
| bool success = EnterState(display_state, power_state); |
| if (mirroring_controller_) { |
| bool enable_software_mirroring = false; |
| if (!success && display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR) { |
| if (display_state_ != MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED || |
| power_state_ != power_state) |
| EnterState(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED, power_state); |
| enable_software_mirroring = success = |
| display_state_ == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED; |
| } |
| mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring); |
| } |
| return success; |
| } |
| |
| bool DisplayConfigurator::EnterState(MultipleDisplayState display_state, |
| chromeos::DisplayPowerState power_state) { |
| std::vector<bool> display_power; |
| int num_on_displays = |
| GetDisplayPower(cached_displays_, power_state, &display_power); |
| VLOG(1) << "EnterState: display=" << DisplayStateToString(display_state) |
| << " power=" << DisplayPowerStateToString(power_state); |
| |
| // Framebuffer dimensions. |
| gfx::Size size; |
| |
| std::vector<gfx::Point> new_origins(cached_displays_.size(), gfx::Point()); |
| std::vector<const DisplayMode*> new_mode; |
| for (size_t i = 0; i < cached_displays_.size(); ++i) |
| new_mode.push_back(cached_displays_[i].display->current_mode()); |
| |
| switch (display_state) { |
| case MULTIPLE_DISPLAY_STATE_INVALID: |
| NOTREACHED() << "Ignoring request to enter invalid state with " |
| << cached_displays_.size() << " connected display(s)"; |
| return false; |
| case MULTIPLE_DISPLAY_STATE_HEADLESS: |
| if (cached_displays_.size() != 0) { |
| LOG(WARNING) << "Ignoring request to enter headless mode with " |
| << cached_displays_.size() << " connected display(s)"; |
| return false; |
| } |
| break; |
| case MULTIPLE_DISPLAY_STATE_SINGLE: { |
| // If there are multiple displays connected, only one should be turned on. |
| if (cached_displays_.size() != 1 && num_on_displays != 1) { |
| LOG(WARNING) << "Ignoring request to enter single mode with " |
| << cached_displays_.size() << " connected displays and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* state = &cached_displays_[i]; |
| new_mode[i] = display_power[i] ? state->selected_mode : NULL; |
| |
| if (display_power[i] || cached_displays_.size() == 1) { |
| const DisplayMode* mode_info = state->selected_mode; |
| if (!mode_info) |
| return false; |
| if (mode_info->size() == gfx::Size(1024, 768)) { |
| VLOG(1) << "Potentially misdetecting display(1024x768):" |
| << " displays size=" << cached_displays_.size() |
| << ", num_on_displays=" << num_on_displays |
| << ", current size:" << size.width() << "x" << size.height() |
| << ", i=" << i << ", display=" << state->display->ToString() |
| << ", display_mode=" << mode_info->ToString(); |
| } |
| size = mode_info->size(); |
| } |
| } |
| break; |
| } |
| case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR: { |
| if (cached_displays_.size() != 2 || |
| (num_on_displays != 0 && num_on_displays != 2)) { |
| LOG(WARNING) << "Ignoring request to enter mirrored mode with " |
| << cached_displays_.size() << " connected display(s) and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| const DisplayMode* mode_info = cached_displays_[0].mirror_mode; |
| if (!mode_info) |
| return false; |
| size = mode_info->size(); |
| |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* state = &cached_displays_[i]; |
| new_mode[i] = display_power[i] ? state->mirror_mode : NULL; |
| if (state->touch_device_id) { |
| // CTM needs to be calculated if aspect preserving scaling is used. |
| // Otherwise, assume it is full screen, and use identity CTM. |
| if (state->mirror_mode != state->display->native_mode() && |
| state->display->is_aspect_preserving_scaling()) { |
| state->transform = GetMirrorModeCTM(*state); |
| mirrored_display_area_ratio_map_[state->touch_device_id] = |
| GetMirroredDisplayAreaRatio(*state); |
| } |
| } |
| } |
| break; |
| } |
| case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED: { |
| if (cached_displays_.size() != 2 || |
| (num_on_displays != 0 && num_on_displays != 2)) { |
| LOG(WARNING) << "Ignoring request to enter extended mode with " |
| << cached_displays_.size() << " connected display(s) and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* state = &cached_displays_[i]; |
| new_origins[i].set_y(size.height() ? size.height() + kVerticalGap : 0); |
| new_mode[i] = display_power[i] ? state->selected_mode : NULL; |
| |
| // Retain the full screen size even if all displays are off so the |
| // same desktop configuration can be restored when the displays are |
| // turned back on. |
| const DisplayMode* mode_info = cached_displays_[i].selected_mode; |
| if (!mode_info) |
| return false; |
| |
| size.set_width(std::max<int>(size.width(), mode_info->size().width())); |
| size.set_height(size.height() + (size.height() ? kVerticalGap : 0) + |
| mode_info->size().height()); |
| } |
| |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* state = &cached_displays_[i]; |
| if (state->touch_device_id) |
| state->transform = GetExtendedModeCTM(*state, new_origins[i], size); |
| } |
| break; |
| } |
| } |
| |
| // Finally, apply the desired changes. |
| bool all_succeeded = true; |
| if (!cached_displays_.empty()) { |
| native_display_delegate_->CreateFrameBuffer(size); |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| const DisplayState& state = cached_displays_[i]; |
| bool configure_succeeded = false; |
| |
| while (true) { |
| if (native_display_delegate_->Configure( |
| *state.display, new_mode[i], new_origins[i])) { |
| state.display->set_current_mode(new_mode[i]); |
| state.display->set_origin(new_origins[i]); |
| |
| configure_succeeded = true; |
| break; |
| } |
| |
| const DisplayMode* mode_info = new_mode[i]; |
| if (!mode_info) |
| break; |
| |
| // Find the mode with the next-best resolution and see if that can |
| // be set. |
| int best_mode_pixels = 0; |
| |
| int current_mode_pixels = mode_info->size().GetArea(); |
| for (DisplayModeList::const_iterator it = |
| state.display->modes().begin(); |
| it != state.display->modes().end(); |
| it++) { |
| int pixel_count = (*it)->size().GetArea(); |
| if ((pixel_count < current_mode_pixels) && |
| (pixel_count > best_mode_pixels)) { |
| new_mode[i] = *it; |
| best_mode_pixels = pixel_count; |
| } |
| } |
| |
| if (best_mode_pixels == 0) |
| break; |
| } |
| |
| if (configure_succeeded) { |
| if (state.touch_device_id) |
| touchscreen_delegate_->ConfigureCTM(state.touch_device_id, |
| state.transform); |
| } else { |
| all_succeeded = false; |
| } |
| |
| // If we are trying to set mirror mode and one of the modesets fails, |
| // then the two monitors will be mis-matched. In this case, return |
| // false to let the observers be aware. |
| if (display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR && |
| display_power[i] && |
| state.display->current_mode() != state.mirror_mode) |
| all_succeeded = false; |
| } |
| } |
| |
| if (all_succeeded) { |
| display_state_ = display_state; |
| power_state_ = power_state; |
| } |
| return all_succeeded; |
| } |
| |
| MultipleDisplayState DisplayConfigurator::ChooseDisplayState( |
| chromeos::DisplayPowerState power_state) const { |
| int num_on_displays = GetDisplayPower(cached_displays_, power_state, NULL); |
| switch (cached_displays_.size()) { |
| case 0: |
| return MULTIPLE_DISPLAY_STATE_HEADLESS; |
| case 1: |
| return MULTIPLE_DISPLAY_STATE_SINGLE; |
| case 2: { |
| if (num_on_displays == 1) { |
| // If only one display is currently turned on, return the "single" |
| // state so that its native mode will be used. |
| return MULTIPLE_DISPLAY_STATE_SINGLE; |
| } else { |
| if (!state_controller_) |
| return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED; |
| // With either both displays on or both displays off, use one of the |
| // dual modes. |
| std::vector<int64_t> display_ids; |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| // If display id isn't available, switch to extended mode. |
| if (!cached_displays_[i].display->has_proper_display_id()) |
| return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED; |
| display_ids.push_back(cached_displays_[i].display->display_id()); |
| } |
| return state_controller_->GetStateForDisplayIds(display_ids); |
| } |
| } |
| default: |
| NOTREACHED(); |
| } |
| return MULTIPLE_DISPLAY_STATE_INVALID; |
| } |
| |
| DisplayConfigurator::CoordinateTransformation |
| DisplayConfigurator::GetMirrorModeCTM(const DisplayState& display_state) { |
| CoordinateTransformation ctm; // Default to identity |
| const DisplayMode* native_mode_info = display_state.display->native_mode(); |
| const DisplayMode* mirror_mode_info = display_state.mirror_mode; |
| |
| if (!native_mode_info || !mirror_mode_info || |
| native_mode_info->size().height() == 0 || |
| mirror_mode_info->size().height() == 0 || |
| native_mode_info->size().width() == 0 || |
| mirror_mode_info->size().width() == 0) |
| return ctm; |
| |
| float native_mode_ar = static_cast<float>(native_mode_info->size().width()) / |
| static_cast<float>(native_mode_info->size().height()); |
| float mirror_mode_ar = static_cast<float>(mirror_mode_info->size().width()) / |
| static_cast<float>(mirror_mode_info->size().height()); |
| |
| if (mirror_mode_ar > native_mode_ar) { // Letterboxing |
| ctm.x_scale = 1.0; |
| ctm.x_offset = 0.0; |
| ctm.y_scale = mirror_mode_ar / native_mode_ar; |
| ctm.y_offset = (native_mode_ar / mirror_mode_ar - 1.0) * 0.5; |
| return ctm; |
| } |
| if (native_mode_ar > mirror_mode_ar) { // Pillarboxing |
| ctm.y_scale = 1.0; |
| ctm.y_offset = 0.0; |
| ctm.x_scale = native_mode_ar / mirror_mode_ar; |
| ctm.x_offset = (mirror_mode_ar / native_mode_ar - 1.0) * 0.5; |
| return ctm; |
| } |
| |
| return ctm; // Same aspect ratio - return identity |
| } |
| |
| DisplayConfigurator::CoordinateTransformation |
| DisplayConfigurator::GetExtendedModeCTM(const DisplayState& display_state, |
| const gfx::Point& new_origin, |
| const gfx::Size& framebuffer_size) { |
| CoordinateTransformation ctm; // Default to identity |
| const DisplayMode* mode_info = display_state.selected_mode; |
| DCHECK(mode_info); |
| if (!mode_info) |
| return ctm; |
| // An example of how to calculate the CTM. |
| // Suppose we have 2 monitors, the first one has size 1366 x 768. |
| // The second one has size 2560 x 1600 |
| // The total size of framebuffer is 2560 x 2428 |
| // where 2428 = 768 + 60 (hidden gap) + 1600 |
| // and the sceond monitor is translated to Point (0, 828) in the |
| // framebuffer. |
| // X will first map input event location to [0, 2560) x [0, 2428), |
| // then apply CTM on it. |
| // So to compute CTM, for monitor1, we have |
| // x_scale = (1366 - 1) / (2560 - 1) |
| // x_offset = 0 / (2560 - 1) |
| // y_scale = (768 - 1) / (2428 - 1) |
| // y_offset = 0 / (2428 -1) |
| // For Monitor 2, we have |
| // x_scale = (2560 - 1) / (2560 - 1) |
| // x_offset = 0 / (2560 - 1) |
| // y_scale = (1600 - 1) / (2428 - 1) |
| // y_offset = 828 / (2428 -1) |
| // See the unittest DisplayConfiguratorTest.CTMForMultiScreens. |
| ctm.x_scale = static_cast<float>(mode_info->size().width() - 1) / |
| (framebuffer_size.width() - 1); |
| ctm.x_offset = |
| static_cast<float>(new_origin.x()) / (framebuffer_size.width() - 1); |
| ctm.y_scale = static_cast<float>(mode_info->size().height() - 1) / |
| (framebuffer_size.height() - 1); |
| ctm.y_offset = |
| static_cast<float>(new_origin.y()) / (framebuffer_size.height() - 1); |
| return ctm; |
| } |
| |
| float DisplayConfigurator::GetMirroredDisplayAreaRatio( |
| const DisplayState& display_state) { |
| float area_ratio = 1.0f; |
| const DisplayMode* native_mode_info = display_state.display->native_mode(); |
| const DisplayMode* mirror_mode_info = display_state.mirror_mode; |
| |
| if (!native_mode_info || !mirror_mode_info || |
| native_mode_info->size().height() == 0 || |
| mirror_mode_info->size().height() == 0 || |
| native_mode_info->size().width() == 0 || |
| mirror_mode_info->size().width() == 0) |
| return area_ratio; |
| |
| float width_ratio = static_cast<float>(mirror_mode_info->size().width()) / |
| static_cast<float>(native_mode_info->size().width()); |
| float height_ratio = static_cast<float>(mirror_mode_info->size().height()) / |
| static_cast<float>(native_mode_info->size().height()); |
| |
| area_ratio = width_ratio * height_ratio; |
| return area_ratio; |
| } |
| |
| } // namespace ui |