| /* | |
| Simple DirectMedia Layer | |
| Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org> | |
| This software is provided 'as-is', without any express or implied | |
| warranty. In no event will the authors be held liable for any damages | |
| arising from the use of this software. | |
| Permission is granted to anyone to use this software for any purpose, | |
| including commercial applications, and to alter it and redistribute it | |
| freely, subject to the following restrictions: | |
| 1. The origin of this software must not be misrepresented; you must not | |
| claim that you wrote the original software. If you use this software | |
| in a product, an acknowledgment in the product documentation would be | |
| appreciated but is not required. | |
| 2. Altered source versions must be plainly marked as such, and must not be | |
| misrepresented as being the original software. | |
| 3. This notice may not be removed or altered from any source distribution. | |
| */ | |
| #include "../../SDL_internal.h" | |
| #if SDL_VIDEO_DRIVER_RPI | |
| #include "SDL_surface.h" | |
| #include "SDL_hints.h" | |
| #include "SDL_rpivideo.h" | |
| #include "SDL_rpimouse.h" | |
| #include "../SDL_sysvideo.h" | |
| #include "../../events/SDL_mouse_c.h" | |
| #include "../../events/default_cursor.h" | |
| /* Copied from vc_vchi_dispmanx.h which is bugged and tries to include a non existing file */ | |
| /* Attributes changes flag mask */ | |
| #define ELEMENT_CHANGE_LAYER (1<<0) | |
| #define ELEMENT_CHANGE_OPACITY (1<<1) | |
| #define ELEMENT_CHANGE_DEST_RECT (1<<2) | |
| #define ELEMENT_CHANGE_SRC_RECT (1<<3) | |
| #define ELEMENT_CHANGE_MASK_RESOURCE (1<<4) | |
| #define ELEMENT_CHANGE_TRANSFORM (1<<5) | |
| /* End copied from vc_vchi_dispmanx.h */ | |
| static SDL_Cursor *RPI_CreateDefaultCursor(void); | |
| static SDL_Cursor *RPI_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y); | |
| static int RPI_ShowCursor(SDL_Cursor * cursor); | |
| static void RPI_MoveCursor(SDL_Cursor * cursor); | |
| static void RPI_FreeCursor(SDL_Cursor * cursor); | |
| static void RPI_WarpMouse(SDL_Window * window, int x, int y); | |
| static int RPI_WarpMouseGlobal(int x, int y); | |
| static SDL_Cursor *global_cursor; | |
| static SDL_Cursor * | |
| RPI_CreateDefaultCursor(void) | |
| { | |
| return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY); | |
| } | |
| /* Create a cursor from a surface */ | |
| static SDL_Cursor * | |
| RPI_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) | |
| { | |
| RPI_CursorData *curdata; | |
| SDL_Cursor *cursor; | |
| int ret; | |
| VC_RECT_T dst_rect; | |
| Uint32 dummy; | |
| SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); | |
| SDL_assert(surface->pitch == surface->w * 4); | |
| cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor)); | |
| if (cursor == NULL) { | |
| SDL_OutOfMemory(); | |
| return NULL; | |
| } | |
| curdata = (RPI_CursorData *) SDL_calloc(1, sizeof(*curdata)); | |
| if (curdata == NULL) { | |
| SDL_OutOfMemory(); | |
| SDL_free(cursor); | |
| return NULL; | |
| } | |
| curdata->hot_x = hot_x; | |
| curdata->hot_y = hot_y; | |
| curdata->w = surface->w; | |
| curdata->h = surface->h; | |
| /* This usage is inspired by Wayland/Weston RPI code, how they figured this out is anyone's guess */ | |
| curdata->resource = vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, surface->w | (surface->pitch << 16), surface->h | (surface->h << 16), &dummy); | |
| SDL_assert(curdata->resource); | |
| vc_dispmanx_rect_set(&dst_rect, 0, 0, curdata->w, curdata->h); | |
| /* A note from Weston: | |
| * vc_dispmanx_resource_write_data() ignores ifmt, | |
| * rect.x, rect.width, and uses stride only for computing | |
| * the size of the transfer as rect.height * stride. | |
| * Therefore we can only write rows starting at x=0. | |
| */ | |
| ret = vc_dispmanx_resource_write_data(curdata->resource, VC_IMAGE_ARGB8888, surface->pitch, surface->pixels, &dst_rect); | |
| SDL_assert (ret == DISPMANX_SUCCESS); | |
| cursor->driverdata = curdata; | |
| return cursor; | |
| } | |
| /* Show the specified cursor, or hide if cursor is NULL */ | |
| static int | |
| RPI_ShowCursor(SDL_Cursor * cursor) | |
| { | |
| int ret; | |
| DISPMANX_UPDATE_HANDLE_T update; | |
| RPI_CursorData *curdata; | |
| VC_RECT_T src_rect, dst_rect; | |
| SDL_Mouse *mouse; | |
| SDL_VideoDisplay *display; | |
| SDL_DisplayData *data; | |
| VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FROM_SOURCE /* flags */ , 255 /*opacity 0->255*/, 0 /* mask */ }; | |
| uint32_t layer = SDL_RPI_MOUSELAYER; | |
| const char *env; | |
| mouse = SDL_GetMouse(); | |
| if (mouse == NULL) { | |
| return -1; | |
| } | |
| if (cursor != global_cursor) { | |
| if (global_cursor != NULL) { | |
| curdata = (RPI_CursorData *) global_cursor->driverdata; | |
| if (curdata && curdata->element > DISPMANX_NO_HANDLE) { | |
| update = vc_dispmanx_update_start(0); | |
| SDL_assert(update); | |
| ret = vc_dispmanx_element_remove(update, curdata->element); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| ret = vc_dispmanx_update_submit_sync(update); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| curdata->element = DISPMANX_NO_HANDLE; | |
| } | |
| } | |
| global_cursor = cursor; | |
| } | |
| if (cursor == NULL) { | |
| return 0; | |
| } | |
| curdata = (RPI_CursorData *) cursor->driverdata; | |
| if (curdata == NULL) { | |
| return -1; | |
| } | |
| if (mouse->focus == NULL) { | |
| return -1; | |
| } | |
| display = SDL_GetDisplayForWindow(mouse->focus); | |
| if (display == NULL) { | |
| return -1; | |
| } | |
| data = (SDL_DisplayData*) display->driverdata; | |
| if (data == NULL) { | |
| return -1; | |
| } | |
| if (curdata->element == DISPMANX_NO_HANDLE) { | |
| vc_dispmanx_rect_set(&src_rect, 0, 0, curdata->w << 16, curdata->h << 16); | |
| vc_dispmanx_rect_set(&dst_rect, mouse->x - curdata->hot_x, mouse->y - curdata->hot_y, curdata->w, curdata->h); | |
| update = vc_dispmanx_update_start(0); | |
| SDL_assert(update); | |
| env = SDL_GetHint(SDL_HINT_RPI_VIDEO_LAYER); | |
| if (env) { | |
| layer = SDL_atoi(env) + 1; | |
| } | |
| curdata->element = vc_dispmanx_element_add(update, | |
| data->dispman_display, | |
| layer, | |
| &dst_rect, | |
| curdata->resource, | |
| &src_rect, | |
| DISPMANX_PROTECTION_NONE, | |
| &alpha, | |
| DISPMANX_NO_HANDLE, // clamp | |
| DISPMANX_NO_ROTATE); | |
| SDL_assert(curdata->element > DISPMANX_NO_HANDLE); | |
| ret = vc_dispmanx_update_submit_sync(update); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| } | |
| return 0; | |
| } | |
| /* Free a window manager cursor */ | |
| static void | |
| RPI_FreeCursor(SDL_Cursor * cursor) | |
| { | |
| int ret; | |
| DISPMANX_UPDATE_HANDLE_T update; | |
| RPI_CursorData *curdata; | |
| if (cursor != NULL) { | |
| curdata = (RPI_CursorData *) cursor->driverdata; | |
| if (curdata != NULL) { | |
| if (curdata->element != DISPMANX_NO_HANDLE) { | |
| update = vc_dispmanx_update_start(0); | |
| SDL_assert(update); | |
| ret = vc_dispmanx_element_remove(update, curdata->element); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| ret = vc_dispmanx_update_submit_sync(update); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| } | |
| if (curdata->resource != DISPMANX_NO_HANDLE) { | |
| ret = vc_dispmanx_resource_delete(curdata->resource); | |
| SDL_assert(ret == DISPMANX_SUCCESS); | |
| } | |
| SDL_free(cursor->driverdata); | |
| } | |
| SDL_free(cursor); | |
| if (cursor == global_cursor) { | |
| global_cursor = NULL; | |
| } | |
| } | |
| } | |
| /* Warp the mouse to (x,y) */ | |
| static void | |
| RPI_WarpMouse(SDL_Window * window, int x, int y) | |
| { | |
| RPI_WarpMouseGlobal(x, y); | |
| } | |
| /* Warp the mouse to (x,y) */ | |
| static int | |
| RPI_WarpMouseGlobal(int x, int y) | |
| { | |
| RPI_CursorData *curdata; | |
| DISPMANX_UPDATE_HANDLE_T update; | |
| int ret; | |
| VC_RECT_T dst_rect; | |
| VC_RECT_T src_rect; | |
| SDL_Mouse *mouse = SDL_GetMouse(); | |
| if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) { | |
| return 0; | |
| } | |
| /* Update internal mouse position. */ | |
| SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y); | |
| curdata = (RPI_CursorData *) mouse->cur_cursor->driverdata; | |
| if (curdata->element == DISPMANX_NO_HANDLE) { | |
| return 0; | |
| } | |
| update = vc_dispmanx_update_start(0); | |
| if (!update) { | |
| return 0; | |
| } | |
| src_rect.x = 0; | |
| src_rect.y = 0; | |
| src_rect.width = curdata->w << 16; | |
| src_rect.height = curdata->h << 16; | |
| dst_rect.x = x - curdata->hot_x; | |
| dst_rect.y = y - curdata->hot_y; | |
| dst_rect.width = curdata->w; | |
| dst_rect.height = curdata->h; | |
| ret = vc_dispmanx_element_change_attributes( | |
| update, | |
| curdata->element, | |
| 0, | |
| 0, | |
| 0, | |
| &dst_rect, | |
| &src_rect, | |
| DISPMANX_NO_HANDLE, | |
| DISPMANX_NO_ROTATE); | |
| if (ret != DISPMANX_SUCCESS) { | |
| return SDL_SetError("vc_dispmanx_element_change_attributes() failed"); | |
| } | |
| /* Submit asynchronously, otherwise the peformance suffers a lot */ | |
| ret = vc_dispmanx_update_submit(update, 0, NULL); | |
| if (ret != DISPMANX_SUCCESS) { | |
| return SDL_SetError("vc_dispmanx_update_submit() failed"); | |
| } | |
| return 0; | |
| } | |
| /* Warp the mouse to (x,y) */ | |
| static int | |
| RPI_WarpMouseGlobalGraphicOnly(int x, int y) | |
| { | |
| RPI_CursorData *curdata; | |
| DISPMANX_UPDATE_HANDLE_T update; | |
| int ret; | |
| VC_RECT_T dst_rect; | |
| VC_RECT_T src_rect; | |
| SDL_Mouse *mouse = SDL_GetMouse(); | |
| if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) { | |
| return 0; | |
| } | |
| curdata = (RPI_CursorData *) mouse->cur_cursor->driverdata; | |
| if (curdata->element == DISPMANX_NO_HANDLE) { | |
| return 0; | |
| } | |
| update = vc_dispmanx_update_start(0); | |
| if (!update) { | |
| return 0; | |
| } | |
| src_rect.x = 0; | |
| src_rect.y = 0; | |
| src_rect.width = curdata->w << 16; | |
| src_rect.height = curdata->h << 16; | |
| dst_rect.x = x - curdata->hot_x; | |
| dst_rect.y = y - curdata->hot_y; | |
| dst_rect.width = curdata->w; | |
| dst_rect.height = curdata->h; | |
| ret = vc_dispmanx_element_change_attributes( | |
| update, | |
| curdata->element, | |
| 0, | |
| 0, | |
| 0, | |
| &dst_rect, | |
| &src_rect, | |
| DISPMANX_NO_HANDLE, | |
| DISPMANX_NO_ROTATE); | |
| if (ret != DISPMANX_SUCCESS) { | |
| return SDL_SetError("vc_dispmanx_element_change_attributes() failed"); | |
| } | |
| /* Submit asynchronously, otherwise the peformance suffers a lot */ | |
| ret = vc_dispmanx_update_submit(update, 0, NULL); | |
| if (ret != DISPMANX_SUCCESS) { | |
| return SDL_SetError("vc_dispmanx_update_submit() failed"); | |
| } | |
| return 0; | |
| } | |
| void | |
| RPI_InitMouse(_THIS) | |
| { | |
| /* FIXME: Using UDEV it should be possible to scan all mice | |
| * but there's no point in doing so as there's no multimice support...yet! | |
| */ | |
| SDL_Mouse *mouse = SDL_GetMouse(); | |
| mouse->CreateCursor = RPI_CreateCursor; | |
| mouse->ShowCursor = RPI_ShowCursor; | |
| mouse->MoveCursor = RPI_MoveCursor; | |
| mouse->FreeCursor = RPI_FreeCursor; | |
| mouse->WarpMouse = RPI_WarpMouse; | |
| mouse->WarpMouseGlobal = RPI_WarpMouseGlobal; | |
| SDL_SetDefaultCursor(RPI_CreateDefaultCursor()); | |
| } | |
| void | |
| RPI_QuitMouse(_THIS) | |
| { | |
| } | |
| /* This is called when a mouse motion event occurs */ | |
| static void | |
| RPI_MoveCursor(SDL_Cursor * cursor) | |
| { | |
| SDL_Mouse *mouse = SDL_GetMouse(); | |
| /* We must NOT call SDL_SendMouseMotion() on the next call or we will enter recursivity, | |
| * so we create a version of WarpMouseGlobal without it. */ | |
| RPI_WarpMouseGlobalGraphicOnly(mouse->x, mouse->y); | |
| } | |
| #endif /* SDL_VIDEO_DRIVER_RPI */ | |
| /* vi: set ts=4 sw=4 expandtab: */ |