| // 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/display_mode.h" |
| #include "ui/display/types/display_snapshot.h" |
| #include "ui/display/types/native_display_delegate.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| typedef std::vector<const DisplayMode*> DisplayModeList; |
| |
| // The delay to perform configuration after RRNotify. See the comment for |
| // |configure_timer_|. |
| const int kConfigureDelayMs = 500; |
| |
| // The delay spent before reading the display configuration after coming out of |
| // suspend. While coming out of suspend the display state may be updating. This |
| // is used to wait until the hardware had a chance to update the display state |
| // such that we read an up to date state. |
| const int kResumeDelayMs = 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 |
| |
| |
| const int DisplayConfigurator::kSetDisplayPowerNoFlags = 0; |
| const int DisplayConfigurator::kSetDisplayPowerForceProbe = 1 << 0; |
| const int |
| DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay = 1 << 1; |
| |
| DisplayConfigurator::DisplayState::DisplayState() |
| : display(NULL), |
| selected_mode(NULL), |
| mirror_mode(NULL) {} |
| |
| bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() { |
| if (configurator_->configure_timer_.IsRunning()) { |
| configurator_->configure_timer_.user_task().Run(); |
| configurator_->configure_timer_.Stop(); |
| 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 (mode == display.native_mode()) { |
| best_mode = mode; |
| break; |
| } |
| |
| 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), |
| requested_power_state_(chromeos::DISPLAY_POWER_ALL_ON), |
| current_power_state_(chromeos::DISPLAY_POWER_ALL_ON), |
| next_display_protection_client_id_(1), |
| display_externally_controlled_(false) { |
| } |
| |
| DisplayConfigurator::~DisplayConfigurator() { |
| if (native_display_delegate_) |
| native_display_delegate_->RemoveObserver(this); |
| } |
| |
| void DisplayConfigurator::SetDelegateForTesting( |
| scoped_ptr<NativeDisplayDelegate> display_delegate) { |
| DCHECK(!native_display_delegate_); |
| |
| native_display_delegate_ = display_delegate.Pass(); |
| configure_display_ = true; |
| } |
| |
| void DisplayConfigurator::SetInitialDisplayPower( |
| chromeos::DisplayPowerState power_state) { |
| DCHECK_EQ(display_state_, MULTIPLE_DISPLAY_STATE_INVALID); |
| requested_power_state_ = current_power_state_ = power_state; |
| } |
| |
| void DisplayConfigurator::Init(bool is_panel_fitting_enabled) { |
| is_panel_fitting_enabled_ = is_panel_fitting_enabled; |
| if (!configure_display_ || display_externally_controlled_) |
| return; |
| |
| // If the delegate is already initialized don't update it (For example, tests |
| // set their own delegates). |
| if (!native_display_delegate_) { |
| native_display_delegate_ = CreatePlatformNativeDisplayDelegate(); |
| native_display_delegate_->AddObserver(this); |
| } |
| } |
| |
| void DisplayConfigurator::TakeControl() { |
| if (cached_displays_.empty()) |
| return; |
| |
| if (!display_externally_controlled_) |
| return; |
| |
| if (!native_display_delegate_->TakeDisplayControl()) |
| return; |
| |
| display_externally_controlled_ = false; |
| |
| for (DisplayStateList::const_iterator it = cached_displays_.begin(); |
| it != cached_displays_.end(); ++it) { |
| native_display_delegate_->Configure(*(it->display), it->selected_mode, |
| it->display->origin()); |
| } |
| } |
| |
| void DisplayConfigurator::RelinquishControl() { |
| if (display_externally_controlled_) |
| return; |
| |
| display_externally_controlled_ = true; |
| native_display_delegate_->RelinquishDisplayControl(); |
| } |
| |
| void DisplayConfigurator::ForceInitialConfigure( |
| uint32_t background_color_argb) { |
| if (!configure_display_ || display_externally_controlled_) |
| 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( |
| requested_power_state_); |
| const bool success = EnterStateOrFallBackToSoftwareMirroring( |
| new_state, requested_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::IsMirroring() const { |
| return display_state_ == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR || |
| (mirroring_controller_ && |
| mirroring_controller_->SoftwareMirroringEnabled()); |
| } |
| |
| bool DisplayConfigurator::ApplyProtections(const ContentProtections& requests) { |
| for (DisplayStateList::const_iterator it = cached_displays_.begin(); |
| it != cached_displays_.end(); |
| ++it) { |
| uint32_t all_desired = 0; |
| |
| // In mirror mode, protection request of all displays need to be fulfilled. |
| // In non-mirror mode, only request of client's display needs to be |
| // fulfilled. |
| ContentProtections::const_iterator request_it; |
| if (IsMirroring()) { |
| for (request_it = requests.begin(); |
| request_it != requests.end(); |
| ++request_it) |
| all_desired |= request_it->second; |
| } else { |
| 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 current_state; |
| // Need to poll the driver for updates since other applications may |
| // have updated the state. |
| if (!native_display_delegate_->GetHDCPState(*it->display, |
| ¤t_state)) |
| return false; |
| bool current_desired = (current_state != HDCP_STATE_UNDESIRED); |
| bool new_desired = (all_desired & CONTENT_PROTECTION_METHOD_HDCP); |
| // Don't enable again if HDCP is already active. Some buggy drivers |
| // may disable and enable if setting "desired" in active state. |
| if (current_desired != new_desired) { |
| HDCPState new_state = |
| new_desired ? HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED; |
| if (!native_display_delegate_->SetHDCPState(*it->display, new_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_ || display_externally_controlled_) |
| 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_ || display_externally_controlled_) |
| 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) { |
| // Query display if it is in mirror mode or client on the same display. |
| if (!IsMirroring() && 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_ || display_externally_controlled_) |
| 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_ || display_externally_controlled_) |
| return false; |
| |
| VLOG(1) << "SetDisplayPower: power_state=" |
| << DisplayPowerStateToString(power_state) << " flags=" << flags |
| << ", configure timer=" |
| << (configure_timer_.IsRunning() ? "Running" : "Stopped"); |
| if (power_state == current_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 success; |
| } |
| |
| bool DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) { |
| if (!configure_display_ || display_externally_controlled_) |
| 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, requested_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_.IsRunning()) { |
| // Note: when the timer is running it is possible that a different task |
| // (RestoreRequestedPowerStateAfterResume()) is scheduled. In these cases, |
| // prefer the already scheduled task to ConfigureDisplays() since |
| // ConfigureDisplays() performs only basic configuration while |
| // RestoreRequestedPowerStateAfterResume() will perform additional |
| // operations. |
| configure_timer_.Reset(); |
| } else { |
| 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 (requested_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() { |
| configure_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kResumeDelayMs), |
| base::Bind(&DisplayConfigurator::RestoreRequestedPowerStateAfterResume, |
| base::Unretained(this))); |
| } |
| |
| 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); |
| } |
| |
| // Set |selected_mode| fields. |
| for (size_t i = 0; i < cached_displays_.size(); ++i) { |
| DisplayState* display_state = &cached_displays_[i]; |
| 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() { |
| if (!configure_display_ || display_externally_controlled_) |
| return; |
| |
| native_display_delegate_->GrabServer(); |
| UpdateCachedDisplays(); |
| const MultipleDisplayState new_state = ChooseDisplayState( |
| requested_power_state_); |
| const bool success = EnterStateOrFallBackToSoftwareMirroring( |
| new_state, requested_power_state_); |
| native_display_delegate_->UngrabServer(); |
| |
| NotifyObservers(success, new_state); |
| } |
| |
| void DisplayConfigurator::RestoreRequestedPowerStateAfterResume() { |
| // Force probing to ensure that we pick up any changes that were made while |
| // the system was suspended. |
| SetDisplayPower(requested_power_state_, kSetDisplayPowerForceProbe); |
| } |
| |
| 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 || |
| current_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); |
| |
| // Save the requested state so we'll try to use it next time even if we fail. |
| requested_power_state_ = 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) { |
| LOG(WARNING) << "No selected mode when configuring display: " |
| << state->display->ToString(); |
| 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) { |
| LOG(WARNING) << "No mirror mode when configuring display: " |
| << cached_displays_[0].display->ToString(); |
| 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; |
| } |
| 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) { |
| LOG(WARNING) << "No selected mode when configuring display: " |
| << state->display->ToString(); |
| 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()); |
| } |
| 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) |
| 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; |
| current_power_state_ = power_state; |
| framebuffer_size_ = size; |
| } |
| 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) |
| display_ids.push_back(cached_displays_[i].display->display_id()); |
| |
| return state_controller_->GetStateForDisplayIds(display_ids); |
| } |
| } |
| default: |
| NOTREACHED(); |
| } |
| return MULTIPLE_DISPLAY_STATE_INVALID; |
| } |
| |
| } // namespace ui |