| // 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/sys_info.h" |
| #include "base/time/time.h" |
| #include "ui/display/chromeos/apply_content_protection_task.h" |
| #include "ui/display/chromeos/display_layout_manager.h" |
| #include "ui/display/chromeos/display_util.h" |
| #include "ui/display/chromeos/update_display_configuration_task.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; |
| |
| struct DisplayState { |
| DisplaySnapshot* display = nullptr; // Not owned. |
| |
| // User-selected mode for the display. |
| const DisplayMode* selected_mode = nullptr; |
| |
| // Mode used when displaying the same desktop on multiple displays. |
| const DisplayMode* mirror_mode = nullptr; |
| }; |
| |
| void DoNothing(bool status) { |
| } |
| |
| } // namespace |
| |
| const int DisplayConfigurator::kSetDisplayPowerNoFlags = 0; |
| const int DisplayConfigurator::kSetDisplayPowerForceProbe = 1 << 0; |
| const int |
| DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay = 1 << 1; |
| |
| bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() { |
| if (configurator_->configure_timer_.IsRunning()) { |
| configurator_->configure_timer_.user_task().Run(); |
| configurator_->configure_timer_.Stop(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DisplayConfigurator::DisplayLayoutManagerImpl implementation |
| |
| class DisplayConfigurator::DisplayLayoutManagerImpl |
| : public DisplayLayoutManager { |
| public: |
| DisplayLayoutManagerImpl(DisplayConfigurator* configurator); |
| ~DisplayLayoutManagerImpl() override; |
| |
| // DisplayConfigurator::DisplayLayoutManager: |
| SoftwareMirroringController* GetSoftwareMirroringController() const override; |
| StateController* GetStateController() const override; |
| MultipleDisplayState GetDisplayState() const override; |
| chromeos::DisplayPowerState GetPowerState() const override; |
| bool GetDisplayLayout(const std::vector<DisplaySnapshot*>& displays, |
| MultipleDisplayState new_display_state, |
| chromeos::DisplayPowerState new_power_state, |
| std::vector<DisplayConfigureRequest>* requests, |
| gfx::Size* framebuffer_size) const override; |
| DisplayStateList GetDisplayStates() const override; |
| bool IsMirroring() const override; |
| |
| private: |
| // Parses the |displays| into a list of DisplayStates. This effectively adds |
| // |mirror_mode| and |selected_mode| to the returned results. |
| // TODO(dnicoara): Break this into GetSelectedMode() and GetMirrorMode() and |
| // remove DisplayState. |
| std::vector<DisplayState> ParseDisplays( |
| const std::vector<DisplaySnapshot*>& displays) const; |
| |
| const DisplayMode* GetUserSelectedMode(const DisplaySnapshot& display) const; |
| |
| // Helper method for ParseDisplays() that initializes the passed-in |
| // displays' |mirror_mode| fields by looking for a mode in |internal_display| |
| // and |external_display| having the same resolution. Returns false if a |
| // shared mode wasn't found or created. |
| // |
| // |try_panel_fitting| allows creating a panel-fitting mode for |
| // |internal_display| instead of only searching for a matching mode (note that |
| // it may lead to a crash if |internal_display| is not capable of panel |
| // fitting). |
| // |
| // |preserve_aspect| limits the search/creation only to the modes having the |
| // native aspect ratio of |external_display|. |
| bool FindMirrorMode(DisplayState* internal_display, |
| DisplayState* external_display, |
| bool try_panel_fitting, |
| bool preserve_aspect) const; |
| |
| DisplayConfigurator* configurator_; // Not owned. |
| |
| DISALLOW_COPY_AND_ASSIGN(DisplayLayoutManagerImpl); |
| }; |
| |
| DisplayConfigurator::DisplayLayoutManagerImpl::DisplayLayoutManagerImpl( |
| DisplayConfigurator* configurator) |
| : configurator_(configurator) { |
| } |
| |
| DisplayConfigurator::DisplayLayoutManagerImpl::~DisplayLayoutManagerImpl() { |
| } |
| |
| DisplayConfigurator::SoftwareMirroringController* |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetSoftwareMirroringController() |
| const { |
| return configurator_->mirroring_controller_; |
| } |
| |
| DisplayConfigurator::StateController* |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetStateController() const { |
| return configurator_->state_controller_; |
| } |
| |
| MultipleDisplayState |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayState() const { |
| return configurator_->current_display_state_; |
| } |
| |
| chromeos::DisplayPowerState |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetPowerState() const { |
| return configurator_->current_power_state_; |
| } |
| |
| std::vector<DisplayState> |
| DisplayConfigurator::DisplayLayoutManagerImpl::ParseDisplays( |
| const std::vector<DisplaySnapshot*>& snapshots) const { |
| std::vector<DisplayState> cached_displays; |
| for (auto snapshot : snapshots) { |
| DisplayState display_state; |
| display_state.display = snapshot; |
| display_state.selected_mode = GetUserSelectedMode(*snapshot); |
| cached_displays.push_back(display_state); |
| } |
| |
| // 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) |
| << "At least 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) { |
| can_mirror = FindMirrorMode(&cached_displays[one_is_internal ? 0 : 1], |
| &cached_displays[one_is_internal ? 1 : 0], |
| configurator_->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); |
| } |
| } |
| } |
| } |
| |
| return cached_displays; |
| } |
| |
| bool DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayLayout( |
| const std::vector<DisplaySnapshot*>& displays, |
| MultipleDisplayState new_display_state, |
| chromeos::DisplayPowerState new_power_state, |
| std::vector<DisplayConfigureRequest>* requests, |
| gfx::Size* framebuffer_size) const { |
| std::vector<DisplayState> states = ParseDisplays(displays); |
| std::vector<bool> display_power; |
| int num_on_displays = |
| GetDisplayPower(displays, new_power_state, &display_power); |
| VLOG(1) << "EnterState: display=" |
| << MultipleDisplayStateToString(new_display_state) |
| << " power=" << DisplayPowerStateToString(new_power_state); |
| |
| // Framebuffer dimensions. |
| gfx::Size size; |
| |
| for (size_t i = 0; i < displays.size(); ++i) { |
| requests->push_back(DisplayConfigureRequest( |
| displays[i], displays[i]->current_mode(), gfx::Point())); |
| } |
| |
| switch (new_display_state) { |
| case MULTIPLE_DISPLAY_STATE_INVALID: |
| NOTREACHED() << "Ignoring request to enter invalid state with " |
| << displays.size() << " connected display(s)"; |
| return false; |
| case MULTIPLE_DISPLAY_STATE_HEADLESS: |
| if (displays.size() != 0) { |
| LOG(WARNING) << "Ignoring request to enter headless mode with " |
| << 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 (displays.size() != 1 && num_on_displays != 1) { |
| LOG(WARNING) << "Ignoring request to enter single mode with " |
| << displays.size() << " connected displays and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| for (size_t i = 0; i < states.size(); ++i) { |
| const DisplayState* state = &states[i]; |
| (*requests)[i].mode = display_power[i] ? state->selected_mode : NULL; |
| |
| if (display_power[i] || states.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=" << states.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 (states.size() != 2 || |
| (num_on_displays != 0 && num_on_displays != 2)) { |
| LOG(WARNING) << "Ignoring request to enter mirrored mode with " |
| << states.size() << " connected display(s) and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| const DisplayMode* mode_info = states[0].mirror_mode; |
| if (!mode_info) { |
| LOG(WARNING) << "No mirror mode when configuring display: " |
| << states[0].display->ToString(); |
| return false; |
| } |
| size = mode_info->size(); |
| |
| for (size_t i = 0; i < states.size(); ++i) { |
| const DisplayState* state = &states[i]; |
| (*requests)[i].mode = display_power[i] ? state->mirror_mode : NULL; |
| } |
| break; |
| } |
| case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED: |
| case MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED: { |
| if ((new_display_state == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED && |
| states.size() != 2) || |
| (new_display_state == MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED && |
| states.size() <= 2) || |
| (num_on_displays != 0 && |
| num_on_displays != static_cast<int>(displays.size()))) { |
| LOG(WARNING) << "Ignoring request to enter extended mode with " |
| << states.size() << " connected display(s) and " |
| << num_on_displays << " turned on"; |
| return false; |
| } |
| |
| for (size_t i = 0; i < states.size(); ++i) { |
| const DisplayState* state = &states[i]; |
| (*requests)[i].origin.set_y(size.height() ? size.height() + kVerticalGap |
| : 0); |
| (*requests)[i].mode = 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 = states[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; |
| } |
| } |
| DCHECK(new_display_state == MULTIPLE_DISPLAY_STATE_HEADLESS || |
| !size.IsEmpty()); |
| *framebuffer_size = size; |
| return true; |
| } |
| |
| DisplayConfigurator::DisplayStateList |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayStates() const { |
| return configurator_->cached_displays(); |
| } |
| |
| bool DisplayConfigurator::DisplayLayoutManagerImpl::IsMirroring() const { |
| if (GetDisplayState() == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR) |
| return true; |
| |
| return GetSoftwareMirroringController() && |
| GetSoftwareMirroringController()->SoftwareMirroringEnabled(); |
| } |
| |
| const DisplayMode* |
| DisplayConfigurator::DisplayLayoutManagerImpl::GetUserSelectedMode( |
| const DisplaySnapshot& display) const { |
| gfx::Size size; |
| const DisplayMode* selected_mode = nullptr; |
| if (GetStateController() && |
| GetStateController()->GetResolutionForDisplayId(display.display_id(), |
| &size)) { |
| selected_mode = FindDisplayModeMatchingSize(display, size); |
| } |
| |
| // Fall back to native mode. |
| return selected_mode ? selected_mode : display.native_mode(); |
| } |
| |
| bool DisplayConfigurator::DisplayLayoutManagerImpl::FindMirrorMode( |
| DisplayState* internal_display, |
| DisplayState* external_display, |
| bool try_panel_fitting, |
| bool preserve_aspect) const { |
| 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 (const auto* external_mode : external_display->display->modes()) { |
| bool is_native_aspect_ratio = |
| external_native_info->size().width() * external_mode->size().height() == |
| external_native_info->size().height() * external_mode->size().width(); |
| if (preserve_aspect && !is_native_aspect_ratio) |
| continue; // Allow only aspect ratio preserving modes for mirroring. |
| |
| // Try finding an exact match. |
| for (const auto* internal_mode : internal_display->display->modes()) { |
| if (internal_mode->size() == external_mode->size() && |
| internal_mode->is_interlaced() == external_mode->is_interlaced()) { |
| internal_display->mirror_mode = internal_mode; |
| external_display->mirror_mode = external_mode; |
| 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_mode->size().width() && |
| internal_native_info->size().height() >= |
| external_mode->size().height() && |
| !external_mode->is_interlaced(); |
| if (can_fit) { |
| configurator_->native_display_delegate_->AddMode( |
| *internal_display->display, external_mode); |
| internal_display->display->add_mode(external_mode); |
| internal_display->mirror_mode = external_mode; |
| external_display->mirror_mode = external_mode; |
| return true; // Mirror mode created. |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DisplayConfigurator implementation |
| |
| // static |
| const DisplayMode* DisplayConfigurator::FindDisplayModeMatchingSize( |
| const DisplaySnapshot& display, |
| const gfx::Size& size) { |
| const DisplayMode* best_mode = NULL; |
| for (const DisplayMode* mode : display.modes()) { |
| 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()), |
| current_display_state_(MULTIPLE_DISPLAY_STATE_INVALID), |
| current_power_state_(chromeos::DISPLAY_POWER_ALL_ON), |
| requested_display_state_(MULTIPLE_DISPLAY_STATE_INVALID), |
| requested_power_state_(chromeos::DISPLAY_POWER_ALL_ON), |
| requested_power_state_change_(false), |
| requested_power_flags_(kSetDisplayPowerNoFlags), |
| force_configure_(false), |
| next_display_protection_client_id_(1), |
| display_externally_controlled_(false), |
| display_control_changing_(false), |
| displays_suspended_(false), |
| layout_manager_(new DisplayLayoutManagerImpl(this)), |
| weak_ptr_factory_(this) { |
| } |
| |
| DisplayConfigurator::~DisplayConfigurator() { |
| if (native_display_delegate_) |
| native_display_delegate_->RemoveObserver(this); |
| |
| CallAndClearInProgressCallbacks(false); |
| CallAndClearQueuedCallbacks(false); |
| |
| while (!query_protection_callbacks_.empty()) { |
| query_protection_callbacks_.front().Run(QueryProtectionResponse()); |
| query_protection_callbacks_.pop(); |
| } |
| |
| while (!enable_protection_callbacks_.empty()) { |
| enable_protection_callbacks_.front().Run(false); |
| enable_protection_callbacks_.pop(); |
| } |
| } |
| |
| 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(current_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(const DisplayControlCallback& callback) { |
| if (display_control_changing_) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (!display_externally_controlled_) { |
| callback.Run(true); |
| return; |
| } |
| |
| display_control_changing_ = true; |
| native_display_delegate_->TakeDisplayControl( |
| base::Bind(&DisplayConfigurator::OnDisplayControlTaken, |
| weak_ptr_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void DisplayConfigurator::OnDisplayControlTaken( |
| const DisplayControlCallback& callback, |
| bool success) { |
| display_control_changing_ = false; |
| display_externally_controlled_ = !success; |
| if (success) { |
| force_configure_ = true; |
| RunPendingConfiguration(); |
| } |
| |
| callback.Run(success); |
| } |
| |
| void DisplayConfigurator::RelinquishControl( |
| const DisplayControlCallback& callback) { |
| if (display_control_changing_) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (display_externally_controlled_) { |
| callback.Run(true); |
| return; |
| } |
| |
| // For simplicity, just fail if in the middle of a display configuration. |
| if (configuration_task_) { |
| callback.Run(false); |
| return; |
| } |
| |
| // Set the flag early such that an incoming configuration event won't start |
| // while we're releasing control of the displays. |
| display_control_changing_ = true; |
| display_externally_controlled_ = true; |
| native_display_delegate_->RelinquishDisplayControl( |
| base::Bind(&DisplayConfigurator::OnDisplayControlRelinquished, |
| weak_ptr_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void DisplayConfigurator::OnDisplayControlRelinquished( |
| const DisplayControlCallback& callback, |
| bool success) { |
| display_control_changing_ = false; |
| display_externally_controlled_ = success; |
| if (!success) { |
| force_configure_ = true; |
| RunPendingConfiguration(); |
| } |
| |
| callback.Run(success); |
| } |
| |
| void DisplayConfigurator::ForceInitialConfigure( |
| uint32_t background_color_argb) { |
| if (!configure_display_ || display_externally_controlled_) |
| return; |
| |
| native_display_delegate_->Initialize(); |
| |
| // ForceInitialConfigure should be the first configuration so there shouldn't |
| // be anything scheduled. |
| DCHECK(!configuration_task_); |
| |
| configuration_task_.reset(new UpdateDisplayConfigurationTask( |
| native_display_delegate_.get(), layout_manager_.get(), |
| requested_display_state_, requested_power_state_, |
| kSetDisplayPowerForceProbe, background_color_argb, true, |
| base::Bind(&DisplayConfigurator::OnConfigured, |
| weak_ptr_factory_.GetWeakPtr()))); |
| configuration_task_->Run(); |
| } |
| |
| 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 (const auto& requests_pair : client_protection_requests_) { |
| for (const auto& protections_pair : requests_pair.second) { |
| protections[protections_pair.first] |= protections_pair.second; |
| } |
| } |
| |
| enable_protection_callbacks_.push(base::Bind(&DoNothing)); |
| ApplyContentProtectionTask* task = new ApplyContentProtectionTask( |
| layout_manager_.get(), native_display_delegate_.get(), protections, |
| base::Bind(&DisplayConfigurator::OnContentProtectionClientUnregistered, |
| weak_ptr_factory_.GetWeakPtr())); |
| content_protection_tasks_.push( |
| base::Bind(&ApplyContentProtectionTask::Run, base::Owned(task))); |
| |
| if (content_protection_tasks_.size() == 1) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| void DisplayConfigurator::OnContentProtectionClientUnregistered(bool success) { |
| DCHECK(!content_protection_tasks_.empty()); |
| content_protection_tasks_.pop(); |
| |
| DCHECK(!enable_protection_callbacks_.empty()); |
| EnableProtectionCallback callback = enable_protection_callbacks_.front(); |
| enable_protection_callbacks_.pop(); |
| |
| if (!content_protection_tasks_.empty()) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| void DisplayConfigurator::QueryContentProtectionStatus( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| const QueryProtectionCallback& callback) { |
| if (!configure_display_ || display_externally_controlled_) { |
| callback.Run(QueryProtectionResponse()); |
| return; |
| } |
| |
| query_protection_callbacks_.push(callback); |
| QueryContentProtectionTask* task = new QueryContentProtectionTask( |
| layout_manager_.get(), native_display_delegate_.get(), display_id, |
| base::Bind(&DisplayConfigurator::OnContentProtectionQueried, |
| weak_ptr_factory_.GetWeakPtr(), client_id, display_id)); |
| content_protection_tasks_.push( |
| base::Bind(&QueryContentProtectionTask::Run, base::Owned(task))); |
| if (content_protection_tasks_.size() == 1) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| void DisplayConfigurator::OnContentProtectionQueried( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| QueryContentProtectionTask::Response task_response) { |
| QueryProtectionResponse response; |
| response.success = task_response.success; |
| response.link_mask = task_response.link_mask; |
| |
| // Don't reveal protections requested by other clients. |
| ProtectionRequests::iterator it = client_protection_requests_.find(client_id); |
| if (response.success && 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]; |
| response.protection_mask = |
| task_response.enabled & ~task_response.unfulfilled & requested_mask; |
| } |
| |
| DCHECK(!content_protection_tasks_.empty()); |
| content_protection_tasks_.pop(); |
| |
| DCHECK(!query_protection_callbacks_.empty()); |
| QueryProtectionCallback callback = query_protection_callbacks_.front(); |
| query_protection_callbacks_.pop(); |
| callback.Run(response); |
| |
| if (!content_protection_tasks_.empty()) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| void DisplayConfigurator::EnableContentProtection( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| uint32_t desired_method_mask, |
| const EnableProtectionCallback& callback) { |
| if (!configure_display_ || display_externally_controlled_) { |
| callback.Run(false); |
| return; |
| } |
| |
| ContentProtections protections; |
| for (const auto& requests_pair : client_protection_requests_) { |
| for (const auto& protections_pair : requests_pair.second) { |
| if (requests_pair.first == client_id && |
| protections_pair.first == display_id) |
| continue; |
| |
| protections[protections_pair.first] |= protections_pair.second; |
| } |
| } |
| protections[display_id] |= desired_method_mask; |
| |
| enable_protection_callbacks_.push(callback); |
| ApplyContentProtectionTask* task = new ApplyContentProtectionTask( |
| layout_manager_.get(), native_display_delegate_.get(), protections, |
| base::Bind(&DisplayConfigurator::OnContentProtectionEnabled, |
| weak_ptr_factory_.GetWeakPtr(), client_id, display_id, |
| desired_method_mask)); |
| content_protection_tasks_.push( |
| base::Bind(&ApplyContentProtectionTask::Run, base::Owned(task))); |
| if (content_protection_tasks_.size() == 1) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| void DisplayConfigurator::OnContentProtectionEnabled( |
| ContentProtectionClientId client_id, |
| int64_t display_id, |
| uint32_t desired_method_mask, |
| bool success) { |
| DCHECK(!content_protection_tasks_.empty()); |
| content_protection_tasks_.pop(); |
| |
| DCHECK(!enable_protection_callbacks_.empty()); |
| EnableProtectionCallback callback = enable_protection_callbacks_.front(); |
| enable_protection_callbacks_.pop(); |
| |
| if (!success) { |
| callback.Run(false); |
| return; |
| } |
| |
| 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; |
| } |
| |
| callback.Run(true); |
| if (!content_protection_tasks_.empty()) |
| content_protection_tasks_.front().Run(); |
| } |
| |
| std::vector<ui::ColorCalibrationProfile> |
| DisplayConfigurator::GetAvailableColorCalibrationProfiles(int64_t display_id) { |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableDisplayColorCalibration)) { |
| for (const DisplaySnapshot* display : cached_displays_) { |
| if (display->display_id() == display_id) { |
| return native_display_delegate_->GetAvailableColorCalibrationProfiles( |
| *display); |
| } |
| } |
| } |
| |
| return std::vector<ui::ColorCalibrationProfile>(); |
| } |
| |
| bool DisplayConfigurator::SetColorCalibrationProfile( |
| int64_t display_id, |
| ui::ColorCalibrationProfile new_profile) { |
| for (const DisplaySnapshot* display : cached_displays_) { |
| if (display->display_id() == display_id) { |
| return native_display_delegate_->SetColorCalibrationProfile(*display, |
| new_profile); |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DisplayConfigurator::SetGammaRamp( |
| int64_t display_id, |
| const std::vector<GammaRampRGBEntry>& lut) { |
| for (const DisplaySnapshot* display : cached_displays_) { |
| if (display->display_id() == display_id) |
| return native_display_delegate_->SetGammaRamp(*display, lut); |
| } |
| |
| return false; |
| } |
| |
| void DisplayConfigurator::PrepareForExit() { |
| configure_display_ = false; |
| } |
| |
| void DisplayConfigurator::SetDisplayPower( |
| chromeos::DisplayPowerState power_state, |
| int flags, |
| const ConfigurationCallback& callback) { |
| if (!configure_display_ || display_externally_controlled_) { |
| callback.Run(false); |
| return; |
| } |
| |
| VLOG(1) << "SetDisplayPower: power_state=" |
| << DisplayPowerStateToString(power_state) << " flags=" << flags |
| << ", configure timer=" |
| << (configure_timer_.IsRunning() ? "Running" : "Stopped"); |
| if (power_state == requested_power_state_ && |
| !(flags & kSetDisplayPowerForceProbe)) { |
| callback.Run(true); |
| return; |
| } |
| |
| requested_power_state_ = power_state; |
| requested_power_state_change_ = true; |
| requested_power_flags_ = flags; |
| queued_configuration_callbacks_.push_back(callback); |
| |
| RunPendingConfiguration(); |
| } |
| |
| void DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) { |
| if (!configure_display_ || display_externally_controlled_) |
| return; |
| |
| VLOG(1) << "SetDisplayMode: state=" |
| << MultipleDisplayStateToString(new_state); |
| if (current_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; |
| } |
| |
| requested_display_state_ = new_state; |
| |
| RunPendingConfiguration(); |
| } |
| |
| void DisplayConfigurator::OnConfigurationChanged() { |
| // Don't do anything if the displays are currently suspended. Instead we will |
| // probe and reconfigure the displays if necessary in ResumeDisplays(). |
| if (displays_suspended_) { |
| VLOG(1) << "Displays are currently suspended. Not attempting to " |
| << "reconfigure them."; |
| return; |
| } |
| |
| // 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( |
| const ConfigurationCallback& callback) { |
| // 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, callback); |
| |
| // 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(); |
| } else { |
| callback.Run(true); |
| } |
| |
| displays_suspended_ = true; |
| |
| // Stop |configure_timer_| because we will force probe and configure all the |
| // displays at resume time anyway. |
| configure_timer_.Stop(); |
| } |
| |
| void DisplayConfigurator::ResumeDisplays() { |
| displays_suspended_ = false; |
| |
| configure_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kResumeDelayMs), |
| base::Bind(&DisplayConfigurator::RestoreRequestedPowerStateAfterResume, |
| base::Unretained(this))); |
| } |
| |
| void DisplayConfigurator::ConfigureDisplays() { |
| if (!configure_display_ || display_externally_controlled_) |
| return; |
| |
| force_configure_ = true; |
| RunPendingConfiguration(); |
| } |
| |
| void DisplayConfigurator::RunPendingConfiguration() { |
| // Configuration task is currently running. Do not start a second |
| // configuration. |
| if (configuration_task_) |
| return; |
| |
| if (!ShouldRunConfigurationTask()) { |
| LOG(ERROR) << "Called RunPendingConfiguration without any changes" |
| " requested"; |
| CallAndClearQueuedCallbacks(true); |
| return; |
| } |
| |
| configuration_task_.reset(new UpdateDisplayConfigurationTask( |
| native_display_delegate_.get(), layout_manager_.get(), |
| requested_display_state_, requested_power_state_, requested_power_flags_, |
| 0, force_configure_, base::Bind(&DisplayConfigurator::OnConfigured, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| // Reset the flags before running the task; otherwise it may end up scheduling |
| // another configuration. |
| force_configure_ = false; |
| requested_power_flags_ = kSetDisplayPowerNoFlags; |
| requested_power_state_change_ = false; |
| requested_display_state_ = MULTIPLE_DISPLAY_STATE_INVALID; |
| |
| DCHECK(in_progress_configuration_callbacks_.empty()); |
| in_progress_configuration_callbacks_.swap(queued_configuration_callbacks_); |
| |
| configuration_task_->Run(); |
| } |
| |
| void DisplayConfigurator::OnConfigured( |
| bool success, |
| const std::vector<DisplaySnapshot*>& displays, |
| const gfx::Size& framebuffer_size, |
| MultipleDisplayState new_display_state, |
| chromeos::DisplayPowerState new_power_state) { |
| VLOG(1) << "OnConfigured: success=" << success << " new_display_state=" |
| << MultipleDisplayStateToString(new_display_state) |
| << " new_power_state=" << DisplayPowerStateToString(new_power_state); |
| |
| cached_displays_ = displays; |
| if (success) { |
| current_display_state_ = new_display_state; |
| current_power_state_ = new_power_state; |
| |
| // |framebuffer_size| is empty in software mirroring mode, headless mode, |
| // or all displays are off. |
| DCHECK(!framebuffer_size.IsEmpty() || |
| (mirroring_controller_ && |
| mirroring_controller_->SoftwareMirroringEnabled()) || |
| new_display_state == MULTIPLE_DISPLAY_STATE_HEADLESS || |
| new_power_state == chromeos::DISPLAY_POWER_ALL_OFF); |
| |
| if (!framebuffer_size.IsEmpty()) |
| framebuffer_size_ = framebuffer_size; |
| |
| // If the requested power state hasn't changed then make sure that value |
| // gets updated as well since the last requested value may have been |
| // dependent on certain conditions (ie: if only the internal monitor was |
| // present). |
| if (!requested_power_state_change_) |
| requested_power_state_ = new_power_state; |
| } |
| |
| configuration_task_.reset(); |
| NotifyObservers(success, new_display_state); |
| CallAndClearInProgressCallbacks(success); |
| |
| if (success && !configure_timer_.IsRunning() && |
| ShouldRunConfigurationTask()) { |
| configure_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kConfigureDelayMs), |
| this, &DisplayConfigurator::RunPendingConfiguration); |
| } else { |
| // If a new configuration task isn't scheduled respond to all queued |
| // callbacks (for example if requested state is current state). |
| if (!configure_timer_.IsRunning()) |
| CallAndClearQueuedCallbacks(success); |
| } |
| } |
| |
| bool DisplayConfigurator::ShouldRunConfigurationTask() const { |
| if (force_configure_) |
| return true; |
| |
| // Schedule if there is a request to change the display state. |
| if (requested_display_state_ != current_display_state_ && |
| requested_display_state_ != MULTIPLE_DISPLAY_STATE_INVALID) |
| return true; |
| |
| // Schedule if there is a request to change the power state. |
| if (requested_power_state_change_) |
| return true; |
| |
| return false; |
| } |
| |
| void DisplayConfigurator::CallAndClearInProgressCallbacks(bool success) { |
| for (const auto& callback : in_progress_configuration_callbacks_) |
| callback.Run(success); |
| |
| in_progress_configuration_callbacks_.clear(); |
| } |
| |
| void DisplayConfigurator::CallAndClearQueuedCallbacks(bool success) { |
| for (const auto& callback : queued_configuration_callbacks_) |
| callback.Run(success); |
| |
| queued_configuration_callbacks_.clear(); |
| } |
| |
| 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, |
| base::Bind(&DoNothing)); |
| } |
| |
| 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(cached_displays_, |
| attempted_state)); |
| } |
| } |
| |
| } // namespace ui |