diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 160dfad9cd73b5..4e8ff9424aae6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -419,6 +419,7 @@ jobs: pkg-config \ python3-dev \ qtbase5-dev \ + qtbase5-private-dev \ libqt5svg5-dev \ swig \ libcmocka-dev \ diff --git a/CI/install-dependencies-linux.sh b/CI/install-dependencies-linux.sh index 28f558caff2537..6f403eb455d378 100755 --- a/CI/install-dependencies-linux.sh +++ b/CI/install-dependencies-linux.sh @@ -42,6 +42,7 @@ sudo apt-get install -y \ pkg-config \ python3-dev \ qtbase5-dev \ + qtbase5-private-dev \ libqt5svg5-dev \ swig \ linux-generic \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 426d0628b9effd..1e873810a0d5fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,6 +202,8 @@ if(APPLE) list(APPEND CMAKE_INSTALL_RPATH "@loader_path/" "@executable_path/") elseif(UNIX) option(USE_XDG "Utilize XDG Base Directory Specification" ON) + option(ENABLE_WAYLAND "Build support for Wayland" ON) + if(USE_XDG) add_definitions(-DUSE_XDG) endif() diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index ab10c577b1c9ae..d2fba770e9f747 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -396,6 +396,16 @@ if(WIN32) OUTPUT_NAME "obs${_output_suffix}") endif() +if (ENABLE_WAYLAND) + find_package(Qt5Gui REQUIRED) + include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) + + set(obs_PLATFORM_LIBRARIES + ${obs_PLATFORM_LIBRARIES} + Qt5::Gui + Qt5::GuiPrivate) +endif() + target_link_libraries(obs libobs Qt5::Widgets diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index e72f73d1c12b2b..e8faf32f35ddb7 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -58,6 +58,16 @@ #include #endif +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#include + +#ifdef ENABLE_WAYLAND +#include +#endif + +#endif + #include #include "ui-config.h" @@ -1384,6 +1394,29 @@ bool OBSApp::OBSInit() qRegisterMetaType(); +#if !defined(_WIN32) && !defined(__APPLE__) + obs_set_nix_platform(OBS_NIX_PLATFORM_X11_GLX); + if (QApplication::platformName() == "xcb") { + if (getenv("OBS_USE_EGL")) { + blog(LOG_INFO, "Using EGL/X11"); + obs_set_nix_platform(OBS_NIX_PLATFORM_X11_EGL); + } + obs_set_nix_platform_display(QX11Info::display()); + } + +#ifdef ENABLE_WAYLAND + if (QApplication::platformName().contains("wayland")) { + obs_set_nix_platform(OBS_NIX_PLATFORM_WAYLAND); + QPlatformNativeInterface *native = + QGuiApplication::platformNativeInterface(); + obs_set_nix_platform_display( + native->nativeResourceForIntegration("display")); + + blog(LOG_INFO, "Platform: Wayland"); + } +#endif +#endif + if (!StartupOBS(locale.c_str(), GetProfilerNameStore())) return false; diff --git a/UI/qt-display.cpp b/UI/qt-display.cpp index 1bb97c7baef6b6..5d81c463074ada 100644 --- a/UI/qt-display.cpp +++ b/UI/qt-display.cpp @@ -6,6 +6,62 @@ #include #include +#include + +#ifdef ENABLE_WAYLAND +#include + +class SurfaceEventFilter : public QObject { + OBSQTDisplay *display; + int mTimerId; + +public: + SurfaceEventFilter(OBSQTDisplay *src) : display(src), mTimerId(0) {} + +protected: + bool eventFilter(QObject *obj, QEvent *event) override + { + bool result = QObject::eventFilter(obj, event); + QPlatformSurfaceEvent *surfaceEvent; + + switch (event->type()) { + case QEvent::PlatformSurface: + surfaceEvent = + static_cast(event); + if (surfaceEvent->surfaceEventType() != + QPlatformSurfaceEvent::SurfaceCreated) + return result; + + if (display->windowHandle()->isExposed()) + createOBSDisplay(); + else + mTimerId = startTimer(67); // Arbitrary + break; + case QEvent::Expose: + createOBSDisplay(); + break; + default: + break; + } + + return result; + } + + void timerEvent(QTimerEvent *) { createOBSDisplay(true); } + +private: + void createOBSDisplay(bool force = false) + { + display->CreateDisplay(force); + if (mTimerId > 0) { + killTimer(mTimerId); + mTimerId = 0; + } + } +}; + +#endif + static inline long long color_to_int(const QColor &color) { auto shift = [&](unsigned val, int shift) { @@ -33,8 +89,13 @@ OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags) setAttribute(Qt::WA_NativeWindow); auto windowVisible = [this](bool visible) { - if (!visible) + if (!visible) { +#ifdef ENABLE_WAYLAND + if (obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND) + display = nullptr; +#endif return; + } if (!display) { CreateDisplay(); @@ -45,7 +106,7 @@ OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags) } }; - auto sizeChanged = [this](QScreen *) { + auto screenChanged = [this](QScreen *) { CreateDisplay(); QSize size = GetPixelSize(this); @@ -53,7 +114,13 @@ OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags) }; connect(windowHandle(), &QWindow::visibleChanged, windowVisible); - connect(windowHandle(), &QWindow::screenChanged, sizeChanged); + connect(windowHandle(), &QWindow::screenChanged, screenChanged); + +#ifdef ENABLE_WAYLAND + if (obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND) + windowHandle()->installEventFilter( + new SurfaceEventFilter(this)); +#endif } QColor OBSQTDisplay::GetDisplayBackgroundColor() const @@ -76,9 +143,12 @@ void OBSQTDisplay::UpdateDisplayBackgroundColor() obs_display_set_background_color(display, backgroundColor); } -void OBSQTDisplay::CreateDisplay() +void OBSQTDisplay::CreateDisplay(bool force) { - if (display || !windowHandle()->isExposed()) + if (display) + return; + + if (!windowHandle()->isExposed() && !force) return; QSize size = GetPixelSize(this); @@ -89,7 +159,8 @@ void OBSQTDisplay::CreateDisplay() info.format = GS_BGRA; info.zsformat = GS_ZS_NONE; - QTToGSWindow(winId(), info.window); + if (!QTToGSWindow(windowHandle(), info.window)) + return; display = obs_display_create(&info, backgroundColor); diff --git a/UI/qt-display.hpp b/UI/qt-display.hpp index 816d96bce184d2..c48c126733b609 100644 --- a/UI/qt-display.hpp +++ b/UI/qt-display.hpp @@ -13,8 +13,6 @@ class OBSQTDisplay : public QWidget { OBSDisplay display; - void CreateDisplay(); - void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; @@ -25,6 +23,7 @@ class OBSQTDisplay : public QWidget { public: OBSQTDisplay(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ~OBSQTDisplay() { display = nullptr; } virtual QPaintEngine *paintEngine() const override; @@ -35,4 +34,5 @@ class OBSQTDisplay : public QWidget { QColor GetDisplayBackgroundColor() const; void SetDisplayBackgroundColor(const QColor &color); void UpdateDisplayBackgroundColor(); + void CreateDisplay(bool force = false); }; diff --git a/UI/qt-wrappers.cpp b/UI/qt-wrappers.cpp index 0f13ac4c847d6b..6967f5a732677c 100644 --- a/UI/qt-wrappers.cpp +++ b/UI/qt-wrappers.cpp @@ -30,9 +30,14 @@ #include #if !defined(_WIN32) && !defined(__APPLE__) +#include #include #endif +#ifdef ENABLE_WAYLAND +#include +#endif + static inline void OBSErrorBoxva(QWidget *parent, const char *msg, va_list args) { char full_message[4096]; @@ -108,16 +113,33 @@ void OBSMessageBox::critical(QWidget *parent, const QString &title, mb.exec(); } -void QTToGSWindow(WId windowId, gs_window &gswindow) +bool QTToGSWindow(QWindow *window, gs_window &gswindow) { + bool success = true; + #ifdef _WIN32 - gswindow.hwnd = (HWND)windowId; + gswindow.hwnd = (HWND)window->winId(); #elif __APPLE__ - gswindow.view = (id)windowId; + gswindow.view = (id)window->winId(); #else - gswindow.id = windowId; - gswindow.display = QX11Info::display(); + switch (obs_get_nix_platform()) { + case OBS_NIX_PLATFORM_X11_GLX: + case OBS_NIX_PLATFORM_X11_EGL: + gswindow.id = window->winId(); + gswindow.display = obs_get_nix_platform_display(); + break; +#ifdef ENABLE_WAYLAND + case OBS_NIX_PLATFORM_WAYLAND: + QPlatformNativeInterface *native = + QGuiApplication::platformNativeInterface(); + gswindow.display = + native->nativeResourceForWindow("surface", window); + success = gswindow.display != nullptr; + break; +#endif + } #endif + return success; } uint32_t TranslateQtKeyboardEventModifiers(Qt::KeyboardModifiers mods) diff --git a/UI/qt-wrappers.hpp b/UI/qt-wrappers.hpp index 600bab2fe26341..5042487e0b9f15 100644 --- a/UI/qt-wrappers.hpp +++ b/UI/qt-wrappers.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -56,7 +57,7 @@ class OBSMessageBox { void OBSErrorBox(QWidget *parent, const char *msg, ...); -void QTToGSWindow(WId windowId, gs_window &gswindow); +bool QTToGSWindow(QWindow *window, gs_window &gswindow); uint32_t TranslateQtKeyboardEventModifiers(Qt::KeyboardModifiers mods); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 845d509046284c..f2e64e0b9ad637 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -79,6 +79,10 @@ #include +#ifdef ENABLE_WAYLAND +#include +#endif + using namespace json11; using namespace std; @@ -1865,9 +1869,22 @@ void OBSBasic::OBSInit() bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop"); - if (alwaysOnTop || opt_always_on_top) { + +#ifdef ENABLE_WAYLAND + bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; +#else + bool isWayland = false; +#endif + + if (!isWayland && (alwaysOnTop || opt_always_on_top)) { SetAlwaysOnTop(this, true); ui->actionAlwaysOnTop->setChecked(true); + } else if (isWayland) { + if (opt_always_on_top) + blog(LOG_INFO, + "Always On Top not available on Wayland, ignoring…"); + ui->actionAlwaysOnTop->setEnabled(false); + ui->actionAlwaysOnTop->setVisible(false); } #ifndef _WIN32 diff --git a/cmake/Modules/FindEGL.cmake b/cmake/Modules/FindEGL.cmake new file mode 100644 index 00000000000000..ee27cc906b6d0f --- /dev/null +++ b/cmake/Modules/FindEGL.cmake @@ -0,0 +1,53 @@ +# - Try to Find EGL +# Once done, this will define +# +# EGL_FOUND - system has EGL installed. +# EGL_INCLUDE_DIRS - directories which contain the EGL headers. +# EGL_LIBRARIES - libraries required to link against EGL. +# EGL_DEFINITIONS - Compiler switches required for using EGL. +# +# Copyright (C) 2012 Intel Corporation. All rights reserved. +# 2020 Georges Basile Stavracas Neto +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +find_package(PkgConfig) + +pkg_check_modules(PC_EGL egl) + +if (PC_EGL_FOUND) + set(EGL_DEFINITIONS ${PC_EGL_CFLAGS_OTHER}) +endif () + +find_path(EGL_INCLUDE_DIRS NAMES EGL/egl.h + HINTS ${PC_EGL_INCLUDE_DIR} ${PC_EGL_INCLUDE_DIRS} +) + +find_library(EGL_LIBRARIES NAMES egl EGL + HINTS ${PC_EGL_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(EGL DEFAULT_MSG EGL_INCLUDE_DIRS EGL_LIBRARIES) + +mark_as_advanced(EGL_INCLUDE_DIRS EGL_LIBRARIES) diff --git a/cmake/Modules/FindWayland.cmake b/cmake/Modules/FindWayland.cmake new file mode 100644 index 00000000000000..377f0545cda572 --- /dev/null +++ b/cmake/Modules/FindWayland.cmake @@ -0,0 +1,78 @@ +# Try to find Wayland on a Unix system +# +# This will define: +# +# WAYLAND_FOUND - True if Wayland is found +# WAYLAND_LIBRARIES - Link these to use Wayland +# WAYLAND_INCLUDE_DIRS - Include directory for Wayland +# WAYLAND_DEFINITIONS - Compiler flags for using Wayland +# +# In addition the following more fine grained variables will be defined: +# +# Wayland_Client_FOUND WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES +# Wayland_Server_FOUND WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES +# Wayland_EGL_FOUND WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES +# Wayland_Cursor_FOUND WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES +# +# Copyright (c) 2013 Martin Gräßlin +# 2020 Georges Basile Stavracas Neto +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +IF (NOT WIN32) + + # Use pkg-config to get the directories and then use these values + # in the find_path() and find_library() calls + find_package(PkgConfig) + PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor) + + set(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS}) + + find_path(WAYLAND_CLIENT_INCLUDE_DIRS NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_CLIENT_INCLUDE_DIRS AND WAYLAND_CLIENT_LIBRARIES) + set(Wayland_Client_FOUND TRUE) + else() + set(Wayland_Client_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES) + + find_path(WAYLAND_CURSOR_INCLUDE_DIRS NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_CURSOR_INCLUDE_DIRS AND WAYLAND_CURSOR_LIBRARIES) + set(Wayland_Cursor_FOUND TRUE) + else() + set(Wayland_Cursor_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES) + + find_path(WAYLAND_EGL_INCLUDE_DIRS NAMES wayland-egl.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_EGL_INCLUDE_DIRS AND WAYLAND_EGL_LIBRARIES) + set(Wayland_EGL_FOUND TRUE) + else() + set(Wayland_EGL_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES) + + find_path(WAYLAND_SERVER_INCLUDE_DIRS NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) + find_library(WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + if(WAYLAND_SERVER_INCLUDE_DIRS AND WAYLAND_SERVER_LIBRARIES) + set(Wayland_Server_FOUND TRUE) + else() + set(Wayland_Server_FOUND FALSE) + endif() + mark_as_advanced(WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES) + + set(WAYLAND_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIRS} ${WAYLAND_SERVER_INCLUDE_DIRS} ${WAYLAND_EGL_INCLUDE_DIRS} ${WAYLAND_CURSOR_INCLUDE_DIRS}) + set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES}) + mark_as_advanced(WAYLAND_INCLUDE_DIRS WAYLAND_LIBRARIES) + + list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIRS) + + include(FindPackageHandleStandardArgs) + + find_package_handle_standard_args(Wayland REQUIRED_VARS WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIRS HANDLE_COMPONENTS) + +ENDIF () diff --git a/deps/glad/CMakeLists.txt b/deps/glad/CMakeLists.txt index 17ccb9a987425e..83f4efac554a2f 100644 --- a/deps/glad/CMakeLists.txt +++ b/deps/glad/CMakeLists.txt @@ -3,7 +3,8 @@ project(glad) find_package(OpenGL) if(NOT WIN32 AND NOT APPLE) - find_package(X11) + find_package(X11 REQUIRED) + find_package(EGL REQUIRED) endif() set(glad_SOURCES @@ -19,7 +20,9 @@ if(WIN32) obsglad.rc) elseif(NOT APPLE) set(glad_PLATFORM_SOURCES + src/glad_egl.c src/glad_glx.c + include/glad/glad_egl.h include/glad/glad_glx.h) endif() @@ -28,7 +31,9 @@ set(glad_include_dirs if (UNIX AND NOT APPLE) list (APPEND glad_include_dirs - PRIVATE ${X11_X11_INCLUDE_PATH}) + PRIVATE + ${X11_X11_INCLUDE_PATH} + ${EGL_INCLUDE_DIRS}) endif() add_library(glad SHARED @@ -53,7 +58,9 @@ endif() if(NOT WIN32 AND NOT APPLE) set(glad_PLATFORM_DEPS - ${X11_X11_LIB}) + ${X11_X11_LIB} + ${EGL_LIBRARIES}) + # only link to libdl on linux if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(glad_PLATFORM_DEPS diff --git a/deps/glad/include/glad/glad_egl.h b/deps/glad/include/glad/glad_egl.h new file mode 100644 index 00000000000000..ac07814e51486a --- /dev/null +++ b/deps/glad/include/glad/glad_egl.h @@ -0,0 +1,385 @@ +/* + + EGL loader generated by glad 0.1.33 on Thu Aug 27 19:18:06 2020. + + Language/Generator: C/C++ + Specification: egl + APIs: egl=1.5 + Profile: - + Extensions: + EGL_EXT_platform_wayland, + EGL_EXT_platform_x11, + EGL_KHR_create_context, + EGL_KHR_create_context_no_error, + EGL_KHR_platform_wayland, + EGL_KHR_platform_x11 + Loader: True + Local files: False + Omit khrplatform: False + Reproducible: False + + Commandline: + --api="egl=1.5" --generator="c" --spec="egl" --extensions="EGL_EXT_platform_wayland,EGL_EXT_platform_x11,EGL_KHR_create_context,EGL_KHR_create_context_no_error,EGL_KHR_platform_wayland,EGL_KHR_platform_x11" + Online: + https://glad.dav1d.de/#language=c&specification=egl&loader=on&api=egl%3D1.5&extensions=EGL_EXT_platform_wayland&extensions=EGL_EXT_platform_x11&extensions=EGL_KHR_create_context&extensions=EGL_KHR_create_context_no_error&extensions=EGL_KHR_platform_wayland&extensions=EGL_KHR_platform_x11 +*/ + + +#ifndef __glad_egl_h_ + +#ifdef __egl_h_ +#error EGL header already included, remove this include, glad already provides it +#endif + +#define __glad_egl_h_ +#define __egl_h_ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define APIENTRY __stdcall +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* (* GLADloadproc)(const char *name); + +#define GLAD_GLAPI_EXPORT + +#ifndef GLAPI +# if defined(GLAD_GLAPI_EXPORT) +# if defined(WIN32) || defined(__CYGWIN__) +# if defined(GLAD_GLAPI_EXPORT_BUILD) +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllexport)) extern +# else +# define GLAPI __declspec(dllexport) extern +# endif +# else +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllimport)) extern +# else +# define GLAPI __declspec(dllimport) extern +# endif +# endif +# elif defined(__GNUC__) && defined(GLAD_GLAPI_EXPORT_BUILD) +# define GLAPI __attribute__ ((visibility ("default"))) extern +# else +# define GLAPI extern +# endif +# else +# define GLAPI extern +# endif +#endif + +GLAPI int gladLoadEGL(void); +GLAPI int gladLoadEGLLoader(GLADloadproc); + +#include +#include +struct AHardwareBuffer; +struct wl_buffer; +struct wl_display; +struct wl_resource; +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; +typedef intptr_t EGLAttribKHR; +typedef intptr_t EGLAttrib; +typedef void *EGLClientBuffer; +typedef void *EGLConfig; +typedef void *EGLContext; +typedef void *EGLDeviceEXT; +typedef void *EGLDisplay; +typedef void *EGLImage; +typedef void *EGLImageKHR; +typedef void *EGLLabelKHR; +typedef void *EGLObjectKHR; +typedef void *EGLOutputLayerEXT; +typedef void *EGLOutputPortEXT; +typedef void *EGLStreamKHR; +typedef void *EGLSurface; +typedef void *EGLSync; +typedef void *EGLSyncKHR; +typedef void *EGLSyncNV; +typedef void (*__eglMustCastToProperFunctionPointerType)(void); +typedef khronos_utime_nanoseconds_t EGLTimeKHR; +typedef khronos_utime_nanoseconds_t EGLTime; +typedef khronos_utime_nanoseconds_t EGLTimeNV; +typedef khronos_utime_nanoseconds_t EGLuint64NV; +typedef khronos_uint64_t EGLuint64KHR; +typedef khronos_stime_nanoseconds_t EGLnsecsANDROID; +typedef int EGLNativeFileDescriptorKHR; +typedef khronos_ssize_t EGLsizeiANDROID; +typedef void (*EGLSetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize); +typedef EGLsizeiANDROID (*EGLGetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, void *value, EGLsizeiANDROID valueSize); +struct EGLClientPixmapHI { + void *pData; + EGLint iWidth; + EGLint iHeight; + EGLint iStride; +}; +typedef void (APIENTRY *EGLDEBUGPROCKHR)(EGLenum error,const char *command,EGLint messageType,EGLLabelKHR threadLabel,EGLLabelKHR objectLabel,const char* message); +#define PFNEGLBINDWAYLANDDISPLAYWL PFNEGLBINDWAYLANDDISPLAYWLPROC +#define PFNEGLUNBINDWAYLANDDISPLAYWL PFNEGLUNBINDWAYLANDDISPLAYWLPROC +#define PFNEGLQUERYWAYLANDBUFFERWL PFNEGLQUERYWAYLANDBUFFERWLPROC +#define PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_BAD_ACCESS 0x3002 +#define EGL_BAD_ALLOC 0x3003 +#define EGL_BAD_ATTRIBUTE 0x3004 +#define EGL_BAD_CONFIG 0x3005 +#define EGL_BAD_CONTEXT 0x3006 +#define EGL_BAD_CURRENT_SURFACE 0x3007 +#define EGL_BAD_DISPLAY 0x3008 +#define EGL_BAD_MATCH 0x3009 +#define EGL_BAD_NATIVE_PIXMAP 0x300A +#define EGL_BAD_NATIVE_WINDOW 0x300B +#define EGL_BAD_PARAMETER 0x300C +#define EGL_BAD_SURFACE 0x300D +#define EGL_BLUE_SIZE 0x3022 +#define EGL_BUFFER_SIZE 0x3020 +#define EGL_CONFIG_CAVEAT 0x3027 +#define EGL_CONFIG_ID 0x3028 +#define EGL_CORE_NATIVE_ENGINE 0x305B +#define EGL_DEPTH_SIZE 0x3025 +#define EGL_DONT_CARE EGL_CAST(EGLint,-1) +#define EGL_DRAW 0x3059 +#define EGL_EXTENSIONS 0x3055 +#define EGL_FALSE 0 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_HEIGHT 0x3056 +#define EGL_LARGEST_PBUFFER 0x3058 +#define EGL_LEVEL 0x3029 +#define EGL_MAX_PBUFFER_HEIGHT 0x302A +#define EGL_MAX_PBUFFER_PIXELS 0x302B +#define EGL_MAX_PBUFFER_WIDTH 0x302C +#define EGL_NATIVE_RENDERABLE 0x302D +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_NATIVE_VISUAL_TYPE 0x302F +#define EGL_NONE 0x3038 +#define EGL_NON_CONFORMANT_CONFIG 0x3051 +#define EGL_NOT_INITIALIZED 0x3001 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext,0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay,0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface,0) +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_PIXMAP_BIT 0x0002 +#define EGL_READ 0x305A +#define EGL_RED_SIZE 0x3024 +#define EGL_SAMPLES 0x3031 +#define EGL_SAMPLE_BUFFERS 0x3032 +#define EGL_SLOW_CONFIG 0x3050 +#define EGL_STENCIL_SIZE 0x3026 +#define EGL_SUCCESS 0x3000 +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_TRANSPARENT_BLUE_VALUE 0x3035 +#define EGL_TRANSPARENT_GREEN_VALUE 0x3036 +#define EGL_TRANSPARENT_RED_VALUE 0x3037 +#define EGL_TRANSPARENT_RGB 0x3052 +#define EGL_TRANSPARENT_TYPE 0x3034 +#define EGL_TRUE 1 +#define EGL_VENDOR 0x3053 +#define EGL_VERSION 0x3054 +#define EGL_WIDTH 0x3057 +#define EGL_WINDOW_BIT 0x0004 +#define EGL_BACK_BUFFER 0x3084 +#define EGL_BIND_TO_TEXTURE_RGB 0x3039 +#define EGL_BIND_TO_TEXTURE_RGBA 0x303A +#define EGL_CONTEXT_LOST 0x300E +#define EGL_MIN_SWAP_INTERVAL 0x303B +#define EGL_MAX_SWAP_INTERVAL 0x303C +#define EGL_MIPMAP_TEXTURE 0x3082 +#define EGL_MIPMAP_LEVEL 0x3083 +#define EGL_NO_TEXTURE 0x305C +#define EGL_TEXTURE_2D 0x305F +#define EGL_TEXTURE_FORMAT 0x3080 +#define EGL_TEXTURE_RGB 0x305D +#define EGL_TEXTURE_RGBA 0x305E +#define EGL_TEXTURE_TARGET 0x3081 +#define EGL_ALPHA_FORMAT 0x3088 +#define EGL_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_ALPHA_FORMAT_PRE 0x308C +#define EGL_ALPHA_MASK_SIZE 0x303E +#define EGL_BUFFER_PRESERVED 0x3094 +#define EGL_BUFFER_DESTROYED 0x3095 +#define EGL_CLIENT_APIS 0x308D +#define EGL_COLORSPACE 0x3087 +#define EGL_COLORSPACE_sRGB 0x3089 +#define EGL_COLORSPACE_LINEAR 0x308A +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_CONTEXT_CLIENT_TYPE 0x3097 +#define EGL_DISPLAY_SCALING 10000 +#define EGL_HORIZONTAL_RESOLUTION 0x3090 +#define EGL_LUMINANCE_BUFFER 0x308F +#define EGL_LUMINANCE_SIZE 0x303D +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENVG_BIT 0x0002 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_OPENVG_API 0x30A1 +#define EGL_OPENVG_IMAGE 0x3096 +#define EGL_PIXEL_ASPECT_RATIO 0x3092 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_RENDER_BUFFER 0x3086 +#define EGL_RGB_BUFFER 0x308E +#define EGL_SINGLE_BUFFER 0x3085 +#define EGL_SWAP_BEHAVIOR 0x3093 +#define EGL_UNKNOWN EGL_CAST(EGLint,-1) +#define EGL_VERTICAL_RESOLUTION 0x3091 +#define EGL_CONFORMANT 0x3042 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_MATCH_NATIVE_PIXMAP 0x3041 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_VG_ALPHA_FORMAT 0x3088 +#define EGL_VG_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_VG_ALPHA_FORMAT_PRE 0x308C +#define EGL_VG_ALPHA_FORMAT_PRE_BIT 0x0040 +#define EGL_VG_COLORSPACE 0x3087 +#define EGL_VG_COLORSPACE_sRGB 0x3089 +#define EGL_VG_COLORSPACE_LINEAR 0x308A +#define EGL_VG_COLORSPACE_LINEAR_BIT 0x0020 +#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType,0) +#define EGL_MULTISAMPLE_RESOLVE_BOX_BIT 0x0200 +#define EGL_MULTISAMPLE_RESOLVE 0x3099 +#define EGL_MULTISAMPLE_RESOLVE_DEFAULT 0x309A +#define EGL_MULTISAMPLE_RESOLVE_BOX 0x309B +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_SWAP_BEHAVIOR_PRESERVED_BIT 0x0400 +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_CONTEXT_MINOR_VERSION 0x30FB +#define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY 0x31BD +#define EGL_NO_RESET_NOTIFICATION 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET 0x31BF +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define EGL_CONTEXT_OPENGL_DEBUG 0x31B0 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS 0x31B2 +#define EGL_OPENGL_ES3_BIT 0x00000040 +#define EGL_CL_EVENT_HANDLE 0x309C +#define EGL_SYNC_CL_EVENT 0x30FE +#define EGL_SYNC_CL_EVENT_COMPLETE 0x30FF +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE 0x30F0 +#define EGL_SYNC_TYPE 0x30F7 +#define EGL_SYNC_STATUS 0x30F1 +#define EGL_SYNC_CONDITION 0x30F8 +#define EGL_SIGNALED 0x30F2 +#define EGL_UNSIGNALED 0x30F3 +#define EGL_SYNC_FLUSH_COMMANDS_BIT 0x0001 +#define EGL_FOREVER 0xFFFFFFFFFFFFFFFF +#define EGL_TIMEOUT_EXPIRED 0x30F5 +#define EGL_CONDITION_SATISFIED 0x30F6 +#define EGL_NO_SYNC EGL_CAST(EGLSync,0) +#define EGL_SYNC_FENCE 0x30F9 +#define EGL_GL_COLORSPACE 0x309D +#define EGL_GL_COLORSPACE_SRGB 0x3089 +#define EGL_GL_COLORSPACE_LINEAR 0x308A +#define EGL_GL_RENDERBUFFER 0x30B9 +#define EGL_GL_TEXTURE_2D 0x30B1 +#define EGL_GL_TEXTURE_LEVEL 0x30BC +#define EGL_GL_TEXTURE_3D 0x30B2 +#define EGL_GL_TEXTURE_ZOFFSET 0x30BD +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x30B3 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x30B4 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x30B5 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x30B6 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x30B7 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x30B8 +#define EGL_IMAGE_PRESERVED 0x30D2 +#define EGL_NO_IMAGE EGL_CAST(EGLImage,0) +EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLBoolean eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); +EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); +EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); +EGLSurface eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); +EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); +EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx); +EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface); +EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); +EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLDisplay eglGetCurrentDisplay(void); +EGLSurface eglGetCurrentSurface(EGLint readdraw); +EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id); +EGLint eglGetError(void); +__eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname); +EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor); +EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); +EGLBoolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); +const char *eglQueryString(EGLDisplay dpy, EGLint name); +EGLBoolean eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); +EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface); +EGLBoolean eglTerminate(EGLDisplay dpy); +EGLBoolean eglWaitGL(void); +EGLBoolean eglWaitNative(EGLint engine); +EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); +EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval); +EGLBoolean eglBindAPI(EGLenum api); +EGLenum eglQueryAPI(void); +EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); +EGLBoolean eglReleaseThread(void); +EGLBoolean eglWaitClient(void); +EGLContext eglGetCurrentContext(void); +EGLSync eglCreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); +EGLBoolean eglDestroySync(EGLDisplay dpy, EGLSync sync); +EGLint eglClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); +EGLBoolean eglGetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); +EGLImage eglCreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); +EGLBoolean eglDestroyImage(EGLDisplay dpy, EGLImage image); +EGLDisplay eglGetPlatformDisplay(EGLenum platform, void *native_display, const EGLAttrib *attrib_list); +EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); +EGLSurface eglCreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); +EGLBoolean eglWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags); +#define EGL_PLATFORM_WAYLAND_EXT 0x31D8 +#define EGL_PLATFORM_X11_EXT 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_EXT 0x31D6 +#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD +#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 +#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#define EGL_PLATFORM_X11_KHR 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_KHR 0x31D6 +#ifndef EGL_EXT_platform_wayland +#define EGL_EXT_platform_wayland 1 +#endif +#ifndef EGL_EXT_platform_x11 +#define EGL_EXT_platform_x11 1 +#endif +#ifndef EGL_KHR_create_context +#define EGL_KHR_create_context 1 +#endif +#ifndef EGL_KHR_create_context_no_error +#define EGL_KHR_create_context_no_error 1 +#endif +#ifndef EGL_KHR_platform_wayland +#define EGL_KHR_platform_wayland 1 +#endif +#ifndef EGL_KHR_platform_x11 +#define EGL_KHR_platform_x11 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/glad/src/glad_egl.c b/deps/glad/src/glad_egl.c new file mode 100644 index 00000000000000..e3cd1fc2948b70 --- /dev/null +++ b/deps/glad/src/glad_egl.c @@ -0,0 +1,48 @@ +/* + + EGL loader generated by glad 0.1.33 on Mon Mar 9 17:01:26 2020. + + Language/Generator: C/C++ + Specification: egl + APIs: egl=1.5 + Profile: - + Extensions: + EGL_EXT_platform_wayland, + EGL_EXT_platform_x11, + EGL_KHR_platform_wayland, + EGL_KHR_platform_x11 + Loader: True + Local files: False + Omit khrplatform: False + Reproducible: False + + Commandline: + --api="egl=1.5" --generator="c" --spec="egl" --extensions="EGL_EXT_platform_wayland,EGL_EXT_platform_x11,EGL_KHR_platform_wayland,EGL_KHR_platform_x11" + Online: + https://glad.dav1d.de/#language=c&specification=egl&loader=on&api=egl%3D1.5&extensions=EGL_EXT_platform_wayland&extensions=EGL_EXT_platform_x11&extensions=EGL_KHR_platform_wayland&extensions=EGL_KHR_platform_x11 +*/ + +#include +#include +#include +#include + +int gladLoadEGL(void) { + return gladLoadEGLLoader((GLADloadproc)eglGetProcAddress); +} + +static int find_extensionsEGL(void) { + return 1; +} + +static void find_coreEGL(void) { +} + +int gladLoadEGLLoader(GLADloadproc load) { + (void) load; + find_coreEGL(); + + if (!find_extensionsEGL()) return 0; + return 1; +} + diff --git a/libobs-opengl/CMakeLists.txt b/libobs-opengl/CMakeLists.txt index aa25d74ff50375..e9c4cabd6b28dd 100644 --- a/libobs-opengl/CMakeLists.txt +++ b/libobs-opengl/CMakeLists.txt @@ -28,7 +28,7 @@ elseif(APPLE) ${COCOA} ${IOSURF} ${OPENGL_gl_LIBRARY}) -else() #This needs to change to be more specific to get ready for Wayland +else() find_package(XCB COMPONENTS XCB REQUIRED) find_package(X11_XCB REQUIRED) @@ -45,7 +45,32 @@ else() #This needs to change to be more specific to get ready for Wayland ${X11_XCB_LIBRARIES}) set(libobs-opengl_PLATFORM_SOURCES - gl-x11.c) + gl-nix.c + gl-x11-egl.c + gl-x11-glx.c) + + if(ENABLE_WAYLAND) + find_package(EGL REQUIRED) + find_package(Wayland REQUIRED) + + include_directories( + ${WAYLAND_CLIENT_INCLUDE_DIRS} + ${WAYLAND_EGL_INCLUDE_DIRS} + ${EGL_INCLUDE_DIRS}) + + add_definitions( + ${WAYLAND_DEFINITIONS}) + + set(libobs-opengl_PLATFORM_DEPS + ${libobs-opengl_PLATFORM_DEPS} + ${WAYLAND_CLIENT_LIBRARIES} + ${WAYLAND_EGL_LIBRARIES} + ${EGL_LIBRARIES}) + + set(libobs-opengl_PLATFORM_SOURCES + ${libobs-opengl_PLATFORM_SOURCES} + gl-wayland-egl.c) + endif() endif() set(libobs-opengl_SOURCES diff --git a/libobs-opengl/gl-nix.c b/libobs-opengl/gl-nix.c new file mode 100644 index 00000000000000..6c272c3d977bb8 --- /dev/null +++ b/libobs-opengl/gl-nix.c @@ -0,0 +1,125 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "gl-nix.h" +#include "gl-x11-glx.h" +#include "gl-x11-egl.h" + +#ifdef ENABLE_WAYLAND +#include "gl-wayland-egl.h" +#endif + +static const struct gl_winsys_vtable *gl_vtable = NULL; + +static void init_winsys(void) +{ + assert(gl_vtable == NULL); + + switch (obs_get_nix_platform()) { + case OBS_NIX_PLATFORM_X11_GLX: + gl_vtable = gl_x11_glx_get_winsys_vtable(); + break; + case OBS_NIX_PLATFORM_X11_EGL: + gl_vtable = gl_x11_egl_get_winsys_vtable(); + break; +#ifdef ENABLE_WAYLAND + case OBS_NIX_PLATFORM_WAYLAND: + gl_vtable = gl_wayland_egl_get_winsys_vtable(); + blog(LOG_INFO, "Using EGL/Wayland"); + break; +#endif + } + + assert(gl_vtable != NULL); +} + +extern struct gl_windowinfo * +gl_windowinfo_create(const struct gs_init_data *info) +{ + return gl_vtable->windowinfo_create(info); +} + +extern void gl_windowinfo_destroy(struct gl_windowinfo *info) +{ + gl_vtable->windowinfo_destroy(info); +} + +extern struct gl_platform *gl_platform_create(gs_device_t *device, + uint32_t adapter) +{ + init_winsys(); + + return gl_vtable->platform_create(device, adapter); +} + +extern void gl_platform_destroy(struct gl_platform *plat) +{ + gl_vtable->platform_destroy(plat); + + gl_vtable = NULL; +} + +extern bool gl_platform_init_swapchain(struct gs_swap_chain *swap) +{ + return gl_vtable->platform_init_swapchain(swap); +} + +extern void gl_platform_cleanup_swapchain(struct gs_swap_chain *swap) +{ + gl_vtable->platform_cleanup_swapchain(swap); +} + +extern void device_enter_context(gs_device_t *device) +{ + gl_vtable->device_enter_context(device); +} + +extern void device_leave_context(gs_device_t *device) +{ + gl_vtable->device_leave_context(device); +} + +extern void *device_get_device_obj(gs_device_t *device) +{ + return gl_vtable->device_get_device_obj(device); +} + +extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width, + uint32_t *height) +{ + gl_vtable->getclientsize(swap, width, height); +} + +extern void gl_clear_context(gs_device_t *device) +{ + gl_vtable->clear_context(device); +} + +extern void gl_update(gs_device_t *device) +{ + gl_vtable->update(device); +} + +extern void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap) +{ + gl_vtable->device_load_swapchain(device, swap); +} + +extern void device_present(gs_device_t *device) +{ + gl_vtable->device_present(device); +} diff --git a/libobs-opengl/gl-nix.h b/libobs-opengl/gl-nix.h new file mode 100644 index 00000000000000..741154da58b391 --- /dev/null +++ b/libobs-opengl/gl-nix.h @@ -0,0 +1,56 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include + +#include "gl-subsystem.h" + +struct gl_winsys_vtable { + struct gl_windowinfo *(*windowinfo_create)( + const struct gs_init_data *info); + void (*windowinfo_destroy)(struct gl_windowinfo *info); + + struct gl_platform *(*platform_create)(gs_device_t *device, + uint32_t adapter); + + void (*platform_destroy)(struct gl_platform *plat); + + bool (*platform_init_swapchain)(struct gs_swap_chain *swap); + + void (*platform_cleanup_swapchain)(struct gs_swap_chain *swap); + + void (*device_enter_context)(gs_device_t *device); + + void (*device_leave_context)(gs_device_t *device); + + void *(*device_get_device_obj)(gs_device_t *device); + + void (*getclientsize)(const struct gs_swap_chain *swap, uint32_t *width, + uint32_t *height); + + void (*clear_context)(gs_device_t *device); + + void (*update)(gs_device_t *device); + + void (*device_load_swapchain)(gs_device_t *device, + gs_swapchain_t *swap); + + void (*device_present)(gs_device_t *device); +}; diff --git a/libobs-opengl/gl-wayland-egl.c b/libobs-opengl/gl-wayland-egl.c new file mode 100644 index 00000000000000..aad6993e8dacc3 --- /dev/null +++ b/libobs-opengl/gl-wayland-egl.c @@ -0,0 +1,342 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "gl-wayland-egl.h" + +#include +#include + +#include + +static const EGLint config_attribs[] = {EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_STENCIL_SIZE, + 0, + EGL_DEPTH_SIZE, + 0, + EGL_BUFFER_SIZE, + 32, + EGL_ALPHA_SIZE, + 8, + EGL_NATIVE_RENDERABLE, + EGL_TRUE, + EGL_NONE}; + +static const EGLint ctx_attribs[] = { +#ifdef _DEBUG + EGL_CONTEXT_OPENGL_DEBUG, + EGL_TRUE, +#endif + EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_NONE}; + +static const EGLint khr_ctx_attribs[] = { +#ifdef _DEBUG + EGL_CONTEXT_FLAGS_KHR, + EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, +#endif + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_CONTEXT_MAJOR_VERSION_KHR, + 3, + EGL_CONTEXT_MINOR_VERSION_KHR, + 3, + EGL_NONE}; + +struct gl_windowinfo { + struct wl_egl_window *window; + EGLSurface egl_surface; +}; + +struct gl_platform { + struct wl_display *wl_display; + EGLDisplay display; + EGLConfig config; + EGLContext context; +}; + +struct gl_windowinfo * +gl_wayland_egl_windowinfo_create(const struct gs_init_data *info) +{ + struct wl_egl_window *window = + wl_egl_window_create(info->window.display, info->cx, info->cy); + if (window == NULL) { + blog(LOG_ERROR, "wl_egl_window_create failed"); + return NULL; + } + + struct gl_windowinfo *wi = bmalloc(sizeof(struct gl_windowinfo)); + wi->window = window; + return wi; +} + +static void gl_wayland_egl_windowinfo_destroy(struct gl_windowinfo *info) +{ + wl_egl_window_destroy(info->window); + bfree(info); +} + +static bool egl_make_current(EGLDisplay display, EGLSurface surface, + EGLContext context) +{ + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + blog(LOG_ERROR, "eglBindAPI failed"); + } + + if (!eglMakeCurrent(display, surface, surface, context)) { + blog(LOG_ERROR, "eglMakeCurrent failed"); + return false; + } + return true; +} + +static bool egl_context_create(struct gl_platform *plat, const EGLint *attribs) +{ + bool success = false; + EGLint num_config; + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + blog(LOG_ERROR, "eglBindAPI failed"); + } + + EGLBoolean result = eglChooseConfig(plat->display, config_attribs, + &plat->config, 1, &num_config); + if (result != EGL_TRUE || num_config == 0) { + blog(LOG_ERROR, "eglChooseConfig failed"); + goto error; + } + + plat->context = eglCreateContext(plat->display, plat->config, + EGL_NO_CONTEXT, attribs); + if (plat->context == EGL_NO_CONTEXT) { + blog(LOG_ERROR, "eglCreateContext failed"); + goto error; + } + + success = + egl_make_current(plat->display, EGL_NO_SURFACE, plat->context); + +error: + return success; +} + +static void egl_context_destroy(struct gl_platform *plat) +{ + egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(plat->display, plat->context); +} + +static bool extension_supported(const char *extensions, const char *search) +{ + const char *result = strstr(extensions, search); + unsigned long len = strlen(search); + return result != NULL && + (result == extensions || *(result - 1) == ' ') && + (result[len] == ' ' || result[len] == '\0'); +} + +static struct gl_platform *gl_wayland_egl_platform_create(gs_device_t *device, + uint32_t adapter) +{ + struct gl_platform *plat = bmalloc(sizeof(struct gl_platform)); + + plat->wl_display = obs_get_nix_platform_display(); + + device->plat = plat; + + plat->display = eglGetDisplay(plat->wl_display); + if (plat->display == EGL_NO_DISPLAY) { + blog(LOG_ERROR, "eglGetDisplay failed"); + goto fail_display_init; + } + + EGLint major; + EGLint minor; + + if (eglInitialize(plat->display, &major, &minor) == EGL_FALSE) { + blog(LOG_ERROR, "eglInitialize failed"); + goto fail_display_init; + } + + blog(LOG_INFO, "Initialized EGL %d.%d", major, minor); + + const char *extensions = eglQueryString(plat->display, EGL_EXTENSIONS); + blog(LOG_DEBUG, "Supported EGL Extensions: %s", extensions); + + const EGLint *attribs = ctx_attribs; + if (major == 1 && minor == 4) { + if (extension_supported(extensions, "EGL_KHR_create_context")) { + attribs = khr_ctx_attribs; + } else { + blog(LOG_ERROR, + "EGL_KHR_create_context extension is required to use EGL 1.4."); + goto fail_context_create; + } + } else if (major < 1 || (major == 1 && minor < 4)) { + blog(LOG_ERROR, "EGL 1.4 or higher is required."); + goto fail_context_create; + } + + if (!egl_context_create(plat, attribs)) { + goto fail_context_create; + } + + if (!gladLoadGL()) { + blog(LOG_ERROR, "Failed to load OpenGL entry functions."); + goto fail_load_gl; + } + + goto success; + +fail_load_gl: + egl_context_destroy(plat); +fail_context_create: + eglTerminate(plat->display); +fail_display_init: + bfree(plat); + plat = NULL; +success: + UNUSED_PARAMETER(adapter); + return plat; +} + +static void gl_wayland_egl_platform_destroy(struct gl_platform *plat) +{ + if (plat) { + egl_context_destroy(plat); + eglTerminate(plat->display); + bfree(plat); + } +} + +static bool gl_wayland_egl_platform_init_swapchain(struct gs_swap_chain *swap) +{ + struct gl_platform *plat = swap->device->plat; + EGLSurface egl_surface = eglCreateWindowSurface( + plat->display, plat->config, swap->wi->window, NULL); + if (egl_surface == EGL_NO_SURFACE) { + blog(LOG_ERROR, "eglCreateWindowSurface failed"); + return false; + } + swap->wi->egl_surface = egl_surface; + return true; +} + +static void +gl_wayland_egl_platform_cleanup_swapchain(struct gs_swap_chain *swap) +{ + struct gl_platform *plat = swap->device->plat; + eglDestroySurface(plat->display, swap->wi->egl_surface); +} + +static void gl_wayland_egl_device_enter_context(gs_device_t *device) +{ + struct gl_platform *plat = device->plat; + EGLSurface surface = EGL_NO_SURFACE; + if (device->cur_swap != NULL) + surface = device->cur_swap->wi->egl_surface; + egl_make_current(plat->display, surface, plat->context); +} + +static void gl_wayland_egl_device_leave_context(gs_device_t *device) +{ + struct gl_platform *plat = device->plat; + egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +static void *gl_wayland_egl_device_get_device_obj(gs_device_t *device) +{ + return device->plat->context; +} + +static void gl_wayland_egl_getclientsize(const struct gs_swap_chain *swap, + uint32_t *width, uint32_t *height) +{ + wl_egl_window_get_attached_size(swap->wi->window, (void *)width, + (void *)height); +} + +static void gl_wayland_egl_clear_context(gs_device_t *device) +{ + struct gl_platform *plat = device->plat; + egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +static void gl_wayland_egl_update(gs_device_t *device) +{ + wl_egl_window_resize(device->cur_swap->wi->window, + device->cur_swap->info.cx, + device->cur_swap->info.cy, 0, 0); +} + +static void gl_wayland_egl_device_load_swapchain(gs_device_t *device, + gs_swapchain_t *swap) +{ + if (device->cur_swap == swap) + return; + + device->cur_swap = swap; + + struct gl_platform *plat = device->plat; + if (swap == NULL) { + egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } else { + egl_make_current(plat->display, swap->wi->egl_surface, + plat->context); + } +} + +static void gl_wayland_egl_device_present(gs_device_t *device) +{ + struct gl_platform *plat = device->plat; + struct gl_windowinfo *wi = device->cur_swap->wi; + if (eglSwapInterval(plat->display, 0) == EGL_FALSE) { + blog(LOG_ERROR, "eglSwapInterval failed"); + } + if (eglSwapBuffers(plat->display, wi->egl_surface) == EGL_FALSE) { + blog(LOG_ERROR, "eglSwapBuffers failed"); + } +} + +static const struct gl_winsys_vtable egl_wayland_winsys_vtable = { + .windowinfo_create = gl_wayland_egl_windowinfo_create, + .windowinfo_destroy = gl_wayland_egl_windowinfo_destroy, + .platform_create = gl_wayland_egl_platform_create, + .platform_destroy = gl_wayland_egl_platform_destroy, + .platform_init_swapchain = gl_wayland_egl_platform_init_swapchain, + .platform_cleanup_swapchain = gl_wayland_egl_platform_cleanup_swapchain, + .device_enter_context = gl_wayland_egl_device_enter_context, + .device_leave_context = gl_wayland_egl_device_leave_context, + .device_get_device_obj = gl_wayland_egl_device_get_device_obj, + .getclientsize = gl_wayland_egl_getclientsize, + .clear_context = gl_wayland_egl_clear_context, + .update = gl_wayland_egl_update, + .device_load_swapchain = gl_wayland_egl_device_load_swapchain, + .device_present = gl_wayland_egl_device_present, +}; + +const struct gl_winsys_vtable *gl_wayland_egl_get_winsys_vtable(void) +{ + return &egl_wayland_winsys_vtable; +} diff --git a/libobs-opengl/gl-wayland-egl.h b/libobs-opengl/gl-wayland-egl.h new file mode 100644 index 00000000000000..3384576f1df506 --- /dev/null +++ b/libobs-opengl/gl-wayland-egl.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "gl-nix.h" + +const struct gl_winsys_vtable *gl_wayland_egl_get_winsys_vtable(void); diff --git a/libobs-opengl/gl-x11-egl.c b/libobs-opengl/gl-x11-egl.c new file mode 100644 index 00000000000000..47b8e420ae9a55 --- /dev/null +++ b/libobs-opengl/gl-x11-egl.c @@ -0,0 +1,657 @@ +/****************************************************************************** + Copyright (C) 2019 by Ivan Avdeev + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +/* GL context initialization using EGL instead of GLX + * Which is essential for improved and more performant screen grabbing and + * VA-API feeding techniques. + * + * Note: most of x11-related functionality was taken from gl-x11.c + */ + +#include +#include + +#include + +#include + +#include "gl-x11-egl.h" + +#include + +typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)( + EGLenum platform, void *native_display, const EGLint *attrib_list); + +static const int ctx_attribs[] = { +#ifdef _DEBUG + EGL_CONTEXT_OPENGL_DEBUG, + EGL_TRUE, +#endif + EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_NONE, +}; + +static int ctx_pbuffer_attribs[] = {EGL_WIDTH, 2, EGL_HEIGHT, 2, EGL_NONE}; + +static const EGLint ctx_config_attribs[] = {EGL_STENCIL_SIZE, + 0, + EGL_DEPTH_SIZE, + 0, + EGL_BUFFER_SIZE, + 32, + EGL_ALPHA_SIZE, + 8, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_NONE}; + +struct gl_windowinfo { + EGLConfig config; + + /* Windows in X11 are defined with integers (XID). + * xcb_window_t is a define for this... they are + * compatible with Xlib as well. + */ + xcb_window_t window; + EGLSurface surface; + + /* We can't fetch screen without a request so we cache it. */ + int screen; +}; + +struct gl_platform { + Display *xdisplay; + EGLDisplay edisplay; + EGLConfig config; + EGLContext context; + EGLSurface pbuffer; +}; + +/* The following utility functions are copied verbatim from GLX code. */ + +/* + * Since we cannot take advantage of the asynchronous nature of xcb, + * all of the helper functions are synchronous but thread-safe. + * + * They check for errors and will return 0 on problems + * with the exception of when 0 is a valid return value... in which case + * read the specific function comments. + */ + +/* Returns -1 on invalid screen. */ +static int get_screen_num_from_xcb_screen(xcb_connection_t *xcb_conn, + xcb_screen_t *screen) +{ + xcb_screen_iterator_t iter = + xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + int screen_num = 0; + + for (; iter.rem; xcb_screen_next(&iter), ++screen_num) + if (iter.data == screen) + return screen_num; + + return -1; +} + +static xcb_screen_t *get_screen_from_root(xcb_connection_t *xcb_conn, + xcb_window_t root) +{ + xcb_screen_iterator_t iter = + xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + + while (iter.rem) { + if (iter.data->root == root) + return iter.data; + + xcb_screen_next(&iter); + } + + return 0; +} + +static inline int get_screen_num_from_root(xcb_connection_t *xcb_conn, + xcb_window_t root) +{ + xcb_screen_t *screen = get_screen_from_root(xcb_conn, root); + + if (!screen) + return -1; + + return get_screen_num_from_xcb_screen(xcb_conn, screen); +} + +static xcb_get_geometry_reply_t *get_window_geometry(xcb_connection_t *xcb_conn, + xcb_drawable_t drawable) +{ + xcb_get_geometry_cookie_t cookie; + xcb_generic_error_t *error; + xcb_get_geometry_reply_t *reply; + + cookie = xcb_get_geometry(xcb_conn, drawable); + reply = xcb_get_geometry_reply(xcb_conn, cookie, &error); + + if (error) { + blog(LOG_ERROR, "Failed to fetch parent window geometry!"); + free(error); + free(reply); + return 0; + } + + free(error); + return reply; +} + +static const char *get_egl_error_string2(const EGLint error) +{ + switch (error) { +#define OBS_EGL_CASE_ERROR(e) \ + case e: \ + return #e; + OBS_EGL_CASE_ERROR(EGL_SUCCESS) + OBS_EGL_CASE_ERROR(EGL_NOT_INITIALIZED) + OBS_EGL_CASE_ERROR(EGL_BAD_ACCESS) + OBS_EGL_CASE_ERROR(EGL_BAD_ALLOC) + OBS_EGL_CASE_ERROR(EGL_BAD_ATTRIBUTE) + OBS_EGL_CASE_ERROR(EGL_BAD_CONTEXT) + OBS_EGL_CASE_ERROR(EGL_BAD_CONFIG) + OBS_EGL_CASE_ERROR(EGL_BAD_CURRENT_SURFACE) + OBS_EGL_CASE_ERROR(EGL_BAD_DISPLAY) + OBS_EGL_CASE_ERROR(EGL_BAD_SURFACE) + OBS_EGL_CASE_ERROR(EGL_BAD_MATCH) + OBS_EGL_CASE_ERROR(EGL_BAD_PARAMETER) + OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_PIXMAP) + OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_WINDOW) + OBS_EGL_CASE_ERROR(EGL_CONTEXT_LOST) +#undef OBS_EGL_CASE_ERROR + default: + return "Unknown"; + } +} +static const char *get_egl_error_string() +{ + return get_egl_error_string2(eglGetError()); +} + +static EGLDisplay get_egl_display(struct gl_platform *plat) +{ + Display *display = plat->xdisplay; + EGLDisplay edisplay = EGL_NO_DISPLAY; + const char *egl_client_extensions = NULL; + + egl_client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = + (PFNEGLGETPLATFORMDISPLAYEXTPROC)( + strstr(egl_client_extensions, "EGL_EXT_platform_base") + ? eglGetProcAddress("eglGetPlatformDisplayEXT") + : NULL); + + if (eglGetPlatformDisplayEXT) { + edisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, + display, NULL); + if (EGL_NO_DISPLAY == edisplay) + blog(LOG_ERROR, "Failed to get EGL/X11 display"); + } + + if (EGL_NO_DISPLAY == edisplay) + edisplay = eglGetDisplay(display); + + return edisplay; +} + +static bool gl_context_create(struct gl_platform *plat) +{ + Display *display = plat->xdisplay; + int frame_buf_config_count = 0; + EGLDisplay edisplay = EGL_NO_DISPLAY; + EGLConfig config = NULL; + EGLContext context = EGL_NO_CONTEXT; + int egl_min = 0, egl_maj = 0; + bool success = false; + + eglBindAPI(EGL_OPENGL_API); + + edisplay = get_egl_display(plat); + + if (EGL_NO_DISPLAY == edisplay) { + blog(LOG_ERROR, + "Failed to get EGL display using eglGetDisplay"); + return false; + } + + if (!eglInitialize(edisplay, &egl_maj, &egl_min)) { + blog(LOG_ERROR, "Failed to initialize EGL: %s", + get_egl_error_string()); + return false; + } + + if (!eglChooseConfig(edisplay, ctx_config_attribs, &config, 1, + &frame_buf_config_count)) { + blog(LOG_ERROR, "Unable to find suitable EGL config: %s", + get_egl_error_string()); + goto error; + } + + context = + eglCreateContext(edisplay, config, EGL_NO_CONTEXT, ctx_attribs); +#ifdef _DEBUG + if (EGL_NO_CONTEXT == context) { + const EGLint error = eglGetError(); + if (error == EGL_BAD_ATTRIBUTE) { + /* Sometimes creation fails because debug gl is not supported */ + blog(LOG_ERROR, + "Unable to create EGL context with DEBUG attrib, trying without"); + context = eglCreateContext(edisplay, config, + EGL_NO_CONTEXT, + ctx_attribs + 2); + } else { + blog(LOG_ERROR, "Unable to create EGL context: %s", + get_egl_error_string2(error)); + goto error; + } + } +#endif + if (EGL_NO_CONTEXT == context) { + blog(LOG_ERROR, "Unable to create EGL context: %s", + get_egl_error_string()); + goto error; + } + + plat->pbuffer = + eglCreatePbufferSurface(edisplay, config, ctx_pbuffer_attribs); + if (EGL_NO_SURFACE == plat->pbuffer) { + blog(LOG_ERROR, "Failed to create OpenGL pbuffer: %s", + get_egl_error_string()); + goto error; + } + + plat->edisplay = edisplay; + plat->config = config; + plat->context = context; + + success = true; + blog(LOG_DEBUG, "Created EGLDisplay %p", plat->edisplay); + +error: + if (!success) { + if (EGL_NO_CONTEXT != context) + eglDestroyContext(edisplay, context); + eglTerminate(edisplay); + } + + XSync(display, false); + return success; +} + +static void gl_context_destroy(struct gl_platform *plat) +{ + eglMakeCurrent(plat->edisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroyContext(plat->edisplay, plat->context); +} + +static struct gl_windowinfo * +gl_x11_egl_windowinfo_create(const struct gs_init_data *info) +{ + UNUSED_PARAMETER(info); + return bmalloc(sizeof(struct gl_windowinfo)); +} + +static void gl_x11_egl_windowinfo_destroy(struct gl_windowinfo *info) +{ + UNUSED_PARAMETER(info); + bfree(info); +} + +static Display *open_windowless_display(Display *platform_display) +{ + Display *display; + xcb_connection_t *xcb_conn; + xcb_screen_iterator_t screen_iterator; + xcb_screen_t *screen; + int screen_num; + + if (platform_display) + display = platform_display; + else + display = XOpenDisplay(NULL); + + if (!display) { + blog(LOG_ERROR, "Unable to open new X connection!"); + return NULL; + } + + xcb_conn = XGetXCBConnection(display); + if (!xcb_conn) { + blog(LOG_ERROR, "Unable to get XCB connection to main display"); + goto error; + } + + screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + screen = screen_iterator.data; + if (!screen) { + blog(LOG_ERROR, "Unable to get screen root"); + goto error; + } + + screen_num = get_screen_num_from_root(xcb_conn, screen->root); + if (screen_num == -1) { + blog(LOG_ERROR, "Unable to get screen number from root"); + goto error; + } + + if (!gladLoadEGL()) { + blog(LOG_ERROR, "Unable to load EGL entry functions."); + goto error; + } + + return display; + +error: + if (display) + XCloseDisplay(display); + return NULL; +} + +static int x_error_handler(Display *display, XErrorEvent *error) +{ + char str1[512]; + char str2[512]; + char str3[512]; + XGetErrorText(display, error->error_code, str1, sizeof(str1)); + XGetErrorText(display, error->request_code, str2, sizeof(str2)); + XGetErrorText(display, error->minor_code, str3, sizeof(str3)); + + blog(LOG_ERROR, + "X Error: %s, Major opcode: %s, " + "Minor opcode: %s, Serial: %lu", + str1, str2, str3, error->serial); + return 0; +} + +static struct gl_platform *gl_x11_egl_platform_create(gs_device_t *device, + uint32_t adapter) +{ + /* There's some trickery here... we're mixing libX11, xcb, and EGL + For an explanation see here: http://xcb.freedesktop.org/MixingCalls/ + Essentially, EGL requires Xlib. Everything else we use xcb. */ + struct gl_platform *plat = bmalloc(sizeof(struct gl_platform)); + Display *platform_display = obs_get_nix_platform_display(); + Display *display = open_windowless_display(platform_display); + + if (!display) { + goto fail_display_open; + } + + XSetEventQueueOwner(display, XCBOwnsEventQueue); + XSetErrorHandler(x_error_handler); + + /* We assume later that cur_swap is already set. */ + device->plat = plat; + + plat->xdisplay = display; + + if (!gl_context_create(plat)) { + blog(LOG_ERROR, "Failed to create context!"); + goto fail_context_create; + } + + if (!eglMakeCurrent(plat->edisplay, plat->pbuffer, plat->pbuffer, + plat->context)) { + blog(LOG_ERROR, "Failed to make context current: %s", + get_egl_error_string()); + goto fail_make_current; + } + + if (!gladLoadGL()) { + blog(LOG_ERROR, "Failed to load OpenGL entry functions."); + goto fail_load_gl; + } + + goto success; + +fail_make_current: + gl_context_destroy(plat); +fail_context_create: +fail_load_gl: + XCloseDisplay(display); +fail_display_open: + bfree(plat); + plat = NULL; +success: + UNUSED_PARAMETER(adapter); + return plat; +} + +static void gl_x11_egl_platform_destroy(struct gl_platform *plat) +{ + if (!plat) + return; + + gl_context_destroy(plat); + eglTerminate(plat->edisplay); + bfree(plat); +} + +static bool gl_x11_egl_platform_init_swapchain(struct gs_swap_chain *swap) +{ + const struct gl_platform *plat = swap->device->plat; + Display *display = plat->xdisplay; + xcb_connection_t *xcb_conn = XGetXCBConnection(display); + xcb_window_t wid = xcb_generate_id(xcb_conn); + xcb_window_t parent = swap->info.window.id; + xcb_get_geometry_reply_t *geometry = + get_window_geometry(xcb_conn, parent); + bool status = false; + + int screen_num; + int visual; + + if (!geometry) + goto fail_geometry_request; + + screen_num = get_screen_num_from_root(xcb_conn, geometry->root); + if (screen_num == -1) { + goto fail_screen; + } + + { + if (!eglGetConfigAttrib(plat->edisplay, plat->config, + EGL_NATIVE_VISUAL_ID, + (EGLint *)&visual)) { + blog(LOG_ERROR, + "Cannot get visual id for EGL context: %s", + get_egl_error_string()); + goto fail_visual_id; + } + } + + xcb_colormap_t colormap = xcb_generate_id(xcb_conn); + uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP; + uint32_t mask_values[] = {0, colormap, 0}; + + xcb_create_colormap(xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, parent, + visual); + + xcb_create_window(xcb_conn, 24 /* Hardcoded? */, wid, parent, 0, 0, + geometry->width, geometry->height, 0, 0, visual, mask, + mask_values); + + const EGLSurface surface = + eglCreateWindowSurface(plat->edisplay, plat->config, wid, 0); + if (EGL_NO_SURFACE == surface) { + blog(LOG_ERROR, "Cannot get window EGL surface: %s", + get_egl_error_string()); + goto fail_window_surface; + } + + swap->wi->config = plat->config; + swap->wi->window = wid; + swap->wi->surface = surface; + swap->wi->screen = screen_num; + + xcb_map_window(xcb_conn, wid); + + status = true; + goto success; + +fail_window_surface: +fail_visual_id: +fail_screen: +fail_geometry_request: +success: + free(geometry); + return status; +} + +static void gl_x11_egl_platform_cleanup_swapchain(struct gs_swap_chain *swap) +{ + UNUSED_PARAMETER(swap); + /* Really nothing to clean up? */ +} + +static void gl_x11_egl_device_enter_context(gs_device_t *device) +{ + const EGLContext context = device->plat->context; + const EGLDisplay display = device->plat->edisplay; + const EGLSurface surface = (device->cur_swap) + ? device->cur_swap->wi->surface + : device->plat->pbuffer; + + if (!eglMakeCurrent(display, surface, surface, context)) + blog(LOG_ERROR, "Failed to make context current: %s", + get_egl_error_string()); +} + +static void gl_x11_egl_device_leave_context(gs_device_t *device) +{ + const EGLDisplay display = device->plat->edisplay; + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + blog(LOG_ERROR, "Failed to reset current context: %s", + get_egl_error_string()); + } +} + +static void *gl_x11_egl_device_get_device_obj(gs_device_t *device) +{ + return device->plat->context; +} + +static void gl_x11_egl_getclientsize(const struct gs_swap_chain *swap, + uint32_t *width, uint32_t *height) +{ + xcb_connection_t *xcb_conn = + XGetXCBConnection(swap->device->plat->xdisplay); + xcb_window_t window = swap->wi->window; + + xcb_get_geometry_reply_t *geometry = + get_window_geometry(xcb_conn, window); + if (geometry) { + *width = geometry->width; + *height = geometry->height; + } + + free(geometry); +} + +static void gl_x11_egl_update(gs_device_t *device) +{ + Display *display = device->plat->xdisplay; + xcb_window_t window = device->cur_swap->wi->window; + + uint32_t values[] = {device->cur_swap->info.cx, + device->cur_swap->info.cy}; + + xcb_configure_window(XGetXCBConnection(display), window, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values); +} + +static void gl_x11_egl_clear_context(gs_device_t *device) +{ + Display *display = device->plat->edisplay; + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + blog(LOG_ERROR, "Failed to reset current context."); + } +} + +static void gl_x11_egl_device_load_swapchain(gs_device_t *device, + gs_swapchain_t *swap) +{ + if (device->cur_swap == swap) + return; + + device->cur_swap = swap; + + device_enter_context(device); +} + +enum swap_type { + SWAP_TYPE_NORMAL, + SWAP_TYPE_EXT, + SWAP_TYPE_MESA, + SWAP_TYPE_SGI, +}; + +static void gl_x11_egl_device_present(gs_device_t *device) +{ + Display *display = device->plat->xdisplay; + + xcb_connection_t *xcb_conn = XGetXCBConnection(display); + xcb_generic_event_t *xcb_event; + while ((xcb_event = xcb_poll_for_event(xcb_conn))) { + free(xcb_event); + } + + if (!eglSwapBuffers(device->plat->edisplay, + device->cur_swap->wi->surface)) + blog(LOG_ERROR, "Cannot swap EGL buffers: %s", + get_egl_error_string()); +} + +static const struct gl_winsys_vtable egl_x11_winsys_vtable = { + .windowinfo_create = gl_x11_egl_windowinfo_create, + .windowinfo_destroy = gl_x11_egl_windowinfo_destroy, + .platform_create = gl_x11_egl_platform_create, + .platform_destroy = gl_x11_egl_platform_destroy, + .platform_init_swapchain = gl_x11_egl_platform_init_swapchain, + .platform_cleanup_swapchain = gl_x11_egl_platform_cleanup_swapchain, + .device_enter_context = gl_x11_egl_device_enter_context, + .device_leave_context = gl_x11_egl_device_leave_context, + .device_get_device_obj = gl_x11_egl_device_get_device_obj, + .getclientsize = gl_x11_egl_getclientsize, + .clear_context = gl_x11_egl_clear_context, + .update = gl_x11_egl_update, + .device_load_swapchain = gl_x11_egl_device_load_swapchain, + .device_present = gl_x11_egl_device_present, +}; + +const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void) +{ + return &egl_x11_winsys_vtable; +} diff --git a/libobs-opengl/gl-x11-egl.h b/libobs-opengl/gl-x11-egl.h new file mode 100644 index 00000000000000..44ab3111609717 --- /dev/null +++ b/libobs-opengl/gl-x11-egl.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2019 by Ivan Avdeev + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "gl-nix.h" + +const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void); diff --git a/libobs-opengl/gl-x11.c b/libobs-opengl/gl-x11-glx.c similarity index 86% rename from libobs-opengl/gl-x11.c rename to libobs-opengl/gl-x11-glx.c index d04115b04c4de5..a562b564987a89 100644 --- a/libobs-opengl/gl-x11.c +++ b/libobs-opengl/gl-x11-glx.c @@ -36,7 +36,7 @@ #include -#include "gl-subsystem.h" +#include "gl-nix.h" #include @@ -221,14 +221,14 @@ static void gl_context_destroy(struct gl_platform *plat) bfree(plat); } -extern struct gl_windowinfo * -gl_windowinfo_create(const struct gs_init_data *info) +static struct gl_windowinfo * +gl_x11_glx_windowinfo_create(const struct gs_init_data *info) { UNUSED_PARAMETER(info); return bmalloc(sizeof(struct gl_windowinfo)); } -extern void gl_windowinfo_destroy(struct gl_windowinfo *info) +static void gl_x11_glx_windowinfo_destroy(struct gl_windowinfo *info) { bfree(info); } @@ -294,8 +294,8 @@ static int x_error_handler(Display *display, XErrorEvent *error) return 0; } -extern struct gl_platform *gl_platform_create(gs_device_t *device, - uint32_t adapter) +static struct gl_platform *gl_x11_glx_platform_create(gs_device_t *device, + uint32_t adapter) { /* There's some trickery here... we're mixing libX11, xcb, and GLX For an explanation see here: http://xcb.freedesktop.org/MixingCalls/ @@ -346,7 +346,7 @@ extern struct gl_platform *gl_platform_create(gs_device_t *device, return plat; } -extern void gl_platform_destroy(struct gl_platform *plat) +static void gl_x11_glx_platform_destroy(struct gl_platform *plat) { if (!plat) /* In what case would platform be invalid here? */ return; @@ -354,7 +354,7 @@ extern void gl_platform_destroy(struct gl_platform *plat) gl_context_destroy(plat); } -extern bool gl_platform_init_swapchain(struct gs_swap_chain *swap) +static bool gl_x11_glx_platform_init_swapchain(struct gs_swap_chain *swap) { Display *display = swap->device->plat->display; xcb_connection_t *xcb_conn = XGetXCBConnection(display); @@ -429,13 +429,13 @@ extern bool gl_platform_init_swapchain(struct gs_swap_chain *swap) return status; } -extern void gl_platform_cleanup_swapchain(struct gs_swap_chain *swap) +static void gl_x11_glx_platform_cleanup_swapchain(struct gs_swap_chain *swap) { UNUSED_PARAMETER(swap); /* Really nothing to clean up? */ } -extern void device_enter_context(gs_device_t *device) +static void gl_x11_glx_device_enter_context(gs_device_t *device) { GLXContext context = device->plat->context; Display *display = device->plat->display; @@ -453,7 +453,7 @@ extern void device_enter_context(gs_device_t *device) } } -extern void device_leave_context(gs_device_t *device) +static void gl_x11_glx_device_leave_context(gs_device_t *device) { Display *display = device->plat->display; @@ -462,13 +462,13 @@ extern void device_leave_context(gs_device_t *device) } } -void *device_get_device_obj(gs_device_t *device) +static void *gl_x11_glx_device_get_device_obj(gs_device_t *device) { return device->plat->context; } -extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width, - uint32_t *height) +static void gl_x11_glx_getclientsize(const struct gs_swap_chain *swap, + uint32_t *width, uint32_t *height) { xcb_connection_t *xcb_conn = XGetXCBConnection(swap->device->plat->display); @@ -484,7 +484,7 @@ extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width, free(geometry); } -extern void gl_clear_context(gs_device_t *device) +static void gl_x11_glx_clear_context(gs_device_t *device) { Display *display = device->plat->display; @@ -493,7 +493,7 @@ extern void gl_clear_context(gs_device_t *device) } } -extern void gl_update(gs_device_t *device) +static void gl_x11_glx_update(gs_device_t *device) { Display *display = device->plat->display; xcb_window_t window = device->cur_swap->wi->window; @@ -506,7 +506,8 @@ extern void gl_update(gs_device_t *device) values); } -extern void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap) +static void gl_x11_glx_device_load_swapchain(gs_device_t *device, + gs_swapchain_t *swap) { if (device->cur_swap == swap) return; @@ -536,7 +537,7 @@ enum swap_type { SWAP_TYPE_SGI, }; -extern void device_present(gs_device_t *device) +static void gl_x11_glx_device_present(gs_device_t *device) { static bool initialized = false; static enum swap_type swap_type = SWAP_TYPE_NORMAL; @@ -577,3 +578,25 @@ extern void device_present(gs_device_t *device) glXSwapBuffers(display, window); } + +static const struct gl_winsys_vtable glx_winsys_vtable = { + .windowinfo_create = gl_x11_glx_windowinfo_create, + .windowinfo_destroy = gl_x11_glx_windowinfo_destroy, + .platform_create = gl_x11_glx_platform_create, + .platform_destroy = gl_x11_glx_platform_destroy, + .platform_init_swapchain = gl_x11_glx_platform_init_swapchain, + .platform_cleanup_swapchain = gl_x11_glx_platform_cleanup_swapchain, + .device_enter_context = gl_x11_glx_device_enter_context, + .device_leave_context = gl_x11_glx_device_leave_context, + .device_get_device_obj = gl_x11_glx_device_get_device_obj, + .getclientsize = gl_x11_glx_getclientsize, + .clear_context = gl_x11_glx_clear_context, + .update = gl_x11_glx_update, + .device_load_swapchain = gl_x11_glx_device_load_swapchain, + .device_present = gl_x11_glx_device_present, +}; + +const struct gl_winsys_vtable *gl_x11_glx_get_winsys_vtable(void) +{ + return &glx_winsys_vtable; +} diff --git a/libobs-opengl/gl-x11-glx.h b/libobs-opengl/gl-x11-glx.h new file mode 100644 index 00000000000000..bdedf55c56dcdc --- /dev/null +++ b/libobs-opengl/gl-x11-glx.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2014 by Zachary Lund + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "gl-nix.h" + +const struct gl_winsys_vtable *gl_x11_glx_get_winsys_vtable(void); diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 5eb3042f73d418..1e4518448276d6 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -187,12 +187,30 @@ elseif(APPLE) elseif(UNIX) set(libobs_PLATFORM_SOURCES obs-nix.c + obs-nix-platform.c + obs-nix-x11.c util/threading-posix.c util/pipe-posix.c util/platform-nix.c) set(libobs_PLATFORM_HEADERS - util/threading-posix.h) + util/threading-posix.h + obs-nix-platform.h) + + if(ENABLE_WAYLAND) + find_package(Wayland COMPONENTS Client REQUIRED) + + set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES} + obs-nix-wayland.c) + + include_directories( + ${WAYLAND_CLIENT_INCLUDE_DIR}) + add_definitions( + ${WAYLAND_DEFINITIONS}) + set(libobs_PLATFORM_DEPS + ${libobs_PLATFORM_DEPS} + ${WAYLAND_CLIENT_LIBRARIES}) + endif() if(HAVE_PULSEAUDIO) set(libobs_audio_monitoring_HEADERS diff --git a/libobs/obs-nix-platform.c b/libobs/obs-nix-platform.c new file mode 100644 index 00000000000000..e07a4d7b886328 --- /dev/null +++ b/libobs/obs-nix-platform.c @@ -0,0 +1,42 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-nix-platform.h" + +static enum obs_nix_platform_type obs_nix_platform = OBS_NIX_PLATFORM_X11_GLX; + +static void *obs_nix_platform_display = NULL; + +void obs_set_nix_platform(enum obs_nix_platform_type platform) +{ + obs_nix_platform = platform; +} + +enum obs_nix_platform_type obs_get_nix_platform(void) +{ + return obs_nix_platform; +} + +void obs_set_nix_platform_display(void *display) +{ + obs_nix_platform_display = display; +} + +void *obs_get_nix_platform_display(void) +{ + return obs_nix_platform_display; +} diff --git a/libobs/obs-nix-platform.h b/libobs/obs-nix-platform.h new file mode 100644 index 00000000000000..cef700d775986e --- /dev/null +++ b/libobs/obs-nix-platform.h @@ -0,0 +1,56 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "util/c99defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum obs_nix_platform_type { + OBS_NIX_PLATFORM_X11_GLX, + OBS_NIX_PLATFORM_X11_EGL, +#ifdef ENABLE_WAYLAND + OBS_NIX_PLATFORM_WAYLAND, +#endif + +}; + +/** + * Sets the Unix platform. + * @param platform The platform to select. + */ +EXPORT void obs_set_nix_platform(enum obs_nix_platform_type platform); +/** + * Gets the host platform. + */ +EXPORT enum obs_nix_platform_type obs_get_nix_platform(void); +/** + * Sets the host platform's display connection. + * @param display The host display connection. + */ +EXPORT void obs_set_nix_platform_display(void *display); +/** + * Gets the host platform's display connection. + */ +EXPORT void *obs_get_nix_platform_display(void); + +#ifdef __cplusplus +} +#endif diff --git a/libobs/obs-nix-wayland.c b/libobs/obs-nix-wayland.c new file mode 100644 index 00000000000000..b242017f4e2c3c --- /dev/null +++ b/libobs/obs-nix-wayland.c @@ -0,0 +1,88 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-internal.h" +#include "obs-nix-platform.h" +#include "obs-nix-wayland.h" + +#include + +void obs_nix_wayland_log_info(void) +{ + struct wl_display *display = obs_get_nix_platform_display(); + if (display == NULL) { + blog(LOG_INFO, "Unable to connect to Wayland server"); + return; + } + //TODO: query some information about the wayland server if possible + blog(LOG_INFO, "Connected to Wayland server"); +} + +static bool +obs_nix_wayland_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); + return true; +} + +static void +obs_nix_wayland_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); +} + +static bool +obs_nix_wayland_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key) +{ + UNUSED_PARAMETER(context); + UNUSED_PARAMETER(key); + return false; +} + +static void obs_nix_wayland_key_to_str(obs_key_t key, struct dstr *dstr) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(dstr); +} + +static obs_key_t obs_nix_wayland_key_from_virtual_key(int sym) +{ + UNUSED_PARAMETER(sym); + return OBS_KEY_NONE; +} + +static int obs_nix_wayland_key_to_virtual_key(obs_key_t key) +{ + UNUSED_PARAMETER(key); + return 0; +} + +static const struct obs_nix_hotkeys_vtable wayland_hotkeys_vtable = { + .init = obs_nix_wayland_hotkeys_platform_init, + .free = obs_nix_wayland_hotkeys_platform_free, + .is_pressed = obs_nix_wayland_hotkeys_platform_is_pressed, + .key_to_str = obs_nix_wayland_key_to_str, + .key_from_virtual_key = obs_nix_wayland_key_from_virtual_key, + .key_to_virtual_key = obs_nix_wayland_key_to_virtual_key, + +}; + +const struct obs_nix_hotkeys_vtable *obs_nix_wayland_get_hotkeys_vtable(void) +{ + return &wayland_hotkeys_vtable; +} diff --git a/libobs/obs-nix-wayland.h b/libobs/obs-nix-wayland.h new file mode 100644 index 00000000000000..d44720c54989da --- /dev/null +++ b/libobs/obs-nix-wayland.h @@ -0,0 +1,24 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "obs-nix.h" + +void obs_nix_wayland_log_info(void); + +const struct obs_nix_hotkeys_vtable *obs_nix_wayland_get_hotkeys_vtable(void); diff --git a/libobs/obs-nix-x11.c b/libobs/obs-nix-x11.c new file mode 100644 index 00000000000000..bb3bc0b7cfa8e7 --- /dev/null +++ b/libobs/obs-nix-x11.c @@ -0,0 +1,1271 @@ +/****************************************************************************** + Copyright (C) 2013 by Hugh Bailey + Copyright (C) 2014 by Zachary Lund + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-internal.h" +#include "obs-nix-platform.h" +#include "obs-nix-x11.h" + +#include +#if USE_XINPUT +#include +#endif +#include +#include +#include +#include +#include + +void obs_nix_x11_log_info(void) +{ + Display *dpy = obs_get_nix_platform_display(); + if (!dpy) { + blog(LOG_INFO, "Unable to open X display"); + return; + } + + int protocol_version = ProtocolVersion(dpy); + int protocol_revision = ProtocolRevision(dpy); + int vendor_release = VendorRelease(dpy); + const char *vendor_name = ServerVendor(dpy); + + if (strstr(vendor_name, "X.Org")) { + blog(LOG_INFO, + "Window System: X%d.%d, Vendor: %s, Version: %d" + ".%d.%d", + protocol_version, protocol_revision, vendor_name, + vendor_release / 10000000, (vendor_release / 100000) % 100, + (vendor_release / 1000) % 100); + } else { + blog(LOG_INFO, + "Window System: X%d.%d - vendor string: %s - " + "vendor release: %d", + protocol_version, protocol_revision, vendor_name, + vendor_release); + } +} + +/* So here's how linux works with key mapping: + * + * First, there's a global key symbol enum (xcb_keysym_t) which has unique + * values for all possible symbols keys can have (e.g., '1' and '!' are + * different values). + * + * Then there's a key code (xcb_keycode_t), which is basically an index to the + * actual key itself on the keyboard (e.g., '1' and '!' will share the same + * value). + * + * xcb_keysym_t values should be given to libobs, and libobs will translate it + * to an obs_key_t, and although xcb_keysym_t can differ ('!' vs '1'), it will + * get the obs_key_t value that represents the actual key pressed; in other + * words it will be based on the key code rather than the key symbol. The same + * applies to checking key press states. + */ + +struct keycode_list { + DARRAY(xcb_keycode_t) list; +}; + +struct obs_hotkeys_platform { + Display *display; + xcb_keysym_t base_keysyms[OBS_KEY_LAST_VALUE]; + struct keycode_list keycodes[OBS_KEY_LAST_VALUE]; + xcb_keycode_t min_keycode; + xcb_keycode_t super_l_code; + xcb_keycode_t super_r_code; + + /* stores a copy of the keysym map for keycodes */ + xcb_keysym_t *keysyms; + int num_keysyms; + int syms_per_code; + +#if USE_XINPUT + bool pressed[XINPUT_MOUSE_LEN]; + bool update[XINPUT_MOUSE_LEN]; + bool button_pressed[XINPUT_MOUSE_LEN]; +#endif +}; + +#define MOUSE_1 (1 << 16) +#define MOUSE_2 (2 << 16) +#define MOUSE_3 (3 << 16) +#define MOUSE_4 (4 << 16) +#define MOUSE_5 (5 << 16) + +static int get_keysym(obs_key_t key) +{ + switch (key) { + case OBS_KEY_RETURN: + return XK_Return; + case OBS_KEY_ESCAPE: + return XK_Escape; + case OBS_KEY_TAB: + return XK_Tab; + case OBS_KEY_BACKSPACE: + return XK_BackSpace; + case OBS_KEY_INSERT: + return XK_Insert; + case OBS_KEY_DELETE: + return XK_Delete; + case OBS_KEY_PAUSE: + return XK_Pause; + case OBS_KEY_PRINT: + return XK_Print; + case OBS_KEY_HOME: + return XK_Home; + case OBS_KEY_END: + return XK_End; + case OBS_KEY_LEFT: + return XK_Left; + case OBS_KEY_UP: + return XK_Up; + case OBS_KEY_RIGHT: + return XK_Right; + case OBS_KEY_DOWN: + return XK_Down; + case OBS_KEY_PAGEUP: + return XK_Prior; + case OBS_KEY_PAGEDOWN: + return XK_Next; + + case OBS_KEY_SHIFT: + return XK_Shift_L; + case OBS_KEY_CONTROL: + return XK_Control_L; + case OBS_KEY_ALT: + return XK_Alt_L; + case OBS_KEY_CAPSLOCK: + return XK_Caps_Lock; + case OBS_KEY_NUMLOCK: + return XK_Num_Lock; + case OBS_KEY_SCROLLLOCK: + return XK_Scroll_Lock; + + case OBS_KEY_F1: + return XK_F1; + case OBS_KEY_F2: + return XK_F2; + case OBS_KEY_F3: + return XK_F3; + case OBS_KEY_F4: + return XK_F4; + case OBS_KEY_F5: + return XK_F5; + case OBS_KEY_F6: + return XK_F6; + case OBS_KEY_F7: + return XK_F7; + case OBS_KEY_F8: + return XK_F8; + case OBS_KEY_F9: + return XK_F9; + case OBS_KEY_F10: + return XK_F10; + case OBS_KEY_F11: + return XK_F11; + case OBS_KEY_F12: + return XK_F12; + case OBS_KEY_F13: + return XK_F13; + case OBS_KEY_F14: + return XK_F14; + case OBS_KEY_F15: + return XK_F15; + case OBS_KEY_F16: + return XK_F16; + case OBS_KEY_F17: + return XK_F17; + case OBS_KEY_F18: + return XK_F18; + case OBS_KEY_F19: + return XK_F19; + case OBS_KEY_F20: + return XK_F20; + case OBS_KEY_F21: + return XK_F21; + case OBS_KEY_F22: + return XK_F22; + case OBS_KEY_F23: + return XK_F23; + case OBS_KEY_F24: + return XK_F24; + case OBS_KEY_F25: + return XK_F25; + case OBS_KEY_F26: + return XK_F26; + case OBS_KEY_F27: + return XK_F27; + case OBS_KEY_F28: + return XK_F28; + case OBS_KEY_F29: + return XK_F29; + case OBS_KEY_F30: + return XK_F30; + case OBS_KEY_F31: + return XK_F31; + case OBS_KEY_F32: + return XK_F32; + case OBS_KEY_F33: + return XK_F33; + case OBS_KEY_F34: + return XK_F34; + case OBS_KEY_F35: + return XK_F35; + + case OBS_KEY_MENU: + return XK_Menu; + case OBS_KEY_HYPER_L: + return XK_Hyper_L; + case OBS_KEY_HYPER_R: + return XK_Hyper_R; + case OBS_KEY_HELP: + return XK_Help; + case OBS_KEY_CANCEL: + return XK_Cancel; + case OBS_KEY_FIND: + return XK_Find; + case OBS_KEY_REDO: + return XK_Redo; + case OBS_KEY_UNDO: + return XK_Undo; + case OBS_KEY_SPACE: + return XK_space; + + case OBS_KEY_COPY: + return XF86XK_Copy; + case OBS_KEY_CUT: + return XF86XK_Cut; + case OBS_KEY_OPEN: + return XF86XK_Open; + case OBS_KEY_PASTE: + return XF86XK_Paste; + case OBS_KEY_FRONT: + return SunXK_Front; + case OBS_KEY_PROPS: + return SunXK_Props; + + case OBS_KEY_EXCLAM: + return XK_exclam; + case OBS_KEY_QUOTEDBL: + return XK_quotedbl; + case OBS_KEY_NUMBERSIGN: + return XK_numbersign; + case OBS_KEY_DOLLAR: + return XK_dollar; + case OBS_KEY_PERCENT: + return XK_percent; + case OBS_KEY_AMPERSAND: + return XK_ampersand; + case OBS_KEY_APOSTROPHE: + return XK_apostrophe; + case OBS_KEY_PARENLEFT: + return XK_parenleft; + case OBS_KEY_PARENRIGHT: + return XK_parenright; + case OBS_KEY_ASTERISK: + return XK_asterisk; + case OBS_KEY_PLUS: + return XK_plus; + case OBS_KEY_COMMA: + return XK_comma; + case OBS_KEY_MINUS: + return XK_minus; + case OBS_KEY_PERIOD: + return XK_period; + case OBS_KEY_SLASH: + return XK_slash; + case OBS_KEY_0: + return XK_0; + case OBS_KEY_1: + return XK_1; + case OBS_KEY_2: + return XK_2; + case OBS_KEY_3: + return XK_3; + case OBS_KEY_4: + return XK_4; + case OBS_KEY_5: + return XK_5; + case OBS_KEY_6: + return XK_6; + case OBS_KEY_7: + return XK_7; + case OBS_KEY_8: + return XK_8; + case OBS_KEY_9: + return XK_9; + case OBS_KEY_NUMEQUAL: + return XK_KP_Equal; + case OBS_KEY_NUMASTERISK: + return XK_KP_Multiply; + case OBS_KEY_NUMPLUS: + return XK_KP_Add; + case OBS_KEY_NUMCOMMA: + return XK_KP_Separator; + case OBS_KEY_NUMMINUS: + return XK_KP_Subtract; + case OBS_KEY_NUMPERIOD: + return XK_KP_Decimal; + case OBS_KEY_NUMSLASH: + return XK_KP_Divide; + case OBS_KEY_NUM0: + return XK_KP_0; + case OBS_KEY_NUM1: + return XK_KP_1; + case OBS_KEY_NUM2: + return XK_KP_2; + case OBS_KEY_NUM3: + return XK_KP_3; + case OBS_KEY_NUM4: + return XK_KP_4; + case OBS_KEY_NUM5: + return XK_KP_5; + case OBS_KEY_NUM6: + return XK_KP_6; + case OBS_KEY_NUM7: + return XK_KP_7; + case OBS_KEY_NUM8: + return XK_KP_8; + case OBS_KEY_NUM9: + return XK_KP_9; + case OBS_KEY_COLON: + return XK_colon; + case OBS_KEY_SEMICOLON: + return XK_semicolon; + case OBS_KEY_LESS: + return XK_less; + case OBS_KEY_EQUAL: + return XK_equal; + case OBS_KEY_GREATER: + return XK_greater; + case OBS_KEY_QUESTION: + return XK_question; + case OBS_KEY_AT: + return XK_at; + case OBS_KEY_A: + return XK_A; + case OBS_KEY_B: + return XK_B; + case OBS_KEY_C: + return XK_C; + case OBS_KEY_D: + return XK_D; + case OBS_KEY_E: + return XK_E; + case OBS_KEY_F: + return XK_F; + case OBS_KEY_G: + return XK_G; + case OBS_KEY_H: + return XK_H; + case OBS_KEY_I: + return XK_I; + case OBS_KEY_J: + return XK_J; + case OBS_KEY_K: + return XK_K; + case OBS_KEY_L: + return XK_L; + case OBS_KEY_M: + return XK_M; + case OBS_KEY_N: + return XK_N; + case OBS_KEY_O: + return XK_O; + case OBS_KEY_P: + return XK_P; + case OBS_KEY_Q: + return XK_Q; + case OBS_KEY_R: + return XK_R; + case OBS_KEY_S: + return XK_S; + case OBS_KEY_T: + return XK_T; + case OBS_KEY_U: + return XK_U; + case OBS_KEY_V: + return XK_V; + case OBS_KEY_W: + return XK_W; + case OBS_KEY_X: + return XK_X; + case OBS_KEY_Y: + return XK_Y; + case OBS_KEY_Z: + return XK_Z; + case OBS_KEY_BRACKETLEFT: + return XK_bracketleft; + case OBS_KEY_BACKSLASH: + return XK_backslash; + case OBS_KEY_BRACKETRIGHT: + return XK_bracketright; + case OBS_KEY_ASCIICIRCUM: + return XK_asciicircum; + case OBS_KEY_UNDERSCORE: + return XK_underscore; + case OBS_KEY_QUOTELEFT: + return XK_quoteleft; + case OBS_KEY_BRACELEFT: + return XK_braceleft; + case OBS_KEY_BAR: + return XK_bar; + case OBS_KEY_BRACERIGHT: + return XK_braceright; + case OBS_KEY_ASCIITILDE: + return XK_grave; + case OBS_KEY_NOBREAKSPACE: + return XK_nobreakspace; + case OBS_KEY_EXCLAMDOWN: + return XK_exclamdown; + case OBS_KEY_CENT: + return XK_cent; + case OBS_KEY_STERLING: + return XK_sterling; + case OBS_KEY_CURRENCY: + return XK_currency; + case OBS_KEY_YEN: + return XK_yen; + case OBS_KEY_BROKENBAR: + return XK_brokenbar; + case OBS_KEY_SECTION: + return XK_section; + case OBS_KEY_DIAERESIS: + return XK_diaeresis; + case OBS_KEY_COPYRIGHT: + return XK_copyright; + case OBS_KEY_ORDFEMININE: + return XK_ordfeminine; + case OBS_KEY_GUILLEMOTLEFT: + return XK_guillemotleft; + case OBS_KEY_NOTSIGN: + return XK_notsign; + case OBS_KEY_HYPHEN: + return XK_hyphen; + case OBS_KEY_REGISTERED: + return XK_registered; + case OBS_KEY_MACRON: + return XK_macron; + case OBS_KEY_DEGREE: + return XK_degree; + case OBS_KEY_PLUSMINUS: + return XK_plusminus; + case OBS_KEY_TWOSUPERIOR: + return XK_twosuperior; + case OBS_KEY_THREESUPERIOR: + return XK_threesuperior; + case OBS_KEY_ACUTE: + return XK_acute; + case OBS_KEY_MU: + return XK_mu; + case OBS_KEY_PARAGRAPH: + return XK_paragraph; + case OBS_KEY_PERIODCENTERED: + return XK_periodcentered; + case OBS_KEY_CEDILLA: + return XK_cedilla; + case OBS_KEY_ONESUPERIOR: + return XK_onesuperior; + case OBS_KEY_MASCULINE: + return XK_masculine; + case OBS_KEY_GUILLEMOTRIGHT: + return XK_guillemotright; + case OBS_KEY_ONEQUARTER: + return XK_onequarter; + case OBS_KEY_ONEHALF: + return XK_onehalf; + case OBS_KEY_THREEQUARTERS: + return XK_threequarters; + case OBS_KEY_QUESTIONDOWN: + return XK_questiondown; + case OBS_KEY_AGRAVE: + return XK_Agrave; + case OBS_KEY_AACUTE: + return XK_Aacute; + case OBS_KEY_ACIRCUMFLEX: + return XK_Acircumflex; + case OBS_KEY_ATILDE: + return XK_Atilde; + case OBS_KEY_ADIAERESIS: + return XK_Adiaeresis; + case OBS_KEY_ARING: + return XK_Aring; + case OBS_KEY_AE: + return XK_AE; + case OBS_KEY_CCEDILLA: + return XK_cedilla; + case OBS_KEY_EGRAVE: + return XK_Egrave; + case OBS_KEY_EACUTE: + return XK_Eacute; + case OBS_KEY_ECIRCUMFLEX: + return XK_Ecircumflex; + case OBS_KEY_EDIAERESIS: + return XK_Ediaeresis; + case OBS_KEY_IGRAVE: + return XK_Igrave; + case OBS_KEY_IACUTE: + return XK_Iacute; + case OBS_KEY_ICIRCUMFLEX: + return XK_Icircumflex; + case OBS_KEY_IDIAERESIS: + return XK_Idiaeresis; + case OBS_KEY_ETH: + return XK_ETH; + case OBS_KEY_NTILDE: + return XK_Ntilde; + case OBS_KEY_OGRAVE: + return XK_Ograve; + case OBS_KEY_OACUTE: + return XK_Oacute; + case OBS_KEY_OCIRCUMFLEX: + return XK_Ocircumflex; + case OBS_KEY_ODIAERESIS: + return XK_Odiaeresis; + case OBS_KEY_MULTIPLY: + return XK_multiply; + case OBS_KEY_OOBLIQUE: + return XK_Ooblique; + case OBS_KEY_UGRAVE: + return XK_Ugrave; + case OBS_KEY_UACUTE: + return XK_Uacute; + case OBS_KEY_UCIRCUMFLEX: + return XK_Ucircumflex; + case OBS_KEY_UDIAERESIS: + return XK_Udiaeresis; + case OBS_KEY_YACUTE: + return XK_Yacute; + case OBS_KEY_THORN: + return XK_Thorn; + case OBS_KEY_SSHARP: + return XK_ssharp; + case OBS_KEY_DIVISION: + return XK_division; + case OBS_KEY_YDIAERESIS: + return XK_Ydiaeresis; + case OBS_KEY_MULTI_KEY: + return XK_Multi_key; + case OBS_KEY_CODEINPUT: + return XK_Codeinput; + case OBS_KEY_SINGLECANDIDATE: + return XK_SingleCandidate; + case OBS_KEY_MULTIPLECANDIDATE: + return XK_MultipleCandidate; + case OBS_KEY_PREVIOUSCANDIDATE: + return XK_PreviousCandidate; + case OBS_KEY_MODE_SWITCH: + return XK_Mode_switch; + case OBS_KEY_KANJI: + return XK_Kanji; + case OBS_KEY_MUHENKAN: + return XK_Muhenkan; + case OBS_KEY_HENKAN: + return XK_Henkan; + case OBS_KEY_ROMAJI: + return XK_Romaji; + case OBS_KEY_HIRAGANA: + return XK_Hiragana; + case OBS_KEY_KATAKANA: + return XK_Katakana; + case OBS_KEY_HIRAGANA_KATAKANA: + return XK_Hiragana_Katakana; + case OBS_KEY_ZENKAKU: + return XK_Zenkaku; + case OBS_KEY_HANKAKU: + return XK_Hankaku; + case OBS_KEY_ZENKAKU_HANKAKU: + return XK_Zenkaku_Hankaku; + case OBS_KEY_TOUROKU: + return XK_Touroku; + case OBS_KEY_MASSYO: + return XK_Massyo; + case OBS_KEY_KANA_LOCK: + return XK_Kana_Lock; + case OBS_KEY_KANA_SHIFT: + return XK_Kana_Shift; + case OBS_KEY_EISU_SHIFT: + return XK_Eisu_Shift; + case OBS_KEY_EISU_TOGGLE: + return XK_Eisu_toggle; + case OBS_KEY_HANGUL: + return XK_Hangul; + case OBS_KEY_HANGUL_START: + return XK_Hangul_Start; + case OBS_KEY_HANGUL_END: + return XK_Hangul_End; + case OBS_KEY_HANGUL_HANJA: + return XK_Hangul_Hanja; + case OBS_KEY_HANGUL_JAMO: + return XK_Hangul_Jamo; + case OBS_KEY_HANGUL_ROMAJA: + return XK_Hangul_Romaja; + case OBS_KEY_HANGUL_BANJA: + return XK_Hangul_Banja; + case OBS_KEY_HANGUL_PREHANJA: + return XK_Hangul_PreHanja; + case OBS_KEY_HANGUL_POSTHANJA: + return XK_Hangul_PostHanja; + case OBS_KEY_HANGUL_SPECIAL: + return XK_Hangul_Special; + case OBS_KEY_DEAD_GRAVE: + return XK_dead_grave; + case OBS_KEY_DEAD_ACUTE: + return XK_dead_acute; + case OBS_KEY_DEAD_CIRCUMFLEX: + return XK_dead_circumflex; + case OBS_KEY_DEAD_TILDE: + return XK_dead_tilde; + case OBS_KEY_DEAD_MACRON: + return XK_dead_macron; + case OBS_KEY_DEAD_BREVE: + return XK_dead_breve; + case OBS_KEY_DEAD_ABOVEDOT: + return XK_dead_abovedot; + case OBS_KEY_DEAD_DIAERESIS: + return XK_dead_diaeresis; + case OBS_KEY_DEAD_ABOVERING: + return XK_dead_abovering; + case OBS_KEY_DEAD_DOUBLEACUTE: + return XK_dead_doubleacute; + case OBS_KEY_DEAD_CARON: + return XK_dead_caron; + case OBS_KEY_DEAD_CEDILLA: + return XK_dead_cedilla; + case OBS_KEY_DEAD_OGONEK: + return XK_dead_ogonek; + case OBS_KEY_DEAD_IOTA: + return XK_dead_iota; + case OBS_KEY_DEAD_VOICED_SOUND: + return XK_dead_voiced_sound; + case OBS_KEY_DEAD_SEMIVOICED_SOUND: + return XK_dead_semivoiced_sound; + case OBS_KEY_DEAD_BELOWDOT: + return XK_dead_belowdot; + case OBS_KEY_DEAD_HOOK: + return XK_dead_hook; + case OBS_KEY_DEAD_HORN: + return XK_dead_horn; + + case OBS_KEY_MOUSE1: + return MOUSE_1; + case OBS_KEY_MOUSE2: + return MOUSE_2; + case OBS_KEY_MOUSE3: + return MOUSE_3; + case OBS_KEY_MOUSE4: + return MOUSE_4; + case OBS_KEY_MOUSE5: + return MOUSE_5; + + /* TODO: Implement keys for non-US keyboards */ + default:; + } + return 0; +} + +static inline void fill_base_keysyms(struct obs_core_hotkeys *hotkeys) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + hotkeys->platform_context->base_keysyms[i] = get_keysym(i); +} + +static obs_key_t key_from_base_keysym(obs_hotkeys_platform_t *context, + xcb_keysym_t code) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + if (context->base_keysyms[i] == (xcb_keysym_t)code) { + return (obs_key_t)i; + } + } + + return OBS_KEY_NONE; +} + +static inline void add_key(obs_hotkeys_platform_t *context, obs_key_t key, + int code) +{ + xcb_keycode_t kc = (xcb_keycode_t)code; + da_push_back(context->keycodes[key].list, &kc); + + if (context->keycodes[key].list.num > 1) { + blog(LOG_DEBUG, + "found alternate keycode %d for %s " + "which already has keycode %d", + code, obs_key_to_name(key), + (int)context->keycodes[key].list.array[0]); + } +} + +static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + xcb_connection_t *connection = XGetXCBConnection(context->display); + const struct xcb_setup_t *setup = xcb_get_setup(connection); + xcb_get_keyboard_mapping_cookie_t cookie; + xcb_get_keyboard_mapping_reply_t *reply; + xcb_generic_error_t *error = NULL; + int code; + + int mincode = setup->min_keycode; + int maxcode = setup->max_keycode; + + context->min_keycode = setup->min_keycode; + + cookie = xcb_get_keyboard_mapping(connection, mincode, + maxcode - mincode + 1); + + reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); + + if (error || !reply) { + blog(LOG_WARNING, "xcb_get_keyboard_mapping_reply failed"); + goto error1; + } + + const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); + int syms_per_code = (int)reply->keysyms_per_keycode; + + context->num_keysyms = (maxcode - mincode + 1) * syms_per_code; + context->syms_per_code = syms_per_code; + context->keysyms = + bmemdup(keysyms, sizeof(xcb_keysym_t) * context->num_keysyms); + + for (code = mincode; code <= maxcode; code++) { + const xcb_keysym_t *sym; + obs_key_t key; + + sym = &keysyms[(code - mincode) * syms_per_code]; + + for (int i = 0; i < syms_per_code; i++) { + if (!sym[i]) + break; + + if (sym[i] == XK_Super_L) { + context->super_l_code = code; + break; + } else if (sym[i] == XK_Super_R) { + context->super_r_code = code; + break; + } else { + key = key_from_base_keysym(context, sym[i]); + + if (key != OBS_KEY_NONE) { + add_key(context, key, code); + break; + } + } + } + } + +error1: + free(reply); + free(error); + + return error != NULL || reply == NULL; +} + +static xcb_screen_t *default_screen(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + int def_screen_idx = XDefaultScreen(context->display); + xcb_screen_iterator_t iter; + + iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); + while (iter.rem) { + if (def_screen_idx-- == 0) + return iter.data; + + xcb_screen_next(&iter); + } + + return NULL; +} + +static inline xcb_window_t root_window(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + xcb_screen_t *screen = default_screen(context, connection); + if (screen) + return screen->root; + return 0; +} + +#if USE_XINPUT +static inline void registerMouseEvents(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + xcb_connection_t *connection = XGetXCBConnection(context->display); + xcb_window_t window = root_window(context, connection); + + struct { + xcb_input_event_mask_t head; + xcb_input_xi_event_mask_t mask; + } mask; + mask.head.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; + mask.head.mask_len = sizeof(mask.mask) / sizeof(uint32_t); + mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | + XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE; + + xcb_input_xi_select_events(connection, window, 1, &mask.head); + xcb_flush(connection); +} +#endif + +static bool obs_nix_x11_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + Display *display = obs_get_nix_platform_display(); + if (!display) + return false; + + hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); + hotkeys->platform_context->display = display; + +#if USE_XINPUT + registerMouseEvents(hotkeys); +#endif + fill_base_keysyms(hotkeys); + fill_keycodes(hotkeys); + return true; +} + +static void obs_nix_x11_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + da_free(context->keycodes[i].list); + + bfree(context->keysyms); + bfree(context); + + hotkeys->platform_context = NULL; +} + +static bool mouse_button_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + bool ret = false; + +#if USE_XINPUT + memset(context->pressed, 0, XINPUT_MOUSE_LEN); + memset(context->update, 0, XINPUT_MOUSE_LEN); + + xcb_generic_event_t *ev; + while ((ev = xcb_poll_for_event(connection))) { + if ((ev->response_type & ~80) == XCB_GE_GENERIC) { + switch (((xcb_ge_event_t *)ev)->event_type) { + case XCB_INPUT_RAW_BUTTON_PRESS: { + xcb_input_raw_button_press_event_t *mot; + mot = (xcb_input_raw_button_press_event_t *)ev; + if (mot->detail < XINPUT_MOUSE_LEN) { + context->pressed[mot->detail - 1] = + true; + context->update[mot->detail - 1] = true; + } else { + blog(LOG_WARNING, "Unsupported button"); + } + break; + } + case XCB_INPUT_RAW_BUTTON_RELEASE: { + xcb_input_raw_button_release_event_t *mot; + mot = (xcb_input_raw_button_release_event_t *)ev; + if (mot->detail < XINPUT_MOUSE_LEN) + context->update[mot->detail - 1] = true; + else + blog(LOG_WARNING, "Unsupported button"); + break; + } + default: + break; + } + } + free(ev); + } + + // Mouse 2 for OBS is Right Click and Mouse 3 is Wheel Click. + // Mouse Wheel axis clicks (xinput mot->detail 4 5 6 7) are ignored. + switch (key) { + case OBS_KEY_MOUSE1: + ret = context->pressed[0] || context->button_pressed[0]; + break; + case OBS_KEY_MOUSE2: + ret = context->pressed[2] || context->button_pressed[2]; + break; + case OBS_KEY_MOUSE3: + ret = context->pressed[1] || context->button_pressed[1]; + break; + case OBS_KEY_MOUSE4: + ret = context->pressed[7] || context->button_pressed[7]; + break; + case OBS_KEY_MOUSE5: + ret = context->pressed[8] || context->button_pressed[8]; + break; + case OBS_KEY_MOUSE6: + ret = context->pressed[9] || context->button_pressed[9]; + break; + case OBS_KEY_MOUSE7: + ret = context->pressed[10] || context->button_pressed[10]; + break; + case OBS_KEY_MOUSE8: + ret = context->pressed[11] || context->button_pressed[11]; + break; + case OBS_KEY_MOUSE9: + ret = context->pressed[12] || context->button_pressed[12]; + break; + case OBS_KEY_MOUSE10: + ret = context->pressed[13] || context->button_pressed[13]; + break; + case OBS_KEY_MOUSE11: + ret = context->pressed[14] || context->button_pressed[14]; + break; + case OBS_KEY_MOUSE12: + ret = context->pressed[15] || context->button_pressed[15]; + break; + case OBS_KEY_MOUSE13: + ret = context->pressed[16] || context->button_pressed[16]; + break; + case OBS_KEY_MOUSE14: + ret = context->pressed[17] || context->button_pressed[17]; + break; + case OBS_KEY_MOUSE15: + ret = context->pressed[18] || context->button_pressed[18]; + break; + case OBS_KEY_MOUSE16: + ret = context->pressed[19] || context->button_pressed[19]; + break; + case OBS_KEY_MOUSE17: + ret = context->pressed[20] || context->button_pressed[20]; + break; + case OBS_KEY_MOUSE18: + ret = context->pressed[21] || context->button_pressed[21]; + break; + case OBS_KEY_MOUSE19: + ret = context->pressed[22] || context->button_pressed[22]; + break; + case OBS_KEY_MOUSE20: + ret = context->pressed[23] || context->button_pressed[23]; + break; + case OBS_KEY_MOUSE21: + ret = context->pressed[24] || context->button_pressed[24]; + break; + case OBS_KEY_MOUSE22: + ret = context->pressed[25] || context->button_pressed[25]; + break; + case OBS_KEY_MOUSE23: + ret = context->pressed[26] || context->button_pressed[26]; + break; + case OBS_KEY_MOUSE24: + ret = context->pressed[27] || context->button_pressed[27]; + break; + case OBS_KEY_MOUSE25: + ret = context->pressed[28] || context->button_pressed[28]; + break; + case OBS_KEY_MOUSE26: + ret = context->pressed[29] || context->button_pressed[29]; + break; + case OBS_KEY_MOUSE27: + ret = context->pressed[30] || context->button_pressed[30]; + break; + case OBS_KEY_MOUSE28: + ret = context->pressed[31] || context->button_pressed[31]; + break; + case OBS_KEY_MOUSE29: + ret = context->pressed[32] || context->button_pressed[32]; + break; + default: + break; + } + + for (int i = 0; i != XINPUT_MOUSE_LEN; i++) + if (context->update[i]) + context->button_pressed[i] = context->pressed[i]; +#else + xcb_generic_error_t *error = NULL; + xcb_query_pointer_cookie_t qpc; + xcb_query_pointer_reply_t *reply; + + qpc = xcb_query_pointer(connection, root_window(context, connection)); + reply = xcb_query_pointer_reply(connection, qpc, &error); + + if (error) { + blog(LOG_WARNING, "xcb_query_pointer_reply failed"); + } else { + uint16_t buttons = reply->mask; + + switch (key) { + case OBS_KEY_MOUSE1: + ret = buttons & XCB_BUTTON_MASK_1; + break; + case OBS_KEY_MOUSE2: + ret = buttons & XCB_BUTTON_MASK_3; + break; + case OBS_KEY_MOUSE3: + ret = buttons & XCB_BUTTON_MASK_2; + break; + default:; + } + } + + free(reply); + free(error); +#endif + return ret; +} + +static inline bool keycode_pressed(xcb_query_keymap_reply_t *reply, + xcb_keycode_t code) +{ + return (reply->keys[code / 8] & (1 << (code % 8))) != 0; +} + +static bool key_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + struct keycode_list *codes = &context->keycodes[key]; + xcb_generic_error_t *error = NULL; + xcb_query_keymap_reply_t *reply; + bool pressed = false; + + reply = xcb_query_keymap_reply(connection, xcb_query_keymap(connection), + &error); + if (error) { + blog(LOG_WARNING, "xcb_query_keymap failed"); + + } else if (key == OBS_KEY_META) { + pressed = keycode_pressed(reply, context->super_l_code) || + keycode_pressed(reply, context->super_r_code); + + } else { + for (size_t i = 0; i < codes->list.num; i++) { + if (keycode_pressed(reply, codes->list.array[i])) { + pressed = true; + break; + } + } + } + + free(reply); + free(error); + return pressed; +} + +static bool +obs_nix_x11_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key) +{ + xcb_connection_t *conn = XGetXCBConnection(context->display); + + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + return mouse_button_pressed(conn, context, key); + } else { + return key_pressed(conn, context, key); + } +} + +static bool get_key_translation(struct dstr *dstr, xcb_keycode_t keycode) +{ + xcb_connection_t *connection; + char name[128]; + + connection = XGetXCBConnection(obs->hotkeys.platform_context->display); + + XKeyEvent event = {0}; + event.type = KeyPress; + event.display = obs->hotkeys.platform_context->display; + event.keycode = keycode; + event.root = root_window(obs->hotkeys.platform_context, connection); + event.window = event.root; + + if (keycode) { + int len = XLookupString(&event, name, 128, NULL, NULL); + if (len) { + dstr_ncopy(dstr, name, len); + dstr_to_upper(dstr); + return true; + } + } + + return false; +} + +static void obs_nix_x11_key_to_str(obs_key_t key, struct dstr *dstr) +{ + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Mouse %d", + (int)(key - OBS_KEY_MOUSE1 + 1)); + } + return; + } + + if (key >= OBS_KEY_NUM0 && key <= OBS_KEY_NUM9) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Numpad %d", + (int)(key - OBS_KEY_NUM0)); + } + return; + } + +#define translate_key(key, def) \ + dstr_copy(dstr, obs_get_hotkey_translation(key, def)) + + switch (key) { + case OBS_KEY_INSERT: + return translate_key(key, "Insert"); + case OBS_KEY_DELETE: + return translate_key(key, "Delete"); + case OBS_KEY_HOME: + return translate_key(key, "Home"); + case OBS_KEY_END: + return translate_key(key, "End"); + case OBS_KEY_PAGEUP: + return translate_key(key, "Page Up"); + case OBS_KEY_PAGEDOWN: + return translate_key(key, "Page Down"); + case OBS_KEY_NUMLOCK: + return translate_key(key, "Num Lock"); + case OBS_KEY_SCROLLLOCK: + return translate_key(key, "Scroll Lock"); + case OBS_KEY_CAPSLOCK: + return translate_key(key, "Caps Lock"); + case OBS_KEY_BACKSPACE: + return translate_key(key, "Backspace"); + case OBS_KEY_TAB: + return translate_key(key, "Tab"); + case OBS_KEY_PRINT: + return translate_key(key, "Print"); + case OBS_KEY_PAUSE: + return translate_key(key, "Pause"); + case OBS_KEY_LEFT: + return translate_key(key, "Left"); + case OBS_KEY_RIGHT: + return translate_key(key, "Right"); + case OBS_KEY_UP: + return translate_key(key, "Up"); + case OBS_KEY_DOWN: + return translate_key(key, "Down"); + case OBS_KEY_SHIFT: + return translate_key(key, "Shift"); + case OBS_KEY_ALT: + return translate_key(key, "Alt"); + case OBS_KEY_CONTROL: + return translate_key(key, "Control"); + case OBS_KEY_META: + return translate_key(key, "Super"); + case OBS_KEY_MENU: + return translate_key(key, "Menu"); + case OBS_KEY_NUMASTERISK: + return translate_key(key, "Numpad *"); + case OBS_KEY_NUMPLUS: + return translate_key(key, "Numpad +"); + case OBS_KEY_NUMCOMMA: + return translate_key(key, "Numpad ,"); + case OBS_KEY_NUMPERIOD: + return translate_key(key, "Numpad ."); + case OBS_KEY_NUMSLASH: + return translate_key(key, "Numpad /"); + case OBS_KEY_SPACE: + return translate_key(key, "Space"); + case OBS_KEY_ESCAPE: + return translate_key(key, "Escape"); + default:; + } + + if (key >= OBS_KEY_F1 && key <= OBS_KEY_F35) { + dstr_printf(dstr, "F%d", (int)(key - OBS_KEY_F1 + 1)); + return; + } + + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + struct keycode_list *keycodes = &context->keycodes[key]; + + for (size_t i = 0; i < keycodes->list.num; i++) { + if (get_key_translation(dstr, keycodes->list.array[i])) { + break; + } + } + + if (key != OBS_KEY_NONE && dstr_is_empty(dstr)) { + dstr_copy(dstr, obs_key_to_name(key)); + } +} + +static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context, + xcb_keycode_t code) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + struct keycode_list *codes = &context->keycodes[i]; + + for (size_t j = 0; j < codes->list.num; j++) { + if (codes->list.array[j] == code) { + return (obs_key_t)i; + } + } + } + + return OBS_KEY_NONE; +} + +static obs_key_t obs_nix_x11_key_from_virtual_key(int sym) +{ + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + const xcb_keysym_t *keysyms = context->keysyms; + int syms_per_code = context->syms_per_code; + int num_keysyms = context->num_keysyms; + + if (sym == 0) + return OBS_KEY_NONE; + + for (int i = 0; i < num_keysyms; i++) { + if (keysyms[i] == (xcb_keysym_t)sym) { + xcb_keycode_t code = (xcb_keycode_t)(i / syms_per_code); + code += context->min_keycode; + obs_key_t key = key_from_keycode(context, code); + + return key; + } + } + + return OBS_KEY_NONE; +} + +static int obs_nix_x11_key_to_virtual_key(obs_key_t key) +{ + if (key == OBS_KEY_META) + return XK_Super_L; + + return (int)obs->hotkeys.platform_context->base_keysyms[(int)key]; +} + +static const struct obs_nix_hotkeys_vtable x11_hotkeys_vtable = { + .init = obs_nix_x11_hotkeys_platform_init, + .free = obs_nix_x11_hotkeys_platform_free, + .is_pressed = obs_nix_x11_hotkeys_platform_is_pressed, + .key_to_str = obs_nix_x11_key_to_str, + .key_from_virtual_key = obs_nix_x11_key_from_virtual_key, + .key_to_virtual_key = obs_nix_x11_key_to_virtual_key, +}; + +const struct obs_nix_hotkeys_vtable *obs_nix_x11_get_hotkeys_vtable(void) +{ + return &x11_hotkeys_vtable; +} diff --git a/libobs/obs-nix-x11.h b/libobs/obs-nix-x11.h new file mode 100644 index 00000000000000..34b9dd0f57d1cd --- /dev/null +++ b/libobs/obs-nix-x11.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "obs-nix.h" + +void obs_nix_x11_log_info(void); + +const struct obs_nix_hotkeys_vtable *obs_nix_x11_get_hotkeys_vtable(void); diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 382fa054604b36..7354a556add87e 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -17,6 +17,14 @@ ******************************************************************************/ #include "obs-internal.h" +#include "obs-nix.h" +#include "obs-nix-platform.h" +#include "obs-nix-x11.h" + +#ifdef ENABLE_WAYLAND +#include "obs-nix-wayland.h" +#endif + #if defined(__FreeBSD__) #define _GNU_SOURCE #endif @@ -30,15 +38,6 @@ #include #endif #include -#include -#if USE_XINPUT -#include -#endif -#include -#include -#include -#include -#include #include const char *get_module_extension(void) @@ -64,6 +63,8 @@ static const char *module_data[] = { static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); +static const struct obs_nix_hotkeys_vtable *hotkeys_vtable = NULL; + void add_default_module_paths(void) { for (int i = 0; i < module_patterns_size; i++) @@ -256,37 +257,6 @@ static void log_kernel_version(void) blog(LOG_INFO, "Kernel Version: %s %s", info.sysname, info.release); } -static void log_x_info(void) -{ - Display *dpy = XOpenDisplay(NULL); - if (!dpy) { - blog(LOG_INFO, "Unable to open X display"); - return; - } - - int protocol_version = ProtocolVersion(dpy); - int protocol_revision = ProtocolRevision(dpy); - int vendor_release = VendorRelease(dpy); - const char *vendor_name = ServerVendor(dpy); - - if (strstr(vendor_name, "X.Org")) { - blog(LOG_INFO, - "Window System: X%d.%d, Vendor: %s, Version: %d" - ".%d.%d", - protocol_version, protocol_revision, vendor_name, - vendor_release / 10000000, (vendor_release / 100000) % 100, - (vendor_release / 1000) % 100); - } else { - blog(LOG_INFO, - "Window System: X%d.%d - vendor string: %s - " - "vendor release: %d", - protocol_version, protocol_revision, vendor_name, - vendor_release); - } - - XCloseDisplay(dpy); -} - #if defined(__linux__) static void log_distribution_info(void) { @@ -352,1203 +322,60 @@ void log_system_info(void) log_distribution_info(); log_desktop_session_info(); #endif - log_x_info(); -} - -/* So here's how linux works with key mapping: - * - * First, there's a global key symbol enum (xcb_keysym_t) which has unique - * values for all possible symbols keys can have (e.g., '1' and '!' are - * different values). - * - * Then there's a key code (xcb_keycode_t), which is basically an index to the - * actual key itself on the keyboard (e.g., '1' and '!' will share the same - * value). - * - * xcb_keysym_t values should be given to libobs, and libobs will translate it - * to an obs_key_t, and although xcb_keysym_t can differ ('!' vs '1'), it will - * get the obs_key_t value that represents the actual key pressed; in other - * words it will be based on the key code rather than the key symbol. The same - * applies to checking key press states. - */ - -struct keycode_list { - DARRAY(xcb_keycode_t) list; -}; - -struct obs_hotkeys_platform { - Display *display; - xcb_keysym_t base_keysyms[OBS_KEY_LAST_VALUE]; - struct keycode_list keycodes[OBS_KEY_LAST_VALUE]; - xcb_keycode_t min_keycode; - xcb_keycode_t super_l_code; - xcb_keycode_t super_r_code; - - /* stores a copy of the keysym map for keycodes */ - xcb_keysym_t *keysyms; - int num_keysyms; - int syms_per_code; - -#if USE_XINPUT - bool pressed[XINPUT_MOUSE_LEN]; - bool update[XINPUT_MOUSE_LEN]; - bool button_pressed[XINPUT_MOUSE_LEN]; + switch (obs_get_nix_platform()) { + case OBS_NIX_PLATFORM_X11_GLX: + case OBS_NIX_PLATFORM_X11_EGL: + obs_nix_x11_log_info(); + break; +#ifdef ENABLE_WAYLAND + case OBS_NIX_PLATFORM_WAYLAND: + break; #endif -}; - -#define MOUSE_1 (1 << 16) -#define MOUSE_2 (2 << 16) -#define MOUSE_3 (3 << 16) -#define MOUSE_4 (4 << 16) -#define MOUSE_5 (5 << 16) - -static int get_keysym(obs_key_t key) -{ - switch (key) { - case OBS_KEY_RETURN: - return XK_Return; - case OBS_KEY_ESCAPE: - return XK_Escape; - case OBS_KEY_TAB: - return XK_Tab; - case OBS_KEY_BACKSPACE: - return XK_BackSpace; - case OBS_KEY_INSERT: - return XK_Insert; - case OBS_KEY_DELETE: - return XK_Delete; - case OBS_KEY_PAUSE: - return XK_Pause; - case OBS_KEY_PRINT: - return XK_Print; - case OBS_KEY_HOME: - return XK_Home; - case OBS_KEY_END: - return XK_End; - case OBS_KEY_LEFT: - return XK_Left; - case OBS_KEY_UP: - return XK_Up; - case OBS_KEY_RIGHT: - return XK_Right; - case OBS_KEY_DOWN: - return XK_Down; - case OBS_KEY_PAGEUP: - return XK_Prior; - case OBS_KEY_PAGEDOWN: - return XK_Next; - - case OBS_KEY_SHIFT: - return XK_Shift_L; - case OBS_KEY_CONTROL: - return XK_Control_L; - case OBS_KEY_ALT: - return XK_Alt_L; - case OBS_KEY_CAPSLOCK: - return XK_Caps_Lock; - case OBS_KEY_NUMLOCK: - return XK_Num_Lock; - case OBS_KEY_SCROLLLOCK: - return XK_Scroll_Lock; - - case OBS_KEY_F1: - return XK_F1; - case OBS_KEY_F2: - return XK_F2; - case OBS_KEY_F3: - return XK_F3; - case OBS_KEY_F4: - return XK_F4; - case OBS_KEY_F5: - return XK_F5; - case OBS_KEY_F6: - return XK_F6; - case OBS_KEY_F7: - return XK_F7; - case OBS_KEY_F8: - return XK_F8; - case OBS_KEY_F9: - return XK_F9; - case OBS_KEY_F10: - return XK_F10; - case OBS_KEY_F11: - return XK_F11; - case OBS_KEY_F12: - return XK_F12; - case OBS_KEY_F13: - return XK_F13; - case OBS_KEY_F14: - return XK_F14; - case OBS_KEY_F15: - return XK_F15; - case OBS_KEY_F16: - return XK_F16; - case OBS_KEY_F17: - return XK_F17; - case OBS_KEY_F18: - return XK_F18; - case OBS_KEY_F19: - return XK_F19; - case OBS_KEY_F20: - return XK_F20; - case OBS_KEY_F21: - return XK_F21; - case OBS_KEY_F22: - return XK_F22; - case OBS_KEY_F23: - return XK_F23; - case OBS_KEY_F24: - return XK_F24; - case OBS_KEY_F25: - return XK_F25; - case OBS_KEY_F26: - return XK_F26; - case OBS_KEY_F27: - return XK_F27; - case OBS_KEY_F28: - return XK_F28; - case OBS_KEY_F29: - return XK_F29; - case OBS_KEY_F30: - return XK_F30; - case OBS_KEY_F31: - return XK_F31; - case OBS_KEY_F32: - return XK_F32; - case OBS_KEY_F33: - return XK_F33; - case OBS_KEY_F34: - return XK_F34; - case OBS_KEY_F35: - return XK_F35; - - case OBS_KEY_MENU: - return XK_Menu; - case OBS_KEY_HYPER_L: - return XK_Hyper_L; - case OBS_KEY_HYPER_R: - return XK_Hyper_R; - case OBS_KEY_HELP: - return XK_Help; - case OBS_KEY_CANCEL: - return XK_Cancel; - case OBS_KEY_FIND: - return XK_Find; - case OBS_KEY_REDO: - return XK_Redo; - case OBS_KEY_UNDO: - return XK_Undo; - case OBS_KEY_SPACE: - return XK_space; - - case OBS_KEY_COPY: - return XF86XK_Copy; - case OBS_KEY_CUT: - return XF86XK_Cut; - case OBS_KEY_OPEN: - return XF86XK_Open; - case OBS_KEY_PASTE: - return XF86XK_Paste; - case OBS_KEY_FRONT: - return SunXK_Front; - case OBS_KEY_PROPS: - return SunXK_Props; - - case OBS_KEY_EXCLAM: - return XK_exclam; - case OBS_KEY_QUOTEDBL: - return XK_quotedbl; - case OBS_KEY_NUMBERSIGN: - return XK_numbersign; - case OBS_KEY_DOLLAR: - return XK_dollar; - case OBS_KEY_PERCENT: - return XK_percent; - case OBS_KEY_AMPERSAND: - return XK_ampersand; - case OBS_KEY_APOSTROPHE: - return XK_apostrophe; - case OBS_KEY_PARENLEFT: - return XK_parenleft; - case OBS_KEY_PARENRIGHT: - return XK_parenright; - case OBS_KEY_ASTERISK: - return XK_asterisk; - case OBS_KEY_PLUS: - return XK_plus; - case OBS_KEY_COMMA: - return XK_comma; - case OBS_KEY_MINUS: - return XK_minus; - case OBS_KEY_PERIOD: - return XK_period; - case OBS_KEY_SLASH: - return XK_slash; - case OBS_KEY_0: - return XK_0; - case OBS_KEY_1: - return XK_1; - case OBS_KEY_2: - return XK_2; - case OBS_KEY_3: - return XK_3; - case OBS_KEY_4: - return XK_4; - case OBS_KEY_5: - return XK_5; - case OBS_KEY_6: - return XK_6; - case OBS_KEY_7: - return XK_7; - case OBS_KEY_8: - return XK_8; - case OBS_KEY_9: - return XK_9; - case OBS_KEY_NUMEQUAL: - return XK_KP_Equal; - case OBS_KEY_NUMASTERISK: - return XK_KP_Multiply; - case OBS_KEY_NUMPLUS: - return XK_KP_Add; - case OBS_KEY_NUMCOMMA: - return XK_KP_Separator; - case OBS_KEY_NUMMINUS: - return XK_KP_Subtract; - case OBS_KEY_NUMPERIOD: - return XK_KP_Decimal; - case OBS_KEY_NUMSLASH: - return XK_KP_Divide; - case OBS_KEY_NUM0: - return XK_KP_0; - case OBS_KEY_NUM1: - return XK_KP_1; - case OBS_KEY_NUM2: - return XK_KP_2; - case OBS_KEY_NUM3: - return XK_KP_3; - case OBS_KEY_NUM4: - return XK_KP_4; - case OBS_KEY_NUM5: - return XK_KP_5; - case OBS_KEY_NUM6: - return XK_KP_6; - case OBS_KEY_NUM7: - return XK_KP_7; - case OBS_KEY_NUM8: - return XK_KP_8; - case OBS_KEY_NUM9: - return XK_KP_9; - case OBS_KEY_COLON: - return XK_colon; - case OBS_KEY_SEMICOLON: - return XK_semicolon; - case OBS_KEY_LESS: - return XK_less; - case OBS_KEY_EQUAL: - return XK_equal; - case OBS_KEY_GREATER: - return XK_greater; - case OBS_KEY_QUESTION: - return XK_question; - case OBS_KEY_AT: - return XK_at; - case OBS_KEY_A: - return XK_A; - case OBS_KEY_B: - return XK_B; - case OBS_KEY_C: - return XK_C; - case OBS_KEY_D: - return XK_D; - case OBS_KEY_E: - return XK_E; - case OBS_KEY_F: - return XK_F; - case OBS_KEY_G: - return XK_G; - case OBS_KEY_H: - return XK_H; - case OBS_KEY_I: - return XK_I; - case OBS_KEY_J: - return XK_J; - case OBS_KEY_K: - return XK_K; - case OBS_KEY_L: - return XK_L; - case OBS_KEY_M: - return XK_M; - case OBS_KEY_N: - return XK_N; - case OBS_KEY_O: - return XK_O; - case OBS_KEY_P: - return XK_P; - case OBS_KEY_Q: - return XK_Q; - case OBS_KEY_R: - return XK_R; - case OBS_KEY_S: - return XK_S; - case OBS_KEY_T: - return XK_T; - case OBS_KEY_U: - return XK_U; - case OBS_KEY_V: - return XK_V; - case OBS_KEY_W: - return XK_W; - case OBS_KEY_X: - return XK_X; - case OBS_KEY_Y: - return XK_Y; - case OBS_KEY_Z: - return XK_Z; - case OBS_KEY_BRACKETLEFT: - return XK_bracketleft; - case OBS_KEY_BACKSLASH: - return XK_backslash; - case OBS_KEY_BRACKETRIGHT: - return XK_bracketright; - case OBS_KEY_ASCIICIRCUM: - return XK_asciicircum; - case OBS_KEY_UNDERSCORE: - return XK_underscore; - case OBS_KEY_QUOTELEFT: - return XK_quoteleft; - case OBS_KEY_BRACELEFT: - return XK_braceleft; - case OBS_KEY_BAR: - return XK_bar; - case OBS_KEY_BRACERIGHT: - return XK_braceright; - case OBS_KEY_ASCIITILDE: - return XK_grave; - case OBS_KEY_NOBREAKSPACE: - return XK_nobreakspace; - case OBS_KEY_EXCLAMDOWN: - return XK_exclamdown; - case OBS_KEY_CENT: - return XK_cent; - case OBS_KEY_STERLING: - return XK_sterling; - case OBS_KEY_CURRENCY: - return XK_currency; - case OBS_KEY_YEN: - return XK_yen; - case OBS_KEY_BROKENBAR: - return XK_brokenbar; - case OBS_KEY_SECTION: - return XK_section; - case OBS_KEY_DIAERESIS: - return XK_diaeresis; - case OBS_KEY_COPYRIGHT: - return XK_copyright; - case OBS_KEY_ORDFEMININE: - return XK_ordfeminine; - case OBS_KEY_GUILLEMOTLEFT: - return XK_guillemotleft; - case OBS_KEY_NOTSIGN: - return XK_notsign; - case OBS_KEY_HYPHEN: - return XK_hyphen; - case OBS_KEY_REGISTERED: - return XK_registered; - case OBS_KEY_MACRON: - return XK_macron; - case OBS_KEY_DEGREE: - return XK_degree; - case OBS_KEY_PLUSMINUS: - return XK_plusminus; - case OBS_KEY_TWOSUPERIOR: - return XK_twosuperior; - case OBS_KEY_THREESUPERIOR: - return XK_threesuperior; - case OBS_KEY_ACUTE: - return XK_acute; - case OBS_KEY_MU: - return XK_mu; - case OBS_KEY_PARAGRAPH: - return XK_paragraph; - case OBS_KEY_PERIODCENTERED: - return XK_periodcentered; - case OBS_KEY_CEDILLA: - return XK_cedilla; - case OBS_KEY_ONESUPERIOR: - return XK_onesuperior; - case OBS_KEY_MASCULINE: - return XK_masculine; - case OBS_KEY_GUILLEMOTRIGHT: - return XK_guillemotright; - case OBS_KEY_ONEQUARTER: - return XK_onequarter; - case OBS_KEY_ONEHALF: - return XK_onehalf; - case OBS_KEY_THREEQUARTERS: - return XK_threequarters; - case OBS_KEY_QUESTIONDOWN: - return XK_questiondown; - case OBS_KEY_AGRAVE: - return XK_Agrave; - case OBS_KEY_AACUTE: - return XK_Aacute; - case OBS_KEY_ACIRCUMFLEX: - return XK_Acircumflex; - case OBS_KEY_ATILDE: - return XK_Atilde; - case OBS_KEY_ADIAERESIS: - return XK_Adiaeresis; - case OBS_KEY_ARING: - return XK_Aring; - case OBS_KEY_AE: - return XK_AE; - case OBS_KEY_CCEDILLA: - return XK_cedilla; - case OBS_KEY_EGRAVE: - return XK_Egrave; - case OBS_KEY_EACUTE: - return XK_Eacute; - case OBS_KEY_ECIRCUMFLEX: - return XK_Ecircumflex; - case OBS_KEY_EDIAERESIS: - return XK_Ediaeresis; - case OBS_KEY_IGRAVE: - return XK_Igrave; - case OBS_KEY_IACUTE: - return XK_Iacute; - case OBS_KEY_ICIRCUMFLEX: - return XK_Icircumflex; - case OBS_KEY_IDIAERESIS: - return XK_Idiaeresis; - case OBS_KEY_ETH: - return XK_ETH; - case OBS_KEY_NTILDE: - return XK_Ntilde; - case OBS_KEY_OGRAVE: - return XK_Ograve; - case OBS_KEY_OACUTE: - return XK_Oacute; - case OBS_KEY_OCIRCUMFLEX: - return XK_Ocircumflex; - case OBS_KEY_ODIAERESIS: - return XK_Odiaeresis; - case OBS_KEY_MULTIPLY: - return XK_multiply; - case OBS_KEY_OOBLIQUE: - return XK_Ooblique; - case OBS_KEY_UGRAVE: - return XK_Ugrave; - case OBS_KEY_UACUTE: - return XK_Uacute; - case OBS_KEY_UCIRCUMFLEX: - return XK_Ucircumflex; - case OBS_KEY_UDIAERESIS: - return XK_Udiaeresis; - case OBS_KEY_YACUTE: - return XK_Yacute; - case OBS_KEY_THORN: - return XK_Thorn; - case OBS_KEY_SSHARP: - return XK_ssharp; - case OBS_KEY_DIVISION: - return XK_division; - case OBS_KEY_YDIAERESIS: - return XK_Ydiaeresis; - case OBS_KEY_MULTI_KEY: - return XK_Multi_key; - case OBS_KEY_CODEINPUT: - return XK_Codeinput; - case OBS_KEY_SINGLECANDIDATE: - return XK_SingleCandidate; - case OBS_KEY_MULTIPLECANDIDATE: - return XK_MultipleCandidate; - case OBS_KEY_PREVIOUSCANDIDATE: - return XK_PreviousCandidate; - case OBS_KEY_MODE_SWITCH: - return XK_Mode_switch; - case OBS_KEY_KANJI: - return XK_Kanji; - case OBS_KEY_MUHENKAN: - return XK_Muhenkan; - case OBS_KEY_HENKAN: - return XK_Henkan; - case OBS_KEY_ROMAJI: - return XK_Romaji; - case OBS_KEY_HIRAGANA: - return XK_Hiragana; - case OBS_KEY_KATAKANA: - return XK_Katakana; - case OBS_KEY_HIRAGANA_KATAKANA: - return XK_Hiragana_Katakana; - case OBS_KEY_ZENKAKU: - return XK_Zenkaku; - case OBS_KEY_HANKAKU: - return XK_Hankaku; - case OBS_KEY_ZENKAKU_HANKAKU: - return XK_Zenkaku_Hankaku; - case OBS_KEY_TOUROKU: - return XK_Touroku; - case OBS_KEY_MASSYO: - return XK_Massyo; - case OBS_KEY_KANA_LOCK: - return XK_Kana_Lock; - case OBS_KEY_KANA_SHIFT: - return XK_Kana_Shift; - case OBS_KEY_EISU_SHIFT: - return XK_Eisu_Shift; - case OBS_KEY_EISU_TOGGLE: - return XK_Eisu_toggle; - case OBS_KEY_HANGUL: - return XK_Hangul; - case OBS_KEY_HANGUL_START: - return XK_Hangul_Start; - case OBS_KEY_HANGUL_END: - return XK_Hangul_End; - case OBS_KEY_HANGUL_HANJA: - return XK_Hangul_Hanja; - case OBS_KEY_HANGUL_JAMO: - return XK_Hangul_Jamo; - case OBS_KEY_HANGUL_ROMAJA: - return XK_Hangul_Romaja; - case OBS_KEY_HANGUL_BANJA: - return XK_Hangul_Banja; - case OBS_KEY_HANGUL_PREHANJA: - return XK_Hangul_PreHanja; - case OBS_KEY_HANGUL_POSTHANJA: - return XK_Hangul_PostHanja; - case OBS_KEY_HANGUL_SPECIAL: - return XK_Hangul_Special; - case OBS_KEY_DEAD_GRAVE: - return XK_dead_grave; - case OBS_KEY_DEAD_ACUTE: - return XK_dead_acute; - case OBS_KEY_DEAD_CIRCUMFLEX: - return XK_dead_circumflex; - case OBS_KEY_DEAD_TILDE: - return XK_dead_tilde; - case OBS_KEY_DEAD_MACRON: - return XK_dead_macron; - case OBS_KEY_DEAD_BREVE: - return XK_dead_breve; - case OBS_KEY_DEAD_ABOVEDOT: - return XK_dead_abovedot; - case OBS_KEY_DEAD_DIAERESIS: - return XK_dead_diaeresis; - case OBS_KEY_DEAD_ABOVERING: - return XK_dead_abovering; - case OBS_KEY_DEAD_DOUBLEACUTE: - return XK_dead_doubleacute; - case OBS_KEY_DEAD_CARON: - return XK_dead_caron; - case OBS_KEY_DEAD_CEDILLA: - return XK_dead_cedilla; - case OBS_KEY_DEAD_OGONEK: - return XK_dead_ogonek; - case OBS_KEY_DEAD_IOTA: - return XK_dead_iota; - case OBS_KEY_DEAD_VOICED_SOUND: - return XK_dead_voiced_sound; - case OBS_KEY_DEAD_SEMIVOICED_SOUND: - return XK_dead_semivoiced_sound; - case OBS_KEY_DEAD_BELOWDOT: - return XK_dead_belowdot; - case OBS_KEY_DEAD_HOOK: - return XK_dead_hook; - case OBS_KEY_DEAD_HORN: - return XK_dead_horn; - - case OBS_KEY_MOUSE1: - return MOUSE_1; - case OBS_KEY_MOUSE2: - return MOUSE_2; - case OBS_KEY_MOUSE3: - return MOUSE_3; - case OBS_KEY_MOUSE4: - return MOUSE_4; - case OBS_KEY_MOUSE5: - return MOUSE_5; - - /* TODO: Implement keys for non-US keyboards */ - default:; - } - return 0; -} - -static inline void fill_base_keysyms(struct obs_core_hotkeys *hotkeys) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) - hotkeys->platform_context->base_keysyms[i] = get_keysym(i); -} - -static obs_key_t key_from_base_keysym(obs_hotkeys_platform_t *context, - xcb_keysym_t code) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { - if (context->base_keysyms[i] == (xcb_keysym_t)code) { - return (obs_key_t)i; - } - } - - return OBS_KEY_NONE; -} - -static inline void add_key(obs_hotkeys_platform_t *context, obs_key_t key, - int code) -{ - xcb_keycode_t kc = (xcb_keycode_t)code; - da_push_back(context->keycodes[key].list, &kc); - - if (context->keycodes[key].list.num > 1) { - blog(LOG_DEBUG, - "found alternate keycode %d for %s " - "which already has keycode %d", - code, obs_key_to_name(key), - (int)context->keycodes[key].list.array[0]); - } -} - -static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) -{ - obs_hotkeys_platform_t *context = hotkeys->platform_context; - xcb_connection_t *connection = XGetXCBConnection(context->display); - const struct xcb_setup_t *setup = xcb_get_setup(connection); - xcb_get_keyboard_mapping_cookie_t cookie; - xcb_get_keyboard_mapping_reply_t *reply; - xcb_generic_error_t *error = NULL; - int code; - - int mincode = setup->min_keycode; - int maxcode = setup->max_keycode; - - context->min_keycode = setup->min_keycode; - - cookie = xcb_get_keyboard_mapping(connection, mincode, - maxcode - mincode + 1); - - reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); - - if (error || !reply) { - blog(LOG_WARNING, "xcb_get_keyboard_mapping_reply failed"); - goto error1; } - - const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); - int syms_per_code = (int)reply->keysyms_per_keycode; - - context->num_keysyms = (maxcode - mincode + 1) * syms_per_code; - context->syms_per_code = syms_per_code; - context->keysyms = - bmemdup(keysyms, sizeof(xcb_keysym_t) * context->num_keysyms); - - for (code = mincode; code <= maxcode; code++) { - const xcb_keysym_t *sym; - obs_key_t key; - - sym = &keysyms[(code - mincode) * syms_per_code]; - - for (int i = 0; i < syms_per_code; i++) { - if (!sym[i]) - break; - - if (sym[i] == XK_Super_L) { - context->super_l_code = code; - break; - } else if (sym[i] == XK_Super_R) { - context->super_r_code = code; - break; - } else { - key = key_from_base_keysym(context, sym[i]); - - if (key != OBS_KEY_NONE) { - add_key(context, key, code); - break; - } - } - } - } - -error1: - free(reply); - free(error); - - return error != NULL || reply == NULL; } -static xcb_screen_t *default_screen(obs_hotkeys_platform_t *context, - xcb_connection_t *connection) -{ - int def_screen_idx = XDefaultScreen(context->display); - xcb_screen_iterator_t iter; - - iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); - while (iter.rem) { - if (def_screen_idx-- == 0) - return iter.data; - - xcb_screen_next(&iter); - } - - return NULL; -} - -static inline xcb_window_t root_window(obs_hotkeys_platform_t *context, - xcb_connection_t *connection) -{ - xcb_screen_t *screen = default_screen(context, connection); - if (screen) - return screen->root; - return 0; -} - -#if USE_XINPUT -static inline void registerMouseEvents(struct obs_core_hotkeys *hotkeys) -{ - obs_hotkeys_platform_t *context = hotkeys->platform_context; - xcb_connection_t *connection = XGetXCBConnection(context->display); - xcb_window_t window = root_window(context, connection); - - struct { - xcb_input_event_mask_t head; - xcb_input_xi_event_mask_t mask; - } mask; - mask.head.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; - mask.head.mask_len = sizeof(mask.mask) / sizeof(uint32_t); - mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | - XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE; - - xcb_input_xi_select_events(connection, window, 1, &mask.head); - xcb_flush(connection); -} -#endif - bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - Display *display = XOpenDisplay(NULL); - if (!display) - return false; - - hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); - hotkeys->platform_context->display = display; - -#if USE_XINPUT - registerMouseEvents(hotkeys); -#endif - fill_base_keysyms(hotkeys); - fill_keycodes(hotkeys); - return true; -} - -void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) -{ - obs_hotkeys_platform_t *context = hotkeys->platform_context; - - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) - da_free(context->keycodes[i].list); - - XCloseDisplay(context->display); - bfree(context->keysyms); - bfree(context); - - hotkeys->platform_context = NULL; -} - -static bool mouse_button_pressed(xcb_connection_t *connection, - obs_hotkeys_platform_t *context, obs_key_t key) -{ - bool ret = false; - -#if USE_XINPUT - memset(context->pressed, 0, XINPUT_MOUSE_LEN); - memset(context->update, 0, XINPUT_MOUSE_LEN); - - xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_event(connection))) { - if ((ev->response_type & ~80) == XCB_GE_GENERIC) { - switch (((xcb_ge_event_t *)ev)->event_type) { - case XCB_INPUT_RAW_BUTTON_PRESS: { - xcb_input_raw_button_press_event_t *mot; - mot = (xcb_input_raw_button_press_event_t *)ev; - if (mot->detail < XINPUT_MOUSE_LEN) { - context->pressed[mot->detail - 1] = - true; - context->update[mot->detail - 1] = true; - } else { - blog(LOG_WARNING, "Unsupported button"); - } - break; - } - case XCB_INPUT_RAW_BUTTON_RELEASE: { - xcb_input_raw_button_release_event_t *mot; - mot = (xcb_input_raw_button_release_event_t *)ev; - if (mot->detail < XINPUT_MOUSE_LEN) - context->update[mot->detail - 1] = true; - else - blog(LOG_WARNING, "Unsupported button"); - break; - } - default: - break; - } - } - free(ev); - } - - // Mouse 2 for OBS is Right Click and Mouse 3 is Wheel Click. - // Mouse Wheel axis clicks (xinput mot->detail 4 5 6 7) are ignored. - switch (key) { - case OBS_KEY_MOUSE1: - ret = context->pressed[0] || context->button_pressed[0]; - break; - case OBS_KEY_MOUSE2: - ret = context->pressed[2] || context->button_pressed[2]; - break; - case OBS_KEY_MOUSE3: - ret = context->pressed[1] || context->button_pressed[1]; - break; - case OBS_KEY_MOUSE4: - ret = context->pressed[7] || context->button_pressed[7]; - break; - case OBS_KEY_MOUSE5: - ret = context->pressed[8] || context->button_pressed[8]; - break; - case OBS_KEY_MOUSE6: - ret = context->pressed[9] || context->button_pressed[9]; - break; - case OBS_KEY_MOUSE7: - ret = context->pressed[10] || context->button_pressed[10]; - break; - case OBS_KEY_MOUSE8: - ret = context->pressed[11] || context->button_pressed[11]; - break; - case OBS_KEY_MOUSE9: - ret = context->pressed[12] || context->button_pressed[12]; - break; - case OBS_KEY_MOUSE10: - ret = context->pressed[13] || context->button_pressed[13]; - break; - case OBS_KEY_MOUSE11: - ret = context->pressed[14] || context->button_pressed[14]; - break; - case OBS_KEY_MOUSE12: - ret = context->pressed[15] || context->button_pressed[15]; - break; - case OBS_KEY_MOUSE13: - ret = context->pressed[16] || context->button_pressed[16]; - break; - case OBS_KEY_MOUSE14: - ret = context->pressed[17] || context->button_pressed[17]; - break; - case OBS_KEY_MOUSE15: - ret = context->pressed[18] || context->button_pressed[18]; - break; - case OBS_KEY_MOUSE16: - ret = context->pressed[19] || context->button_pressed[19]; - break; - case OBS_KEY_MOUSE17: - ret = context->pressed[20] || context->button_pressed[20]; - break; - case OBS_KEY_MOUSE18: - ret = context->pressed[21] || context->button_pressed[21]; - break; - case OBS_KEY_MOUSE19: - ret = context->pressed[22] || context->button_pressed[22]; - break; - case OBS_KEY_MOUSE20: - ret = context->pressed[23] || context->button_pressed[23]; - break; - case OBS_KEY_MOUSE21: - ret = context->pressed[24] || context->button_pressed[24]; + switch (obs_get_nix_platform()) { + case OBS_NIX_PLATFORM_X11_GLX: + case OBS_NIX_PLATFORM_X11_EGL: + hotkeys_vtable = obs_nix_x11_get_hotkeys_vtable(); break; - case OBS_KEY_MOUSE22: - ret = context->pressed[25] || context->button_pressed[25]; +#ifdef ENABLE_WAYLAND + case OBS_NIX_PLATFORM_WAYLAND: + hotkeys_vtable = obs_nix_wayland_get_hotkeys_vtable(); break; - case OBS_KEY_MOUSE23: - ret = context->pressed[26] || context->button_pressed[26]; - break; - case OBS_KEY_MOUSE24: - ret = context->pressed[27] || context->button_pressed[27]; - break; - case OBS_KEY_MOUSE25: - ret = context->pressed[28] || context->button_pressed[28]; - break; - case OBS_KEY_MOUSE26: - ret = context->pressed[29] || context->button_pressed[29]; - break; - case OBS_KEY_MOUSE27: - ret = context->pressed[30] || context->button_pressed[30]; - break; - case OBS_KEY_MOUSE28: - ret = context->pressed[31] || context->button_pressed[31]; - break; - case OBS_KEY_MOUSE29: - ret = context->pressed[32] || context->button_pressed[32]; - break; - default: - break; - } - - for (int i = 0; i != XINPUT_MOUSE_LEN; i++) - if (context->update[i]) - context->button_pressed[i] = context->pressed[i]; -#else - xcb_generic_error_t *error = NULL; - xcb_query_pointer_cookie_t qpc; - xcb_query_pointer_reply_t *reply; - - qpc = xcb_query_pointer(connection, root_window(context, connection)); - reply = xcb_query_pointer_reply(connection, qpc, &error); - - if (error) { - blog(LOG_WARNING, "xcb_query_pointer_reply failed"); - } else { - uint16_t buttons = reply->mask; - - switch (key) { - case OBS_KEY_MOUSE1: - ret = buttons & XCB_BUTTON_MASK_1; - break; - case OBS_KEY_MOUSE2: - ret = buttons & XCB_BUTTON_MASK_3; - break; - case OBS_KEY_MOUSE3: - ret = buttons & XCB_BUTTON_MASK_2; - break; - default:; - } - } - - free(reply); - free(error); #endif - return ret; -} + } -static inline bool keycode_pressed(xcb_query_keymap_reply_t *reply, - xcb_keycode_t code) -{ - return (reply->keys[code / 8] & (1 << (code % 8))) != 0; + return hotkeys_vtable->init(hotkeys); } -static bool key_pressed(xcb_connection_t *connection, - obs_hotkeys_platform_t *context, obs_key_t key) +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - struct keycode_list *codes = &context->keycodes[key]; - xcb_generic_error_t *error = NULL; - xcb_query_keymap_reply_t *reply; - bool pressed = false; - - reply = xcb_query_keymap_reply(connection, xcb_query_keymap(connection), - &error); - if (error) { - blog(LOG_WARNING, "xcb_query_keymap failed"); - - } else if (key == OBS_KEY_META) { - pressed = keycode_pressed(reply, context->super_l_code) || - keycode_pressed(reply, context->super_r_code); - - } else { - for (size_t i = 0; i < codes->list.num; i++) { - if (keycode_pressed(reply, codes->list.array[i])) { - pressed = true; - break; - } - } - } - - free(reply); - free(error); - return pressed; + hotkeys_vtable->free(hotkeys); + hotkeys_vtable = NULL; } bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, obs_key_t key) { - xcb_connection_t *conn = XGetXCBConnection(context->display); - - if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { - return mouse_button_pressed(conn, context, key); - } else { - return key_pressed(conn, context, key); - } -} - -static bool get_key_translation(struct dstr *dstr, xcb_keycode_t keycode) -{ - xcb_connection_t *connection; - char name[128]; - - connection = XGetXCBConnection(obs->hotkeys.platform_context->display); - - XKeyEvent event = {0}; - event.type = KeyPress; - event.display = obs->hotkeys.platform_context->display; - event.keycode = keycode; - event.root = root_window(obs->hotkeys.platform_context, connection); - event.window = event.root; - - if (keycode) { - int len = XLookupString(&event, name, 128, NULL, NULL); - if (len) { - dstr_ncopy(dstr, name, len); - dstr_to_upper(dstr); - return true; - } - } - - return false; + return hotkeys_vtable->is_pressed(context, key); } void obs_key_to_str(obs_key_t key, struct dstr *dstr) { - if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { - if (obs->hotkeys.translations[key]) { - dstr_copy(dstr, obs->hotkeys.translations[key]); - } else { - dstr_printf(dstr, "Mouse %d", - (int)(key - OBS_KEY_MOUSE1 + 1)); - } - return; - } - - if (key >= OBS_KEY_NUM0 && key <= OBS_KEY_NUM9) { - if (obs->hotkeys.translations[key]) { - dstr_copy(dstr, obs->hotkeys.translations[key]); - } else { - dstr_printf(dstr, "Numpad %d", - (int)(key - OBS_KEY_NUM0)); - } - return; - } - -#define translate_key(key, def) \ - dstr_copy(dstr, obs_get_hotkey_translation(key, def)) - - switch (key) { - case OBS_KEY_INSERT: - return translate_key(key, "Insert"); - case OBS_KEY_DELETE: - return translate_key(key, "Delete"); - case OBS_KEY_HOME: - return translate_key(key, "Home"); - case OBS_KEY_END: - return translate_key(key, "End"); - case OBS_KEY_PAGEUP: - return translate_key(key, "Page Up"); - case OBS_KEY_PAGEDOWN: - return translate_key(key, "Page Down"); - case OBS_KEY_NUMLOCK: - return translate_key(key, "Num Lock"); - case OBS_KEY_SCROLLLOCK: - return translate_key(key, "Scroll Lock"); - case OBS_KEY_CAPSLOCK: - return translate_key(key, "Caps Lock"); - case OBS_KEY_BACKSPACE: - return translate_key(key, "Backspace"); - case OBS_KEY_TAB: - return translate_key(key, "Tab"); - case OBS_KEY_PRINT: - return translate_key(key, "Print"); - case OBS_KEY_PAUSE: - return translate_key(key, "Pause"); - case OBS_KEY_LEFT: - return translate_key(key, "Left"); - case OBS_KEY_RIGHT: - return translate_key(key, "Right"); - case OBS_KEY_UP: - return translate_key(key, "Up"); - case OBS_KEY_DOWN: - return translate_key(key, "Down"); - case OBS_KEY_SHIFT: - return translate_key(key, "Shift"); - case OBS_KEY_ALT: - return translate_key(key, "Alt"); - case OBS_KEY_CONTROL: - return translate_key(key, "Control"); - case OBS_KEY_META: - return translate_key(key, "Super"); - case OBS_KEY_MENU: - return translate_key(key, "Menu"); - case OBS_KEY_NUMASTERISK: - return translate_key(key, "Numpad *"); - case OBS_KEY_NUMPLUS: - return translate_key(key, "Numpad +"); - case OBS_KEY_NUMCOMMA: - return translate_key(key, "Numpad ,"); - case OBS_KEY_NUMPERIOD: - return translate_key(key, "Numpad ."); - case OBS_KEY_NUMSLASH: - return translate_key(key, "Numpad /"); - case OBS_KEY_SPACE: - return translate_key(key, "Space"); - case OBS_KEY_ESCAPE: - return translate_key(key, "Escape"); - default:; - } - - if (key >= OBS_KEY_F1 && key <= OBS_KEY_F35) { - dstr_printf(dstr, "F%d", (int)(key - OBS_KEY_F1 + 1)); - return; - } - - obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; - struct keycode_list *keycodes = &context->keycodes[key]; - - for (size_t i = 0; i < keycodes->list.num; i++) { - if (get_key_translation(dstr, keycodes->list.array[i])) { - break; - } - } - - if (key != OBS_KEY_NONE && dstr_is_empty(dstr)) { - dstr_copy(dstr, obs_key_to_name(key)); - } -} - -static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context, - xcb_keycode_t code) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { - struct keycode_list *codes = &context->keycodes[i]; - - for (size_t j = 0; j < codes->list.num; j++) { - if (codes->list.array[j] == code) { - return (obs_key_t)i; - } - } - } - - return OBS_KEY_NONE; + return hotkeys_vtable->key_to_str(key, dstr); } obs_key_t obs_key_from_virtual_key(int sym) { - obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; - const xcb_keysym_t *keysyms = context->keysyms; - int syms_per_code = context->syms_per_code; - int num_keysyms = context->num_keysyms; - - if (sym == 0) - return OBS_KEY_NONE; - - for (int i = 0; i < num_keysyms; i++) { - if (keysyms[i] == (xcb_keysym_t)sym) { - xcb_keycode_t code = (xcb_keycode_t)(i / syms_per_code); - code += context->min_keycode; - obs_key_t key = key_from_keycode(context, code); - - return key; - } - } - - return OBS_KEY_NONE; + return hotkeys_vtable->key_from_virtual_key(sym); } int obs_key_to_virtual_key(obs_key_t key) { - if (key == OBS_KEY_META) - return XK_Super_L; - - return (int)obs->hotkeys.platform_context->base_keysyms[(int)key]; + return hotkeys_vtable->key_to_virtual_key(key); } static inline void add_combo_key(obs_key_t key, struct dstr *str) diff --git a/libobs/obs-nix.h b/libobs/obs-nix.h new file mode 100644 index 00000000000000..46eb9f3d3d16a1 --- /dev/null +++ b/libobs/obs-nix.h @@ -0,0 +1,42 @@ +/****************************************************************************** + Copyright (C) 2020 by Georges Basile Stavracas Neto + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "obs-internal.h" + +struct obs_nix_hotkeys_vtable { + bool (*init)(struct obs_core_hotkeys *hotkeys); + + void (*free)(struct obs_core_hotkeys *hotkeys); + + bool (*is_pressed)(obs_hotkeys_platform_t *context, obs_key_t key); + + void (*key_to_str)(obs_key_t key, struct dstr *dstr); + + obs_key_t (*key_from_virtual_key)(int sym); + + int (*key_to_virtual_key)(obs_key_t key); +}; + +#ifdef __cplusplus +} +#endif diff --git a/libobs/obsconfig.h.in b/libobs/obsconfig.h.in index 70f6da716cc3c7..5997b1b08860e5 100644 --- a/libobs/obsconfig.h.in +++ b/libobs/obsconfig.h.in @@ -22,6 +22,8 @@ #define LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE 7 #define LIBOBS_IMAGEMAGICK_DIR_STYLE @LIBOBS_IMAGEMAGICK_DIR_STYLE@ +#cmakedefine ENABLE_WAYLAND + /* NOTE: Release candidate version numbers internally are always the previous * main release number! For example, if the current public release is 21.0 and * the build is 22.0 release candidate 1, internally the build number (defined diff --git a/plugins/linux-capture/linux-capture.c b/plugins/linux-capture/linux-capture.c index ce49ee72ea224c..56ff485c4bf308 100644 --- a/plugins/linux-capture/linux-capture.c +++ b/plugins/linux-capture/linux-capture.c @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include +#include OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("linux-xshm", "en-US") @@ -30,6 +31,11 @@ extern void xcomposite_unload(void); bool obs_module_load(void) { + if (obs_get_nix_platform() != OBS_NIX_PLATFORM_X11_GLX) { + blog(LOG_ERROR, "linux-capture cannot run on EGL platforms"); + return false; + } + obs_register_source(&xshm_input); xcomposite_load(); return true;