465 lines
15 KiB
C
465 lines
15 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2024 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"
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11
|
|
|
|
#include <unistd.h> /* For getpid() and readlink() */
|
|
|
|
#include "../../core/linux/SDL_system_theme.h"
|
|
#include "../SDL_pixels_c.h"
|
|
#include "../SDL_sysvideo.h"
|
|
|
|
#include "SDL_x11framebuffer.h"
|
|
#include "SDL_x11pen.h"
|
|
#include "SDL_x11touch.h"
|
|
#include "SDL_x11video.h"
|
|
#include "SDL_x11xfixes.h"
|
|
#include "SDL_x11xinput2.h"
|
|
|
|
#ifdef SDL_VIDEO_OPENGL_EGL
|
|
#include "SDL_x11opengles.h"
|
|
#endif
|
|
|
|
/* Initialization/Query functions */
|
|
static int X11_VideoInit(SDL_VideoDevice *_this);
|
|
static void X11_VideoQuit(SDL_VideoDevice *_this);
|
|
|
|
/* X11 driver bootstrap functions */
|
|
|
|
static int (*orig_x11_errhandler)(Display *, XErrorEvent *) = NULL;
|
|
|
|
static void X11_DeleteDevice(SDL_VideoDevice *device)
|
|
{
|
|
SDL_VideoData *data = device->driverdata;
|
|
if (device->vulkan_config.loader_handle) {
|
|
device->Vulkan_UnloadLibrary(device);
|
|
}
|
|
if (data->display) {
|
|
X11_XSetErrorHandler(orig_x11_errhandler);
|
|
X11_XCloseDisplay(data->display);
|
|
}
|
|
if (data->request_display) {
|
|
X11_XCloseDisplay(data->request_display);
|
|
}
|
|
SDL_free(data->windowlist);
|
|
if (device->wakeup_lock) {
|
|
SDL_DestroyMutex(device->wakeup_lock);
|
|
}
|
|
SDL_free(device->driverdata);
|
|
SDL_free(device);
|
|
|
|
SDL_X11_UnloadSymbols();
|
|
}
|
|
|
|
/* An error handler to reset the vidmode and then call the default handler. */
|
|
static SDL_bool safety_net_triggered = SDL_FALSE;
|
|
static int X11_SafetyNetErrHandler(Display *d, XErrorEvent *e)
|
|
{
|
|
SDL_VideoDevice *device = NULL;
|
|
/* if we trigger an error in our error handler, don't try again. */
|
|
if (!safety_net_triggered) {
|
|
safety_net_triggered = SDL_TRUE;
|
|
device = SDL_GetVideoDevice();
|
|
if (device) {
|
|
int i;
|
|
for (i = 0; i < device->num_displays; i++) {
|
|
SDL_VideoDisplay *display = device->displays[i];
|
|
if (SDL_GetCurrentDisplayMode(display->id) != SDL_GetDesktopDisplayMode(display->id)) {
|
|
X11_SetDisplayMode(device, display, &display->desktop_mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (orig_x11_errhandler) {
|
|
return orig_x11_errhandler(d, e); /* probably terminate. */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SDL_VideoDevice *X11_CreateDevice(void)
|
|
{
|
|
SDL_VideoDevice *device;
|
|
SDL_VideoData *data;
|
|
const char *display = NULL; /* Use the DISPLAY environment variable */
|
|
Display *x11_display = NULL;
|
|
|
|
if (!SDL_X11_LoadSymbols()) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Need for threading gl calls. This is also required for the proprietary
|
|
nVidia driver to be threaded. */
|
|
X11_XInitThreads();
|
|
|
|
/* Open the display first to be sure that X11 is available */
|
|
x11_display = X11_XOpenDisplay(display);
|
|
|
|
if (!x11_display) {
|
|
SDL_X11_UnloadSymbols();
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize all variables that we clean on shutdown */
|
|
device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
|
|
if (!device) {
|
|
return NULL;
|
|
}
|
|
data = (struct SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
|
|
if (!data) {
|
|
SDL_free(device);
|
|
return NULL;
|
|
}
|
|
device->driverdata = data;
|
|
|
|
data->global_mouse_changed = SDL_TRUE;
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
|
|
data->active_cursor_confined_window = NULL;
|
|
#endif /* SDL_VIDEO_DRIVER_X11_XFIXES */
|
|
|
|
data->display = x11_display;
|
|
data->request_display = X11_XOpenDisplay(display);
|
|
if (!data->request_display) {
|
|
X11_XCloseDisplay(data->display);
|
|
SDL_free(device->driverdata);
|
|
SDL_free(device);
|
|
SDL_X11_UnloadSymbols();
|
|
return NULL;
|
|
}
|
|
|
|
device->wakeup_lock = SDL_CreateMutex();
|
|
|
|
#ifdef X11_DEBUG
|
|
X11_XSynchronize(data->display, True);
|
|
#endif
|
|
|
|
/* Hook up an X11 error handler to recover the desktop resolution. */
|
|
safety_net_triggered = SDL_FALSE;
|
|
orig_x11_errhandler = X11_XSetErrorHandler(X11_SafetyNetErrHandler);
|
|
|
|
/* Steam Deck will have an on-screen keyboard, so check their environment
|
|
* variable so we can make use of SDL_StartTextInput.
|
|
*/
|
|
data->is_steam_deck = SDL_GetHintBoolean("SteamDeck", SDL_FALSE);
|
|
|
|
/* Set the function pointers */
|
|
device->VideoInit = X11_VideoInit;
|
|
device->VideoQuit = X11_VideoQuit;
|
|
device->ResetTouch = X11_ResetTouch;
|
|
device->GetDisplayModes = X11_GetDisplayModes;
|
|
device->GetDisplayBounds = X11_GetDisplayBounds;
|
|
device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds;
|
|
device->GetWindowICCProfile = X11_GetWindowICCProfile;
|
|
device->SetDisplayMode = X11_SetDisplayMode;
|
|
device->SuspendScreenSaver = X11_SuspendScreenSaver;
|
|
device->PumpEvents = X11_PumpEvents;
|
|
device->WaitEventTimeout = X11_WaitEventTimeout;
|
|
device->SendWakeupEvent = X11_SendWakeupEvent;
|
|
|
|
device->CreateSDLWindow = X11_CreateWindow;
|
|
device->SetWindowTitle = X11_SetWindowTitle;
|
|
device->SetWindowIcon = X11_SetWindowIcon;
|
|
device->SetWindowPosition = X11_SetWindowPosition;
|
|
device->SetWindowSize = X11_SetWindowSize;
|
|
device->SetWindowMinimumSize = X11_SetWindowMinimumSize;
|
|
device->SetWindowMaximumSize = X11_SetWindowMaximumSize;
|
|
device->GetWindowBordersSize = X11_GetWindowBordersSize;
|
|
device->SetWindowOpacity = X11_SetWindowOpacity;
|
|
device->SetWindowModalFor = X11_SetWindowModalFor;
|
|
device->SetWindowInputFocus = X11_SetWindowInputFocus;
|
|
device->ShowWindow = X11_ShowWindow;
|
|
device->HideWindow = X11_HideWindow;
|
|
device->RaiseWindow = X11_RaiseWindow;
|
|
device->MaximizeWindow = X11_MaximizeWindow;
|
|
device->MinimizeWindow = X11_MinimizeWindow;
|
|
device->RestoreWindow = X11_RestoreWindow;
|
|
device->SetWindowBordered = X11_SetWindowBordered;
|
|
device->SetWindowResizable = X11_SetWindowResizable;
|
|
device->SetWindowAlwaysOnTop = X11_SetWindowAlwaysOnTop;
|
|
device->SetWindowFullscreen = X11_SetWindowFullscreen;
|
|
device->SetWindowMouseGrab = X11_SetWindowMouseGrab;
|
|
device->SetWindowKeyboardGrab = X11_SetWindowKeyboardGrab;
|
|
device->DestroyWindow = X11_DestroyWindow;
|
|
device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
|
|
device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
|
|
device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
|
|
device->SetWindowHitTest = X11_SetWindowHitTest;
|
|
device->AcceptDragAndDrop = X11_AcceptDragAndDrop;
|
|
device->FlashWindow = X11_FlashWindow;
|
|
device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu;
|
|
device->SetWindowFocusable = X11_SetWindowFocusable;
|
|
device->SyncWindow = X11_SyncWindow;
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
|
|
device->SetWindowMouseRect = X11_SetWindowMouseRect;
|
|
#endif /* SDL_VIDEO_DRIVER_X11_XFIXES */
|
|
|
|
#ifdef SDL_VIDEO_OPENGL_GLX
|
|
device->GL_LoadLibrary = X11_GL_LoadLibrary;
|
|
device->GL_GetProcAddress = X11_GL_GetProcAddress;
|
|
device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
|
|
device->GL_CreateContext = X11_GL_CreateContext;
|
|
device->GL_MakeCurrent = X11_GL_MakeCurrent;
|
|
device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
|
|
device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
|
|
device->GL_SwapWindow = X11_GL_SwapWindow;
|
|
device->GL_DeleteContext = X11_GL_DeleteContext;
|
|
device->GL_GetEGLSurface = NULL;
|
|
#endif
|
|
#ifdef SDL_VIDEO_OPENGL_EGL
|
|
#ifdef SDL_VIDEO_OPENGL_GLX
|
|
if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, SDL_FALSE)) {
|
|
#endif
|
|
device->GL_LoadLibrary = X11_GLES_LoadLibrary;
|
|
device->GL_GetProcAddress = X11_GLES_GetProcAddress;
|
|
device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
|
|
device->GL_CreateContext = X11_GLES_CreateContext;
|
|
device->GL_MakeCurrent = X11_GLES_MakeCurrent;
|
|
device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
|
|
device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
|
|
device->GL_SwapWindow = X11_GLES_SwapWindow;
|
|
device->GL_DeleteContext = X11_GLES_DeleteContext;
|
|
device->GL_GetEGLSurface = X11_GLES_GetEGLSurface;
|
|
#ifdef SDL_VIDEO_OPENGL_GLX
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
device->GetTextMimeTypes = X11_GetTextMimeTypes;
|
|
device->SetClipboardData = X11_SetClipboardData;
|
|
device->GetClipboardData = X11_GetClipboardData;
|
|
device->HasClipboardData = X11_HasClipboardData;
|
|
device->SetPrimarySelectionText = X11_SetPrimarySelectionText;
|
|
device->GetPrimarySelectionText = X11_GetPrimarySelectionText;
|
|
device->HasPrimarySelectionText = X11_HasPrimarySelectionText;
|
|
device->StartTextInput = X11_StartTextInput;
|
|
device->StopTextInput = X11_StopTextInput;
|
|
device->SetTextInputRect = X11_SetTextInputRect;
|
|
device->HasScreenKeyboardSupport = X11_HasScreenKeyboardSupport;
|
|
device->ShowScreenKeyboard = X11_ShowScreenKeyboard;
|
|
device->HideScreenKeyboard = X11_HideScreenKeyboard;
|
|
device->IsScreenKeyboardShown = X11_IsScreenKeyboardShown;
|
|
|
|
device->free = X11_DeleteDevice;
|
|
|
|
#ifdef SDL_VIDEO_VULKAN
|
|
device->Vulkan_LoadLibrary = X11_Vulkan_LoadLibrary;
|
|
device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary;
|
|
device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions;
|
|
device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface;
|
|
#endif
|
|
|
|
#ifdef SDL_USE_LIBDBUS
|
|
if (SDL_SystemTheme_Init())
|
|
device->system_theme = SDL_SystemTheme_Get();
|
|
#endif
|
|
|
|
device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT |
|
|
VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS;
|
|
|
|
return device;
|
|
}
|
|
|
|
VideoBootStrap X11_bootstrap = {
|
|
"x11", "SDL X11 video driver",
|
|
X11_CreateDevice
|
|
};
|
|
|
|
static int (*handler)(Display *, XErrorEvent *) = NULL;
|
|
static int X11_CheckWindowManagerErrorHandler(Display *d, XErrorEvent *e)
|
|
{
|
|
if (e->error_code == BadWindow) {
|
|
return 0;
|
|
} else {
|
|
return handler(d, e);
|
|
}
|
|
}
|
|
|
|
static void X11_CheckWindowManager(SDL_VideoDevice *_this)
|
|
{
|
|
SDL_VideoData *data = _this->driverdata;
|
|
Display *display = data->display;
|
|
Atom _NET_SUPPORTING_WM_CHECK;
|
|
int status, real_format;
|
|
Atom real_type;
|
|
unsigned long items_read = 0, items_left = 0;
|
|
unsigned char *propdata = NULL;
|
|
Window wm_window = 0;
|
|
#ifdef DEBUG_WINDOW_MANAGER
|
|
char *wm_name;
|
|
#endif
|
|
|
|
/* Set up a handler to gracefully catch errors */
|
|
X11_XSync(display, False);
|
|
handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
|
|
|
|
_NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
|
|
status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
|
|
if (status == Success) {
|
|
if (items_read) {
|
|
wm_window = ((Window *)propdata)[0];
|
|
}
|
|
if (propdata) {
|
|
X11_XFree(propdata);
|
|
propdata = NULL;
|
|
}
|
|
}
|
|
|
|
if (wm_window) {
|
|
status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
|
|
if (status != Success || !items_read || wm_window != ((Window *)propdata)[0]) {
|
|
wm_window = None;
|
|
}
|
|
if (status == Success && propdata) {
|
|
X11_XFree(propdata);
|
|
propdata = NULL;
|
|
}
|
|
}
|
|
|
|
/* Reset the error handler, we're done checking */
|
|
X11_XSync(display, False);
|
|
X11_XSetErrorHandler(handler);
|
|
|
|
if (!wm_window) {
|
|
#ifdef DEBUG_WINDOW_MANAGER
|
|
printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
|
|
#endif
|
|
return;
|
|
}
|
|
data->net_wm = SDL_TRUE;
|
|
|
|
#ifdef DEBUG_WINDOW_MANAGER
|
|
wm_name = X11_GetWindowTitle(_this, wm_window);
|
|
printf("Window manager: %s\n", wm_name);
|
|
SDL_free(wm_name);
|
|
#endif
|
|
}
|
|
|
|
int X11_VideoInit(SDL_VideoDevice *_this)
|
|
{
|
|
SDL_VideoData *data = _this->driverdata;
|
|
|
|
/* Get the process PID to be associated to the window */
|
|
data->pid = getpid();
|
|
|
|
/* I have no idea how random this actually is, or has to be. */
|
|
data->window_group = (XID)(((size_t)data->pid) ^ ((size_t)_this));
|
|
|
|
/* Look up some useful Atoms */
|
|
#define GET_ATOM(X) data->X = X11_XInternAtom(data->display, #X, False)
|
|
GET_ATOM(WM_PROTOCOLS);
|
|
GET_ATOM(WM_DELETE_WINDOW);
|
|
GET_ATOM(WM_TAKE_FOCUS);
|
|
GET_ATOM(WM_NAME);
|
|
GET_ATOM(_NET_WM_STATE);
|
|
GET_ATOM(_NET_WM_STATE_HIDDEN);
|
|
GET_ATOM(_NET_WM_STATE_FOCUSED);
|
|
GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
|
|
GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
|
|
GET_ATOM(_NET_WM_STATE_FULLSCREEN);
|
|
GET_ATOM(_NET_WM_STATE_ABOVE);
|
|
GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
|
|
GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
|
|
GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
|
|
GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
|
|
GET_ATOM(_NET_WM_NAME);
|
|
GET_ATOM(_NET_WM_ICON_NAME);
|
|
GET_ATOM(_NET_WM_ICON);
|
|
GET_ATOM(_NET_WM_PING);
|
|
GET_ATOM(_NET_WM_WINDOW_OPACITY);
|
|
GET_ATOM(_NET_WM_USER_TIME);
|
|
GET_ATOM(_NET_ACTIVE_WINDOW);
|
|
GET_ATOM(_NET_FRAME_EXTENTS);
|
|
GET_ATOM(_SDL_WAKEUP);
|
|
GET_ATOM(UTF8_STRING);
|
|
GET_ATOM(PRIMARY);
|
|
GET_ATOM(XdndEnter);
|
|
GET_ATOM(XdndPosition);
|
|
GET_ATOM(XdndStatus);
|
|
GET_ATOM(XdndTypeList);
|
|
GET_ATOM(XdndActionCopy);
|
|
GET_ATOM(XdndDrop);
|
|
GET_ATOM(XdndFinished);
|
|
GET_ATOM(XdndSelection);
|
|
GET_ATOM(XKLAVIER_STATE);
|
|
|
|
/* Detect the window manager */
|
|
X11_CheckWindowManager(_this);
|
|
|
|
if (X11_InitModes(_this) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
X11_InitXinput2(_this);
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
|
|
X11_InitXfixes(_this);
|
|
#endif /* SDL_VIDEO_DRIVER_X11_XFIXES */
|
|
|
|
#ifndef X_HAVE_UTF8_STRING
|
|
#warning X server does not support UTF8_STRING, a feature introduced in 2000! This is likely to become a hard error in a future libSDL3.
|
|
#endif
|
|
|
|
if (X11_InitKeyboard(_this) != 0) {
|
|
return -1;
|
|
}
|
|
X11_InitMouse(_this);
|
|
|
|
X11_InitTouch(_this);
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
|
|
X11_InitPen(_this);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void X11_VideoQuit(SDL_VideoDevice *_this)
|
|
{
|
|
SDL_VideoData *data = _this->driverdata;
|
|
|
|
if (data->clipboard_window) {
|
|
X11_XDestroyWindow(data->display, data->clipboard_window);
|
|
}
|
|
|
|
#ifdef X_HAVE_UTF8_STRING
|
|
if (data->im) {
|
|
X11_XCloseIM(data->im);
|
|
}
|
|
#endif
|
|
|
|
X11_QuitModes(_this);
|
|
X11_QuitKeyboard(_this);
|
|
X11_QuitMouse(_this);
|
|
X11_QuitTouch(_this);
|
|
X11_QuitClipboard(_this);
|
|
}
|
|
|
|
SDL_bool X11_UseDirectColorVisuals(void)
|
|
{
|
|
return (SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") == NULL);
|
|
}
|
|
|
|
#endif /* SDL_VIDEO_DRIVER_X11 */
|