x11_cursor_loader.cc 18 KB

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