// Copyright (c) 2013 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/real_output_configurator_delegate.h" #include #include #include #include #include #include #include #include #include #include "base/logging.h" #include "base/message_loop/message_pump_x11.h" #include "base/x11/edid_parser_x11.h" #include "base/x11/x11_error_tracker.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/power_manager_client.h" #include "chromeos/display/output_util.h" namespace chromeos { namespace { // DPI measurements. const float kMmInInch = 25.4; const float kDpi96 = 96.0; const float kPixelsToMmScale = kMmInInch / kDpi96; // Prefixes of output name const char kOutputName_VGA[] = "VGA"; const char kOutputName_HDMI[] = "HDMI"; const char kOutputName_DVI[] = "DVI"; const char kOutputName_DisplayPort[] = "DP"; const char kContentProtectionAtomName[] = "Content Protection"; const char kProtectionUndesiredAtomName[] = "Undesired"; const char kProtectionDesiredAtomName[] = "Desired"; const char kProtectionEnabledAtomName[] = "Enabled"; bool IsInternalOutput(const XRROutputInfo* output_info) { return IsInternalOutputName(std::string(output_info->name)); } RRMode GetOutputNativeMode(const XRROutputInfo* output_info) { return output_info->nmode > 0 ? output_info->modes[0] : None; } } // namespace RealOutputConfiguratorDelegate::RealOutputConfiguratorDelegate() : display_(base::MessagePumpX11::GetDefaultXDisplay()), window_(DefaultRootWindow(display_)), screen_(NULL) { } RealOutputConfiguratorDelegate::~RealOutputConfiguratorDelegate() { } void RealOutputConfiguratorDelegate::InitXRandRExtension(int* event_base) { int error_base_ignored = 0; XRRQueryExtension(display_, event_base, &error_base_ignored); } void RealOutputConfiguratorDelegate::UpdateXRandRConfiguration( const base::NativeEvent& event) { XRRUpdateConfiguration(event); } void RealOutputConfiguratorDelegate::GrabServer() { CHECK(!screen_) << "Server already grabbed"; XGrabServer(display_); screen_ = XRRGetScreenResources(display_, window_); CHECK(screen_); } void RealOutputConfiguratorDelegate::UngrabServer() { CHECK(screen_) << "Server not grabbed"; XRRFreeScreenResources(screen_); screen_ = NULL; XUngrabServer(display_); } void RealOutputConfiguratorDelegate::SyncWithServer() { XSync(display_, 0); } void RealOutputConfiguratorDelegate::SetBackgroundColor(uint32 color_argb) { // Configuring CRTCs/Framebuffer clears the boot screen image. Set the // same background color while configuring the display to minimize the // duration of black screen at boot time. The background is filled with // black later in ash::DisplayManager. crbug.com/171050. XSetWindowAttributes swa = {0}; XColor color; Colormap colormap = DefaultColormap(display_, 0); // XColor uses 16 bits per color. color.red = (color_argb & 0x00FF0000) >> 8; color.green = (color_argb & 0x0000FF00); color.blue = (color_argb & 0x000000FF) << 8; color.flags = DoRed | DoGreen | DoBlue; XAllocColor(display_, colormap, &color); swa.background_pixel = color.pixel; XChangeWindowAttributes(display_, window_, CWBackPixel, &swa); XFreeColors(display_, colormap, &color.pixel, 1, 0); } void RealOutputConfiguratorDelegate::ForceDPMSOn() { CHECK(DPMSEnable(display_)); CHECK(DPMSForceLevel(display_, DPMSModeOn)); } std::vector RealOutputConfiguratorDelegate::GetOutputs() { CHECK(screen_) << "Server not grabbed"; std::vector outputs; RRCrtc last_used_crtc = None; for (int i = 0; i < screen_->noutput && outputs.size() < 2; ++i) { RROutput output_id = screen_->outputs[i]; XRROutputInfo* output_info = XRRGetOutputInfo(display_, screen_, output_id); if (output_info->connection == RR_Connected) { OutputConfigurator::OutputSnapshot output = InitOutputSnapshot( output_id, output_info, &last_used_crtc, i); VLOG(2) << "Found display " << outputs.size() << ":" << " output=" << output.output << " crtc=" << output.crtc << " current_mode=" << output.current_mode; outputs.push_back(output); } XRRFreeOutputInfo(output_info); } GetTouchscreens(&outputs); return outputs; } void RealOutputConfiguratorDelegate::AddOutputMode(RROutput output, RRMode mode) { CHECK(screen_) << "Server not grabbed"; VLOG(1) << "AddOutputMode: output=" << output << " mode=" << mode; XRRAddOutputMode(display_, output, mode); } bool RealOutputConfiguratorDelegate::ConfigureCrtc( RRCrtc crtc, RRMode mode, RROutput output, int x, int y) { CHECK(screen_) << "Server not grabbed"; VLOG(1) << "ConfigureCrtc: crtc=" << crtc << " mode=" << mode << " output=" << output << " x=" << x << " y=" << y; // Xrandr.h is full of lies. XRRSetCrtcConfig() is defined as returning a // Status, which is typically 0 for failure and 1 for success. In // actuality it returns a RRCONFIGSTATUS, which uses 0 for success. return XRRSetCrtcConfig(display_, screen_, crtc, CurrentTime, x, y, mode, RR_Rotate_0, (output && mode) ? &output : NULL, (output && mode) ? 1 : 0) == RRSetConfigSuccess; } void RealOutputConfiguratorDelegate::CreateFrameBuffer( int width, int height, const std::vector& outputs) { CHECK(screen_) << "Server not grabbed"; int current_width = DisplayWidth(display_, DefaultScreen(display_)); int current_height = DisplayHeight(display_, DefaultScreen(display_)); VLOG(1) << "CreateFrameBuffer: new=" << width << "x" << height << " current=" << current_width << "x" << current_height; if (width == current_width && height == current_height) return; DestroyUnusedCrtcs(outputs); int mm_width = width * kPixelsToMmScale; int mm_height = height * kPixelsToMmScale; XRRSetScreenSize(display_, window_, width, height, mm_width, mm_height); } void RealOutputConfiguratorDelegate::ConfigureCTM( int touch_device_id, const OutputConfigurator::CoordinateTransformation& ctm) { VLOG(1) << "ConfigureCTM: id=" << touch_device_id << " scale=" << ctm.x_scale << "x" << ctm.y_scale << " offset=(" << ctm.x_offset << ", " << ctm.y_offset << ")"; int ndevices = 0; XIDeviceInfo* info = XIQueryDevice(display_, touch_device_id, &ndevices); Atom prop = XInternAtom(display_, "Coordinate Transformation Matrix", False); Atom float_atom = XInternAtom(display_, "FLOAT", False); if (ndevices == 1 && prop != None && float_atom != None) { Atom type; int format; unsigned long num_items; unsigned long bytes_after; unsigned char* data = NULL; // Verify that the property exists with correct format, type, etc. int status = XIGetProperty(display_, info->deviceid, prop, 0, 0, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &data); if (data) XFree(data); if (status == Success && type == float_atom && format == 32) { float value[3][3] = { { ctm.x_scale, 0.0, ctm.x_offset }, { 0.0, ctm.y_scale, ctm.y_offset }, { 0.0, 0.0, 1.0 } }; XIChangeProperty(display_, info->deviceid, prop, type, format, PropModeReplace, reinterpret_cast(value), 9); } } XIFreeDeviceInfo(info); } void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager( bool projecting) { chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> SetIsProjecting(projecting); } bool RealOutputConfiguratorDelegate::InitModeInfo( RRMode mode, OutputConfigurator::ModeInfo* mode_info) { DCHECK(mode_info); CHECK(screen_) << "Server not grabbed"; // 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. for (int i = 0; i < screen_->nmode; ++i) { if (mode == screen_->modes[i].id) { const XRRModeInfo& info = screen_->modes[i]; mode_info->width = info.width; mode_info->height = info.height; mode_info->interlaced = info.modeFlags & RR_Interlace; if (info.hTotal && info.vTotal) { mode_info->refresh_rate = static_cast(info.dotClock) / (static_cast(info.hTotal) * static_cast(info.vTotal)); } else { mode_info->refresh_rate = 0.0f; } return true; } } return false; } OutputConfigurator::OutputSnapshot RealOutputConfiguratorDelegate::InitOutputSnapshot( RROutput id, XRROutputInfo* info, RRCrtc* last_used_crtc, int index) { OutputConfigurator::OutputSnapshot output; output.output = id; output.width_mm = info->mm_width; output.height_mm = info->mm_height; output.has_display_id = base::GetDisplayId(id, index, &output.display_id); output.index = index; bool is_internal = IsInternalOutput(info); // Use the index as a valid display ID even if the internal // display doesn't have valid EDID because the index // will never change. if (!output.has_display_id && is_internal) output.has_display_id = true; if (info->crtc) { XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display_, screen_, info->crtc); output.current_mode = crtc_info->mode; output.x = crtc_info->x; output.y = crtc_info->y; XRRFreeCrtcInfo(crtc_info); } // Assign a CRTC that isn't already in use. for (int i = 0; i < info->ncrtc; ++i) { if (info->crtcs[i] != *last_used_crtc) { output.crtc = info->crtcs[i]; *last_used_crtc = output.crtc; break; } } output.native_mode = GetOutputNativeMode(info); output.is_aspect_preserving_scaling = IsOutputAspectPreservingScaling(id); output.touch_device_id = None; for (int i = 0; i < info->nmode; ++i) { const RRMode mode = info->modes[i]; OutputConfigurator::ModeInfo mode_info; if (InitModeInfo(mode, &mode_info)) output.mode_infos.insert(std::make_pair(mode, mode_info)); else LOG(WARNING) << "Unable to find XRRModeInfo for mode " << mode; } std::string name(info->name); if (is_internal) { output.type = OUTPUT_TYPE_INTERNAL; } else if (name.find(kOutputName_VGA) == 0) { output.type = OUTPUT_TYPE_VGA; } else if (name.find(kOutputName_HDMI) == 0) { output.type = OUTPUT_TYPE_HDMI; } else if (name.find(kOutputName_DVI) == 0) { output.type = OUTPUT_TYPE_DVI; } else if (name.find(kOutputName_DisplayPort) == 0) { output.type = OUTPUT_TYPE_DISPLAYPORT; } else { LOG(ERROR) << "Unknown link type: " << name; output.type = OUTPUT_TYPE_UNKNOWN; } return output; } bool RealOutputConfiguratorDelegate::GetHDCPState(RROutput id, HDCPState* state) { unsigned char* values = NULL; int actual_format = 0; unsigned long nitems = 0; unsigned long bytes_after = 0; Atom actual_type = None; int success = 0; // TODO(kcwu): Use X11AtomCache to save round trip time of XInternAtom. Atom prop = XInternAtom(display_, kContentProtectionAtomName, False); bool ok = true; // TODO(kcwu): Move this to x11_util (similar method calls in this file and // output_util.cc) success = XRRGetOutputProperty(display_, id, prop, 0, 100, False, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &values); if (actual_type == None) { LOG(ERROR) << "Property '" << kContentProtectionAtomName << "' does not exist"; ok = false; } else if (success == Success && actual_type == XA_ATOM && actual_format == 32 && nitems == 1) { Atom value = reinterpret_cast(values)[0]; if (value == XInternAtom(display_, kProtectionUndesiredAtomName, False)) { *state = HDCP_STATE_UNDESIRED; } else if (value == XInternAtom(display_, kProtectionDesiredAtomName, False)) { *state = HDCP_STATE_DESIRED; } else if (value == XInternAtom(display_, kProtectionEnabledAtomName, False)) { *state = HDCP_STATE_ENABLED; } else { LOG(ERROR) << "Unknown " << kContentProtectionAtomName << " value: " << value; ok = false; } } else { LOG(ERROR) << "XRRGetOutputProperty failed"; ok = false; } if (values) XFree(values); VLOG(3) << "HDCP state: " << ok << "," << *state; return ok; } bool RealOutputConfiguratorDelegate::SetHDCPState(RROutput id, HDCPState state) { Atom name = XInternAtom(display_, kContentProtectionAtomName, False); Atom value = None; switch (state) { case HDCP_STATE_UNDESIRED: value = XInternAtom(display_, kProtectionUndesiredAtomName, False); break; case HDCP_STATE_DESIRED: value = XInternAtom(display_, kProtectionDesiredAtomName, False); break; default: NOTREACHED() << "Invalid HDCP state: " << state; return false; } base::X11ErrorTracker err_tracker; unsigned char* data = reinterpret_cast(&value); XRRChangeOutputProperty(display_, id, name, XA_ATOM, 32, PropModeReplace, data, 1); if (err_tracker.FoundNewError()) { LOG(ERROR) << "XRRChangeOutputProperty failed"; return false; } else { return true; } } void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs( const std::vector& outputs) { CHECK(screen_) << "Server not grabbed"; // Setting the screen size will fail if any CRTC doesn't fit afterwards. // At the same time, turning CRTCs off and back on uses up a lot of time. // This function tries to be smart to avoid too many off/on cycles: // - We disable all the CRTCs we won't need after the FB resize. // - We set the new modes on CRTCs, if they fit in both the old and new // FBs, and park them at (0,0) // - We disable the CRTCs we will need but don't fit in the old FB. Those // will be reenabled after the resize. // We don't worry about the cached state of the outputs here since we are // not interested in the state we are setting - we just try to get the CRTCs // out of the way so we can rebuild the frame buffer. for (int i = 0; i < screen_->ncrtc; ++i) { // Default config is to disable the crtcs. RRCrtc crtc = screen_->crtcs[i]; RRMode mode = None; RROutput output = None; const OutputConfigurator::ModeInfo* mode_info = NULL; for (std::vector::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { if (crtc == it->crtc) { mode = it->current_mode; output = it->output; if (mode != None) mode_info = OutputConfigurator::GetModeInfo(*it, mode); break; } } if (mode_info) { // In case our CRTC doesn't fit in our current framebuffer, disable it. // It'll get reenabled after we resize the framebuffer. int current_width = DisplayWidth(display_, DefaultScreen(display_)); int current_height = DisplayHeight(display_, DefaultScreen(display_)); if (mode_info->width > current_width || mode_info->height > current_height) { mode = None; output = None; mode_info = NULL; } } ConfigureCrtc(crtc, mode, output, 0, 0); } } bool RealOutputConfiguratorDelegate::IsOutputAspectPreservingScaling( RROutput id) { bool ret = false; Atom scaling_prop = XInternAtom(display_, "scaling mode", False); Atom full_aspect_atom = XInternAtom(display_, "Full aspect", False); if (scaling_prop == None || full_aspect_atom == None) return false; int nprop = 0; Atom* props = XRRListOutputProperties(display_, id, &nprop); for (int j = 0; j < nprop && !ret; j++) { Atom prop = props[j]; if (scaling_prop == prop) { unsigned char* values = NULL; int actual_format; unsigned long nitems; unsigned long bytes_after; Atom actual_type; int success; success = XRRGetOutputProperty(display_, id, prop, 0, 100, False, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &values); if (success == Success && actual_type == XA_ATOM && actual_format == 32 && nitems == 1) { Atom value = reinterpret_cast(values)[0]; if (full_aspect_atom == value) ret = true; } if (values) XFree(values); } } if (props) XFree(props); return ret; } void RealOutputConfiguratorDelegate::GetTouchscreens( std::vector* outputs) { int ndevices = 0; Atom valuator_x = XInternAtom(display_, "Abs MT Position X", False); Atom valuator_y = XInternAtom(display_, "Abs MT Position Y", False); if (valuator_x == None || valuator_y == None) return; std::set no_match_touchscreen; XIDeviceInfo* info = XIQueryDevice(display_, XIAllDevices, &ndevices); for (int i = 0; i < ndevices; i++) { if (!info[i].enabled || info[i].use != XIFloatingSlave) continue; // Assume all touchscreens are floating slaves double width = -1.0; double height = -1.0; bool is_direct_touch = false; for (int j = 0; j < info[i].num_classes; j++) { XIAnyClassInfo* class_info = info[i].classes[j]; if (class_info->type == XIValuatorClass) { XIValuatorClassInfo* valuator_info = reinterpret_cast(class_info); if (valuator_x == valuator_info->label) { // Ignore X axis valuator with unexpected properties if (valuator_info->number == 0 && valuator_info->mode == Absolute && valuator_info->min == 0.0) { width = valuator_info->max; } } else if (valuator_y == valuator_info->label) { // Ignore Y axis valuator with unexpected properties if (valuator_info->number == 1 && valuator_info->mode == Absolute && valuator_info->min == 0.0) { height = valuator_info->max; } } } #if defined(USE_XI2_MT) if (class_info->type == XITouchClass) { XITouchClassInfo* touch_info = reinterpret_cast(class_info); is_direct_touch = touch_info->mode == XIDirectTouch; } #endif } // Touchscreens should have absolute X and Y axes, // and be direct touch devices. if (width > 0.0 && height > 0.0 && is_direct_touch) { size_t k = 0; for (; k < outputs->size(); k++) { OutputConfigurator::OutputSnapshot* output = &(*outputs)[k]; if (output->native_mode == None || output->touch_device_id != None) continue; const OutputConfigurator::ModeInfo* mode_info = OutputConfigurator::GetModeInfo(*output, output->native_mode); if (!mode_info) continue; // Allow 1 pixel difference between screen and touchscreen // resolutions. Because in some cases for monitor resolution // 1024x768 touchscreen's resolution would be 1024x768, but for // some 1023x767. It really depends on touchscreen's firmware // configuration. if (std::abs(mode_info->width - width) <= 1.0 && std::abs(mode_info->height - height) <= 1.0) { output->touch_device_id = info[i].deviceid; VLOG(2) << "Found touchscreen for output #" << k << " id " << output->touch_device_id << " width " << width << " height " << height; break; } } if (k == outputs->size()) { no_match_touchscreen.insert(info[i].deviceid); VLOG(2) << "No matching output for touchscreen" << " id " << info[i].deviceid << " width " << width << " height " << height; } } } // Sometimes we can't find a matching screen for the touchscreen, e.g. // due to the touchscreen's reporting range having no correlation with the // screen's resolution. In this case, we arbitrarily assign unmatched // touchscreens to unmatched screens. for (std::set::iterator it = no_match_touchscreen.begin(); it != no_match_touchscreen.end(); it++) { for (size_t i = 0; i < outputs->size(); i++) { if ((*outputs)[i].type != OUTPUT_TYPE_INTERNAL && (*outputs)[i].native_mode != None && (*outputs)[i].touch_device_id == None ) { (*outputs)[i].touch_device_id = *it; VLOG(2) << "Arbitrarily matching touchscreen " << (*outputs)[i].touch_device_id << " to output #" << i; break; } } } XIFreeDeviceInfo(info); } } // namespace chromeos