// 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