// Copyright (c) 2012 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 "chromeos/display/output_configurator.h" #include <X11/Xlib.h> #include <X11/extensions/Xrandr.h> #include <X11/extensions/XInput2.h> #include "base/bind.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 "chromeos/display/output_util.h" #include "chromeos/display/real_output_configurator_delegate.h" namespace chromeos { namespace { // The delay to perform configuration after RRNotify. See the comment // in |Dispatch()|. const int64 kConfigureDelayMs = 500; // Returns a string describing |state|. std::string DisplayPowerStateToString(DisplayPowerState state) { switch (state) { case DISPLAY_POWER_ALL_ON: return "ALL_ON"; case DISPLAY_POWER_ALL_OFF: return "ALL_OFF"; case DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: return "INTERNAL_OFF_EXTERNAL_ON"; case DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: return "INTERNAL_ON_EXTERNAL_OFF"; default: return "unknown (" + base::IntToString(state) + ")"; } } // Returns a string describing |state|. std::string OutputStateToString(OutputState state) { switch (state) { case STATE_INVALID: return "INVALID"; case STATE_HEADLESS: return "HEADLESS"; case STATE_SINGLE: return "SINGLE"; case STATE_DUAL_MIRROR: return "DUAL_MIRROR"; case STATE_DUAL_EXTENDED: return "DUAL_EXTENDED"; } NOTREACHED() << "Unknown state " << state; return "INVALID"; } // Returns a string representation of OutputSnapshot. std::string OutputSnapshotToString( const OutputConfigurator::OutputSnapshot* output) { return base::StringPrintf( "[type=%d, output=%ld, crtc=%ld, mode=%ld, dim=%dx%d]", output->type, output->output, output->crtc, output->current_mode, static_cast<int>(output->width_mm), static_cast<int>(output->height_mm)); } // Returns a string representation of ModeInfo. std::string ModeInfoToString(const OutputConfigurator::ModeInfo* mode) { return base::StringPrintf("[%dx%d %srate=%f]", mode->width, mode->height, mode->interlaced ? "interlaced " : "", mode->refresh_rate); } // Returns the number of outputs in |outputs| that should be turned on, per // |state|. If |output_power| is non-NULL, it is updated to contain the // on/off state of each corresponding entry in |outputs|. int GetOutputPower( const std::vector<OutputConfigurator::OutputSnapshot>& outputs, DisplayPowerState state, std::vector<bool>* output_power) { int num_on_outputs = 0; if (output_power) output_power->resize(outputs.size()); for (size_t i = 0; i < outputs.size(); ++i) { bool internal = outputs[i].is_internal; bool on = state == DISPLAY_POWER_ALL_ON || (state == DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && !internal) || (state == DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); if (output_power) (*output_power)[i] = on; if (on) num_on_outputs++; } return num_on_outputs; } // Determine if there is an "internal" output and how many outputs are // connected. bool IsProjecting( const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { bool has_internal_output = false; int connected_output_count = outputs.size(); for (size_t i = 0; i < outputs.size(); ++i) has_internal_output |= outputs[i].is_internal; // "Projecting" is defined as having more than 1 output connected while at // least one of them is an internal output. return has_internal_output && (connected_output_count > 1); } } // namespace OutputConfigurator::ModeInfo::ModeInfo() : width(0), height(0), interlaced(false), refresh_rate(0.0) {} OutputConfigurator::ModeInfo::ModeInfo(int width, int height, bool interlaced, float refresh_rate) : width(width), height(height), interlaced(interlaced), refresh_rate(refresh_rate) {} OutputConfigurator::CoordinateTransformation::CoordinateTransformation() : x_scale(1.0), x_offset(0.0), y_scale(1.0), y_offset(0.0) {} OutputConfigurator::OutputSnapshot::OutputSnapshot() : output(None), crtc(None), current_mode(None), native_mode(None), mirror_mode(None), selected_mode(None), x(0), y(0), width_mm(0), height_mm(0), is_internal(false), is_aspect_preserving_scaling(false), type(OUTPUT_TYPE_UNKNOWN), touch_device_id(0), display_id(0), has_display_id(false), index(0) {} OutputConfigurator::OutputSnapshot::~OutputSnapshot() {} void OutputConfigurator::TestApi::SendScreenChangeEvent() { XRRScreenChangeNotifyEvent event = {0}; event.type = xrandr_event_base_ + RRScreenChangeNotify; configurator_->Dispatch(reinterpret_cast<const base::NativeEvent>(&event)); } void OutputConfigurator::TestApi::SendOutputChangeEvent(RROutput output, RRCrtc crtc, RRMode mode, bool connected) { XRROutputChangeNotifyEvent event = {0}; event.type = xrandr_event_base_ + RRNotify; event.subtype = RRNotify_OutputChange; event.output = output; event.crtc = crtc; event.mode = mode; event.connection = connected ? RR_Connected : RR_Disconnected; configurator_->Dispatch(reinterpret_cast<const base::NativeEvent>(&event)); } bool OutputConfigurator::TestApi::TriggerConfigureTimeout() { if (configurator_->configure_timer_.get() && configurator_->configure_timer_->IsRunning()) { configurator_->configure_timer_.reset(); configurator_->ConfigureOutputs(); return true; } else { return false; } } // static const OutputConfigurator::ModeInfo* OutputConfigurator::GetModeInfo( const OutputSnapshot& output, RRMode mode) { if (mode == None) return NULL; ModeInfoMap::const_iterator it = output.mode_infos.find(mode); if (it == output.mode_infos.end()) { LOG(WARNING) << "Unable to find info about mode " << mode << " for output " << output.output; return NULL; } return &it->second; } // static RRMode OutputConfigurator::FindOutputModeMatchingSize( const OutputSnapshot& output, int width, int height) { RRMode found = None; float best_rate = 0; bool non_interlaced_found = false; for (ModeInfoMap::const_iterator it = output.mode_infos.begin(); it != output.mode_infos.end(); ++it) { RRMode mode = it->first; const ModeInfo& info = it->second; if (info.width == width && info.height == height) { if (info.interlaced) { if (non_interlaced_found) continue; } else { // Reset the best rate if the non interlaced is // found the first time. if (!non_interlaced_found) best_rate = info.refresh_rate; non_interlaced_found = true; } if (info.refresh_rate < best_rate) continue; found = mode; best_rate = info.refresh_rate; } } return found; } OutputConfigurator::OutputConfigurator() : state_controller_(NULL), mirroring_controller_(NULL), is_panel_fitting_enabled_(false), configure_display_(base::SysInfo::IsRunningOnChromeOS()), xrandr_event_base_(0), output_state_(STATE_INVALID), power_state_(DISPLAY_POWER_ALL_ON), next_output_protection_client_id_(1) { } OutputConfigurator::~OutputConfigurator() {} void OutputConfigurator::SetDelegateForTesting(scoped_ptr<Delegate> delegate) { delegate_ = delegate.Pass(); configure_display_ = true; } void OutputConfigurator::SetInitialDisplayPower(DisplayPowerState power_state) { DCHECK_EQ(output_state_, STATE_INVALID); power_state_ = power_state; } void OutputConfigurator::Init(bool is_panel_fitting_enabled) { is_panel_fitting_enabled_ = is_panel_fitting_enabled; if (!configure_display_) return; if (!delegate_) delegate_.reset(new RealOutputConfiguratorDelegate()); } void OutputConfigurator::Start(uint32 background_color_argb) { if (!configure_display_) return; delegate_->GrabServer(); delegate_->InitXRandRExtension(&xrandr_event_base_); UpdateCachedOutputs(); if (cached_outputs_.size() > 1 && background_color_argb) delegate_->SetBackgroundColor(background_color_argb); const OutputState new_state = ChooseOutputState(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. delegate_->ForceDPMSOn(); delegate_->UngrabServer(); delegate_->SendProjectingStateToPowerManager(IsProjecting(cached_outputs_)); NotifyObservers(success, new_state); } bool OutputConfigurator::ApplyProtections(const DisplayProtections& requests) { for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin(); it != cached_outputs_.end(); ++it) { RROutput this_id = it->output; uint32_t all_desired = 0; DisplayProtections::const_iterator request_it = requests.find( it->display_id); if (request_it != requests.end()) all_desired = request_it->second; switch (it->type) { case OUTPUT_TYPE_UNKNOWN: return false; // DisplayPort, DVI, and HDMI all support HDCP. case OUTPUT_TYPE_DISPLAYPORT: case OUTPUT_TYPE_DVI: case OUTPUT_TYPE_HDMI: { HDCPState new_desired_state = (all_desired & OUTPUT_PROTECTION_METHOD_HDCP) ? HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED; if (!delegate_->SetHDCPState(this_id, new_desired_state)) return false; break; } case OUTPUT_TYPE_INTERNAL: case OUTPUT_TYPE_VGA: case OUTPUT_TYPE_NETWORK: // No protections for these types. Do nothing. break; case OUTPUT_TYPE_NONE: NOTREACHED(); break; } } return true; } OutputConfigurator::OutputProtectionClientId OutputConfigurator::RegisterOutputProtectionClient() { if (!configure_display_) return kInvalidClientId; return next_output_protection_client_id_++; } void OutputConfigurator::UnregisterOutputProtectionClient( OutputProtectionClientId client_id) { client_protection_requests_.erase(client_id); DisplayProtections protections; for (ProtectionRequests::const_iterator it = client_protection_requests_.begin(); it != client_protection_requests_.end(); ++it) { for (DisplayProtections::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { protections[it2->first] |= it2->second; } } ApplyProtections(protections); } bool OutputConfigurator::QueryOutputProtectionStatus( OutputProtectionClientId client_id, int64 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 (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin(); it != cached_outputs_.end(); ++it) { RROutput this_id = it->output; if (it->display_id != display_id) continue; *link_mask |= it->type; switch (it->type) { case OUTPUT_TYPE_UNKNOWN: return false; // DisplayPort, DVI, and HDMI all support HDCP. case OUTPUT_TYPE_DISPLAYPORT: case OUTPUT_TYPE_DVI: case OUTPUT_TYPE_HDMI: { HDCPState state; if (!delegate_->GetHDCPState(this_id, &state)) return false; if (state == HDCP_STATE_ENABLED) enabled |= OUTPUT_PROTECTION_METHOD_HDCP; else unfulfilled |= OUTPUT_PROTECTION_METHOD_HDCP; break; } case OUTPUT_TYPE_INTERNAL: case OUTPUT_TYPE_VGA: case OUTPUT_TYPE_NETWORK: // No protections for these types. Do nothing. break; case OUTPUT_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 OutputConfigurator::EnableOutputProtection( OutputProtectionClientId client_id, int64 display_id, uint32_t desired_method_mask) { if (!configure_display_) return false; DisplayProtections protections; for (ProtectionRequests::const_iterator it = client_protection_requests_.begin(); it != client_protection_requests_.end(); ++it) { for (DisplayProtections::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 == OUTPUT_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; } void OutputConfigurator::Stop() { configure_display_ = false; } bool OutputConfigurator::SetDisplayPower(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; delegate_->GrabServer(); UpdateCachedOutputs(); const OutputState new_state = ChooseOutputState(power_state); bool attempted_change = false; bool success = false; bool only_if_single_internal_display = flags & kSetDisplayPowerOnlyIfSingleInternalDisplay; bool single_internal_display = cached_outputs_.size() == 1 && cached_outputs_[0].is_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 != DISPLAY_POWER_ALL_OFF) delegate_->ForceDPMSOn(); } delegate_->UngrabServer(); if (attempted_change) NotifyObservers(success, new_state); return true; } bool OutputConfigurator::SetDisplayMode(OutputState new_state) { if (!configure_display_) return false; VLOG(1) << "SetDisplayMode: state=" << OutputStateToString(new_state); if (output_state_ == new_state) { // Cancel software mirroring if the state is moving from // STATE_DUAL_EXTENDED to STATE_DUAL_EXTENDED. if (mirroring_controller_ && new_state == STATE_DUAL_EXTENDED) mirroring_controller_->SetSoftwareMirroring(false); NotifyObservers(true, new_state); return true; } delegate_->GrabServer(); UpdateCachedOutputs(); const bool success = EnterStateOrFallBackToSoftwareMirroring( new_state, power_state_); delegate_->UngrabServer(); NotifyObservers(success, new_state); return success; } bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { if (!configure_display_) return true; if (event->type - xrandr_event_base_ == RRScreenChangeNotify) { VLOG(1) << "Received RRScreenChangeNotify event"; delegate_->UpdateXRandRConfiguration(event); return true; } // Bail out early for everything except RRNotify_OutputChange events // about an output getting connected or disconnected. if (event->type - xrandr_event_base_ != RRNotify) return true; const XRRNotifyEvent* notify_event = reinterpret_cast<XRRNotifyEvent*>(event); if (notify_event->subtype != RRNotify_OutputChange) return true; const XRROutputChangeNotifyEvent* output_change_event = reinterpret_cast<XRROutputChangeNotifyEvent*>(event); const int action = output_change_event->connection; if (action != RR_Connected && action != RR_Disconnected) return true; const bool connected = (action == RR_Connected); VLOG(1) << "Received RRNotify_OutputChange event:" << " output=" << output_change_event->output << " crtc=" << output_change_event->crtc << " mode=" << output_change_event->mode << " action=" << (connected ? "connected" : "disconnected"); bool found_changed_output = false; for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin(); it != cached_outputs_.end(); ++it) { if (it->output == output_change_event->output) { if (connected && it->crtc == output_change_event->crtc && it->current_mode == output_change_event->mode) { VLOG(1) << "Ignoring event describing already-cached state"; return true; } found_changed_output = true; break; } } if (!connected && !found_changed_output) { VLOG(1) << "Ignoring event describing already-disconnected output"; return true; } // Connecting/disconnecting a display may generate multiple events. Defer // configuring outputs to avoid grabbing X and configuring displays // multiple times. ScheduleConfigureOutputs(); return true; } base::EventStatus OutputConfigurator::WillProcessEvent( const base::NativeEvent& event) { // XI_HierarchyChanged events are special. There is no window associated with // these events. So process them directly from here. if (configure_display_ && event->type == GenericEvent && event->xgeneric.evtype == XI_HierarchyChanged) { VLOG(1) << "Received XI_HierarchyChanged event"; // Defer configuring outputs to not stall event processing. // This also takes care of same event being received twice. ScheduleConfigureOutputs(); } return base::EVENT_CONTINUE; } void OutputConfigurator::DidProcessEvent(const base::NativeEvent& event) { } void OutputConfigurator::AddObserver(Observer* observer) { observers_.AddObserver(observer); } void OutputConfigurator::RemoveObserver(Observer* observer) { observers_.RemoveObserver(observer); } void OutputConfigurator::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_ == DISPLAY_POWER_ALL_OFF) { SetDisplayPower(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. delegate_->SyncWithServer(); } } void OutputConfigurator::ResumeDisplays() { // Force probing to ensure that we pick up any changes that were made // while the system was suspended. SetDisplayPower(power_state_, kSetDisplayPowerForceProbe); } void OutputConfigurator::ScheduleConfigureOutputs() { if (configure_timer_.get()) { configure_timer_->Reset(); } else { configure_timer_.reset(new base::OneShotTimer<OutputConfigurator>()); configure_timer_->Start( FROM_HERE, base::TimeDelta::FromMilliseconds(kConfigureDelayMs), this, &OutputConfigurator::ConfigureOutputs); } } void OutputConfigurator::UpdateCachedOutputs() { cached_outputs_ = delegate_->GetOutputs(); // Set |selected_mode| fields. for (size_t i = 0; i < cached_outputs_.size(); ++i) { OutputSnapshot* output = &cached_outputs_[i]; if (output->has_display_id) { int width = 0, height = 0; if (state_controller_ && state_controller_->GetResolutionForDisplayId( output->display_id, &width, &height)) { output->selected_mode = FindOutputModeMatchingSize(*output, width, height); } } // Fall back to native mode. if (output->selected_mode == None) output->selected_mode = output->native_mode; } // Set |mirror_mode| fields. if (cached_outputs_.size() == 2) { bool one_is_internal = cached_outputs_[0].is_internal; bool two_is_internal = cached_outputs_[1].is_internal; int internal_outputs = (one_is_internal ? 1 : 0) + (two_is_internal ? 1 : 0); DCHECK_LT(internal_outputs, 2); LOG_IF(WARNING, internal_outputs == 2) << "Two internal outputs detected."; bool can_mirror = false; for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) { // Try preserving external output's aspect ratio on the first attempt. // If that fails, fall back to the highest matching resolution. bool preserve_aspect = attempt == 0; if (internal_outputs == 1) { if (one_is_internal) { can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[1], is_panel_fitting_enabled_, preserve_aspect); } else { DCHECK(two_is_internal); can_mirror = FindMirrorMode(&cached_outputs_[1], &cached_outputs_[0], is_panel_fitting_enabled_, preserve_aspect); } } else { // if (internal_outputs == 0) // No panel fitting for external outputs, so fall back to exact match. can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[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_outputs_[1], &cached_outputs_[0], false, preserve_aspect); } } } } } bool OutputConfigurator::FindMirrorMode(OutputSnapshot* internal_output, OutputSnapshot* external_output, bool try_panel_fitting, bool preserve_aspect) { const ModeInfo* internal_native_info = GetModeInfo(*internal_output, internal_output->native_mode); const ModeInfo* external_native_info = GetModeInfo(*external_output, external_output->native_mode); if (!internal_native_info || !external_native_info) return false; // Check if some external output resolution can be mirrored on internal. // Prefer the modes in the order that X sorts them, assuming this is the order // in which they look better on the monitor. for (ModeInfoMap::const_iterator external_it = external_output->mode_infos.begin(); external_it != external_output->mode_infos.end(); ++external_it) { const ModeInfo& external_info = external_it->second; bool is_native_aspect_ratio = external_native_info->width * external_info.height == external_native_info->height * external_info.width; if (preserve_aspect && !is_native_aspect_ratio) continue; // Allow only aspect ratio preserving modes for mirroring. // Try finding an exact match. for (ModeInfoMap::const_iterator internal_it = internal_output->mode_infos.begin(); internal_it != internal_output->mode_infos.end(); ++internal_it) { const ModeInfo& internal_info = internal_it->second; if (internal_info.width == external_info.width && internal_info.height == external_info.height && internal_info.interlaced == external_info.interlaced) { internal_output->mirror_mode = internal_it->first; external_output->mirror_mode = external_it->first; return true; // Mirror mode found. } } // Try to create a matching internal output 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->width >= external_info.width && internal_native_info->height >= external_info.height && !external_info.interlaced; if (can_fit) { RRMode mode = external_it->first; delegate_->AddOutputMode(internal_output->output, mode); internal_output->mode_infos.insert(std::make_pair(mode, external_info)); internal_output->mirror_mode = mode; external_output->mirror_mode = mode; return true; // Mirror mode created. } } } return false; } void OutputConfigurator::ConfigureOutputs() { configure_timer_.reset(); delegate_->GrabServer(); UpdateCachedOutputs(); const OutputState new_state = ChooseOutputState(power_state_); const bool success = EnterStateOrFallBackToSoftwareMirroring( new_state, power_state_); delegate_->UngrabServer(); NotifyObservers(success, new_state); delegate_->SendProjectingStateToPowerManager(IsProjecting(cached_outputs_)); } void OutputConfigurator::NotifyObservers(bool success, OutputState attempted_state) { if (success) { FOR_EACH_OBSERVER(Observer, observers_, OnDisplayModeChanged(cached_outputs_)); } else { FOR_EACH_OBSERVER(Observer, observers_, OnDisplayModeChangeFailed(attempted_state)); } } bool OutputConfigurator::EnterStateOrFallBackToSoftwareMirroring( OutputState output_state, DisplayPowerState power_state) { bool success = EnterState(output_state, power_state); if (mirroring_controller_) { bool enable_software_mirroring = false; if (!success && output_state == STATE_DUAL_MIRROR) { if (output_state_ != STATE_DUAL_EXTENDED || power_state_ != power_state) EnterState(STATE_DUAL_EXTENDED, power_state); enable_software_mirroring = success = output_state_ == STATE_DUAL_EXTENDED; } mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring); } return success; } bool OutputConfigurator::EnterState( OutputState output_state, DisplayPowerState power_state) { std::vector<bool> output_power; int num_on_outputs = GetOutputPower( cached_outputs_, power_state, &output_power); VLOG(1) << "EnterState: output=" << OutputStateToString(output_state) << " power=" << DisplayPowerStateToString(power_state); // Framebuffer dimensions. int width = 0, height = 0; std::vector<OutputSnapshot> updated_outputs = cached_outputs_; switch (output_state) { case STATE_INVALID: NOTREACHED() << "Ignoring request to enter invalid state with " << updated_outputs.size() << " connected output(s)"; return false; case STATE_HEADLESS: if (updated_outputs.size() != 0) { LOG(WARNING) << "Ignoring request to enter headless mode with " << updated_outputs.size() << " connected output(s)"; return false; } break; case STATE_SINGLE: { // If there are multiple outputs connected, only one should be turned on. if (updated_outputs.size() != 1 && num_on_outputs != 1) { LOG(WARNING) << "Ignoring request to enter single mode with " << updated_outputs.size() << " connected outputs and " << num_on_outputs << " turned on"; return false; } for (size_t i = 0; i < updated_outputs.size(); ++i) { OutputSnapshot* output = &updated_outputs[i]; output->x = 0; output->y = 0; output->current_mode = output_power[i] ? output->selected_mode : None; if (output_power[i] || updated_outputs.size() == 1) { const ModeInfo* mode_info = GetModeInfo(*output, output->selected_mode); if (!mode_info) return false; if (mode_info->width == 1024 && mode_info->height == 768) { VLOG(1) << "Potentially misdetecting display(1024x768):" << " outputs size=" << updated_outputs.size() << ", num_on_outputs=" << num_on_outputs << ", current size:" << width << "x" << height << ", i=" << i << ", output=" << OutputSnapshotToString(output) << ", mode_info=" << ModeInfoToString(mode_info); } width = mode_info->width; height = mode_info->height; } } break; } case STATE_DUAL_MIRROR: { if (updated_outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { LOG(WARNING) << "Ignoring request to enter mirrored mode with " << updated_outputs.size() << " connected output(s) and " << num_on_outputs << " turned on"; return false; } if (!updated_outputs[0].mirror_mode) return false; const ModeInfo* mode_info = GetModeInfo(updated_outputs[0], updated_outputs[0].mirror_mode); if (!mode_info) return false; width = mode_info->width; height = mode_info->height; for (size_t i = 0; i < updated_outputs.size(); ++i) { OutputSnapshot* output = &updated_outputs[i]; output->x = 0; output->y = 0; output->current_mode = output_power[i] ? output->mirror_mode : None; if (output->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 (output->mirror_mode != output->native_mode && output->is_aspect_preserving_scaling) { output->transform = GetMirrorModeCTM(*output); mirrored_display_area_ratio_map_[output->touch_device_id] = GetMirroredDisplayAreaRatio(*output); } } } break; } case STATE_DUAL_EXTENDED: { if (updated_outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { LOG(WARNING) << "Ignoring request to enter extended mode with " << updated_outputs.size() << " connected output(s) and " << num_on_outputs << " turned on"; return false; } for (size_t i = 0; i < updated_outputs.size(); ++i) { OutputSnapshot* output = &updated_outputs[i]; output->x = 0; output->y = height ? height + kVerticalGap : 0; output->current_mode = output_power[i] ? output->selected_mode : None; // Retain the full screen size even if all outputs are off so the // same desktop configuration can be restored when the outputs are // turned back on. const ModeInfo* mode_info = GetModeInfo(updated_outputs[i], updated_outputs[i].selected_mode); if (!mode_info) return false; width = std::max<int>(width, mode_info->width); height += (height ? kVerticalGap : 0) + mode_info->height; } for (size_t i = 0; i < updated_outputs.size(); ++i) { OutputSnapshot* output = &updated_outputs[i]; if (output->touch_device_id) output->transform = GetExtendedModeCTM(*output, width, height); } break; } } // Finally, apply the desired changes. DCHECK_EQ(cached_outputs_.size(), updated_outputs.size()); if (!updated_outputs.empty()) { delegate_->CreateFrameBuffer(width, height, updated_outputs); for (size_t i = 0; i < updated_outputs.size(); ++i) { const OutputSnapshot& output = updated_outputs[i]; if (delegate_->ConfigureCrtc(output.crtc, output.current_mode, output.output, output.x, output.y)) { if (output.touch_device_id) delegate_->ConfigureCTM(output.touch_device_id, output.transform); cached_outputs_[i] = updated_outputs[i]; } else { LOG(WARNING) << "Unable to configure CRTC " << output.crtc << ":" << " mode=" << output.current_mode << " output=" << output.output << " x=" << output.x << " y=" << output.y; } } } output_state_ = output_state; power_state_ = power_state; return true; } OutputState OutputConfigurator::ChooseOutputState( DisplayPowerState power_state) const { int num_on_outputs = GetOutputPower(cached_outputs_, power_state, NULL); switch (cached_outputs_.size()) { case 0: return STATE_HEADLESS; case 1: return STATE_SINGLE; case 2: { if (num_on_outputs == 1) { // If only one output is currently turned on, return the "single" // state so that its native mode will be used. return STATE_SINGLE; } else { // With either both outputs on or both outputs off, use one of the // dual modes. std::vector<int64> display_ids; for (size_t i = 0; i < cached_outputs_.size(); ++i) { // If display id isn't available, switch to extended mode. if (!cached_outputs_[i].has_display_id) return STATE_DUAL_EXTENDED; display_ids.push_back(cached_outputs_[i].display_id); } return state_controller_->GetStateForDisplayIds(display_ids); } } default: NOTREACHED(); } return STATE_INVALID; } OutputConfigurator::CoordinateTransformation OutputConfigurator::GetMirrorModeCTM( const OutputConfigurator::OutputSnapshot& output) { CoordinateTransformation ctm; // Default to identity const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode); const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode); if (!native_mode_info || !mirror_mode_info || native_mode_info->height == 0 || mirror_mode_info->height == 0 || native_mode_info->width == 0 || mirror_mode_info->width == 0) return ctm; float native_mode_ar = static_cast<float>(native_mode_info->width) / static_cast<float>(native_mode_info->height); float mirror_mode_ar = static_cast<float>(mirror_mode_info->width) / static_cast<float>(mirror_mode_info->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 } OutputConfigurator::CoordinateTransformation OutputConfigurator::GetExtendedModeCTM( const OutputConfigurator::OutputSnapshot& output, int framebuffer_width, int framebuffer_height) { CoordinateTransformation ctm; // Default to identity const ModeInfo* mode_info = GetModeInfo(output, output.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 OutputConfiguratorTest.CTMForMultiScreens. ctm.x_scale = static_cast<float>(mode_info->width - 1) / (framebuffer_width - 1); ctm.x_offset = static_cast<float>(output.x) / (framebuffer_width - 1); ctm.y_scale = static_cast<float>(mode_info->height - 1) / (framebuffer_height - 1); ctm.y_offset = static_cast<float>(output.y) / (framebuffer_height - 1); return ctm; } float OutputConfigurator::GetMirroredDisplayAreaRatio( const OutputConfigurator::OutputSnapshot& output) { float area_ratio = 1.0f; const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode); const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode); if (!native_mode_info || !mirror_mode_info || native_mode_info->height == 0 || mirror_mode_info->height == 0 || native_mode_info->width == 0 || mirror_mode_info->width == 0) return area_ratio; float width_ratio = static_cast<float>(mirror_mode_info->width) / static_cast<float>(native_mode_info->width); float height_ratio = static_cast<float>(mirror_mode_info->height) / static_cast<float>(native_mode_info->height); area_ratio = width_ratio * height_ratio; return area_ratio; } } // namespace chromeos