123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- // Copyright 2020 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 "ui/base/x/x11_cursor_loader.h"
- #include <dlfcn.h>
- #include <limits>
- #include <string>
- #include "base/bind.h"
- #include "base/compiler_specific.h"
- #include "base/environment.h"
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/memory/ref_counted_memory.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/no_destructor.h"
- #include "base/sequence_checker.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/string_split.h"
- #include "base/strings/string_util.h"
- #include "base/sys_byteorder.h"
- #include "base/task/task_runner_util.h"
- #include "base/task/task_traits.h"
- #include "base/task/thread_pool.h"
- #include "base/time/time.h"
- #include "ui/base/x/x11_util.h"
- #include "ui/gfx/x/connection.h"
- #include "ui/gfx/x/x11_atom_cache.h"
- #include "ui/gfx/x/xproto.h"
- #include "ui/gfx/x/xproto_util.h"
- #if BUILDFLAG(IS_LINUX)
- #include "ui/linux/linux_ui.h"
- #endif
- extern "C" {
- const char* XcursorLibraryPath(void);
- }
- namespace ui {
- namespace {
- // These cursor names are indexed by their ID in a cursor font.
- constexpr const char* cursor_names[] = {
- "X_cursor",
- "arrow",
- "based_arrow_down",
- "based_arrow_up",
- "boat",
- "bogosity",
- "bottom_left_corner",
- "bottom_right_corner",
- "bottom_side",
- "bottom_tee",
- "box_spiral",
- "center_ptr",
- "circle",
- "clock",
- "coffee_mug",
- "cross",
- "cross_reverse",
- "crosshair",
- "diamond_cross",
- "dot",
- "dotbox",
- "double_arrow",
- "draft_large",
- "draft_small",
- "draped_box",
- "exchange",
- "fleur",
- "gobbler",
- "gumby",
- "hand1",
- "hand2",
- "heart",
- "icon",
- "iron_cross",
- "left_ptr",
- "left_side",
- "left_tee",
- "leftbutton",
- "ll_angle",
- "lr_angle",
- "man",
- "middlebutton",
- "mouse",
- "pencil",
- "pirate",
- "plus",
- "question_arrow",
- "right_ptr",
- "right_side",
- "right_tee",
- "rightbutton",
- "rtl_logo",
- "sailboat",
- "sb_down_arrow",
- "sb_h_double_arrow",
- "sb_left_arrow",
- "sb_right_arrow",
- "sb_up_arrow",
- "sb_v_double_arrow",
- "shuttle",
- "sizing",
- "spider",
- "spraycan",
- "star",
- "target",
- "tcross",
- "top_left_arrow",
- "top_left_corner",
- "top_right_corner",
- "top_side",
- "top_tee",
- "trek",
- "ul_angle",
- "umbrella",
- "ur_angle",
- "watch",
- "xterm",
- };
- std::string GetEnv(const std::string& var) {
- auto env = base::Environment::Create();
- std::string value;
- env->GetVar(var, &value);
- return value;
- }
- NO_SANITIZE("cfi-icall")
- std::string CursorPathFromLibXcursor() {
- struct DlCloser {
- void operator()(void* ptr) const { dlclose(ptr); }
- };
- std::unique_ptr<void, DlCloser> lib(dlopen("libXcursor.so.1", RTLD_LAZY));
- if (!lib)
- return "";
- if (auto* sym = reinterpret_cast<decltype(&XcursorLibraryPath)>(
- dlsym(lib.get(), "XcursorLibraryPath"))) {
- if (const char* path = sym())
- return path;
- }
- return "";
- }
- std::string CursorPathImpl() {
- constexpr const char kDefaultPath[] =
- "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"
- "/usr/X11R6/lib/X11/icons";
- auto libxcursor_path = CursorPathFromLibXcursor();
- if (!libxcursor_path.empty())
- return libxcursor_path;
- std::string path = GetEnv("XCURSOR_PATH");
- return path.empty() ? kDefaultPath : path;
- }
- const std::string& CursorPath() {
- static base::NoDestructor<std::string> path(CursorPathImpl());
- return *path;
- }
- x11::Render::PictFormat GetRenderARGBFormat(
- const x11::Render::QueryPictFormatsReply& formats) {
- for (const auto& format : formats.formats) {
- if (format.type == x11::Render::PictType::Direct && format.depth == 32 &&
- format.direct.alpha_shift == 24 && format.direct.alpha_mask == 0xff &&
- format.direct.red_shift == 16 && format.direct.red_mask == 0xff &&
- format.direct.green_shift == 8 && format.direct.green_mask == 0xff &&
- format.direct.blue_shift == 0 && format.direct.blue_mask == 0xff) {
- return format.id;
- }
- }
- return {};
- }
- std::vector<std::string> GetBaseThemes(const base::FilePath& abspath) {
- DCHECK(abspath.IsAbsolute());
- constexpr const char kKeyInherits[] = "Inherits";
- std::string contents;
- base::ReadFileToString(abspath, &contents);
- base::StringPairs pairs;
- base::SplitStringIntoKeyValuePairs(contents, '=', '\n', &pairs);
- for (const auto& pair : pairs) {
- if (base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL) == kKeyInherits) {
- return base::SplitString(pair.second, ",;", base::TRIM_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY);
- }
- }
- return {};
- }
- base::FilePath CanonicalizePath(base::FilePath path) {
- std::vector<std::string> components = path.GetComponents();
- if (components[0] == "~") {
- path = base::GetHomeDir();
- for (size_t i = 1; i < components.size(); i++)
- path = path.Append(components[i]);
- } else {
- path = base::MakeAbsoluteFilePath(path);
- }
- return path;
- }
- // Reads the cursor called |name| for the theme named |theme|. Searches all
- // paths in the XCursor path and parent themes.
- scoped_refptr<base::RefCountedMemory> ReadCursorFromTheme(
- const std::string& theme,
- const std::string& name) {
- constexpr const char kCursorDir[] = "cursors";
- constexpr const char kThemeInfo[] = "index.theme";
- std::vector<std::string> base_themes;
- auto paths = base::SplitString(CursorPath(), ":", base::TRIM_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY);
- for (const auto& path : paths) {
- auto dir = CanonicalizePath(base::FilePath(path));
- if (dir.empty())
- continue;
- base::FilePath theme_dir = dir.Append(theme);
- base::FilePath cursor_dir = theme_dir.Append(kCursorDir);
- std::string contents;
- if (base::ReadFileToString(cursor_dir.Append(name), &contents))
- return base::RefCountedString::TakeString(&contents);
- if (base_themes.empty())
- base_themes = GetBaseThemes(theme_dir.Append(kThemeInfo));
- }
- for (const auto& path : base_themes) {
- if (auto contents = ReadCursorFromTheme(path, name))
- return contents;
- }
- return nullptr;
- }
- scoped_refptr<base::RefCountedMemory> ReadCursorFile(
- const std::string& name,
- const std::string& rm_xcursor_theme) {
- constexpr const char kDefaultTheme[] = "default";
- std::string themes[] = {
- #if BUILDFLAG(IS_LINUX)
- // The toolkit theme has the highest priority.
- LinuxUi::instance() ? LinuxUi::instance()->GetCursorThemeName()
- : std::string(),
- #endif
- // Next try Xcursor.theme.
- rm_xcursor_theme,
- // As a last resort, use the default theme.
- kDefaultTheme,
- };
- for (const std::string& theme : themes) {
- if (theme.empty())
- continue;
- if (auto file = ReadCursorFromTheme(theme, name))
- return file;
- }
- return nullptr;
- }
- std::vector<XCursorLoader::Image> ReadCursorImages(
- const std::vector<std::string>& names,
- const std::string& rm_xcursor_theme,
- uint32_t preferred_size) {
- // Fallback on a left pointer if possible.
- auto names_copy = names;
- names_copy.push_back("left_ptr");
- for (const auto& name : names_copy) {
- if (auto contents = ReadCursorFile(name, rm_xcursor_theme)) {
- auto images = ParseCursorFile(contents, preferred_size);
- if (!images.empty())
- return images;
- }
- }
- return {};
- }
- } // namespace
- XCursorLoader::XCursorLoader(x11::Connection* connection)
- : connection_(connection) {
- auto ver_cookie = connection_->render().QueryVersion(
- {x11::Render::major_version, x11::Render::minor_version});
- auto pf_cookie = connection_->render().QueryPictFormats();
- cursor_font_ = connection_->GenerateId<x11::Font>();
- connection_->OpenFont({cursor_font_, "cursor"});
- std::vector<char> resource_manager;
- if (GetArrayProperty(connection_->default_root(), x11::Atom::RESOURCE_MANAGER,
- &resource_manager)) {
- ParseXResources(
- base::StringPiece(resource_manager.data(), resource_manager.size()));
- }
- if (auto reply = ver_cookie.Sync()) {
- render_version_ =
- base::Version({reply->major_version, reply->minor_version});
- }
- if (auto pf_reply = pf_cookie.Sync())
- pict_format_ = GetRenderARGBFormat(*pf_reply.reply);
- for (uint16_t i = 0; i < std::size(cursor_names); i++)
- cursor_name_to_char_[cursor_names[i]] = i;
- }
- XCursorLoader::~XCursorLoader() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- }
- scoped_refptr<X11Cursor> XCursorLoader::LoadCursor(
- const std::vector<std::string>& names) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto cursor = base::MakeRefCounted<X11Cursor>();
- if (SupportsCreateCursor()) {
- base::ThreadPool::PostTaskAndReplyWithResult(
- FROM_HERE,
- {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
- base::BindOnce(ReadCursorImages, names, rm_xcursor_theme_,
- GetPreferredCursorSize()),
- base::BindOnce(&XCursorLoader::LoadCursorImpl,
- weak_factory_.GetWeakPtr(), cursor, names));
- } else {
- LoadCursorImpl(cursor, names, {});
- }
- return cursor;
- }
- scoped_refptr<X11Cursor> XCursorLoader::CreateCursor(
- const std::vector<Image>& images) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- std::vector<scoped_refptr<X11Cursor>> cursors;
- std::vector<x11::Render::AnimationCursorElement> elements;
- cursors.reserve(images.size());
- elements.reserve(images.size());
- for (const Image& image : images) {
- auto cursor = CreateCursor(image.bitmap, image.hotspot);
- cursors.push_back(cursor);
- elements.push_back(x11::Render::AnimationCursorElement{
- cursor->xcursor_,
- static_cast<uint32_t>(image.frame_delay.InMilliseconds())});
- }
- if (elements.empty())
- return nullptr;
- if (elements.size() == 1 || !SupportsCreateAnimCursor())
- return cursors[0];
- auto cursor = connection_->GenerateId<x11::Cursor>();
- connection_->render().CreateAnimCursor({cursor, elements});
- return base::MakeRefCounted<X11Cursor>(cursor);
- }
- scoped_refptr<X11Cursor> XCursorLoader::CreateCursor(
- const SkBitmap& bitmap,
- const gfx::Point& hotspot) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto pixmap = connection_->GenerateId<x11::Pixmap>();
- auto gc = connection_->GenerateId<x11::GraphicsContext>();
- uint16_t width = bitmap.width();
- uint16_t height = bitmap.height();
- connection_->CreatePixmap(
- {32, pixmap, connection_->default_root(), width, height});
- connection_->CreateGC({gc, pixmap});
- size_t size = bitmap.computeByteSize();
- std::vector<uint8_t> vec(size);
- memcpy(vec.data(), bitmap.getPixels(), size);
- auto* connection = x11::Connection::Get();
- x11::PutImageRequest put_image_request{
- .format = x11::ImageFormat::ZPixmap,
- .drawable = pixmap,
- .gc = gc,
- .width = width,
- .height = height,
- .depth = 32,
- .data = base::RefCountedBytes::TakeVector(&vec),
- };
- connection->PutImage(put_image_request);
- x11::Render::Picture pic = connection_->GenerateId<x11::Render::Picture>();
- connection_->render().CreatePicture({pic, pixmap, pict_format_});
- auto cursor = connection_->GenerateId<x11::Cursor>();
- connection_->render().CreateCursor({cursor, pic,
- static_cast<uint16_t>(hotspot.x()),
- static_cast<uint16_t>(hotspot.y())});
- connection_->render().FreePicture({pic});
- connection_->FreePixmap({pixmap});
- connection_->FreeGC({gc});
- return base::MakeRefCounted<X11Cursor>(cursor);
- }
- void XCursorLoader::LoadCursorImpl(
- scoped_refptr<X11Cursor> cursor,
- const std::vector<std::string>& names,
- const std::vector<XCursorLoader::Image>& images) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto xcursor = connection_->GenerateId<x11::Cursor>();
- if (!images.empty()) {
- xcursor = CreateCursor(images)->ReleaseCursor();
- } else {
- // Fallback to using a font cursor.
- auto core_char = CursorNamesToChar(names);
- constexpr uint16_t kFontCursorFgColor = 0;
- constexpr uint16_t kFontCursorBgColor = 65535;
- connection_->CreateGlyphCursor({xcursor, cursor_font_, cursor_font_,
- static_cast<uint16_t>(2 * core_char),
- static_cast<uint16_t>(2 * core_char + 1),
- kFontCursorFgColor, kFontCursorFgColor,
- kFontCursorFgColor, kFontCursorBgColor,
- kFontCursorBgColor, kFontCursorBgColor});
- }
- cursor->SetCursor(xcursor);
- }
- uint32_t XCursorLoader::GetPreferredCursorSize() const {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- constexpr const char kXcursorSizeEnv[] = "XCURSOR_SIZE";
- constexpr unsigned int kCursorSizeInchNum = 16;
- constexpr unsigned int kCursorSizeInchDen = 72;
- constexpr unsigned int kScreenCursorRatio = 48;
- // Allow the XCURSOR_SIZE environment variable to override GTK settings.
- int size;
- if (base::StringToInt(GetEnv(kXcursorSizeEnv), &size) && size > 0)
- return size;
- #if BUILDFLAG(IS_LINUX)
- // Let the toolkit have the next say.
- auto* linux_ui = LinuxUi::instance();
- size = linux_ui ? linux_ui->GetCursorThemeSize() : 0;
- if (size > 0)
- return size;
- #endif
- // Use Xcursor.size from RESOURCE_MANAGER if available.
- if (rm_xcursor_size_)
- return rm_xcursor_size_;
- // Guess the cursor size based on the DPI.
- if (rm_xft_dpi_)
- return rm_xft_dpi_ * kCursorSizeInchNum / kCursorSizeInchDen;
- // As a last resort, guess the cursor size based on the screen size.
- const auto& screen = connection_->default_screen();
- return std::min(screen.width_in_pixels, screen.height_in_pixels) /
- kScreenCursorRatio;
- }
- void XCursorLoader::ParseXResources(base::StringPiece resources) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- base::StringPairs pairs;
- base::SplitStringIntoKeyValuePairs(resources, ':', '\n', &pairs);
- for (const auto& pair : pairs) {
- auto key = base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL);
- auto value = base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL);
- if (key == "Xcursor.theme")
- rm_xcursor_theme_ = std::string(value);
- else if (key == "Xcursor.size")
- base::StringToUint(value, &rm_xcursor_size_);
- else if (key == "Xft.dpi")
- base::StringToUint(value, &rm_xft_dpi_);
- }
- }
- uint16_t XCursorLoader::CursorNamesToChar(
- const std::vector<std::string>& names) const {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- for (const auto& name : names) {
- auto it = cursor_name_to_char_.find(name);
- if (it != cursor_name_to_char_.end())
- return it->second;
- }
- // Use a left pointer as a fallback.
- return 0;
- }
- bool XCursorLoader::SupportsCreateCursor() const {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return render_version_.IsValid() && render_version_ >= base::Version("0.5");
- }
- bool XCursorLoader::SupportsCreateAnimCursor() const {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return render_version_.IsValid() && render_version_ >= base::Version("0.8");
- }
- // This is ported from libxcb-cursor's parse_cursor_file.c:
- // https://gitlab.freedesktop.org/xorg/lib/libxcb-cursor/-/blob/master/cursor/parse_cursor_file.c
- std::vector<XCursorLoader::Image> ParseCursorFile(
- scoped_refptr<base::RefCountedMemory> file,
- uint32_t preferred_size) {
- constexpr uint32_t kMagic = 0x72756358;
- constexpr uint32_t kImageType = 0xfffd0002;
- const uint8_t* mem = file->data();
- size_t offset = 0;
- auto ReadU32s = [&](void* dest, size_t len) {
- DCHECK_EQ(len % 4, 0u);
- if (offset + len > file->size())
- return false;
- const auto* src32 = reinterpret_cast<const uint32_t*>(mem + offset);
- auto* dest32 = reinterpret_cast<uint32_t*>(dest);
- for (size_t i = 0; i < len / 4; i++)
- dest32[i] = base::ByteSwapToLE32(src32[i]);
- offset += len;
- return true;
- };
- struct FileHeader {
- uint32_t magic;
- uint32_t header;
- uint32_t version;
- uint32_t ntoc;
- } header;
- if (!ReadU32s(&header, sizeof(FileHeader)) || header.magic != kMagic)
- return {};
- struct TableOfContentsEntry {
- uint32_t type;
- uint32_t subtype;
- uint32_t position;
- };
- std::vector<TableOfContentsEntry> toc;
- toc.reserve(header.ntoc);
- for (uint32_t i = 0; i < header.ntoc; i++) {
- TableOfContentsEntry entry;
- if (!ReadU32s(&entry, sizeof(TableOfContentsEntry)))
- return {};
- toc.push_back(entry);
- }
- uint32_t best_size = std::numeric_limits<uint32_t>::max();
- for (const auto& entry : toc) {
- auto delta = [](uint32_t x, uint32_t y) {
- return std::max(x, y) - std::min(x, y);
- };
- if (entry.type != kImageType)
- continue;
- if (delta(entry.subtype, preferred_size) < delta(best_size, preferred_size))
- best_size = entry.subtype;
- }
- std::vector<XCursorLoader::Image> images;
- for (const auto& entry : toc) {
- if (entry.type != kImageType || entry.subtype != best_size)
- continue;
- offset = entry.position;
- struct ChunkHeader {
- uint32_t header;
- uint32_t type;
- uint32_t subtype;
- uint32_t version;
- } chunk_header;
- if (!ReadU32s(&chunk_header, sizeof(ChunkHeader)) ||
- chunk_header.type != entry.type ||
- chunk_header.subtype != entry.subtype) {
- continue;
- }
- struct ImageHeader {
- uint32_t width;
- uint32_t height;
- uint32_t xhot;
- uint32_t yhot;
- uint32_t delay;
- } image;
- if (!ReadU32s(&image, sizeof(ImageHeader)))
- continue;
- SkBitmap bitmap;
- bitmap.allocN32Pixels(image.width, image.height);
- if (!ReadU32s(bitmap.getPixels(), bitmap.computeByteSize()))
- continue;
- images.push_back(XCursorLoader::Image{bitmap,
- gfx::Point(image.xhot, image.yhot),
- base::Milliseconds(image.delay)});
- }
- return images;
- }
- } // namespace ui
|