| // 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 <X11/Xatom.h> |
| #include <X11/Xlib.h> |
| #include <X11/extensions/dpms.h> |
| #include <X11/extensions/XInput.h> |
| #include <X11/extensions/XInput2.h> |
| #include <X11/extensions/Xrandr.h> |
| |
| #include <cmath> |
| |
| #include "base/logging.h" |
| #include "base/message_loop/message_pump_aurax11.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; |
| |
| 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::MessagePumpAuraX11::GetDefaultXDisplay()), |
| window_(DefaultRootWindow(display_)), |
| screen_(NULL), |
| is_panel_fitting_enabled_(false) { |
| } |
| |
| RealOutputConfiguratorDelegate::~RealOutputConfiguratorDelegate() { |
| } |
| |
| void RealOutputConfiguratorDelegate::SetPanelFittingEnabled(bool enabled) { |
| is_panel_fitting_enabled_ = enabled; |
| } |
| |
| 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<OutputConfigurator::OutputSnapshot> |
| RealOutputConfiguratorDelegate::GetOutputs( |
| const OutputConfigurator::StateController* state_controller) { |
| CHECK(screen_) << "Server not grabbed"; |
| |
| std::vector<OutputConfigurator::OutputSnapshot> outputs; |
| XRROutputInfo* one_info = NULL; |
| XRROutputInfo* two_info = NULL; |
| RRCrtc last_used_crtc = None; |
| |
| for (int i = 0; i < screen_->noutput && outputs.size() < 2; ++i) { |
| RROutput this_id = screen_->outputs[i]; |
| XRROutputInfo* output_info = XRRGetOutputInfo(display_, screen_, this_id); |
| bool is_connected = (output_info->connection == RR_Connected); |
| |
| if (is_connected) { |
| OutputConfigurator::OutputSnapshot to_populate; |
| to_populate.output = this_id; |
| to_populate.has_display_id = |
| GetDisplayId(this_id, i, &to_populate.display_id); |
| to_populate.is_internal = IsInternalOutput(output_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 (!to_populate.has_display_id && to_populate.is_internal) |
| to_populate.has_display_id = true; |
| |
| (outputs.empty() ? one_info : two_info) = output_info; |
| |
| // Now, look up the current CRTC and any related info. |
| if (output_info->crtc) { |
| XRRCrtcInfo* crtc_info = XRRGetCrtcInfo( |
| display_, screen_, output_info->crtc); |
| to_populate.current_mode = crtc_info->mode; |
| to_populate.x = crtc_info->x; |
| to_populate.y = crtc_info->y; |
| XRRFreeCrtcInfo(crtc_info); |
| } |
| |
| // Assign a CRTC that isn't already in use. |
| for (int j = 0; j < output_info->ncrtc; ++j) { |
| if (output_info->crtcs[j] != last_used_crtc) { |
| to_populate.crtc = output_info->crtcs[j]; |
| last_used_crtc = to_populate.crtc; |
| break; |
| } |
| } |
| to_populate.native_mode = GetOutputNativeMode(output_info); |
| if (to_populate.has_display_id) { |
| int width = 0, height = 0; |
| if (state_controller && |
| state_controller->GetResolutionForDisplayId( |
| to_populate.display_id, &width, &height)) { |
| to_populate.selected_mode = |
| FindOutputModeMatchingSize(screen_, output_info, width, height); |
| } |
| } |
| // Fallback to native mode. |
| if (to_populate.selected_mode == None) |
| to_populate.selected_mode = to_populate.native_mode; |
| |
| to_populate.is_aspect_preserving_scaling = |
| IsOutputAspectPreservingScaling(this_id); |
| to_populate.touch_device_id = None; |
| |
| VLOG(2) << "Found display " << outputs.size() << ":" |
| << " output=" << to_populate.output |
| << " crtc=" << to_populate.crtc |
| << " current_mode=" << to_populate.current_mode; |
| outputs.push_back(to_populate); |
| } else { |
| XRRFreeOutputInfo(output_info); |
| } |
| } |
| |
| if (outputs.size() == 2) { |
| bool one_is_internal = IsInternalOutput(one_info); |
| bool two_is_internal = IsInternalOutput(two_info); |
| int internal_outputs = (one_is_internal ? 1 : 0) + |
| (two_is_internal ? 1 : 0); |
| DCHECK_LT(internal_outputs, 2); |
| LOG_IF(WARNING, internal_outputs == 2) |
| << "Two internal outputs detected."; |
| |
| bool can_mirror = false; |
| for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) { |
| // Try preserving external output's aspect ratio on the first attempt. |
| // If that fails, fall back to the highest matching resolution. |
| bool preserve_aspect = attempt == 0; |
| |
| if (internal_outputs == 1) { |
| if (one_is_internal) { |
| can_mirror = FindOrCreateMirrorMode(one_info, two_info, |
| outputs[0].output, is_panel_fitting_enabled_, preserve_aspect, |
| &outputs[0].mirror_mode, &outputs[1].mirror_mode); |
| } else { // if (two_is_internal) |
| can_mirror = FindOrCreateMirrorMode(two_info, one_info, |
| outputs[1].output, is_panel_fitting_enabled_, preserve_aspect, |
| &outputs[1].mirror_mode, &outputs[0].mirror_mode); |
| } |
| } else { // if (internal_outputs == 0) |
| // No panel fitting for external outputs, so fall back to exact match. |
| can_mirror = FindOrCreateMirrorMode(one_info, two_info, |
| outputs[0].output, false, preserve_aspect, |
| &outputs[0].mirror_mode, &outputs[1].mirror_mode); |
| if (!can_mirror && preserve_aspect) { |
| // FindOrCreateMirrorMode will try to preserve aspect ratio of |
| // what it thinks is external display, so if it didn't succeed |
| // with one, maybe it will succeed with the other. This way we |
| // will have correct aspect ratio on at least one of them. |
| can_mirror = FindOrCreateMirrorMode(two_info, one_info, |
| outputs[1].output, false, preserve_aspect, |
| &outputs[1].mirror_mode, &outputs[0].mirror_mode); |
| } |
| } |
| } |
| } |
| |
| GetTouchscreens(&outputs); |
| XRRFreeOutputInfo(one_info); |
| XRRFreeOutputInfo(two_info); |
| return outputs; |
| } |
| |
| bool RealOutputConfiguratorDelegate::GetModeDetails(RRMode mode, |
| int* width, |
| int* height, |
| bool* interlaced) { |
| 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]; |
| if (width) |
| *width = info.width; |
| if (height) |
| *height = info.height; |
| if (interlaced) |
| *interlaced = info.modeFlags & RR_Interlace; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| 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<OutputConfigurator::OutputSnapshot>& 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<unsigned char*>(value), |
| 9); |
| } |
| } |
| XIFreeDeviceInfo(info); |
| } |
| |
| void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager( |
| bool projecting) { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| SetIsProjecting(projecting); |
| } |
| |
| void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs( |
| const std::vector<OutputConfigurator::OutputSnapshot>& 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; |
| for (std::vector<OutputConfigurator::OutputSnapshot>::const_iterator it = |
| outputs.begin(); it != outputs.end(); ++it) { |
| if (crtc == it->crtc) { |
| mode = it->current_mode; |
| output = it->output; |
| break; |
| } |
| } |
| |
| if (mode != None) { |
| // In case our CRTC doesn't fit in our current framebuffer, disable it. |
| // It'll get reenabled after we resize the framebuffer. |
| int mode_width = 0, mode_height = 0; |
| CHECK(GetModeDetails(mode, &mode_width, &mode_height, NULL)); |
| int current_width = DisplayWidth(display_, DefaultScreen(display_)); |
| int current_height = DisplayHeight(display_, DefaultScreen(display_)); |
| if (mode_width > current_width || mode_height > current_height) { |
| mode = None; |
| output = None; |
| } |
| } |
| |
| 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<Atom*>(values)[0]; |
| if (full_aspect_atom == value) |
| ret = true; |
| } |
| if (values) |
| XFree(values); |
| } |
| } |
| if (props) |
| XFree(props); |
| |
| return ret; |
| } |
| |
| bool RealOutputConfiguratorDelegate::FindOrCreateMirrorMode( |
| XRROutputInfo* internal_info, |
| XRROutputInfo* external_info, |
| RROutput internal_output_id, |
| bool try_creating, |
| bool preserve_aspect, |
| RRMode* internal_mirror_mode, |
| RRMode* external_mirror_mode) { |
| RRMode internal_mode_id = GetOutputNativeMode(internal_info); |
| RRMode external_mode_id = GetOutputNativeMode(external_info); |
| |
| if (internal_mode_id == None || external_mode_id == None) |
| return false; |
| |
| int internal_native_width = 0, internal_native_height = 0; |
| int external_native_width = 0, external_native_height = 0; |
| CHECK(GetModeDetails(internal_mode_id, &internal_native_width, |
| &internal_native_height, NULL)); |
| CHECK(GetModeDetails(external_mode_id, &external_native_width, |
| &external_native_height, NULL)); |
| |
| // Check if some external output resolution can be mirrored on internal. |
| // Prefer the modes in the order that X sorts them, |
| // assuming this is the order in which they look better on the monitor. |
| // If X's order is not satisfactory, we can either fix X's sorting, |
| // or implement our sorting here. |
| for (int i = 0; i < external_info->nmode; i++) { |
| external_mode_id = external_info->modes[i]; |
| int external_width = 0, external_height = 0; |
| bool is_external_interlaced = false; |
| CHECK(GetModeDetails(external_mode_id, &external_width, &external_height, |
| &is_external_interlaced)); |
| bool is_native_aspect_ratio = |
| external_native_width * external_height == |
| external_native_height * external_width; |
| if (preserve_aspect && !is_native_aspect_ratio) |
| continue; // Allow only aspect ratio preserving modes for mirroring |
| |
| // Try finding exact match |
| for (int j = 0; j < internal_info->nmode; j++) { |
| internal_mode_id = internal_info->modes[j]; |
| int internal_width = 0, internal_height = 0; |
| bool is_internal_interlaced = false; |
| CHECK(GetModeDetails(internal_mode_id, &internal_width, |
| &internal_height, &is_internal_interlaced)); |
| if (internal_width == external_width && |
| internal_height == external_height && |
| is_internal_interlaced == is_external_interlaced) { |
| *internal_mirror_mode = internal_mode_id; |
| *external_mirror_mode = external_mode_id; |
| return true; // Mirror mode found |
| } |
| } |
| |
| // Try to create a matching internal output mode by panel fitting |
| if (try_creating) { |
| // We can downscale by 1.125, and upscale indefinitely |
| // Downscaling looks ugly, so, can fit == can upscale |
| // Also, internal panels don't support fitting interlaced modes |
| bool can_fit = |
| internal_native_width >= external_width && |
| internal_native_height >= external_height && |
| !is_external_interlaced; |
| if (can_fit) { |
| XRRAddOutputMode(display_, internal_output_id, external_mode_id); |
| *internal_mirror_mode = *external_mirror_mode = external_mode_id; |
| return true; // Mirror mode created |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void RealOutputConfiguratorDelegate::GetTouchscreens( |
| std::vector<OutputConfigurator::OutputSnapshot>* 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; |
| |
| 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<XIValuatorClassInfo*>(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<XITouchClassInfo*>(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++) { |
| if ((*outputs)[k].native_mode == None || |
| (*outputs)[k].touch_device_id != None) |
| continue; |
| int native_mode_width = 0, native_mode_height = 0; |
| if (!GetModeDetails((*outputs)[k].native_mode, &native_mode_width, |
| &native_mode_height, NULL)) |
| 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(native_mode_width - width) <= 1.0 && |
| std::abs(native_mode_height - height) <= 1.0) { |
| (*outputs)[k].touch_device_id = info[i].deviceid; |
| |
| VLOG(2) << "Found touchscreen for output #" << k |
| << " id " << (*outputs)[k].touch_device_id |
| << " width " << width |
| << " height " << height; |
| break; |
| } |
| } |
| |
| VLOG_IF(2, k == outputs->size()) |
| << "No matching output - ignoring touchscreen" |
| << " id " << info[i].deviceid |
| << " width " << width |
| << " height " << height; |
| } |
| } |
| |
| XIFreeDeviceInfo(info); |
| } |
| |
| } // namespace chromeos |