1// LAF OS Library
2// Copyright (C) 2021-2022 Igara Studio S.A.
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "os/x11/system.h"
12
13#include "os/x11/cursor.h"
14
15#include <X11/Xcursor/Xcursor.h>
16#include <X11/cursorfont.h>
17
18#include <algorithm>
19#include <array>
20
21namespace os {
22
23class XCursorImageX11 {
24public:
25 XCursorImageX11() : m_ximage(nullptr) { }
26 ~XCursorImageX11() { reset(); }
27
28 ::XcursorImage* recreate(int w, int h) {
29 if (m_ximage && m_ximage->width == w && m_ximage->height == h)
30 return m_ximage;
31 reset();
32 return m_ximage = XcursorImageCreate(w, h);
33 }
34
35 void reset() {
36 if (m_ximage)
37 XcursorImageDestroy(m_ximage);
38 m_ximage = nullptr;
39 }
40
41private:
42 ::XcursorImage* m_ximage;
43};
44
45// Cache of system cursors
46std::array<CursorRef, int(NativeCursor::Cursors)> g_nativeCursors;
47XCursorImageX11 g_cachedCursorImage;
48
49SystemX11::~SystemX11()
50{
51 g_nativeCursors.fill(nullptr);
52 g_cachedCursorImage.reset();
53}
54
55CursorRef SystemX11::getNativeCursor(NativeCursor cursor)
56{
57 int i = int(cursor);
58 if (i < 0 || i >= int(NativeCursor::Cursors))
59 return nullptr;
60 else if (g_nativeCursors[i].get())
61 return g_nativeCursors[i];
62
63 ::Display* display = X11::instance()->display();
64 ::Cursor xcursor = None;
65 switch (cursor) {
66 case NativeCursor::Hidden: {
67 char data = 0;
68 Pixmap image = XCreateBitmapFromData(
69 display, DefaultRootWindow(display), (char*)&data, 1, 1);
70
71 XColor color;
72 xcursor = XCreatePixmapCursor(
73 display, image, image, &color, &color, 0, 0);
74
75 XFreePixmap(display, image);
76 break;
77 }
78 case NativeCursor::Arrow:
79 xcursor = XCreateFontCursor(display, XC_arrow);
80 break;
81 case NativeCursor::Crosshair:
82 xcursor = XCreateFontCursor(display, XC_crosshair);
83 break;
84 case NativeCursor::IBeam:
85 xcursor = XCreateFontCursor(display, XC_xterm);
86 break;
87 case NativeCursor::Wait:
88 xcursor = XCreateFontCursor(display, XC_watch);
89 break;
90 case NativeCursor::Link:
91 xcursor = XCreateFontCursor(display, XC_hand1);
92 break;
93 case NativeCursor::Help:
94 xcursor = XCreateFontCursor(display, XC_question_arrow);
95 break;
96 case NativeCursor::Forbidden:
97 xcursor = XCreateFontCursor(display, XC_X_cursor);
98 break;
99 case NativeCursor::Move:
100 xcursor = XCreateFontCursor(display, XC_fleur);
101 break;
102 case NativeCursor::SizeN:
103 xcursor = XCreateFontCursor(display, XC_top_side);
104 break;
105 case NativeCursor::SizeNS:
106 xcursor = XCreateFontCursor(display, XC_sb_v_double_arrow);
107 break;
108 case NativeCursor::SizeS:
109 xcursor = XCreateFontCursor(display, XC_bottom_side);
110 break;
111 case NativeCursor::SizeW:
112 xcursor = XCreateFontCursor(display, XC_left_side);
113 break;
114 case NativeCursor::SizeE:
115 xcursor = XCreateFontCursor(display, XC_right_side);
116 break;
117 case NativeCursor::SizeWE:
118 xcursor = XCreateFontCursor(display, XC_sb_h_double_arrow);
119 break;
120 case NativeCursor::SizeNW:
121 xcursor = XCreateFontCursor(display, XC_top_left_corner);
122 break;
123 case NativeCursor::SizeNE:
124 xcursor = XCreateFontCursor(display, XC_top_right_corner);
125 break;
126 case NativeCursor::SizeSW:
127 xcursor = XCreateFontCursor(display, XC_bottom_left_corner);
128 break;
129 case NativeCursor::SizeSE:
130 xcursor = XCreateFontCursor(display, XC_bottom_right_corner);
131 break;
132 }
133
134 return g_nativeCursors[i] = make_ref<CursorX11>(xcursor);
135}
136
137CursorRef SystemX11::makeCursor(const Surface* surface,
138 const gfx::Point& focus,
139 const int scale)
140{
141 ASSERT(surface);
142 ::Display* display = X11::instance()->display();
143
144 // This X11 server doesn't support ARGB cursors.
145 if (!XcursorSupportsARGB(display))
146 return nullptr;
147
148 SurfaceFormatData format;
149 surface->getFormat(&format);
150
151 // Only for 32bpp surfaces
152 if (format.bitsPerPixel != 32)
153 return nullptr;
154
155 const int w = scale*surface->width();
156 const int h = scale*surface->height();
157
158 ::Cursor xcursor = None;
159 ::XcursorImage* image = g_cachedCursorImage.recreate(w, h);
160 if (image != None) {
161 XcursorPixel* dst = image->pixels;
162 for (int y=0; y<h; ++y) {
163 const uint32_t* src = (const uint32_t*)surface->getData(0, y/scale);
164 for (int x=0, u=0; x<w; ++x, ++dst) {
165 uint32_t c = *src;
166 *dst =
167 (((c & format.alphaMask) >> format.alphaShift) << 24) |
168 (((c & format.redMask ) >> format.redShift ) << 16) |
169 (((c & format.greenMask) >> format.greenShift) << 8) |
170 (((c & format.blueMask ) >> format.blueShift ));
171 if (++u == scale) {
172 u = 0;
173 ++src;
174 }
175 }
176 }
177
178 // We have to limit the focus position inside the cursor area to
179 // avoid crash from XcursorImageLoadCursor():
180 //
181 // X Error of failed request: BadMatch (invalid parameter attributes)
182 // Major opcode of failed request: 138 (RENDER)
183 // Minor opcode of failed request: 27 (RenderCreateCursor)
184 image->xhot = std::clamp(scale*focus.x + scale/2, 0, w-1);
185 image->yhot = std::clamp(scale*focus.y + scale/2, 0, h-1);
186 xcursor = XcursorImageLoadCursor(display, image);
187 }
188
189 return make_ref<CursorX11>(xcursor);
190}
191
192} // namespace os
193