1 // Copyright 2020 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 "ui/base/x/x11_cursor_loader.h"
6 
7 #include <dlfcn.h>
8 
9 #include <limits>
10 #include <string>
11 
12 #include "base/bind.h"
13 #include "base/compiler_specific.h"
14 #include "base/environment.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/memory/ref_counted_memory.h"
18 #include "base/memory/scoped_refptr.h"
19 #include "base/no_destructor.h"
20 #include "base/sequence_checker.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_split.h"
24 #include "base/strings/string_util.h"
25 #include "base/sys_byteorder.h"
26 #include "base/task/post_task.h"
27 #include "base/task/task_traits.h"
28 #include "base/task/thread_pool.h"
29 #include "base/task_runner_util.h"
30 #include "ui/base/cursor/cursor_theme_manager.h"
31 #include "ui/base/x/x11_util.h"
32 #include "ui/gfx/x/connection.h"
33 #include "ui/gfx/x/xproto.h"
34 
35 extern "C" {
36 const char* XcursorLibraryPath(void);
37 }
38 
39 namespace ui {
40 
41 namespace {
42 
43 // These cursor names are indexed by their ID in a cursor font.
44 constexpr const char* cursor_names[] = {
45     "X_cursor",
46     "arrow",
47     "based_arrow_down",
48     "based_arrow_up",
49     "boat",
50     "bogosity",
51     "bottom_left_corner",
52     "bottom_right_corner",
53     "bottom_side",
54     "bottom_tee",
55     "box_spiral",
56     "center_ptr",
57     "circle",
58     "clock",
59     "coffee_mug",
60     "cross",
61     "cross_reverse",
62     "crosshair",
63     "diamond_cross",
64     "dot",
65     "dotbox",
66     "double_arrow",
67     "draft_large",
68     "draft_small",
69     "draped_box",
70     "exchange",
71     "fleur",
72     "gobbler",
73     "gumby",
74     "hand1",
75     "hand2",
76     "heart",
77     "icon",
78     "iron_cross",
79     "left_ptr",
80     "left_side",
81     "left_tee",
82     "leftbutton",
83     "ll_angle",
84     "lr_angle",
85     "man",
86     "middlebutton",
87     "mouse",
88     "pencil",
89     "pirate",
90     "plus",
91     "question_arrow",
92     "right_ptr",
93     "right_side",
94     "right_tee",
95     "rightbutton",
96     "rtl_logo",
97     "sailboat",
98     "sb_down_arrow",
99     "sb_h_double_arrow",
100     "sb_left_arrow",
101     "sb_right_arrow",
102     "sb_up_arrow",
103     "sb_v_double_arrow",
104     "shuttle",
105     "sizing",
106     "spider",
107     "spraycan",
108     "star",
109     "target",
110     "tcross",
111     "top_left_arrow",
112     "top_left_corner",
113     "top_right_corner",
114     "top_side",
115     "top_tee",
116     "trek",
117     "ul_angle",
118     "umbrella",
119     "ur_angle",
120     "watch",
121     "xterm",
122 };
123 
GetEnv(const std::string & var)124 std::string GetEnv(const std::string& var) {
125   auto env = base::Environment::Create();
126   std::string value;
127   env->GetVar(var, &value);
128   return value;
129 }
130 
131 NO_SANITIZE("cfi-icall")
CursorPathFromLibXcursor()132 std::string CursorPathFromLibXcursor() {
133   struct DlCloser {
134     void operator()(void* ptr) const { dlclose(ptr); }
135   };
136 
137   std::unique_ptr<void, DlCloser> lib(dlopen("libXcursor.so.1", RTLD_LAZY));
138   if (!lib)
139     return "";
140 
141   if (auto* sym = reinterpret_cast<decltype(&XcursorLibraryPath)>(
142           dlsym(lib.get(), "XcursorLibraryPath"))) {
143     if (const char* path = sym())
144       return path;
145   }
146   return "";
147 }
148 
CursorPathImpl()149 std::string CursorPathImpl() {
150   constexpr const char kDefaultPath[] =
151       "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"
152       "/usr/X11R6/lib/X11/icons";
153 
154   auto libxcursor_path = CursorPathFromLibXcursor();
155   if (!libxcursor_path.empty())
156     return libxcursor_path;
157 
158   std::string path = GetEnv("XCURSOR_PATH");
159   return path.empty() ? kDefaultPath : path;
160 }
161 
CursorPath()162 const std::string& CursorPath() {
163   static base::NoDestructor<std::string> path(CursorPathImpl());
164   return *path;
165 }
166 
GetRenderARGBFormat(const x11::Render::QueryPictFormatsReply & formats)167 x11::Render::PictFormat GetRenderARGBFormat(
168     const x11::Render::QueryPictFormatsReply& formats) {
169   for (const auto& format : formats.formats) {
170     if (format.type == x11::Render::PictType::Direct && format.depth == 32 &&
171         format.direct.alpha_shift == 24 && format.direct.alpha_mask == 0xff &&
172         format.direct.red_shift == 16 && format.direct.red_mask == 0xff &&
173         format.direct.green_shift == 8 && format.direct.green_mask == 0xff &&
174         format.direct.blue_shift == 0 && format.direct.blue_mask == 0xff) {
175       return format.id;
176     }
177   }
178   return {};
179 }
180 
GetBaseThemes(const base::FilePath & abspath)181 std::vector<std::string> GetBaseThemes(const base::FilePath& abspath) {
182   DCHECK(abspath.IsAbsolute());
183   constexpr const char kKeyInherits[] = "Inherits";
184   std::string contents;
185   base::ReadFileToString(abspath, &contents);
186   base::StringPairs pairs;
187   base::SplitStringIntoKeyValuePairs(contents, '=', '\n', &pairs);
188   for (const auto& pair : pairs) {
189     if (base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL) == kKeyInherits) {
190       return base::SplitString(pair.second, ",;", base::TRIM_WHITESPACE,
191                                base::SPLIT_WANT_NONEMPTY);
192     }
193   }
194   return {};
195 }
196 
CanonicalizePath(base::FilePath path)197 base::FilePath CanonicalizePath(base::FilePath path) {
198   std::vector<std::string> components;
199   path.GetComponents(&components);
200   if (components[0] == "~") {
201     path = base::GetHomeDir();
202     for (size_t i = 1; i < components.size(); i++)
203       path = path.Append(components[i]);
204   } else {
205     path = base::MakeAbsoluteFilePath(path);
206   }
207   return path;
208 }
209 
210 // Reads the cursor called |name| for the theme named |theme|. Searches  all
211 // paths in the XCursor path and parent themes.
ReadCursorFromTheme(const std::string & theme,const std::string & name)212 scoped_refptr<base::RefCountedMemory> ReadCursorFromTheme(
213     const std::string& theme,
214     const std::string& name) {
215   constexpr const char kCursorDir[] = "cursors";
216   constexpr const char kThemeInfo[] = "index.theme";
217   std::vector<std::string> base_themes;
218 
219   auto paths = base::SplitString(CursorPath(), ":", base::TRIM_WHITESPACE,
220                                  base::SPLIT_WANT_NONEMPTY);
221   for (const auto& path : paths) {
222     auto dir = CanonicalizePath(base::FilePath(path));
223     if (dir.empty())
224       continue;
225     base::FilePath theme_dir = dir.Append(theme);
226     base::FilePath cursor_dir = theme_dir.Append(kCursorDir);
227 
228     std::string contents;
229     if (base::ReadFileToString(cursor_dir.Append(name), &contents))
230       return base::RefCountedString::TakeString(&contents);
231 
232     if (base_themes.empty())
233       base_themes = GetBaseThemes(theme_dir.Append(kThemeInfo));
234   }
235 
236   for (const auto& path : base_themes) {
237     if (auto contents = ReadCursorFromTheme(path, name))
238       return contents;
239   }
240 
241   return nullptr;
242 }
243 
ReadCursorFile(const std::string & name,const std::string & rm_xcursor_theme)244 scoped_refptr<base::RefCountedMemory> ReadCursorFile(
245     const std::string& name,
246     const std::string& rm_xcursor_theme) {
247   constexpr const char kDefaultTheme[] = "default";
248   std::string themes[] = {
249       // The toolkit theme has the highest priority.
250       CursorThemeManager::GetInstance()
251           ? CursorThemeManager::GetInstance()->GetCursorThemeName()
252           : std::string(),
253 
254       // Next try Xcursor.theme.
255       rm_xcursor_theme,
256 
257       // As a last resort, use the default theme.
258       kDefaultTheme,
259   };
260 
261   for (const std::string& theme : themes) {
262     if (theme.empty())
263       continue;
264     if (auto file = ReadCursorFromTheme(theme, name))
265       return file;
266   }
267   return nullptr;
268 }
269 
ReadCursorImages(const std::vector<std::string> & names,const std::string & rm_xcursor_theme,uint32_t preferred_size)270 std::vector<XCursorLoader::Image> ReadCursorImages(
271     const std::vector<std::string>& names,
272     const std::string& rm_xcursor_theme,
273     uint32_t preferred_size) {
274   // Fallback on a left pointer if possible.
275   auto names_copy = names;
276   names_copy.push_back("left_ptr");
277   for (const auto& name : names_copy) {
278     if (auto contents = ReadCursorFile(name, rm_xcursor_theme)) {
279       auto images = ParseCursorFile(contents, preferred_size);
280       if (!images.empty())
281         return images;
282     }
283   }
284   return {};
285 }
286 
287 }  // namespace
288 
XCursorLoader(x11::Connection * connection)289 XCursorLoader::XCursorLoader(x11::Connection* connection)
290     : connection_(connection) {
291   auto ver_cookie = connection_->render().QueryVersion(
292       {x11::Render::major_version, x11::Render::minor_version});
293   auto pf_cookie = connection_->render().QueryPictFormats({});
294   cursor_font_ = connection_->GenerateId<x11::Font>();
295   connection_->OpenFont({cursor_font_, "cursor"});
296 
297   std::string resource_manager;
298   if (ui::GetStringProperty(connection_->default_root(), "RESOURCE_MANAGER",
299                             &resource_manager)) {
300     ParseXResources(resource_manager);
301   }
302 
303   if (auto reply = ver_cookie.Sync()) {
304     render_version_ =
305         base::Version({reply->major_version, reply->minor_version});
306   }
307 
308   if (auto pf_reply = pf_cookie.Sync())
309     pict_format_ = GetRenderARGBFormat(*pf_reply.reply);
310 
311   for (uint16_t i = 0; i < base::size(cursor_names); i++)
312     cursor_name_to_char_[cursor_names[i]] = i;
313 }
314 
~XCursorLoader()315 XCursorLoader::~XCursorLoader() {
316   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
317 }
318 
LoadCursor(const std::vector<std::string> & names)319 scoped_refptr<X11Cursor> XCursorLoader::LoadCursor(
320     const std::vector<std::string>& names) {
321   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
322   auto cursor = base::MakeRefCounted<X11Cursor>();
323   if (SupportsCreateCursor()) {
324     base::PostTaskAndReplyWithResult(
325         FROM_HERE,
326         {base::ThreadPool(), base::MayBlock(),
327          base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
328         base::BindOnce(ReadCursorImages, names, rm_xcursor_theme_,
329                        GetPreferredCursorSize()),
330         base::BindOnce(&XCursorLoader::LoadCursorImpl,
331                        weak_factory_.GetWeakPtr(), cursor, names));
332   } else {
333     LoadCursorImpl(cursor, names, {});
334   }
335   return cursor;
336 }
337 
CreateCursor(const std::vector<Image> & images)338 scoped_refptr<X11Cursor> XCursorLoader::CreateCursor(
339     const std::vector<Image>& images) {
340   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
341   std::vector<scoped_refptr<X11Cursor>> cursors;
342   std::vector<x11::Render::AnimationCursorElement> elements;
343   cursors.reserve(images.size());
344   elements.reserve(images.size());
345 
346   for (const Image& image : images) {
347     auto cursor = CreateCursor(image.bitmap, image.hotspot);
348     cursors.push_back(cursor);
349     elements.push_back(x11::Render::AnimationCursorElement{
350         cursor->xcursor_, image.frame_delay_ms});
351   }
352 
353   if (elements.empty())
354     return nullptr;
355   if (elements.size() == 1 || !SupportsCreateAnimCursor())
356     return cursors[0];
357 
358   auto cursor = connection_->GenerateId<x11::Cursor>();
359   connection_->render().CreateAnimCursor({cursor, elements});
360   return base::MakeRefCounted<X11Cursor>(cursor);
361 }
362 
CreateCursor(const SkBitmap & bitmap,const gfx::Point & hotspot)363 scoped_refptr<X11Cursor> XCursorLoader::CreateCursor(
364     const SkBitmap& bitmap,
365     const gfx::Point& hotspot) {
366   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
367   auto pixmap = connection_->GenerateId<x11::Pixmap>();
368   auto gc = connection_->GenerateId<x11::GraphicsContext>();
369   int width = bitmap.width();
370   int height = bitmap.height();
371   connection_->CreatePixmap(
372       {32, pixmap, connection_->default_root(), width, height});
373   connection_->CreateGC({gc, pixmap});
374 
375   size_t size = bitmap.computeByteSize();
376   std::vector<uint8_t> vec(size);
377   memcpy(vec.data(), bitmap.getPixels(), size);
378   auto* connection = x11::Connection::Get();
379   x11::PutImageRequest put_image_request{
380       .format = x11::ImageFormat::ZPixmap,
381       .drawable = static_cast<x11::Pixmap>(pixmap),
382       .gc = static_cast<x11::GraphicsContext>(gc),
383       .width = width,
384       .height = height,
385       .depth = 32,
386       .data = base::RefCountedBytes::TakeVector(&vec),
387   };
388   connection->PutImage(put_image_request);
389 
390   x11::Render::Picture pic = connection_->GenerateId<x11::Render::Picture>();
391   connection_->render().CreatePicture({pic, pixmap, pict_format_});
392 
393   auto cursor = connection_->GenerateId<x11::Cursor>();
394   connection_->render().CreateCursor({cursor, pic, hotspot.x(), hotspot.y()});
395 
396   connection_->render().FreePicture({pic});
397   connection_->FreePixmap({pixmap});
398   connection_->FreeGC({gc});
399 
400   return base::MakeRefCounted<X11Cursor>(cursor);
401 }
402 
LoadCursorImpl(scoped_refptr<X11Cursor> cursor,const std::vector<std::string> & names,const std::vector<XCursorLoader::Image> & images)403 void XCursorLoader::LoadCursorImpl(
404     scoped_refptr<X11Cursor> cursor,
405     const std::vector<std::string>& names,
406     const std::vector<XCursorLoader::Image>& images) {
407   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
408   auto xcursor = connection_->GenerateId<x11::Cursor>();
409   if (!images.empty()) {
410     xcursor = CreateCursor(images)->ReleaseCursor();
411   } else {
412     // Fallback to using a font cursor.
413     auto core_char = CursorNamesToChar(names);
414     constexpr uint16_t kFontCursorFgColor = 0;
415     constexpr uint16_t kFontCursorBgColor = 65535;
416     connection_->CreateGlyphCursor(
417         {xcursor, cursor_font_, cursor_font_, 2 * core_char, 2 * core_char + 1,
418          kFontCursorFgColor, kFontCursorFgColor, kFontCursorFgColor,
419          kFontCursorBgColor, kFontCursorBgColor, kFontCursorBgColor});
420   }
421   cursor->SetCursor(xcursor);
422 }
423 
GetPreferredCursorSize() const424 uint32_t XCursorLoader::GetPreferredCursorSize() const {
425   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
426 
427   constexpr const char kXcursorSizeEnv[] = "XCURSOR_SIZE";
428   constexpr unsigned int kCursorSizeInchNum = 16;
429   constexpr unsigned int kCursorSizeInchDen = 72;
430   constexpr unsigned int kScreenCursorRatio = 48;
431 
432   // Allow the XCURSOR_SIZE environment variable to override GTK settings.
433   int size;
434   if (base::StringToInt(GetEnv(kXcursorSizeEnv), &size) && size > 0)
435     return size;
436 
437   // Let the toolkit have the next say.
438   auto* manager = CursorThemeManager::GetInstance();
439   size = manager ? manager->GetCursorThemeSize() : 0;
440   if (size > 0)
441     return size;
442 
443   // Use Xcursor.size from RESOURCE_MANAGER if available.
444   if (rm_xcursor_size_)
445     return rm_xcursor_size_;
446 
447   // Guess the cursor size based on the DPI.
448   if (rm_xft_dpi_)
449     return rm_xft_dpi_ * kCursorSizeInchNum / kCursorSizeInchDen;
450 
451   // As a last resort, guess the cursor size based on the screen size.
452   const auto& screen = connection_->default_screen();
453   return std::min(screen.width_in_pixels, screen.height_in_pixels) /
454          kScreenCursorRatio;
455 }
456 
ParseXResources(const std::string & resources)457 void XCursorLoader::ParseXResources(const std::string& resources) {
458   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
459   base::StringPairs pairs;
460   base::SplitStringIntoKeyValuePairs(resources, ':', '\n', &pairs);
461   for (const auto& pair : pairs) {
462     auto key = base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL);
463     auto value = base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL);
464 
465     if (key == "Xcursor.theme")
466       rm_xcursor_theme_ = std::string(value);
467     else if (key == "Xcursor.size")
468       base::StringToUint(value, &rm_xcursor_size_);
469     else if (key == "Xft.dpi")
470       base::StringToUint(value, &rm_xft_dpi_);
471   }
472 }
473 
CursorNamesToChar(const std::vector<std::string> & names) const474 uint16_t XCursorLoader::CursorNamesToChar(
475     const std::vector<std::string>& names) const {
476   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
477   for (const auto& name : names) {
478     auto it = cursor_name_to_char_.find(name);
479     if (it != cursor_name_to_char_.end())
480       return it->second;
481   }
482   // Use a left pointer as a fallback.
483   return 0;
484 }
485 
SupportsCreateCursor() const486 bool XCursorLoader::SupportsCreateCursor() const {
487   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
488   return render_version_.IsValid() && render_version_ >= base::Version("0.5");
489 }
490 
SupportsCreateAnimCursor() const491 bool XCursorLoader::SupportsCreateAnimCursor() const {
492   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
493   return render_version_.IsValid() && render_version_ >= base::Version("0.8");
494 }
495 
496 // This is ported from libxcb-cursor's parse_cursor_file.c:
497 // https://gitlab.freedesktop.org/xorg/lib/libxcb-cursor/-/blob/master/cursor/parse_cursor_file.c
ParseCursorFile(scoped_refptr<base::RefCountedMemory> file,uint32_t preferred_size)498 std::vector<XCursorLoader::Image> ParseCursorFile(
499     scoped_refptr<base::RefCountedMemory> file,
500     uint32_t preferred_size) {
501   constexpr uint32_t kMagic = 0x72756358;
502   constexpr uint32_t kImageType = 0xfffd0002;
503 
504   const uint8_t* mem = file->data();
505   size_t offset = 0;
506 
507   auto ReadU32s = [&](void* dest, size_t len) {
508     DCHECK_EQ(len % 4, 0u);
509     if (offset + len > file->size())
510       return false;
511     const auto* src32 = reinterpret_cast<const uint32_t*>(mem + offset);
512     auto* dest32 = reinterpret_cast<uint32_t*>(dest);
513     for (size_t i = 0; i < len / 4; i++)
514       dest32[i] = base::ByteSwapToLE32(src32[i]);
515     offset += len;
516     return true;
517   };
518 
519   struct FileHeader {
520     uint32_t magic;
521     uint32_t header;
522     uint32_t version;
523     uint32_t ntoc;
524   } header;
525   if (!ReadU32s(&header, sizeof(FileHeader)) || header.magic != kMagic)
526     return {};
527 
528   struct TableOfContentsEntry {
529     uint32_t type;
530     uint32_t subtype;
531     uint32_t position;
532   };
533   std::vector<TableOfContentsEntry> toc;
534   toc.reserve(header.ntoc);
535   for (uint32_t i = 0; i < header.ntoc; i++) {
536     TableOfContentsEntry entry;
537     if (!ReadU32s(&entry, sizeof(TableOfContentsEntry)))
538       return {};
539     toc.push_back(entry);
540   }
541 
542   uint32_t best_size = std::numeric_limits<uint32_t>::max();
543   for (const auto& entry : toc) {
544     auto delta = [](uint32_t x, uint32_t y) {
545       return std::max(x, y) - std::min(x, y);
546     };
547     if (entry.type != kImageType)
548       continue;
549     if (delta(entry.subtype, preferred_size) < delta(best_size, preferred_size))
550       best_size = entry.subtype;
551   }
552 
553   std::vector<XCursorLoader::Image> images;
554   for (const auto& entry : toc) {
555     if (entry.type != kImageType || entry.subtype != best_size)
556       continue;
557     offset = entry.position;
558     struct ChunkHeader {
559       uint32_t header;
560       uint32_t type;
561       uint32_t subtype;
562       uint32_t version;
563     } chunk_header;
564     if (!ReadU32s(&chunk_header, sizeof(ChunkHeader)) ||
565         chunk_header.type != entry.type ||
566         chunk_header.subtype != entry.subtype) {
567       continue;
568     }
569 
570     struct ImageHeader {
571       uint32_t width;
572       uint32_t height;
573       uint32_t xhot;
574       uint32_t yhot;
575       uint32_t delay;
576     } image;
577     if (!ReadU32s(&image, sizeof(ImageHeader)))
578       continue;
579     SkBitmap bitmap;
580     bitmap.allocN32Pixels(image.width, image.height);
581     if (!ReadU32s(bitmap.getPixels(), bitmap.computeByteSize()))
582       continue;
583     images.push_back(XCursorLoader::Image{
584         bitmap, gfx::Point(image.xhot, image.yhot), image.delay});
585   }
586   return images;
587 }
588 
589 }  // namespace ui
590