Home | History | Annotate | Download | only in display
      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 
     22 namespace chromeos {
     23 
     24 namespace {
     25 
     26 // DPI measurements.
     27 const float kMmInInch = 25.4;
     28 const float kDpi96 = 96.0;
     29 const float kPixelsToMmScale = kMmInInch / kDpi96;
     30 
     31 bool IsInternalOutput(const XRROutputInfo* output_info) {
     32   return IsInternalOutputName(std::string(output_info->name));
     33 }
     34 
     35 RRMode GetOutputNativeMode(const XRROutputInfo* output_info) {
     36   return output_info->nmode > 0 ? output_info->modes[0] : None;
     37 }
     38 
     39 }  // namespace
     40 
     41 RealOutputConfiguratorDelegate::RealOutputConfiguratorDelegate()
     42     : display_(base::MessagePumpAuraX11::GetDefaultXDisplay()),
     43       window_(DefaultRootWindow(display_)),
     44       screen_(NULL),
     45       is_panel_fitting_enabled_(false) {
     46 }
     47 
     48 RealOutputConfiguratorDelegate::~RealOutputConfiguratorDelegate() {
     49 }
     50 
     51 void RealOutputConfiguratorDelegate::SetPanelFittingEnabled(bool enabled) {
     52   is_panel_fitting_enabled_ = enabled;
     53 }
     54 
     55 void RealOutputConfiguratorDelegate::InitXRandRExtension(int* event_base) {
     56   int error_base_ignored = 0;
     57   XRRQueryExtension(display_, event_base, &error_base_ignored);
     58 }
     59 
     60 void RealOutputConfiguratorDelegate::UpdateXRandRConfiguration(
     61     const base::NativeEvent& event) {
     62   XRRUpdateConfiguration(event);
     63 }
     64 
     65 void RealOutputConfiguratorDelegate::GrabServer() {
     66   CHECK(!screen_) << "Server already grabbed";
     67   XGrabServer(display_);
     68   screen_ = XRRGetScreenResources(display_, window_);
     69   CHECK(screen_);
     70 }
     71 
     72 void RealOutputConfiguratorDelegate::UngrabServer() {
     73   CHECK(screen_) << "Server not grabbed";
     74   XRRFreeScreenResources(screen_);
     75   screen_ = NULL;
     76   XUngrabServer(display_);
     77 }
     78 
     79 void RealOutputConfiguratorDelegate::SyncWithServer() {
     80   XSync(display_, 0);
     81 }
     82 
     83 void 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 
    102 void RealOutputConfiguratorDelegate::ForceDPMSOn() {
    103   CHECK(DPMSEnable(display_));
    104   CHECK(DPMSForceLevel(display_, DPMSModeOn));
    105 }
    106 
    107 std::vector<OutputConfigurator::OutputSnapshot>
    108 RealOutputConfiguratorDelegate::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 
    231 bool 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 
    255 bool 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 
    282 void 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 
    300 void 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 
    340 void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager(
    341     bool projecting) {
    342   chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
    343       SetIsProjecting(projecting);
    344 }
    345 
    346 void 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 
    391 bool 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 
    431 bool 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 
    505 void 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