x11_display_util.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright 2018 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. #include "ui/base/x/x11_display_util.h"
  5. #include <dlfcn.h>
  6. #include <bitset>
  7. #include "base/bits.h"
  8. #include "base/compiler_specific.h"
  9. #include "base/logging.h"
  10. #include "base/strings/string_util.h"
  11. #include "ui/base/l10n/l10n_util.h"
  12. #include "ui/base/x/x11_util.h"
  13. #include "ui/display/util/display_util.h"
  14. #include "ui/display/util/edid_parser.h"
  15. #include "ui/gfx/color_space.h"
  16. #include "ui/gfx/geometry/matrix3_f.h"
  17. #include "ui/gfx/geometry/rect.h"
  18. #include "ui/gfx/geometry/vector3d_f.h"
  19. #include "ui/gfx/x/randr.h"
  20. #include "ui/gfx/x/x11_atom_cache.h"
  21. #include "ui/gfx/x/xproto_util.h"
  22. #include "ui/strings/grit/ui_strings.h"
  23. namespace ui {
  24. namespace {
  25. constexpr int kMinVersionXrandr = 103; // Need at least xrandr version 1.3.
  26. constexpr const char kRandrEdidProperty[] = "EDID";
  27. std::map<x11::RandR::Output, int> GetMonitors(int version,
  28. x11::RandR* randr,
  29. x11::Window window) {
  30. std::map<x11::RandR::Output, int> output_to_monitor;
  31. if (version >= 105) {
  32. if (auto reply = randr->GetMonitors({window}).Sync()) {
  33. for (size_t monitor = 0; monitor < reply->monitors.size(); monitor++) {
  34. for (x11::RandR::Output output : reply->monitors[monitor].outputs)
  35. output_to_monitor[output] = monitor;
  36. }
  37. }
  38. }
  39. return output_to_monitor;
  40. }
  41. // Sets the work area on a list of displays. The work area for each display
  42. // must already be initialized to the display bounds. At most one display out
  43. // of |displays| will be affected.
  44. void ClipWorkArea(std::vector<display::Display>* displays,
  45. int64_t primary_display_index,
  46. float scale) {
  47. x11::Window x_root_window = ui::GetX11RootWindow();
  48. std::vector<int32_t> value;
  49. if (!GetArrayProperty(x_root_window, x11::GetAtom("_NET_WORKAREA"), &value) ||
  50. value.size() < 4) {
  51. return;
  52. }
  53. gfx::Rect work_area = gfx::ScaleToEnclosingRect(
  54. gfx::Rect(value[0], value[1], value[2], value[3]), 1.0f / scale);
  55. // If the work area entirely contains exactly one display, assume it's meant
  56. // for that display (and so do nothing).
  57. if (std::count_if(displays->begin(), displays->end(),
  58. [&](const display::Display& display) {
  59. return work_area.Contains(display.bounds());
  60. }) == 1) {
  61. return;
  62. }
  63. // If the work area is entirely contained within exactly one display, assume
  64. // it's meant for that display and intersect the work area with only that
  65. // display.
  66. auto found = std::find_if(displays->begin(), displays->end(),
  67. [&](const display::Display& display) {
  68. return display.bounds().Contains(work_area);
  69. });
  70. // If the work area spans multiple displays, intersect the work area with the
  71. // primary display, like GTK does.
  72. display::Display& primary =
  73. found == displays->end() ? (*displays)[primary_display_index] : *found;
  74. work_area.Intersect(primary.work_area());
  75. if (!work_area.IsEmpty())
  76. primary.set_work_area(work_area);
  77. }
  78. float GetRefreshRateFromXRRModeInfo(
  79. const std::vector<x11::RandR::ModeInfo>& modes,
  80. x11::RandR::Mode current_mode_id) {
  81. for (const auto& mode_info : modes) {
  82. if (static_cast<x11::RandR::Mode>(mode_info.id) != current_mode_id)
  83. continue;
  84. if (!mode_info.htotal || !mode_info.vtotal)
  85. return 0;
  86. // Refresh Rate = Pixel Clock / (Horizontal Total * Vertical Total)
  87. return mode_info.dot_clock /
  88. static_cast<float>(mode_info.htotal * mode_info.vtotal);
  89. }
  90. return 0;
  91. }
  92. int DefaultBitsPerComponent() {
  93. auto* connection = x11::Connection::Get();
  94. const x11::VisualType& visual = connection->default_root_visual();
  95. // The mask fields are only valid for DirectColor and TrueColor classes.
  96. if (visual.c_class == x11::VisualClass::DirectColor ||
  97. visual.c_class == x11::VisualClass::TrueColor) {
  98. // RGB components are packed into fixed size integers for each visual. The
  99. // layout of bits in the packing is given by
  100. // |visual.{red,green,blue}_mask|. Count the number of bits to get the
  101. // number of bits per component.
  102. auto bits = [](auto mask) {
  103. return std::bitset<sizeof(mask) * 8>{mask}.count();
  104. };
  105. size_t red_bits = bits(visual.red_mask);
  106. size_t green_bits = bits(visual.green_mask);
  107. size_t blue_bits = bits(visual.blue_mask);
  108. if (red_bits == green_bits && red_bits == blue_bits)
  109. return red_bits;
  110. }
  111. // Next, try getting the number of colormap entries per subfield. If it's a
  112. // power of 2, log2 is a possible guess for the number of bits per component.
  113. if (base::bits::IsPowerOfTwo(visual.colormap_entries))
  114. return base::bits::Log2Ceiling(visual.colormap_entries);
  115. // |bits_per_rgb| can sometimes be unreliable (may be 11 for 30bpp visuals),
  116. // so only use it as a last resort.
  117. return visual.bits_per_rgb_value;
  118. }
  119. // Get the EDID data from the |output| and stores to |edid|.
  120. std::vector<uint8_t> GetEDIDProperty(x11::RandR* randr,
  121. x11::RandR::Output output) {
  122. auto future = randr->GetOutputProperty(x11::RandR::GetOutputPropertyRequest{
  123. .output = output,
  124. .property = x11::GetAtom(kRandrEdidProperty),
  125. .long_length = 128});
  126. auto response = future.Sync();
  127. std::vector<uint8_t> edid;
  128. if (response && response->format == 8 && response->type != x11::Atom::None)
  129. edid = std::move(response->data);
  130. return edid;
  131. }
  132. } // namespace
  133. int GetXrandrVersion() {
  134. auto impl = []() -> int {
  135. auto future = x11::Connection::Get()->randr().QueryVersion(
  136. {x11::RandR::major_version, x11::RandR::minor_version});
  137. if (auto response = future.Sync())
  138. return response->major_version * 100 + response->minor_version;
  139. return 0;
  140. };
  141. static int version = impl();
  142. return version;
  143. }
  144. std::vector<display::Display> GetFallbackDisplayList(float scale) {
  145. const auto& screen = x11::Connection::Get()->default_screen();
  146. gfx::Size physical_size(screen.width_in_millimeters,
  147. screen.height_in_millimeters);
  148. int width = screen.width_in_pixels;
  149. int height = screen.height_in_pixels;
  150. gfx::Rect bounds_in_pixels(0, 0, width, height);
  151. display::Display gfx_display(0, bounds_in_pixels);
  152. if (!display::Display::HasForceDeviceScaleFactor() &&
  153. display::IsDisplaySizeValid(physical_size)) {
  154. DCHECK_LE(1.0f, scale);
  155. gfx_display.SetScaleAndBounds(scale, bounds_in_pixels);
  156. gfx_display.set_work_area(
  157. gfx::ScaleToEnclosingRect(bounds_in_pixels, 1.0f / scale));
  158. } else {
  159. scale = 1;
  160. }
  161. gfx_display.set_color_depth(screen.root_depth);
  162. gfx_display.set_depth_per_component(DefaultBitsPerComponent());
  163. std::vector<display::Display> displays{gfx_display};
  164. ClipWorkArea(&displays, 0, scale);
  165. return displays;
  166. }
  167. std::vector<display::Display> BuildDisplaysFromXRandRInfo(
  168. int version,
  169. float scale,
  170. int64_t* primary_display_index_out) {
  171. DCHECK(primary_display_index_out);
  172. DCHECK_GE(version, kMinVersionXrandr);
  173. auto* connection = x11::Connection::Get();
  174. auto& randr = connection->randr();
  175. auto x_root_window = ui::GetX11RootWindow();
  176. std::vector<display::Display> displays;
  177. auto resources = randr.GetScreenResourcesCurrent({x_root_window}).Sync();
  178. if (!resources) {
  179. LOG(ERROR) << "XRandR returned no displays; falling back to root window";
  180. return GetFallbackDisplayList(scale);
  181. }
  182. const int depth = connection->default_screen().root_depth;
  183. const int bits_per_component = DefaultBitsPerComponent();
  184. std::map<x11::RandR::Output, int> output_to_monitor =
  185. GetMonitors(version, &randr, x_root_window);
  186. *primary_display_index_out = 0;
  187. auto output_primary = randr.GetOutputPrimary({x_root_window}).Sync();
  188. if (!output_primary)
  189. return GetFallbackDisplayList(scale);
  190. x11::RandR::Output primary_display_id = output_primary->output;
  191. int explicit_primary_display_index = -1;
  192. int monitor_order_primary_display_index = -1;
  193. // As per-display scale factor is not supported right now,
  194. // the X11 root window's scale factor is always used.
  195. for (size_t i = 0; i < resources->outputs.size(); i++) {
  196. x11::RandR::Output output_id = resources->outputs[i];
  197. auto output_info =
  198. randr.GetOutputInfo({output_id, resources->config_timestamp}).Sync();
  199. if (!output_info)
  200. continue;
  201. if (output_info->connection != x11::RandR::RandRConnection::Connected)
  202. continue;
  203. bool is_primary_display = (output_id == primary_display_id);
  204. if (output_info->crtc == static_cast<x11::RandR::Crtc>(0))
  205. continue;
  206. auto crtc =
  207. randr.GetCrtcInfo({output_info->crtc, resources->config_timestamp})
  208. .Sync();
  209. if (!crtc)
  210. continue;
  211. display::EdidParser edid_parser(
  212. GetEDIDProperty(&randr, static_cast<x11::RandR::Output>(output_id)));
  213. auto output_32 = static_cast<uint32_t>(output_id);
  214. int64_t display_id =
  215. output_32 > 0xff ? 0 : edid_parser.GetIndexBasedDisplayId(output_32);
  216. // It isn't ideal, but if we can't parse the EDID data, fall back on the
  217. // display number.
  218. if (!display_id)
  219. display_id = i;
  220. gfx::Rect crtc_bounds(crtc->x, crtc->y, crtc->width, crtc->height);
  221. display::Display display(display_id, crtc_bounds);
  222. if (!display::Display::HasForceDeviceScaleFactor()) {
  223. display.SetScaleAndBounds(scale, crtc_bounds);
  224. display.set_work_area(
  225. gfx::ScaleToEnclosingRect(crtc_bounds, 1.0f / scale));
  226. }
  227. switch (crtc->rotation) {
  228. case x11::RandR::Rotation::Rotate_0:
  229. display.set_rotation(display::Display::ROTATE_0);
  230. break;
  231. case x11::RandR::Rotation::Rotate_90:
  232. display.set_rotation(display::Display::ROTATE_90);
  233. break;
  234. case x11::RandR::Rotation::Rotate_180:
  235. display.set_rotation(display::Display::ROTATE_180);
  236. break;
  237. case x11::RandR::Rotation::Rotate_270:
  238. display.set_rotation(display::Display::ROTATE_270);
  239. break;
  240. case x11::RandR::Rotation::Reflect_X:
  241. case x11::RandR::Rotation::Reflect_Y:
  242. NOTIMPLEMENTED();
  243. }
  244. if (is_primary_display)
  245. explicit_primary_display_index = displays.size();
  246. const std::string name(output_info->name.begin(), output_info->name.end());
  247. if (base::StartsWith(name, "eDP") || base::StartsWith(name, "LVDS")) {
  248. display::SetInternalDisplayIds({display_id});
  249. // Use localized variant of "Built-in display" for internal displays.
  250. // This follows the ozone DRM behavior (i.e. ChromeOS).
  251. display.set_label(l10n_util::GetStringUTF8(IDS_DISPLAY_NAME_INTERNAL));
  252. } else {
  253. display.set_label(edid_parser.display_name());
  254. }
  255. auto monitor_iter =
  256. output_to_monitor.find(static_cast<x11::RandR::Output>(output_id));
  257. if (monitor_iter != output_to_monitor.end() && monitor_iter->second == 0)
  258. monitor_order_primary_display_index = displays.size();
  259. if (!display::HasForceDisplayColorProfile()) {
  260. gfx::ICCProfile icc_profile = ui::GetICCProfileForMonitor(
  261. monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second);
  262. gfx::ColorSpace color_space = icc_profile.GetPrimariesOnlyColorSpace();
  263. // Most folks do not have an ICC profile set up, but we still want to
  264. // detect if a display has a wide color gamut so that HDR videos can be
  265. // enabled. Only do this if |bits_per_component| > 8 or else SDR
  266. // screens may have washed out colors.
  267. if (bits_per_component > 8 && !color_space.IsValid())
  268. color_space = display::GetColorSpaceFromEdid(edid_parser);
  269. display.set_color_spaces(
  270. gfx::DisplayColorSpaces(color_space, gfx::BufferFormat::BGRA_8888));
  271. }
  272. display.set_color_depth(depth);
  273. display.set_depth_per_component(bits_per_component);
  274. // Set monitor refresh rate
  275. int refresh_rate = static_cast<int>(
  276. GetRefreshRateFromXRRModeInfo(resources->modes, crtc->mode));
  277. display.set_display_frequency(refresh_rate);
  278. displays.push_back(display);
  279. }
  280. if (explicit_primary_display_index != -1)
  281. *primary_display_index_out = explicit_primary_display_index;
  282. else if (monitor_order_primary_display_index != -1)
  283. *primary_display_index_out = monitor_order_primary_display_index;
  284. if (displays.empty())
  285. return GetFallbackDisplayList(scale);
  286. ClipWorkArea(&displays, *primary_display_index_out, scale);
  287. return displays;
  288. }
  289. base::TimeDelta GetPrimaryDisplayRefreshIntervalFromXrandr() {
  290. constexpr base::TimeDelta kDefaultInterval = base::Seconds(1. / 60);
  291. x11::RandR randr = x11::Connection::Get()->randr();
  292. auto root = ui::GetX11RootWindow();
  293. auto resources = randr.GetScreenResourcesCurrent({root}).Sync();
  294. if (!resources)
  295. return kDefaultInterval;
  296. // TODO(crbug.com/726842): It might make sense here to pick the output that
  297. // the window is on. On the other hand, if compositing is enabled, all drawing
  298. // might be synced to the primary output anyway. Needs investigation.
  299. auto output_primary = randr.GetOutputPrimary({root}).Sync();
  300. if (!output_primary)
  301. return kDefaultInterval;
  302. x11::RandR::Output primary_output = output_primary->output;
  303. bool disconnected_primary = false;
  304. for (size_t i = 0; i < resources->outputs.size(); i++) {
  305. if (!disconnected_primary && resources->outputs[i] != primary_output)
  306. continue;
  307. auto output_info =
  308. randr.GetOutputInfo({primary_output, resources->config_timestamp})
  309. .Sync();
  310. if (!output_info)
  311. continue;
  312. if (output_info->connection != x11::RandR::RandRConnection::Connected) {
  313. // If the primary monitor is disconnected, then start over and choose the
  314. // first connected monitor instead.
  315. if (!disconnected_primary) {
  316. disconnected_primary = true;
  317. i = -1;
  318. }
  319. continue;
  320. }
  321. auto crtc =
  322. randr.GetCrtcInfo({output_info->crtc, resources->config_timestamp})
  323. .Sync();
  324. if (!crtc)
  325. continue;
  326. float refresh_rate =
  327. GetRefreshRateFromXRRModeInfo(resources->modes, crtc->mode);
  328. if (refresh_rate == 0)
  329. continue;
  330. return base::Seconds(1. / refresh_rate);
  331. }
  332. return kDefaultInterval;
  333. }
  334. } // namespace ui