Browse Source

Merge pull request #2484 from GeorgesStavracas/feaneron/egl-wayland

EGL/Wayland (Wayland, pt 3)
Jim 4 years ago
parent
commit
190ab876cc

+ 1 - 0
.github/workflows/main.yml

@@ -419,6 +419,7 @@ jobs:
            pkg-config \
            python3-dev \
            qtbase5-dev \
+           qtbase5-private-dev \
            libqt5svg5-dev \
            swig \
            libcmocka-dev \

+ 1 - 0
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 \

+ 2 - 0
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()

+ 10 - 0
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

+ 33 - 0
UI/obs-app.cpp

@@ -58,6 +58,16 @@
 #include <pthread.h>
 #endif
 
+#if !defined(_WIN32) && !defined(__APPLE__)
+#include <obs-nix-platform.h>
+#include <QX11Info>
+
+#ifdef ENABLE_WAYLAND
+#include <qpa/qplatformnativeinterface.h>
+#endif
+
+#endif
+
 #include <iostream>
 
 #include "ui-config.h"
@@ -1384,6 +1394,29 @@ bool OBSApp::OBSInit()
 
 	qRegisterMetaType<VoidFunc>();
 
+#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;
 

+ 77 - 6
UI/qt-display.cpp

@@ -6,6 +6,62 @@
 #include <QResizeEvent>
 #include <QShowEvent>
 
+#include <obs-config.h>
+
+#ifdef ENABLE_WAYLAND
+#include <obs-nix-platform.h>
+
+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<QPlatformSurfaceEvent *>(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);
 

+ 2 - 2
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 @@ signals:
 public:
 	OBSQTDisplay(QWidget *parent = nullptr,
 		     Qt::WindowFlags flags = Qt::WindowFlags());
+	~OBSQTDisplay() { display = nullptr; }
 
 	virtual QPaintEngine *paintEngine() const override;
 
@@ -35,4 +34,5 @@ public:
 	QColor GetDisplayBackgroundColor() const;
 	void SetDisplayBackgroundColor(const QColor &color);
 	void UpdateDisplayBackgroundColor();
+	void CreateDisplay(bool force = false);
 };

+ 27 - 5
UI/qt-wrappers.cpp

@@ -30,9 +30,14 @@
 #include <QStandardItemModel>
 
 #if !defined(_WIN32) && !defined(__APPLE__)
+#include <obs-nix-platform.h>
 #include <QX11Info>
 #endif
 
+#ifdef ENABLE_WAYLAND
+#include <qpa/qplatformnativeinterface.h>
+#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)

+ 2 - 1
UI/qt-wrappers.hpp

@@ -20,6 +20,7 @@
 #include <QApplication>
 #include <QMessageBox>
 #include <QWidget>
+#include <QWindow>
 #include <QThread>
 #include <obs.hpp>
 
@@ -56,7 +57,7 @@ public:
 
 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);
 

+ 18 - 1
UI/window-basic-main.cpp

@@ -79,6 +79,10 @@
 
 #include <json11.hpp>
 
+#ifdef ENABLE_WAYLAND
+#include <obs-nix-platform.h>
+#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

+ 53 - 0
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)

+ 78 - 0
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 <[email protected]>
+#               2020 Georges Basile Stavracas Neto <[email protected]>
+#
+# 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 ()

+ 10 - 3
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

+ 385 - 0
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 <KHR/khrplatform.h>
+#include <EGL/eglplatform.h>
+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

+ 48 - 0
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glad/glad_egl.h>
+
+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;
+}
+

+ 27 - 2
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

+ 125 - 0
libobs-opengl/gl-nix.c

@@ -0,0 +1,125 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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);
+}

+ 56 - 0
libobs-opengl/gl-nix.h

@@ -0,0 +1,56 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#include <obs.h>
+#include <obs-nix-platform.h>
+
+#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);
+};

+ 342 - 0
libobs-opengl/gl-wayland-egl.c

@@ -0,0 +1,342 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "gl-wayland-egl.h"
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+
+#include <glad/glad_egl.h>
+
+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;
+}

+ 22 - 0
libobs-opengl/gl-wayland-egl.h

@@ -0,0 +1,22 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#include "gl-nix.h"
+
+const struct gl_winsys_vtable *gl_wayland_egl_get_winsys_vtable(void);

+ 657 - 0
libobs-opengl/gl-x11-egl.c

@@ -0,0 +1,657 @@
+/******************************************************************************
+    Copyright (C) 2019 by Ivan Avdeev <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+/* 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 <X11/Xlib.h>
+#include <X11/Xlib-xcb.h>
+
+#include <xcb/xcb.h>
+
+#include <stdio.h>
+
+#include "gl-x11-egl.h"
+
+#include <glad/glad_egl.h>
+
+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;
+}

+ 22 - 0
libobs-opengl/gl-x11-egl.h

@@ -0,0 +1,22 @@
+/******************************************************************************
+    Copyright (C) 2019 by Ivan Avdeev <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#include "gl-nix.h"
+
+const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void);

+ 41 - 18
libobs-opengl/gl-x11.c → libobs-opengl/gl-x11-glx.c

@@ -36,7 +36,7 @@
 
 #include <stdio.h>
 
-#include "gl-subsystem.h"
+#include "gl-nix.h"
 
 #include <glad/glad_glx.h>
 
@@ -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 @@ success:
 	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 @@ success:
 	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;
+}

+ 22 - 0
libobs-opengl/gl-x11-glx.h

@@ -0,0 +1,22 @@
+/******************************************************************************
+    Copyright (C) 2014 by Zachary Lund <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#include "gl-nix.h"
+
+const struct gl_winsys_vtable *gl_x11_glx_get_winsys_vtable(void);

+ 19 - 1
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

+ 42 - 0
libobs/obs-nix-platform.c

@@ -0,0 +1,42 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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;
+}

+ 56 - 0
libobs/obs-nix-platform.h

@@ -0,0 +1,56 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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

+ 88 - 0
libobs/obs-nix-wayland.c

@@ -0,0 +1,88 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "obs-internal.h"
+#include "obs-nix-platform.h"
+#include "obs-nix-wayland.h"
+
+#include <wayland-client.h>
+
+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;
+}

+ 24 - 0
libobs/obs-nix-wayland.h

@@ -0,0 +1,24 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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);

+ 1271 - 0
libobs/obs-nix-x11.c

@@ -0,0 +1,1271 @@
+/******************************************************************************
+    Copyright (C) 2013 by Hugh Bailey <[email protected]>
+    Copyright (C) 2014 by Zachary Lund <[email protected]>
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "obs-internal.h"
+#include "obs-nix-platform.h"
+#include "obs-nix-x11.h"
+
+#include <xcb/xcb.h>
+#if USE_XINPUT
+#include <xcb/xinput.h>
+#endif
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xlib-xcb.h>
+#include <X11/XF86keysym.h>
+#include <X11/Sunkeysym.h>
+
+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;
+}

+ 22 - 0
libobs/obs-nix-x11.h

@@ -0,0 +1,22 @@
+/******************************************************************************
+    Copyright (C) 2019 by Jason Francis <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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);

+ 34 - 1207
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 <sys/sysinfo.h>
 #endif
 #include <sys/utsname.h>
-#include <xcb/xcb.h>
-#if USE_XINPUT
-#include <xcb/xinput.h>
-#endif
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xlib-xcb.h>
-#include <X11/XF86keysym.h>
-#include <X11/Sunkeysym.h>
 #include <inttypes.h>
 
 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)

+ 42 - 0
libobs/obs-nix.h

@@ -0,0 +1,42 @@
+/******************************************************************************
+    Copyright (C) 2020 by Georges Basile Stavracas Neto <[email protected]>
+
+    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 <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#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

+ 2 - 0
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

+ 6 - 0
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 <http://www.gnu.org/licenses/>.
 */
 #include <obs-module.h>
+#include <obs-nix-platform.h>
 
 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;