#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_KMSDRM
#include "SDL_kmsdrmvideo.h"
#include "SDL_kmsdrmmouse.h"
#include "SDL_kmsdrmdyn.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/default_cursor.h"
#include "../SDL_pixels_c.h"
#define USE_ATOMIC_CURSOR 0
static SDL_Cursor *KMSDRM_CreateDefaultCursor(void);
static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y);
static bool KMSDRM_ShowCursor(SDL_Cursor *cursor);
static bool KMSDRM_MoveCursor(SDL_Cursor *cursor);
static void KMSDRM_FreeCursor(SDL_Cursor *cursor);
static SDL_Cursor *KMSDRM_CreateDefaultCursor(void)
{
return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY);
}
void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
{
SDL_DisplayData *dispdata = display->internal;
if (dispdata->cursor_bo) {
SDL_VideoData *viddata = (SDL_VideoData *) _this->internal;
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
if (dispdata->cursor_plane) {
KMSDRM_PlaneInfo info;
SDL_zero(info);
info.plane = dispdata->cursor_plane;
drm_atomic_set_plane_props(dispdata, &info);
if (drm_atomic_commit(_this, dispdata, true, false)) {
SDL_SetError("Failed atomic commit in KMSDRM_DenitMouse.");
}
free_plane(&dispdata->cursor_plane);
}
}
KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
dispdata->cursor_bo = NULL;
dispdata->cursor_bo_drm_fd = -1;
}
}
bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
{
SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *viddata = dev->internal;
SDL_DisplayData *dispdata = display->internal;
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
setup_plane(dev, dispdata, &dispdata->cursor_plane, DRM_PLANE_TYPE_CURSOR);
}
if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
GBM_FORMAT_ARGB8888,
GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
return SDL_SetError("Unsupported pixel format for cursor");
}
if (KMSDRM_drmGetCap(viddata->drm_fd,
DRM_CAP_CURSOR_WIDTH, &dispdata->cursor_w) ||
KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT,
&dispdata->cursor_h)) {
return SDL_SetError("Could not get the recommended GBM cursor size");
}
if (dispdata->cursor_w == 0 || dispdata->cursor_h == 0) {
return SDL_SetError("Could not get an usable GBM cursor size");
}
dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev,
dispdata->cursor_w, dispdata->cursor_h,
GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE | GBM_BO_USE_LINEAR);
if (!dispdata->cursor_bo) {
return SDL_SetError("Could not create GBM cursor BO");
}
dispdata->cursor_bo_drm_fd = viddata->drm_fd;
return true;
}
static bool KMSDRM_RemoveCursorFromBO(SDL_VideoDisplay *display)
{
bool result = true;
SDL_DisplayData *dispdata = display->internal;
SDL_VideoDevice *video_device = SDL_GetVideoDevice();
SDL_VideoData *viddata = video_device->internal;
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
if (dispdata->cursor_plane) {
KMSDRM_PlaneInfo info;
SDL_zero(info);
info.plane = dispdata->cursor_plane;
drm_atomic_set_plane_props(dispdata, &info);
if (drm_atomic_commit(video_device, dispdata, true, false)) {
result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
}
}
} else {
const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, 0, 0, 0);
if (rc < 0) {
result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc));
}
}
return result;
}
static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Mouse *mouse, SDL_Cursor *cursor)
{
SDL_DisplayData *dispdata = display->internal;
SDL_CursorData *curdata = cursor->internal;
SDL_VideoDevice *video_device = SDL_GetVideoDevice();
SDL_VideoData *viddata = video_device->internal;
uint32_t bo_handle;
size_t bo_stride;
size_t bufsize;
uint8_t *ready_buffer = NULL;
uint8_t *src_row;
int i, rc;
bool result = true;
if (!curdata || !dispdata->cursor_bo) {
return SDL_SetError("Cursor or display not initialized properly.");
}
bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo);
bufsize = bo_stride * dispdata->cursor_h;
ready_buffer = (uint8_t *)SDL_calloc(1, bufsize);
if (!ready_buffer) {
result = false;
goto cleanup;
}
for (i = 0; i < curdata->h; i++) {
src_row = &((uint8_t *)curdata->buffer)[i * curdata->w * 4];
SDL_memcpy(ready_buffer + (i * bo_stride), src_row, (size_t)4 * curdata->w);
}
if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, ready_buffer, bufsize)) {
result = SDL_SetError("Could not write to GBM cursor BO");
goto cleanup;
}
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
KMSDRM_FBInfo *fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo);
KMSDRM_PlaneInfo info;
if (!fb) {
result = SDL_SetError("Failed to get cursor FB from BO");
goto cleanup;
}
SDL_zero(info);
info.plane = dispdata->cursor_plane;
info.crtc_id = dispdata->crtc.crtc->crtc_id;
info.fb_id = fb->fb_id;
info.src_w = dispdata->cursor_w;
info.src_h = dispdata->cursor_h;
info.crtc_x = ((int32_t) SDL_roundf(mouse->x)) - curdata->hot_x;
info.crtc_y = ((int32_t) SDL_roundf(mouse->y)) - curdata->hot_y;
info.crtc_w = curdata->w;
info.crtc_h = curdata->h;
drm_atomic_set_plane_props(dispdata, &info);
if (drm_atomic_commit(video_device, dispdata, true, false)) {
result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
goto cleanup;
}
} else {
bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
if (curdata->hot_x == 0 && curdata->hot_y == 0) {
rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h);
} else {
rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y);
}
if (rc < 0) {
result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc));
goto cleanup;
}
}
cleanup:
SDL_free(ready_buffer);
return result;
}
static void KMSDRM_FreeCursor(SDL_Cursor *cursor)
{
SDL_CursorData *curdata;
if (cursor) {
curdata = cursor->internal;
if (curdata->buffer) {
SDL_free(curdata->buffer);
curdata->buffer = NULL;
}
SDL_free(cursor->internal);
SDL_free(cursor);
}
}
static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
{
SDL_CursorData *curdata;
SDL_Cursor *cursor, *result;
curdata = NULL;
result = NULL;
cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
if (!cursor) {
goto cleanup;
}
curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata));
if (!curdata) {
goto cleanup;
}
curdata->hot_x = hot_x;
curdata->hot_y = hot_y;
curdata->w = surface->w;
curdata->h = surface->h;
curdata->buffer = NULL;
curdata->buffer_pitch = surface->w;
curdata->buffer_size = (size_t)surface->w * surface->h * 4;
curdata->buffer = (uint32_t *)SDL_malloc(curdata->buffer_size);
if (!curdata->buffer) {
goto cleanup;
}
SDL_PremultiplyAlpha(surface->w, surface->h,
surface->format, surface->pixels, surface->pitch,
SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4, true);
cursor->internal = curdata;
result = cursor;
cleanup:
if (!result) {
if (curdata) {
SDL_free(curdata->buffer);
SDL_free(curdata);
}
SDL_free(cursor);
}
return result;
}
static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
{
SDL_VideoDisplay *display;
SDL_Window *window;
SDL_Mouse *mouse = SDL_GetMouse();
int i;
bool result = true;
window = mouse->focus;
if (!window || !cursor) {
SDL_DisplayID *displays = SDL_GetDisplays(NULL);
if (displays) {
for (i = 0; i < displays[i]; i++) {
display = SDL_GetVideoDisplay(displays[i]);
result = KMSDRM_RemoveCursorFromBO(display);
}
SDL_free(displays);
}
} else {
display = SDL_GetVideoDisplayForWindow(window);
if (display) {
if (cursor) {
result = KMSDRM_DumpCursorToBO(display, mouse, cursor);
} else {
result = KMSDRM_RemoveCursorFromBO(display);
}
}
}
return result;
}
static void drm_atomic_movecursor(SDL_DisplayData *dispdata, const SDL_CursorData *curdata, uint16_t x, uint16_t y)
{
if (dispdata->cursor_plane) { if (!dispdata->atomic_req) {
dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc();
}
add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_X", x - curdata->hot_x);
add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_Y", y - curdata->hot_y);
}
}
static bool KMSDRM_WarpMouseGlobal(float x, float y)
{
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse && mouse->cur_cursor && mouse->focus) {
SDL_Window *window = mouse->focus;
SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
SDL_SendMouseMotion(0, mouse->focus, SDL_GLOBAL_MOUSE_ID, false, x, y);
if (dispdata->cursor_bo) {
SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *viddata = dev->internal;
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal;
drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) x, (uint16_t) (int) y);
} else {
const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)x, (int)y);
if (rc < 0) {
return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
}
}
} else {
return SDL_SetError("Cursor not initialized properly.");
}
} else {
return SDL_SetError("No mouse or current cursor.");
}
return true;
}
static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y)
{
return KMSDRM_WarpMouseGlobal(x, y);
}
void KMSDRM_InitMouse(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
{
SDL_Mouse *mouse = SDL_GetMouse();
SDL_DisplayData *dispdata = display->internal;
mouse->CreateCursor = KMSDRM_CreateCursor;
mouse->ShowCursor = KMSDRM_ShowCursor;
mouse->MoveCursor = KMSDRM_MoveCursor;
mouse->FreeCursor = KMSDRM_FreeCursor;
mouse->WarpMouse = KMSDRM_WarpMouse;
mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal;
if (!dispdata->default_cursor_init) {
SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor());
dispdata->default_cursor_init = true;
}
}
void KMSDRM_QuitMouse(SDL_VideoDevice *_this)
{
}
static bool KMSDRM_MoveCursor(SDL_Cursor *cursor)
{
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse && mouse->cur_cursor && mouse->focus) {
SDL_Window *window = mouse->focus;
SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
SDL_VideoDevice *dev = SDL_GetVideoDevice();
SDL_VideoData *viddata = dev->internal;
if (!dispdata->cursor_bo) {
return SDL_SetError("Cursor not initialized properly.");
}
if (USE_ATOMIC_CURSOR && viddata->is_atomic) {
const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal;
drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) mouse->x, (uint16_t) (int) mouse->y);
} else {
const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)mouse->x, (int)mouse->y);
if (rc < 0) {
return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
}
}
}
return true;
}
#endif