1// Copyright (c) 2012 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/cursor/cursor_loader_x11.h"
6
7#include <float.h>
8#include <X11/Xlib.h>
9#include <X11/cursorfont.h>
10
11#include "base/logging.h"
12#include "grit/ui_resources.h"
13#include "skia/ext/image_operations.h"
14#include "ui/base/cursor/cursor.h"
15#include "ui/base/resource/resource_bundle.h"
16#include "ui/base/x/x11_util.h"
17#include "ui/gfx/image/image.h"
18#include "ui/gfx/image/image_skia.h"
19#include "ui/gfx/point_conversions.h"
20#include "ui/gfx/size_conversions.h"
21#include "ui/gfx/skbitmap_operations.h"
22#include "ui/gfx/skia_util.h"
23
24namespace {
25
26// Returns X font cursor shape from an Aura cursor.
27int CursorShapeFromNative(const gfx::NativeCursor& native_cursor) {
28  switch (native_cursor.native_type()) {
29    case ui::kCursorMiddlePanning:
30      return XC_fleur;
31    case ui::kCursorEastPanning:
32      return XC_sb_right_arrow;
33    case ui::kCursorNorthPanning:
34      return XC_sb_up_arrow;
35    case ui::kCursorNorthEastPanning:
36      return XC_top_right_corner;
37    case ui::kCursorNorthWestPanning:
38      return XC_top_left_corner;
39    case ui::kCursorSouthPanning:
40      return XC_sb_down_arrow;
41    case ui::kCursorSouthEastPanning:
42      return XC_bottom_right_corner;
43    case ui::kCursorSouthWestPanning:
44      return XC_bottom_left_corner;
45    case ui::kCursorWestPanning:
46      return XC_sb_left_arrow;
47    case ui::kCursorNone:
48    case ui::kCursorGrab:
49    case ui::kCursorGrabbing:
50      // TODO(jamescook): Need cursors for these.  crbug.com/111650
51      return XC_left_ptr;
52
53#if defined(OS_CHROMEOS)
54    case ui::kCursorNull:
55    case ui::kCursorPointer:
56    case ui::kCursorNoDrop:
57    case ui::kCursorNotAllowed:
58    case ui::kCursorCopy:
59    case ui::kCursorMove:
60    case ui::kCursorEastResize:
61    case ui::kCursorNorthResize:
62    case ui::kCursorSouthResize:
63    case ui::kCursorWestResize:
64    case ui::kCursorNorthEastResize:
65    case ui::kCursorNorthWestResize:
66    case ui::kCursorSouthWestResize:
67    case ui::kCursorSouthEastResize:
68    case ui::kCursorIBeam:
69    case ui::kCursorAlias:
70    case ui::kCursorCell:
71    case ui::kCursorContextMenu:
72    case ui::kCursorCross:
73    case ui::kCursorHelp:
74    case ui::kCursorWait:
75    case ui::kCursorNorthSouthResize:
76    case ui::kCursorEastWestResize:
77    case ui::kCursorNorthEastSouthWestResize:
78    case ui::kCursorNorthWestSouthEastResize:
79    case ui::kCursorProgress:
80    case ui::kCursorColumnResize:
81    case ui::kCursorRowResize:
82    case ui::kCursorVerticalText:
83    case ui::kCursorZoomIn:
84    case ui::kCursorZoomOut:
85    case ui::kCursorHand:
86      // In some environments, the image assets are not set (e.g. in
87      // content-browsertests, content-shell etc.).
88      return XC_left_ptr;
89#else  // defined(OS_CHROMEOS)
90    case ui::kCursorNull:
91      return XC_left_ptr;
92    case ui::kCursorPointer:
93      return XC_left_ptr;
94    case ui::kCursorMove:
95      return XC_fleur;
96    case ui::kCursorCross:
97      return XC_crosshair;
98    case ui::kCursorHand:
99      return XC_hand2;
100    case ui::kCursorIBeam:
101      return XC_xterm;
102    case ui::kCursorProgress:
103    case ui::kCursorWait:
104      return XC_watch;
105    case ui::kCursorHelp:
106      return XC_question_arrow;
107    case ui::kCursorEastResize:
108      return XC_right_side;
109    case ui::kCursorNorthResize:
110      return XC_top_side;
111    case ui::kCursorNorthEastResize:
112      return XC_top_right_corner;
113    case ui::kCursorNorthWestResize:
114      return XC_top_left_corner;
115    case ui::kCursorSouthResize:
116      return XC_bottom_side;
117    case ui::kCursorSouthEastResize:
118      return XC_bottom_right_corner;
119    case ui::kCursorSouthWestResize:
120      return XC_bottom_left_corner;
121    case ui::kCursorWestResize:
122      return XC_left_side;
123    case ui::kCursorNorthSouthResize:
124      return XC_sb_v_double_arrow;
125    case ui::kCursorEastWestResize:
126      return XC_sb_h_double_arrow;
127    case ui::kCursorColumnResize:
128      return XC_sb_h_double_arrow;
129    case ui::kCursorRowResize:
130      return XC_sb_v_double_arrow;
131#endif  // defined(OS_CHROMEOS)
132    case ui::kCursorCustom:
133      NOTREACHED();
134      return XC_left_ptr;
135  }
136  NOTREACHED() << "Case not handled for " << native_cursor.native_type();
137  return XC_left_ptr;
138}
139
140}  // namespace
141
142namespace ui {
143
144CursorLoader* CursorLoader::Create() {
145  return new CursorLoaderX11;
146}
147
148CursorLoaderX11::CursorLoaderX11()
149    : invisible_cursor_(CreateInvisibleCursor(), GetXDisplay()) {
150}
151
152CursorLoaderX11::~CursorLoaderX11() {
153  UnloadAll();
154}
155
156void CursorLoaderX11::LoadImageCursor(int id,
157                                      int resource_id,
158                                      const gfx::Point& hot) {
159  const gfx::ImageSkia* image =
160      ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
161  const gfx::ImageSkiaRep& image_rep = image->GetRepresentation(
162      GetScaleFactorFromScale(display().device_scale_factor()));
163  SkBitmap bitmap = image_rep.sk_bitmap();
164  gfx::Point hotpoint = hot;
165  ScaleAndRotateCursorBitmapAndHotpoint(
166      scale(), display().rotation(), &bitmap, &hotpoint);
167
168  XcursorImage* x_image = SkBitmapToXcursorImage(&bitmap, hotpoint);
169  cursors_[id] = CreateReffedCustomXCursor(x_image);
170  // |image_rep| is owned by the resource bundle. So we do not need to free it.
171}
172
173void CursorLoaderX11::LoadAnimatedCursor(int id,
174                                         int resource_id,
175                                         const gfx::Point& hot,
176                                         int frame_delay_ms) {
177  const gfx::ImageSkia* image =
178      ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
179  const gfx::ImageSkiaRep& image_rep = image->GetRepresentation(
180      GetScaleFactorFromScale(display().device_scale_factor()));
181  SkBitmap bitmap = image_rep.sk_bitmap();
182  int frame_width = bitmap.height();
183  int frame_height = frame_width;
184  int total_width = bitmap.width();
185  DCHECK_EQ(total_width % frame_width, 0);
186  int frame_count = total_width / frame_width;
187  DCHECK_GT(frame_count, 0);
188  XcursorImages* x_images = XcursorImagesCreate(frame_count);
189  x_images->nimage = frame_count;
190
191  for (int frame = 0; frame < frame_count; ++frame) {
192    gfx::Point hotpoint = hot;
193    int x_offset = frame_width * frame;
194    DCHECK_LE(x_offset + frame_width, total_width);
195
196    SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(
197        bitmap, x_offset, 0, frame_width, frame_height);
198    DCHECK_EQ(frame_width, cropped.width());
199    DCHECK_EQ(frame_height, cropped.height());
200
201    XcursorImage* x_image = SkBitmapToXcursorImage(&cropped, hotpoint);
202
203    x_image->delay = frame_delay_ms;
204    x_images->images[frame] = x_image;
205  }
206
207  animated_cursors_[id] = std::make_pair(
208      XcursorImagesLoadCursor(GetXDisplay(), x_images), x_images);
209  // |bitmap| is owned by the resource bundle. So we do not need to free it.
210}
211
212void CursorLoaderX11::UnloadAll() {
213  for (ImageCursorMap::const_iterator it = cursors_.begin();
214       it != cursors_.end(); ++it)
215    UnrefCustomXCursor(it->second);
216
217  // Free animated cursors and images.
218  for (AnimatedCursorMap::iterator it = animated_cursors_.begin();
219       it != animated_cursors_.end(); ++it) {
220    XcursorImagesDestroy(it->second.second);  // also frees individual frames.
221    XFreeCursor(GetXDisplay(), it->second.first);
222  }
223}
224
225void CursorLoaderX11::SetPlatformCursor(gfx::NativeCursor* cursor) {
226  DCHECK(cursor);
227
228  ::Cursor xcursor;
229  if (IsImageCursor(*cursor))
230    xcursor = ImageCursorFromNative(*cursor);
231  else if (*cursor == kCursorNone)
232    xcursor =  invisible_cursor_.get();
233  else if (*cursor == kCursorCustom)
234    xcursor = cursor->platform();
235  else if (display().device_scale_factor() == 1.0f &&
236           display().rotation() == gfx::Display::ROTATE_0) {
237    xcursor = GetXCursor(CursorShapeFromNative(*cursor));
238  } else {
239    xcursor = ImageCursorFromNative(kCursorPointer);
240  }
241
242  cursor->SetPlatformCursor(xcursor);
243}
244
245bool CursorLoaderX11::IsImageCursor(gfx::NativeCursor native_cursor) {
246  int type = native_cursor.native_type();
247  return cursors_.count(type) || animated_cursors_.count(type);
248}
249
250::Cursor CursorLoaderX11::ImageCursorFromNative(
251    gfx::NativeCursor native_cursor) {
252  int type = native_cursor.native_type();
253  if (animated_cursors_.count(type))
254    return animated_cursors_[type].first;
255
256  ImageCursorMap::iterator find = cursors_.find(type);
257  if (find != cursors_.end())
258    return cursors_[type];
259  return GetXCursor(CursorShapeFromNative(native_cursor));
260}
261
262void ScaleAndRotateCursorBitmapAndHotpoint(float scale,
263                                           gfx::Display::Rotation rotation,
264                                           SkBitmap* bitmap,
265                                           gfx::Point* hotpoint) {
266  switch (rotation) {
267    case gfx::Display::ROTATE_0:
268      break;
269    case gfx::Display::ROTATE_90:
270      hotpoint->SetPoint(bitmap->height() - hotpoint->y(), hotpoint->x());
271      *bitmap = SkBitmapOperations::Rotate(
272          *bitmap, SkBitmapOperations::ROTATION_90_CW);
273      break;
274    case gfx::Display::ROTATE_180:
275      hotpoint->SetPoint(
276          bitmap->width() - hotpoint->x(), bitmap->height() - hotpoint->y());
277      *bitmap = SkBitmapOperations::Rotate(
278          *bitmap, SkBitmapOperations::ROTATION_180_CW);
279      break;
280    case gfx::Display::ROTATE_270:
281      hotpoint->SetPoint(hotpoint->y(), bitmap->width() - hotpoint->x());
282      *bitmap = SkBitmapOperations::Rotate(
283          *bitmap, SkBitmapOperations::ROTATION_270_CW);
284      break;
285  }
286
287  if (scale < FLT_EPSILON) {
288    NOTREACHED() << "Scale must be larger than 0.";
289    scale = 1.0f;
290  }
291
292  if (scale == 1.0f)
293    return;
294
295  gfx::Size scaled_size = gfx::ToFlooredSize(
296      gfx::ScaleSize(gfx::Size(bitmap->width(), bitmap->height()), scale));
297
298  *bitmap = skia::ImageOperations::Resize(
299      *bitmap,
300      skia::ImageOperations::RESIZE_BETTER,
301      scaled_size.width(),
302      scaled_size.height());
303  *hotpoint = gfx::ToFlooredPoint(gfx::ScalePoint(*hotpoint, scale));
304}
305
306}  // namespace ui
307