diff options
Diffstat (limited to 'chromeos/display/output_configurator.cc')
-rw-r--r-- | chromeos/display/output_configurator.cc | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/chromeos/display/output_configurator.cc b/chromeos/display/output_configurator.cc new file mode 100644 index 0000000..4543a52 --- /dev/null +++ b/chromeos/display/output_configurator.cc @@ -0,0 +1,804 @@ +// 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/dpms.h> +#include <X11/extensions/Xrandr.h> + +// Xlib defines Status as int which causes our include of dbus/bus.h to fail +// when it tries to name an enum Status. Thus, we need to undefine it (note +// that this will cause a problem if code needs to use the Status type). +// RootWindow causes similar problems in that there is a Chromium type with that +// name. +#undef Status +#undef RootWindow + +#include "base/chromeos/chromeos_version.h" +#include "base/logging.h" +#include "base/message_pump_aurax11.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "dbus/bus.h" +#include "dbus/exported_object.h" +#include "dbus/message.h" +#include "dbus/object_path.h" +#include "dbus/object_proxy.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +namespace chromeos { + +namespace { +// DPI measurements. +const float kMmInInch = 25.4; +const float kDpi96 = 96.0; +const float kPixelsToMmScale = kMmInInch / kDpi96; + +// The DPI threshold to detech high density screen. +// Higher DPI than this will use device_scale_factor=2 +// Should be kept in sync with display_change_observer_x11.cc +const unsigned int kHighDensityDIPThreshold = 160; + +// Prefixes for the built-in displays. +const char kInternal_LVDS[] = "LVDS"; +const char kInternal_eDP[] = "eDP"; + +// Gap between screens so cursor at bottom of active display doesn't partially +// appear on top of inactive display. Higher numbers guard against larger +// cursors, but also waste more memory. We will double this gap for screens +// with a device_scale_factor of 2. While this gap will not guard against all +// possible cursors in X, it should handle the ones we actually use. See +// crbug.com/130188 +const int kVerticalGap = 30; + +// TODO: Determine if we need to organize modes in a way which provides better +// than O(n) lookup time. In many call sites, for example, the "next" mode is +// typically what we are looking for so using this helper might be too +// expensive. +static XRRModeInfo* ModeInfoForID(XRRScreenResources* screen, RRMode modeID) { + XRRModeInfo* result = NULL; + for (int i = 0; (i < screen->nmode) && (result == NULL); i++) + if (modeID == screen->modes[i].id) + result = &screen->modes[i]; + + // We can't fail to find a mode referenced from the same screen. + CHECK(result != NULL); + return result; +} + +// Identifies the modes which will be used by the respective outputs when in a +// mirror mode. This means that the two modes will have the same resolution. +// The RROutput IDs |one| and |two| are used to look up the modes and +// |out_one_mode| and |out_two_mode| are the out-parameters for the respective +// modes. +// Returns false if it fails to find a compatible set of modes. +static bool FindMirrorModeForOutputs(Display* display, + XRRScreenResources* screen, + RROutput one, + RROutput two, + RRMode* out_one_mode, + RRMode* out_two_mode) { + XRROutputInfo* primary = XRRGetOutputInfo(display, screen, one); + XRROutputInfo* secondary = XRRGetOutputInfo(display, screen, two); + + int one_index = 0; + int two_index = 0; + bool found = false; + while (!found && + (one_index < primary->nmode) && + (two_index < secondary->nmode)) { + RRMode one_id = primary->modes[one_index]; + RRMode two_id = secondary->modes[two_index]; + XRRModeInfo* one_mode = ModeInfoForID(screen, one_id); + XRRModeInfo* two_mode = ModeInfoForID(screen, two_id); + int one_width = one_mode->width; + int one_height = one_mode->height; + int two_width = two_mode->width; + int two_height = two_mode->height; + if ((one_width == two_width) && (one_height == two_height)) { + *out_one_mode = one_id; + *out_two_mode = two_id; + found = true; + } else { + // The sort order of the modes is NOT by mode area but is sorted by width, + // then by height within each like width. + if (one_width > two_width) { + one_index += 1; + } else if (one_width < two_width) { + two_index += 1; + } else { + if (one_height > two_height) { + one_index += 1; + } else { + two_index += 1; + } + } + } + } + XRRFreeOutputInfo(primary); + XRRFreeOutputInfo(secondary); + return found; +} + +// A helper to call XRRSetCrtcConfig with the given options but some of our +// default output count and rotation arguments. +static void ConfigureCrtc(Display *display, + XRRScreenResources* screen, + RRCrtc crtc, + int x, + int y, + RRMode mode, + RROutput output) { + const Rotation kRotate = RR_Rotate_0; + RROutput* outputs = NULL; + int num_outputs = 0; + + // Check the output and mode argument - if either are None, we should disable. + if ((output != None) && (mode != None)) { + outputs = &output; + num_outputs = 1; + } + + XRRSetCrtcConfig(display, + screen, + crtc, + CurrentTime, + x, + y, + mode, + kRotate, + outputs, + num_outputs); + if (num_outputs == 1) { + // We are enabling a display so make sure it is turned on. + CHECK(DPMSEnable(display)); + CHECK(DPMSForceLevel(display, DPMSModeOn)); + } +} + +// Called to set the frame buffer (underling XRR "screen") size. Has a +// side-effect of disabling all CRTCs. +static void CreateFrameBuffer(Display* display, + XRRScreenResources* screen, + Window window, + int width, + int height) { + // Note that setting the screen size fails if any CRTCs are currently + // pointing into it so disable them all. + for (int i = 0; i < screen->ncrtc; ++i) { + const int x = 0; + const int y = 0; + const RRMode kMode = None; + const RROutput kOutput = None; + + ConfigureCrtc(display, + screen, + screen->crtcs[i], + x, + y, + kMode, + kOutput); + } + int mm_width = width * kPixelsToMmScale; + int mm_height = height * kPixelsToMmScale; + XRRSetScreenSize(display, window, width, height, mm_width, mm_height); +} + +// A helper to get the current CRTC, Mode, and height for a given output. This +// is read from the XRandR configuration and not any of our caches. +static void GetOutputConfiguration(Display* display, + XRRScreenResources* screen, + RROutput output, + RRCrtc* crtc, + RRMode* mode, + int* height) { + XRROutputInfo* output_info = XRRGetOutputInfo(display, screen, output); + CHECK(output_info != NULL); + *crtc = output_info->crtc; + XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display, screen, *crtc); + if (crtc_info != NULL) { + *mode = crtc_info->mode; + *height = crtc_info->height; + XRRFreeCrtcInfo(crtc_info); + } + XRRFreeOutputInfo(output_info); +} + +// A helper to determine the device_scale_factor given pixel width and mm_width. +// This currently only reports two scale factors (1.0 and 2.0) +static float ComputeDeviceScaleFactor(unsigned int width, + unsigned long mm_width) { + float device_scale_factor = 1.0f; + if (mm_width > 0 && (kMmInInch * width / mm_width) > kHighDensityDIPThreshold) + device_scale_factor = 2.0f; + return device_scale_factor; +} + +} // namespace + +bool OutputConfigurator::TryRecacheOutputs(Display* display, + XRRScreenResources* screen) { + bool outputs_did_change = false; + int previous_connected_count = 0; + int new_connected_count = 0; + + if (output_count_ != screen->noutput) { + outputs_did_change = true; + } else { + // The outputs might have changed so compare the connected states in the + // screen to our existing cache. + for (int i = 0; (i < output_count_) && !outputs_did_change; ++i) { + RROutput thisID = screen->outputs[i]; + XRROutputInfo* output = XRRGetOutputInfo(display, screen, thisID); + bool now_connected = (RR_Connected == output->connection); + outputs_did_change = (now_connected != output_cache_[i].is_connected); + XRRFreeOutputInfo(output); + + if (output_cache_[i].is_connected) + previous_connected_count += 1; + if (now_connected) + new_connected_count += 1; + } + } + + if (outputs_did_change) { + // We now know that we need to recache so free and re-alloc the buffer. + output_count_ = screen->noutput; + if (output_count_ == 0) { + output_cache_.reset(NULL); + } else { + // Ideally, this would be allocated inline in the OutputConfigurator + // instance since we support at most 2 connected outputs but this dynamic + // allocation was specifically requested. + output_cache_.reset(new CachedOutputDescription[output_count_]); + } + + // TODO: This approach to finding CRTCs only supports two. Expand on this. + RRCrtc used_crtc = None; + primary_output_index_ = -1; + secondary_output_index_ = -1; + + for (int i = 0; i < output_count_; ++i) { + RROutput this_id = screen->outputs[i]; + XRROutputInfo* output = XRRGetOutputInfo(display, screen, this_id); + bool is_connected = (RR_Connected == output->connection); + RRCrtc crtc = None; + RRMode ideal_mode = None; + int x = 0; + int y = 0; + unsigned long mm_width = output->mm_width; + unsigned long mm_height = output->mm_height; + bool is_internal = false; + + if (is_connected) { + for (int j = 0; (j < output->ncrtc) && (None == crtc); ++j) { + RRCrtc possible = output->crtcs[j]; + if (possible != used_crtc) { + crtc = possible; + used_crtc = possible; + } + } + + const char* name = output->name; + is_internal = + (strncmp(kInternal_LVDS, + name, + arraysize(kInternal_LVDS) - 1) == 0) || + (strncmp(kInternal_eDP, + name, + arraysize(kInternal_eDP) - 1) == 0); + if (output->nmode > 0) + ideal_mode = output->modes[0]; + + if (crtc != None) { + XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screen, crtc); + x = crtcInfo->x; + y = crtcInfo->y; + XRRFreeCrtcInfo(crtcInfo); + } + + // Save this for later mirror mode detection. + if (primary_output_index_ == -1) + primary_output_index_ = i; + else if (secondary_output_index_ == -1) + secondary_output_index_ = i; + } + XRRFreeOutputInfo(output); + + // Now save the cached state for this output (we will default to mirror + // disabled and detect that after we have identified the first two + // connected outputs). + VLOG(1) << "Recache output index: " << i + << ", output id: " << this_id + << ", crtc id: " << crtc + << ", ideal mode id: " << ideal_mode + << ", x: " << x + << ", y: " << y + << ", is connected: " << is_connected + << ", is_internal: " << is_internal + << ", mm_width: " << mm_width + << ", mm_height: " << mm_height; + output_cache_[i].output = this_id; + output_cache_[i].crtc = crtc; + output_cache_[i].mirror_mode = None; + output_cache_[i].ideal_mode = ideal_mode; + output_cache_[i].x = x; + output_cache_[i].y = y; + output_cache_[i].is_connected = is_connected; + output_cache_[i].is_powered_on = true; + output_cache_[i].is_internal = is_internal; + output_cache_[i].mm_width = mm_width; + output_cache_[i].mm_height = mm_height; + } + + // Now, detect the mirror modes if we have two connected outputs. + if ((primary_output_index_ != -1) && (secondary_output_index_ != -1)) { + mirror_supported_ = FindMirrorModeForOutputs( + display, + screen, + output_cache_[primary_output_index_].output, + output_cache_[secondary_output_index_].output, + &output_cache_[primary_output_index_].mirror_mode, + &output_cache_[secondary_output_index_].mirror_mode); + + RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; + RRMode second_mode = output_cache_[secondary_output_index_].mirror_mode; + VLOG(1) << "Mirror mode supported " << mirror_supported_ + << " primary " << primary_mode + << " secondary " << second_mode; + } + } + return outputs_did_change; +} + +OutputConfigurator::OutputConfigurator() + : is_running_on_chrome_os_(base::chromeos::IsRunningOnChromeOS()), + output_count_(0), + output_cache_(NULL), + mirror_supported_(false), + primary_output_index_(-1), + secondary_output_index_(-1), + xrandr_event_base_(0), + output_state_(STATE_INVALID) { + if (is_running_on_chrome_os_) { + // Send the signal to powerd to tell it that we will take over output + // control. + // Note that this can be removed once the legacy powerd support is removed. + chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); + dbus::Bus* bus = manager->GetSystemBus(); + dbus::ExportedObject* remote_object = bus->GetExportedObject( + dbus::ObjectPath(power_manager::kPowerManagerServicePath)); + dbus::Signal signal(power_manager::kPowerManagerInterface, + power_manager::kUseNewMonitorConfigSignal); + CHECK(signal.raw_message() != NULL); + remote_object->SendSignal(&signal); + + // Cache the initial output state. + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + CHECK(display != NULL); + XGrabServer(display); + Window window = DefaultRootWindow(display); + XRRScreenResources* screen = XRRGetScreenResources(display, window); + CHECK(screen != NULL); + bool did_detect_outputs = TryRecacheOutputs(display, screen); + CHECK(did_detect_outputs); + State current_state = InferCurrentState(display, screen); + if (current_state == STATE_INVALID) { + // Unknown state. Transition into the default state. + State state = GetDefaultState(); + UpdateCacheAndXrandrToState(display, screen, window, state); + } else { + // This is a valid state so just save it to |output_state_|. + output_state_ = current_state; + } + // Find xrandr_event_base_ since we need it to interpret events, later. + int error_base_ignored = 0; + XRRQueryExtension(display, &xrandr_event_base_, &error_base_ignored); + // Relinquish X resources. + XRRFreeScreenResources(screen); + XUngrabServer(display); + CheckIsProjectingAndNotify(); + } +} + +OutputConfigurator::~OutputConfigurator() { +} + +void OutputConfigurator::UpdateCacheAndXrandrToState( + Display* display, + XRRScreenResources* screen, + Window window, + State new_state) { + // Default rules: + // - single display = rebuild framebuffer and set to ideal_mode. + // - multi display = rebuild framebuffer and set to mirror_mode. + + // First, calculate the width and height of the framebuffer (we could retain + // the existing buffer, if it isn't resizing, but that causes an odd display + // state where the CRTCs are repositioned over the root windows before Chrome + // can move them). It is a feature worth considering, though, and wouldn't + // be difficult to implement (just check the current framebuffer size before + // changing it). + int width = 0; + int height = 0; + int primary_height = 0; + int secondary_height = 0; + int vertical_gap = 0; + if (new_state == STATE_SINGLE) { + CHECK_NE(-1, primary_output_index_); + + XRRModeInfo* ideal_mode = ModeInfoForID( + screen, + output_cache_[primary_output_index_].ideal_mode); + width = ideal_mode->width; + height = ideal_mode->height; + } else if (new_state == STATE_DUAL_MIRROR) { + CHECK_NE(-1, primary_output_index_); + CHECK_NE(-1, secondary_output_index_); + + XRRModeInfo* mirror_mode = ModeInfoForID( + screen, + output_cache_[primary_output_index_].mirror_mode); + width = mirror_mode->width; + height = mirror_mode->height; + } else if ((new_state == STATE_DUAL_PRIMARY_ONLY) || + (new_state == STATE_DUAL_SECONDARY_ONLY)) { + CHECK_NE(-1, primary_output_index_); + CHECK_NE(-1, secondary_output_index_); + + XRRModeInfo* one_ideal = ModeInfoForID( + screen, + output_cache_[primary_output_index_].ideal_mode); + XRRModeInfo* two_ideal = ModeInfoForID( + screen, + output_cache_[secondary_output_index_].ideal_mode); + + // Compute the device scale factor for the topmost display. We only need + // to take this device's scale factor into account as we are creating a gap + // to avoid the cursor drawing onto the second (unused) display when the + // cursor is near the bottom of the topmost display. + float top_scale_factor; + if (new_state == STATE_DUAL_PRIMARY_ONLY) { + top_scale_factor = ComputeDeviceScaleFactor(one_ideal->width, + output_cache_[primary_output_index_].mm_width); + } else { + top_scale_factor = ComputeDeviceScaleFactor(two_ideal->width, + output_cache_[secondary_output_index_].mm_width); + } + vertical_gap = kVerticalGap * top_scale_factor; + + width = std::max<int>(one_ideal->width, two_ideal->width); + height = one_ideal->height + two_ideal->height + vertical_gap; + primary_height = one_ideal->height; + secondary_height = two_ideal->height; + } + CreateFrameBuffer(display, screen, window, width, height); + + // Now, tile the outputs appropriately. + const int x = 0; + const int y = 0; + switch (new_state) { + case STATE_SINGLE: + ConfigureCrtc(display, + screen, + output_cache_[primary_output_index_].crtc, + x, + y, + output_cache_[primary_output_index_].ideal_mode, + output_cache_[primary_output_index_].output); + break; + case STATE_DUAL_MIRROR: + case STATE_DUAL_PRIMARY_ONLY: + case STATE_DUAL_SECONDARY_ONLY: { + RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; + RRMode secondary_mode = + output_cache_[secondary_output_index_].mirror_mode; + int primary_y = y; + int secondary_y = y; + + if (new_state != STATE_DUAL_MIRROR) { + primary_mode = output_cache_[primary_output_index_].ideal_mode; + secondary_mode = output_cache_[secondary_output_index_].ideal_mode; + } + if (new_state == STATE_DUAL_PRIMARY_ONLY) + secondary_y = y + primary_height + vertical_gap; + if (new_state == STATE_DUAL_SECONDARY_ONLY) + primary_y = y + secondary_height + vertical_gap; + + ConfigureCrtc(display, + screen, + output_cache_[primary_output_index_].crtc, + x, + primary_y, + primary_mode, + output_cache_[primary_output_index_].output); + ConfigureCrtc(display, + screen, + output_cache_[secondary_output_index_].crtc, + x, + secondary_y, + secondary_mode, + output_cache_[secondary_output_index_].output); + } + break; + case STATE_HEADLESS: + // Do nothing. + break; + default: + NOTREACHED() << "Unhandled state " << new_state; + } + output_state_ = new_state; +} + +bool OutputConfigurator::RecacheAndUseDefaultState() { + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + CHECK(display != NULL); + XGrabServer(display); + Window window = DefaultRootWindow(display); + XRRScreenResources* screen = XRRGetScreenResources(display, window); + CHECK(screen != NULL); + + bool did_detect_change = TryRecacheOutputs(display, screen); + if (did_detect_change) { + State state = GetDefaultState(); + UpdateCacheAndXrandrToState(display, screen, window, state); + } + XRRFreeScreenResources(screen); + XUngrabServer(display); + return did_detect_change; +} + +State OutputConfigurator::GetDefaultState() const { + State state = STATE_HEADLESS; + if (-1 != primary_output_index_) { + if (-1 != secondary_output_index_) + state = mirror_supported_ ? STATE_DUAL_MIRROR : STATE_DUAL_PRIMARY_ONLY; + else + state = STATE_SINGLE; + } + return state; +} + +State OutputConfigurator::InferCurrentState(Display* display, + XRRScreenResources* screen) const { + // STATE_INVALID will be our default or "unknown" state. + State state = STATE_INVALID; + // First step: count the number of connected outputs. + if (secondary_output_index_ == -1) { + // No secondary display. + if (primary_output_index_ == -1) { + // No primary display implies HEADLESS. + state = STATE_HEADLESS; + } else { + // The common case of primary-only. + // The only sanity check we require in this case is that the current mode + // of the output's CRTC is the ideal mode we determined for it. + RRCrtc primary_crtc = None; + RRMode primary_mode = None; + int primary_height = 0; + GetOutputConfiguration(display, + screen, + output_cache_[primary_output_index_].output, + &primary_crtc, + &primary_mode, + &primary_height); + if (primary_mode == output_cache_[primary_output_index_].ideal_mode) + state = STATE_SINGLE; + } + } else { + // We have two displays attached so we need to look at their configuration. + // Note that, for simplicity, we will only detect the states that we would + // have used and will assume anything unexpected is INVALID (which should + // not happen in any expected usage scenario). + RRCrtc primary_crtc = None; + RRMode primary_mode = None; + int primary_height = 0; + GetOutputConfiguration(display, + screen, + output_cache_[primary_output_index_].output, + &primary_crtc, + &primary_mode, + &primary_height); + RRCrtc secondary_crtc = None; + RRMode secondary_mode = None; + int secondary_height = 0; + GetOutputConfiguration(display, + screen, + output_cache_[secondary_output_index_].output, + &secondary_crtc, + &secondary_mode, + &secondary_height); + // Make sure the CRTCs are matched to the expected outputs. + if ((output_cache_[primary_output_index_].crtc == primary_crtc) && + (output_cache_[secondary_output_index_].crtc == secondary_crtc)) { + // Check the mode matching: either both mirror or both ideal. + if ((output_cache_[primary_output_index_].mirror_mode == primary_mode) && + (output_cache_[secondary_output_index_].mirror_mode == + secondary_mode)) { + // We are already in mirror mode. + state = STATE_DUAL_MIRROR; + } else if ((output_cache_[primary_output_index_].ideal_mode == + primary_mode) && + (output_cache_[secondary_output_index_].ideal_mode == + secondary_mode)) { + // Both outputs are in their "ideal" mode so check their Y-offsets to + // see which "ideal" configuration this is. + if (primary_height == output_cache_[secondary_output_index_].y) { + // Secondary is tiled first. + state = STATE_DUAL_SECONDARY_ONLY; + } else if (secondary_height == output_cache_[primary_output_index_].y) { + // Primary is tiled first. + state = STATE_DUAL_PRIMARY_ONLY; + } + } + } + } + + return state; +} + +bool OutputConfigurator::CycleDisplayMode() { + VLOG(1) << "CycleDisplayMode"; + bool did_change = false; + if (is_running_on_chrome_os_) { + // Rules: + // - if there are 0 or 1 displays, do nothing and return false. + // - use y-coord of CRTCs to determine if we are mirror, primary-first, or + // secondary-first. The cycle order is: + // mirror->primary->secondary->mirror. + State new_state = STATE_INVALID; + switch (output_state_) { + case STATE_DUAL_MIRROR: + new_state = STATE_DUAL_PRIMARY_ONLY; + break; + case STATE_DUAL_PRIMARY_ONLY: + new_state = STATE_DUAL_SECONDARY_ONLY; + break; + case STATE_DUAL_SECONDARY_ONLY: + new_state = mirror_supported_ ? + STATE_DUAL_MIRROR : + STATE_DUAL_PRIMARY_ONLY; + break; + default: + // Do nothing - we aren't in a mode which we can rotate. + break; + } + if (STATE_INVALID != new_state) + did_change = SetDisplayMode(new_state); + } + return did_change; +} + +bool OutputConfigurator::ScreenPowerSet(bool power_on, bool all_displays) { + VLOG(1) << "OutputConfigurator::SetScreensOn " << power_on + << " all displays " << all_displays; + bool success = false; + if (is_running_on_chrome_os_) { + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + CHECK(display != NULL); + XGrabServer(display); + Window window = DefaultRootWindow(display); + XRRScreenResources* screen = XRRGetScreenResources(display, window); + CHECK(screen != NULL); + + // Set the CRTCs based on whether we want to turn the power on or off and + // select the outputs to operate on by name or all_displays. + for (int i = 0; i < output_count_; ++i) { + if (all_displays || output_cache_[i].is_internal) { + const int x = output_cache_[i].x; + const int y = output_cache_[i].y; + RROutput output = output_cache_[i].output; + RRCrtc crtc = output_cache_[i].crtc; + RRMode mode = None; + if (power_on) { + mode = (STATE_DUAL_MIRROR == output_state_) ? + output_cache_[i].mirror_mode : + output_cache_[i].ideal_mode; + } + + VLOG(1) << "SET POWER crtc: " << crtc + << ", mode " << mode + << ", output " << output + << ", x " << x + << ", y " << y; + ConfigureCrtc(display, + screen, + crtc, + x, + y, + mode, + output); + output_cache_[i].is_powered_on = power_on; + success = true; + } + } + + // Force the DPMS on since the driver doesn't always detect that it should + // turn on. + if (power_on) { + CHECK(DPMSEnable(display)); + CHECK(DPMSForceLevel(display, DPMSModeOn)); + } + + XRRFreeScreenResources(screen); + XUngrabServer(display); + } + return success; +} + +bool OutputConfigurator::SetDisplayMode(State new_state) { + if (output_state_ == STATE_INVALID || + output_state_ == STATE_HEADLESS || + output_state_ == STATE_SINGLE) + return false; + + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + CHECK(display != NULL); + XGrabServer(display); + Window window = DefaultRootWindow(display); + XRRScreenResources* screen = XRRGetScreenResources(display, window); + CHECK(screen != NULL); + + UpdateCacheAndXrandrToState(display, + screen, + window, + new_state); + XRRFreeScreenResources(screen); + XUngrabServer(display); + return true; +} + +bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { + // Ignore this event if the Xrandr extension isn't supported. + if (is_running_on_chrome_os_ && + (event->type - xrandr_event_base_ == RRNotify)) { + XEvent* xevent = static_cast<XEvent*>(event); + XRRNotifyEvent* notify_event = + reinterpret_cast<XRRNotifyEvent*>(xevent); + if (notify_event->subtype == RRNotify_OutputChange) { + XRROutputChangeNotifyEvent* output_change_event = + reinterpret_cast<XRROutputChangeNotifyEvent*>(xevent); + if ((output_change_event->connection == RR_Connected) || + (output_change_event->connection == RR_Disconnected)) { + RecacheAndUseDefaultState(); + CheckIsProjectingAndNotify(); + } + // Ignore the case of RR_UnkownConnection. + } + } + return true; +} + +void OutputConfigurator::CheckIsProjectingAndNotify() { + // Determine if there is an "internal" output and how many outputs are + // connected. + bool has_internal_output = false; + int connected_output_count = 0; + for (int i = 0; i < output_count_; ++i) { + if (output_cache_[i].is_connected) { + connected_output_count += 1; + has_internal_output |= output_cache_[i].is_internal; + } + } + + // "Projecting" is defined as having more than 1 output connected while at + // least one of them is an internal output. + bool is_projecting = has_internal_output && (connected_output_count > 1); + chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); + dbus::Bus* bus = manager->GetSystemBus(); + dbus::ObjectProxy* power_manager_proxy = bus->GetObjectProxy( + power_manager::kPowerManagerServiceName, + dbus::ObjectPath(power_manager::kPowerManagerServicePath)); + dbus::MethodCall method_call( + power_manager::kPowerManagerInterface, + power_manager::kSetIsProjectingMethod); + dbus::MessageWriter writer(&method_call); + writer.AppendBool(is_projecting); + power_manager_proxy->CallMethod( + &method_call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + dbus::ObjectProxy::EmptyResponseCallback()); +} + +} // namespace chromeos |