blob: efc635150fe72ce93e4bc7dddf248a03967c2dd2 [file] [log] [blame]
// Copyright (c) 2012 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/cursor/cursor_loader_x11.h"
#include <float.h>
#include "base/logging.h"
#include "build/build_config.h"
#include "skia/ext/image_operations.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/cursor_util.h"
#include "ui/base/cursor/cursors_aura.h"
#include "ui/base/x/x11_util.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/gfx/skia_util.h"
namespace {
static const struct {
const char* css_name;
const char* fallback_name;
int fallback_shape;
} kCursorFallbacks[] = {
// clang-format off
{ "pointer", "hand", XC_hand2 },
{ "progress", "left_ptr_watch", XC_watch },
{ "wait", nullptr, XC_watch },
{ "cell", nullptr, XC_plus },
{ "all-scroll", nullptr, XC_fleur},
{ "v-scroll", "all-scroll", XC_fleur},
{ "h-scroll", "all-scroll", XC_fleur},
{ "crosshair", nullptr, XC_cross },
{ "text", nullptr, XC_xterm },
{ "not-allowed", "crossed_circle", x11::None },
{ "grabbing", nullptr, XC_hand2 },
{ "col-resize", nullptr, XC_sb_h_double_arrow },
{ "row-resize", nullptr, XC_sb_v_double_arrow},
{ "n-resize", nullptr, XC_top_side},
{ "e-resize", nullptr, XC_right_side},
{ "s-resize", nullptr, XC_bottom_side},
{ "w-resize", nullptr, XC_left_side},
{ "ne-resize", nullptr, XC_top_right_corner},
{ "nw-resize", nullptr, XC_top_left_corner},
{ "se-resize", nullptr, XC_bottom_right_corner},
{ "sw-resize", nullptr, XC_bottom_left_corner},
{ "ew-resize", nullptr, XC_sb_h_double_arrow},
{ "ns-resize", nullptr, XC_sb_v_double_arrow},
{ "nesw-resize", "fd_double_arrow", x11::None},
{ "nwse-resize", "bd_double_arrow", x11::None},
{ "dnd-none", "grabbing", XC_hand2 },
{ "dnd-move", "grabbing", XC_hand2 },
{ "dnd-copy", "grabbing", XC_hand2 },
{ "dnd-link", "grabbing", XC_hand2 },
// clang-format on
};
} // namespace
namespace ui {
CursorLoader* CursorLoader::Create() {
return new CursorLoaderX11;
}
CursorLoaderX11::ImageCursor::ImageCursor(XcursorImage* x_image,
float scale,
display::Display::Rotation rotation)
: scale(scale), rotation(rotation) {
cursor = CreateReffedCustomXCursor(x_image);
}
CursorLoaderX11::ImageCursor::~ImageCursor() {
UnrefCustomXCursor(cursor);
}
CursorLoaderX11::CursorLoaderX11()
: display_(gfx::GetXDisplay()),
invisible_cursor_(CreateInvisibleCursor(), gfx::GetXDisplay()) {
auto* cursor_theme_manager = CursorThemeManagerLinux::GetInstance();
if (cursor_theme_manager) {
cursor_theme_observer_.Add(cursor_theme_manager);
OnCursorThemeNameChanged(cursor_theme_manager->GetCursorThemeName());
OnCursorThemeSizeChanged(cursor_theme_manager->GetCursorThemeSize());
}
}
CursorLoaderX11::~CursorLoaderX11() {
UnloadAll();
}
void CursorLoaderX11::LoadImageCursor(CursorType id,
int resource_id,
const gfx::Point& hot) {
SkBitmap bitmap;
gfx::Point hotspot = hot;
GetImageCursorBitmap(resource_id, scale(), rotation(), &hotspot, &bitmap);
XcursorImage* x_image = SkBitmapToXcursorImage(&bitmap, hotspot);
image_cursors_[id] =
std::make_unique<ImageCursor>(x_image, scale(), rotation());
}
void CursorLoaderX11::LoadAnimatedCursor(CursorType id,
int resource_id,
const gfx::Point& hot,
int frame_delay_ms) {
std::vector<SkBitmap> bitmaps;
gfx::Point hotspot = hot;
GetAnimatedCursorBitmaps(resource_id, scale(), rotation(), &hotspot,
&bitmaps);
XcursorImages* x_images = XcursorImagesCreate(bitmaps.size());
x_images->nimage = bitmaps.size();
for (unsigned int frame = 0; frame < bitmaps.size(); ++frame) {
XcursorImage* x_image = SkBitmapToXcursorImage(&bitmaps[frame], hotspot);
x_image->delay = frame_delay_ms;
x_images->images[frame] = x_image;
}
animated_cursors_[id] = std::make_pair(
XcursorImagesLoadCursor(gfx::GetXDisplay(), x_images), x_images);
}
void CursorLoaderX11::UnloadAll() {
image_cursors_.clear();
// Free animated cursors and images.
for (const auto& cursor : animated_cursors_) {
XcursorImagesDestroy(
cursor.second.second); // also frees individual frames.
XFreeCursor(gfx::GetXDisplay(), cursor.second.first);
}
}
void CursorLoaderX11::SetPlatformCursor(gfx::NativeCursor* cursor) {
DCHECK(cursor);
if (*cursor == CursorType::kNone) {
cursor->SetPlatformCursor(invisible_cursor_.get());
return;
}
if (*cursor == CursorType::kCustom)
return;
cursor->set_device_scale_factor(scale());
cursor->SetPlatformCursor(CursorFromId(cursor->native_type()));
}
const XcursorImage* CursorLoaderX11::GetXcursorImageForTest(CursorType id) {
return test::GetCachedXcursorImage(image_cursors_[id]->cursor);
}
void CursorLoaderX11::OnCursorThemeNameChanged(
const std::string& cursor_theme_name) {
XcursorSetTheme(display_, cursor_theme_name.c_str());
ClearThemeCursors();
}
void CursorLoaderX11::OnCursorThemeSizeChanged(int cursor_theme_size) {
XcursorSetDefaultSize(display_, cursor_theme_size);
ClearThemeCursors();
}
bool CursorLoaderX11::IsImageCursor(gfx::NativeCursor native_cursor) {
CursorType type = native_cursor.native_type();
return image_cursors_.count(type) || animated_cursors_.count(type);
}
::Cursor CursorLoaderX11::CursorFromId(CursorType id) {
auto font_it = font_cursors_.find(id);
if (font_it != font_cursors_.end())
return font_it->second;
auto image_it = image_cursors_.find(id);
if (image_it != image_cursors_.end()) {
if (image_it->second->scale == scale() &&
image_it->second->rotation == rotation()) {
return image_it->second->cursor;
} else {
image_cursors_.erase(image_it);
}
}
// First try to load the cursor directly.
const char* css_name = CursorCssNameFromId(id);
::Cursor cursor = XcursorLibraryLoadCursor(display_, css_name);
if (cursor == x11::None) {
// Try a similar cursor supplied by the native cursor theme.
for (const auto& mapping : kCursorFallbacks) {
if (strcmp(mapping.css_name, css_name) == 0) {
if (mapping.fallback_name)
cursor = XcursorLibraryLoadCursor(display_, mapping.fallback_name);
if (cursor == x11::None && mapping.fallback_shape)
cursor = XCreateFontCursor(display_, mapping.fallback_shape);
}
}
}
if (cursor != x11::None) {
font_cursors_[id] = cursor;
return cursor;
}
// If the theme is missing the desired cursor, use a chromium-supplied
// fallback icon.
int resource_id;
gfx::Point point;
if (ui::GetCursorDataFor(ui::CursorSize::kNormal, id, scale(), &resource_id,
&point)) {
LoadImageCursor(id, resource_id, point);
return image_cursors_[id]->cursor;
}
// As a last resort, return a left pointer.
cursor = XCreateFontCursor(display_, XC_left_ptr);
DCHECK(cursor);
font_cursors_[id] = cursor;
return cursor;
}
void CursorLoaderX11::ClearThemeCursors() {
font_cursors_.clear();
image_cursors_.clear();
}
} // namespace ui