1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chromeos/display/real_output_configurator_delegate.h"
6
7#include <X11/Xatom.h>
8#include <X11/Xlib.h>
9#include <X11/extensions/dpms.h>
10#include <X11/extensions/XInput.h>
11#include <X11/extensions/XInput2.h>
12#include <X11/extensions/Xrandr.h>
13
14#include <cmath>
15
16#include "base/logging.h"
17#include "base/message_loop/message_pump_aurax11.h"
18#include "chromeos/dbus/dbus_thread_manager.h"
19#include "chromeos/dbus/power_manager_client.h"
20#include "chromeos/display/output_util.h"
21
22namespace chromeos {
23
24namespace {
25
26// DPI measurements.
27const float kMmInInch = 25.4;
28const float kDpi96 = 96.0;
29const float kPixelsToMmScale = kMmInInch / kDpi96;
30
31bool IsInternalOutput(const XRROutputInfo* output_info) {
32  return IsInternalOutputName(std::string(output_info->name));
33}
34
35RRMode GetOutputNativeMode(const XRROutputInfo* output_info) {
36  return output_info->nmode > 0 ? output_info->modes[0] : None;
37}
38
39}  // namespace
40
41RealOutputConfiguratorDelegate::RealOutputConfiguratorDelegate()
42    : display_(base::MessagePumpAuraX11::GetDefaultXDisplay()),
43      window_(DefaultRootWindow(display_)),
44      screen_(NULL),
45      is_panel_fitting_enabled_(false) {
46}
47
48RealOutputConfiguratorDelegate::~RealOutputConfiguratorDelegate() {
49}
50
51void RealOutputConfiguratorDelegate::SetPanelFittingEnabled(bool enabled) {
52  is_panel_fitting_enabled_ = enabled;
53}
54
55void RealOutputConfiguratorDelegate::InitXRandRExtension(int* event_base) {
56  int error_base_ignored = 0;
57  XRRQueryExtension(display_, event_base, &error_base_ignored);
58}
59
60void RealOutputConfiguratorDelegate::UpdateXRandRConfiguration(
61    const base::NativeEvent& event) {
62  XRRUpdateConfiguration(event);
63}
64
65void RealOutputConfiguratorDelegate::GrabServer() {
66  CHECK(!screen_) << "Server already grabbed";
67  XGrabServer(display_);
68  screen_ = XRRGetScreenResources(display_, window_);
69  CHECK(screen_);
70}
71
72void RealOutputConfiguratorDelegate::UngrabServer() {
73  CHECK(screen_) << "Server not grabbed";
74  XRRFreeScreenResources(screen_);
75  screen_ = NULL;
76  XUngrabServer(display_);
77}
78
79void RealOutputConfiguratorDelegate::SyncWithServer() {
80  XSync(display_, 0);
81}
82
83void RealOutputConfiguratorDelegate::SetBackgroundColor(uint32 color_argb) {
84  // Configuring CRTCs/Framebuffer clears the boot screen image.  Set the
85  // same background color while configuring the display to minimize the
86  // duration of black screen at boot time. The background is filled with
87  // black later in ash::DisplayManager.  crbug.com/171050.
88  XSetWindowAttributes swa = {0};
89  XColor color;
90  Colormap colormap = DefaultColormap(display_, 0);
91  // XColor uses 16 bits per color.
92  color.red = (color_argb & 0x00FF0000) >> 8;
93  color.green = (color_argb & 0x0000FF00);
94  color.blue = (color_argb & 0x000000FF) << 8;
95  color.flags = DoRed | DoGreen | DoBlue;
96  XAllocColor(display_, colormap, &color);
97  swa.background_pixel = color.pixel;
98  XChangeWindowAttributes(display_, window_, CWBackPixel, &swa);
99  XFreeColors(display_, colormap, &color.pixel, 1, 0);
100}
101
102void RealOutputConfiguratorDelegate::ForceDPMSOn() {
103  CHECK(DPMSEnable(display_));
104  CHECK(DPMSForceLevel(display_, DPMSModeOn));
105}
106
107std::vector<OutputConfigurator::OutputSnapshot>
108RealOutputConfiguratorDelegate::GetOutputs(
109    const OutputConfigurator::StateController* state_controller) {
110  CHECK(screen_) << "Server not grabbed";
111
112  std::vector<OutputConfigurator::OutputSnapshot> outputs;
113  XRROutputInfo* one_info = NULL;
114  XRROutputInfo* two_info = NULL;
115  RRCrtc last_used_crtc = None;
116
117  for (int i = 0; i < screen_->noutput && outputs.size() < 2; ++i) {
118    RROutput this_id = screen_->outputs[i];
119    XRROutputInfo* output_info = XRRGetOutputInfo(display_, screen_, this_id);
120    bool is_connected = (output_info->connection == RR_Connected);
121
122    if (is_connected) {
123      OutputConfigurator::OutputSnapshot to_populate;
124      to_populate.output = this_id;
125      to_populate.has_display_id =
126          GetDisplayId(this_id, i, &to_populate.display_id);
127      to_populate.is_internal = IsInternalOutput(output_info);
128      // Use the index as a valid display id even if the internal
129      // display doesn't have valid EDID because the index
130      // will never change.
131      if (!to_populate.has_display_id && to_populate.is_internal)
132        to_populate.has_display_id = true;
133
134      (outputs.empty() ? one_info : two_info) = output_info;
135
136      // Now, look up the current CRTC and any related info.
137      if (output_info->crtc) {
138        XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(
139            display_, screen_, output_info->crtc);
140        to_populate.current_mode = crtc_info->mode;
141        to_populate.x = crtc_info->x;
142        to_populate.y = crtc_info->y;
143        XRRFreeCrtcInfo(crtc_info);
144      }
145
146      // Assign a CRTC that isn't already in use.
147      for (int j = 0; j < output_info->ncrtc; ++j) {
148        if (output_info->crtcs[j] != last_used_crtc) {
149          to_populate.crtc = output_info->crtcs[j];
150          last_used_crtc = to_populate.crtc;
151          break;
152        }
153      }
154      to_populate.native_mode = GetOutputNativeMode(output_info);
155      if (to_populate.has_display_id) {
156        int width = 0, height = 0;
157        if (state_controller &&
158            state_controller->GetResolutionForDisplayId(
159                to_populate.display_id, &width, &height)) {
160          to_populate.selected_mode =
161              FindOutputModeMatchingSize(screen_, output_info, width, height);
162        }
163      }
164      // Fallback to native mode.
165      if (to_populate.selected_mode == None)
166        to_populate.selected_mode = to_populate.native_mode;
167
168      to_populate.is_aspect_preserving_scaling =
169          IsOutputAspectPreservingScaling(this_id);
170      to_populate.touch_device_id = None;
171
172      VLOG(2) << "Found display " << outputs.size() << ":"
173              << " output=" << to_populate.output
174              << " crtc=" << to_populate.crtc
175              << " current_mode=" << to_populate.current_mode;
176      outputs.push_back(to_populate);
177    } else {
178      XRRFreeOutputInfo(output_info);
179    }
180  }
181
182  if (outputs.size() == 2) {
183    bool one_is_internal = IsInternalOutput(one_info);
184    bool two_is_internal = IsInternalOutput(two_info);
185    int internal_outputs = (one_is_internal ? 1 : 0) +
186        (two_is_internal ? 1 : 0);
187    DCHECK_LT(internal_outputs, 2);
188    LOG_IF(WARNING, internal_outputs == 2)
189        << "Two internal outputs detected.";
190
191    bool can_mirror = false;
192    for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) {
193      // Try preserving external output's aspect ratio on the first attempt.
194      // If that fails, fall back to the highest matching resolution.
195      bool preserve_aspect = attempt == 0;
196
197      if (internal_outputs == 1) {
198        if (one_is_internal) {
199          can_mirror = FindOrCreateMirrorMode(one_info, two_info,
200              outputs[0].output, is_panel_fitting_enabled_, preserve_aspect,
201              &outputs[0].mirror_mode, &outputs[1].mirror_mode);
202        } else {  // if (two_is_internal)
203          can_mirror = FindOrCreateMirrorMode(two_info, one_info,
204              outputs[1].output, is_panel_fitting_enabled_, preserve_aspect,
205              &outputs[1].mirror_mode, &outputs[0].mirror_mode);
206        }
207      } else {  // if (internal_outputs == 0)
208        // No panel fitting for external outputs, so fall back to exact match.
209        can_mirror = FindOrCreateMirrorMode(one_info, two_info,
210            outputs[0].output, false, preserve_aspect,
211            &outputs[0].mirror_mode, &outputs[1].mirror_mode);
212        if (!can_mirror && preserve_aspect) {
213          // FindOrCreateMirrorMode will try to preserve aspect ratio of
214          // what it thinks is external display, so if it didn't succeed
215          // with one, maybe it will succeed with the other.  This way we
216          // will have correct aspect ratio on at least one of them.
217          can_mirror = FindOrCreateMirrorMode(two_info, one_info,
218              outputs[1].output, false, preserve_aspect,
219              &outputs[1].mirror_mode, &outputs[0].mirror_mode);
220        }
221      }
222    }
223  }
224
225  GetTouchscreens(&outputs);
226  XRRFreeOutputInfo(one_info);
227  XRRFreeOutputInfo(two_info);
228  return outputs;
229}
230
231bool RealOutputConfiguratorDelegate::GetModeDetails(RRMode mode,
232                                                    int* width,
233                                                    int* height,
234                                                    bool* interlaced) {
235  CHECK(screen_) << "Server not grabbed";
236  // TODO: Determine if we need to organize modes in a way which provides
237  // better than O(n) lookup time.  In many call sites, for example, the
238  // "next" mode is typically what we are looking for so using this
239  // helper might be too expensive.
240  for (int i = 0; i < screen_->nmode; ++i) {
241    if (mode == screen_->modes[i].id) {
242      const XRRModeInfo& info = screen_->modes[i];
243      if (width)
244        *width = info.width;
245      if (height)
246        *height = info.height;
247      if (interlaced)
248        *interlaced = info.modeFlags & RR_Interlace;
249      return true;
250    }
251  }
252  return false;
253}
254
255bool RealOutputConfiguratorDelegate::ConfigureCrtc(
256    RRCrtc crtc,
257    RRMode mode,
258    RROutput output,
259    int x,
260    int y) {
261  CHECK(screen_) << "Server not grabbed";
262  VLOG(1) << "ConfigureCrtc: crtc=" << crtc
263          << " mode=" << mode
264          << " output=" << output
265          << " x=" << x
266          << " y=" << y;
267  // Xrandr.h is full of lies. XRRSetCrtcConfig() is defined as returning a
268  // Status, which is typically 0 for failure and 1 for success. In
269  // actuality it returns a RRCONFIGSTATUS, which uses 0 for success.
270  return XRRSetCrtcConfig(display_,
271                          screen_,
272                          crtc,
273                          CurrentTime,
274                          x,
275                          y,
276                          mode,
277                          RR_Rotate_0,
278                          (output && mode) ? &output : NULL,
279                          (output && mode) ? 1 : 0) == RRSetConfigSuccess;
280}
281
282void RealOutputConfiguratorDelegate::CreateFrameBuffer(
283    int width,
284    int height,
285    const std::vector<OutputConfigurator::OutputSnapshot>& outputs) {
286  CHECK(screen_) << "Server not grabbed";
287  int current_width = DisplayWidth(display_, DefaultScreen(display_));
288  int current_height = DisplayHeight(display_, DefaultScreen(display_));
289  VLOG(1) << "CreateFrameBuffer: new=" << width << "x" << height
290          << " current=" << current_width << "x" << current_height;
291  if (width ==  current_width && height == current_height)
292    return;
293
294  DestroyUnusedCrtcs(outputs);
295  int mm_width = width * kPixelsToMmScale;
296  int mm_height = height * kPixelsToMmScale;
297  XRRSetScreenSize(display_, window_, width, height, mm_width, mm_height);
298}
299
300void RealOutputConfiguratorDelegate::ConfigureCTM(
301    int touch_device_id,
302    const OutputConfigurator::CoordinateTransformation& ctm) {
303  VLOG(1) << "ConfigureCTM: id=" << touch_device_id
304          << " scale=" << ctm.x_scale << "x" << ctm.y_scale
305          << " offset=(" << ctm.x_offset << ", " << ctm.y_offset << ")";
306  int ndevices = 0;
307  XIDeviceInfo* info = XIQueryDevice(display_, touch_device_id, &ndevices);
308  Atom prop = XInternAtom(display_, "Coordinate Transformation Matrix", False);
309  Atom float_atom = XInternAtom(display_, "FLOAT", False);
310  if (ndevices == 1 && prop != None && float_atom != None) {
311    Atom type;
312    int format;
313    unsigned long num_items;
314    unsigned long bytes_after;
315    unsigned char* data = NULL;
316    // Verify that the property exists with correct format, type, etc.
317    int status = XIGetProperty(display_, info->deviceid, prop, 0, 0, False,
318        AnyPropertyType, &type, &format, &num_items, &bytes_after, &data);
319    if (data)
320      XFree(data);
321    if (status == Success && type == float_atom && format == 32) {
322      float value[3][3] = {
323          { ctm.x_scale,         0.0, ctm.x_offset },
324          {         0.0, ctm.y_scale, ctm.y_offset },
325          {         0.0,         0.0,          1.0 }
326      };
327      XIChangeProperty(display_,
328                       info->deviceid,
329                       prop,
330                       type,
331                       format,
332                       PropModeReplace,
333                       reinterpret_cast<unsigned char*>(value),
334                       9);
335    }
336  }
337  XIFreeDeviceInfo(info);
338}
339
340void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager(
341    bool projecting) {
342  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
343      SetIsProjecting(projecting);
344}
345
346void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs(
347    const std::vector<OutputConfigurator::OutputSnapshot>& outputs) {
348  CHECK(screen_) << "Server not grabbed";
349  // Setting the screen size will fail if any CRTC doesn't fit afterwards.
350  // At the same time, turning CRTCs off and back on uses up a lot of time.
351  // This function tries to be smart to avoid too many off/on cycles:
352  // - We disable all the CRTCs we won't need after the FB resize.
353  // - We set the new modes on CRTCs, if they fit in both the old and new
354  //   FBs, and park them at (0,0)
355  // - We disable the CRTCs we will need but don't fit in the old FB. Those
356  //   will be reenabled after the resize.
357  // We don't worry about the cached state of the outputs here since we are
358  // not interested in the state we are setting - we just try to get the CRTCs
359  // out of the way so we can rebuild the frame buffer.
360  for (int i = 0; i < screen_->ncrtc; ++i) {
361    // Default config is to disable the crtcs.
362    RRCrtc crtc = screen_->crtcs[i];
363    RRMode mode = None;
364    RROutput output = None;
365    for (std::vector<OutputConfigurator::OutputSnapshot>::const_iterator it =
366         outputs.begin(); it != outputs.end(); ++it) {
367      if (crtc == it->crtc) {
368        mode = it->current_mode;
369        output = it->output;
370        break;
371      }
372    }
373
374    if (mode != None) {
375      // In case our CRTC doesn't fit in our current framebuffer, disable it.
376      // It'll get reenabled after we resize the framebuffer.
377      int mode_width = 0, mode_height = 0;
378      CHECK(GetModeDetails(mode, &mode_width, &mode_height, NULL));
379      int current_width = DisplayWidth(display_, DefaultScreen(display_));
380      int current_height = DisplayHeight(display_, DefaultScreen(display_));
381      if (mode_width > current_width || mode_height > current_height) {
382        mode = None;
383        output = None;
384      }
385    }
386
387    ConfigureCrtc(crtc, mode, output, 0, 0);
388  }
389}
390
391bool RealOutputConfiguratorDelegate::IsOutputAspectPreservingScaling(
392    RROutput id) {
393  bool ret = false;
394
395  Atom scaling_prop = XInternAtom(display_, "scaling mode", False);
396  Atom full_aspect_atom = XInternAtom(display_, "Full aspect", False);
397  if (scaling_prop == None || full_aspect_atom == None)
398    return false;
399
400  int nprop = 0;
401  Atom* props = XRRListOutputProperties(display_, id, &nprop);
402  for (int j = 0; j < nprop && !ret; j++) {
403    Atom prop = props[j];
404    if (scaling_prop == prop) {
405      unsigned char* values = NULL;
406      int actual_format;
407      unsigned long nitems;
408      unsigned long bytes_after;
409      Atom actual_type;
410      int success;
411
412      success = XRRGetOutputProperty(display_, id, prop, 0, 100, False, False,
413          AnyPropertyType, &actual_type, &actual_format, &nitems,
414          &bytes_after, &values);
415      if (success == Success && actual_type == XA_ATOM &&
416          actual_format == 32 && nitems == 1) {
417        Atom value = reinterpret_cast<Atom*>(values)[0];
418        if (full_aspect_atom == value)
419          ret = true;
420      }
421      if (values)
422        XFree(values);
423    }
424  }
425  if (props)
426    XFree(props);
427
428  return ret;
429}
430
431bool RealOutputConfiguratorDelegate::FindOrCreateMirrorMode(
432    XRROutputInfo* internal_info,
433    XRROutputInfo* external_info,
434    RROutput internal_output_id,
435    bool try_creating,
436    bool preserve_aspect,
437    RRMode* internal_mirror_mode,
438    RRMode* external_mirror_mode) {
439  RRMode internal_mode_id = GetOutputNativeMode(internal_info);
440  RRMode external_mode_id = GetOutputNativeMode(external_info);
441
442  if (internal_mode_id == None || external_mode_id == None)
443    return false;
444
445  int internal_native_width = 0, internal_native_height = 0;
446  int external_native_width = 0, external_native_height = 0;
447  CHECK(GetModeDetails(internal_mode_id, &internal_native_width,
448                       &internal_native_height, NULL));
449  CHECK(GetModeDetails(external_mode_id, &external_native_width,
450                       &external_native_height, NULL));
451
452  // Check if some external output resolution can be mirrored on internal.
453  // Prefer the modes in the order that X sorts them,
454  // assuming this is the order in which they look better on the monitor.
455  // If X's order is not satisfactory, we can either fix X's sorting,
456  // or implement our sorting here.
457  for (int i = 0; i < external_info->nmode; i++) {
458    external_mode_id = external_info->modes[i];
459    int external_width = 0, external_height = 0;
460    bool is_external_interlaced = false;
461    CHECK(GetModeDetails(external_mode_id, &external_width, &external_height,
462                         &is_external_interlaced));
463    bool is_native_aspect_ratio =
464        external_native_width * external_height ==
465        external_native_height * external_width;
466    if (preserve_aspect && !is_native_aspect_ratio)
467      continue;  // Allow only aspect ratio preserving modes for mirroring
468
469    // Try finding exact match
470    for (int j = 0; j < internal_info->nmode; j++) {
471      internal_mode_id = internal_info->modes[j];
472      int internal_width = 0, internal_height = 0;
473      bool is_internal_interlaced = false;
474      CHECK(GetModeDetails(internal_mode_id, &internal_width,
475                           &internal_height, &is_internal_interlaced));
476      if (internal_width == external_width &&
477          internal_height == external_height &&
478          is_internal_interlaced == is_external_interlaced) {
479        *internal_mirror_mode = internal_mode_id;
480        *external_mirror_mode = external_mode_id;
481        return true;  // Mirror mode found
482      }
483    }
484
485    // Try to create a matching internal output mode by panel fitting
486    if (try_creating) {
487      // We can downscale by 1.125, and upscale indefinitely
488      // Downscaling looks ugly, so, can fit == can upscale
489      // Also, internal panels don't support fitting interlaced modes
490      bool can_fit =
491          internal_native_width >= external_width &&
492          internal_native_height >= external_height &&
493          !is_external_interlaced;
494      if (can_fit) {
495        XRRAddOutputMode(display_, internal_output_id, external_mode_id);
496        *internal_mirror_mode = *external_mirror_mode = external_mode_id;
497        return true;  // Mirror mode created
498      }
499    }
500  }
501
502  return false;
503}
504
505void RealOutputConfiguratorDelegate::GetTouchscreens(
506    std::vector<OutputConfigurator::OutputSnapshot>* outputs) {
507  int ndevices = 0;
508  Atom valuator_x = XInternAtom(display_, "Abs MT Position X", False);
509  Atom valuator_y = XInternAtom(display_, "Abs MT Position Y", False);
510  if (valuator_x == None || valuator_y == None)
511    return;
512
513  XIDeviceInfo* info = XIQueryDevice(display_, XIAllDevices, &ndevices);
514  for (int i = 0; i < ndevices; i++) {
515    if (!info[i].enabled || info[i].use != XIFloatingSlave)
516      continue;  // Assume all touchscreens are floating slaves
517
518    double width = -1.0;
519    double height = -1.0;
520    bool is_direct_touch = false;
521
522    for (int j = 0; j < info[i].num_classes; j++) {
523      XIAnyClassInfo* class_info = info[i].classes[j];
524
525      if (class_info->type == XIValuatorClass) {
526        XIValuatorClassInfo* valuator_info =
527            reinterpret_cast<XIValuatorClassInfo*>(class_info);
528
529        if (valuator_x == valuator_info->label) {
530          // Ignore X axis valuator with unexpected properties
531          if (valuator_info->number == 0 && valuator_info->mode == Absolute &&
532              valuator_info->min == 0.0) {
533            width = valuator_info->max;
534          }
535        } else if (valuator_y == valuator_info->label) {
536          // Ignore Y axis valuator with unexpected properties
537          if (valuator_info->number == 1 && valuator_info->mode == Absolute &&
538              valuator_info->min == 0.0) {
539            height = valuator_info->max;
540          }
541        }
542      }
543#if defined(USE_XI2_MT)
544      if (class_info->type == XITouchClass) {
545        XITouchClassInfo* touch_info =
546            reinterpret_cast<XITouchClassInfo*>(class_info);
547        is_direct_touch = touch_info->mode == XIDirectTouch;
548      }
549#endif
550    }
551
552    // Touchscreens should have absolute X and Y axes,
553    // and be direct touch devices.
554    if (width > 0.0 && height > 0.0 && is_direct_touch) {
555      size_t k = 0;
556      for (; k < outputs->size(); k++) {
557        if ((*outputs)[k].native_mode == None ||
558            (*outputs)[k].touch_device_id != None)
559          continue;
560        int native_mode_width = 0, native_mode_height = 0;
561        if (!GetModeDetails((*outputs)[k].native_mode, &native_mode_width,
562                            &native_mode_height, NULL))
563          continue;
564
565        // Allow 1 pixel difference between screen and touchscreen
566        // resolutions.  Because in some cases for monitor resolution
567        // 1024x768 touchscreen's resolution would be 1024x768, but for
568        // some 1023x767.  It really depends on touchscreen's firmware
569        // configuration.
570        if (std::abs(native_mode_width - width) <= 1.0 &&
571            std::abs(native_mode_height - height) <= 1.0) {
572          (*outputs)[k].touch_device_id = info[i].deviceid;
573
574          VLOG(2) << "Found touchscreen for output #" << k
575                  << " id " << (*outputs)[k].touch_device_id
576                  << " width " << width
577                  << " height " << height;
578          break;
579        }
580      }
581
582      VLOG_IF(2, k == outputs->size())
583          << "No matching output - ignoring touchscreen"
584          << " id " << info[i].deviceid
585          << " width " << width
586          << " height " << height;
587    }
588  }
589
590  XIFreeDeviceInfo(info);
591}
592
593}  // namespace chromeos
594