瀏覽代碼

linux-capture: Rewrite xcomposite

Generally moves all the plugin code into xcomposite-input.cpp and
removes all C++ dependencies.

Migrate as much as possible to xcb from Xlib to enable us to handle
errors and attribute them to the correct callers. This caused many other
knock on issues such as wrongly attributed errors and cleanup code
working incorrectly.

That allows us to use the xcursor-xcb implementation and delete the pure
Xlib implementation. We also add the missing functionality from the Xlib
implementation to the xcb implementation.

Capture glXCreatePixmap errors which occur most commonly on
nvidia+gnome due to nvidia's driver being unable to allocate more than 1
pixmap per window and gnome being the only compositor to read window
data via glx pixmaps.

Fix cleanup after failed glXCreatePixmap that might have leaked pixmaps
and prevented later captures on nvidia drivers for the same reason.
Kurt Kartaltepe 3 年之前
父節點
當前提交
1604400e48

+ 1 - 1
CI/linux/01_install_dependencies.sh

@@ -78,7 +78,7 @@ install_dependencies() {
         "obs-deps libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev \
          libswscale-dev libx264-dev libcurl4-openssl-dev libmbedtls-dev libgl1-mesa-dev libjansson-dev \
          libluajit-5.1-dev python3-dev libx11-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \
-         libxcomposite-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev swig libcmocka-dev \
+         libxcb-composite0-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev swig libcmocka-dev \
          libpci-dev libxss-dev libglvnd-dev libgles2-mesa libgles2-mesa-dev libwayland-dev libxkbcommon-dev"
         "qt-deps qtbase5-dev qtbase5-private-dev libqt5svg5-dev qtwayland5"
         "cef ${LINUX_CEF_BUILD_VERSION:-${CI_LINUX_CEF_VERSION}}"

+ 7 - 13
plugins/linux-capture/CMakeLists.txt

@@ -1,10 +1,10 @@
 project(linux-capture)
 
 find_package(X11 REQUIRED)
-if(NOT TARGET X11::Xcomposite)
-  obs_status(FATAL_ERROR "linux-capture - Xcomposite library not found.")
+find_package(XCB COMPONENTS XCB XFIXES RANDR SHM XINERAMA COMPOSITE)
+if(NOT TARGET XCB::COMPOSITE)
+  obs_status(FATAL_ERROR "xcb composite library not found")
 endif()
-find_package(XCB COMPONENTS XCB XFIXES RANDR SHM XINERAMA)
 
 add_library(linux-capture MODULE)
 add_library(OBS::capture ALIAS linux-capture)
@@ -12,31 +12,25 @@ add_library(OBS::capture ALIAS linux-capture)
 target_sources(
   linux-capture
   PRIVATE linux-capture.c
-          xcursor.c
-          xcursor.h
           xcursor-xcb.c
           xcursor-xcb.h
           xhelpers.c
           xhelpers.h
           xshm-input.c
-          xcomposite-main.cpp
-          xcompcap-main.cpp
-          xcompcap-main.hpp
-          xcompcap-helper.cpp
-          xcompcap-helper.hpp)
+          xcomposite-input.c
+          xcomposite-input.h)
 
 target_link_libraries(
   linux-capture
   PRIVATE OBS::libobs
           OBS::obsglad
           X11::X11
-          X11::Xfixes
-          X11::Xcomposite
           XCB::XCB
           XCB::XFIXES
           XCB::RANDR
           XCB::SHM
-          XCB::XINERAMA)
+          XCB::XINERAMA
+          XCB::COMPOSITE)
 
 set_target_properties(linux-capture PROPERTIES FOLDER "plugins")
 

+ 1 - 3
plugins/linux-capture/linux-capture.c

@@ -16,6 +16,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 #include <obs-module.h>
 #include <obs-nix-platform.h>
+#include "xcomposite-input.h"
 
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("linux-xshm", "en-US")
@@ -26,9 +27,6 @@ MODULE_EXPORT const char *obs_module_description(void)
 
 extern struct obs_source_info xshm_input;
 
-extern void xcomposite_load(void);
-extern void xcomposite_unload(void);
-
 bool obs_module_load(void)
 {
 	enum obs_nix_platform_type platform = obs_get_nix_platform();

+ 0 - 464
plugins/linux-capture/xcompcap-helper.cpp

@@ -1,464 +0,0 @@
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#include <X11/Xutil.h>
-#include <X11/extensions/Xcomposite.h>
-
-#include <unordered_set>
-#include <map>
-#include <pthread.h>
-
-#include <obs-module.h>
-#include <util/platform.h>
-
-#include "xcompcap-helper.hpp"
-
-namespace XCompcap {
-static Display *xdisplay = 0;
-
-Display *disp()
-{
-	if (!xdisplay)
-		xdisplay = XOpenDisplay(NULL);
-
-	return xdisplay;
-}
-
-void cleanupDisplay()
-{
-	if (!xdisplay)
-		return;
-
-	XCloseDisplay(xdisplay);
-	xdisplay = 0;
-}
-
-// Specification for checking for ewmh support at
-// http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472693600
-
-bool ewmhIsSupported()
-{
-	Display *display = disp();
-	Atom netSupportingWmCheck =
-		XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", true);
-	Atom actualType;
-	int format = 0;
-	unsigned long num = 0, bytes = 0;
-	unsigned char *data = NULL;
-	Window ewmh_window = 0;
-
-	int status = XGetWindowProperty(display, DefaultRootWindow(display),
-					netSupportingWmCheck, 0L, 1L, false,
-					XA_WINDOW, &actualType, &format, &num,
-					&bytes, &data);
-
-	if (status == Success) {
-		if (num > 0) {
-			ewmh_window = ((Window *)data)[0];
-		}
-		if (data) {
-			XFree(data);
-			data = NULL;
-		}
-	}
-
-	if (ewmh_window) {
-		status = XGetWindowProperty(display, ewmh_window,
-					    netSupportingWmCheck, 0L, 1L, false,
-					    XA_WINDOW, &actualType, &format,
-					    &num, &bytes, &data);
-		if (status != Success || num == 0 ||
-		    ewmh_window != ((Window *)data)[0]) {
-			ewmh_window = 0;
-		}
-		if (status == Success && data) {
-			XFree(data);
-		}
-	}
-
-	return ewmh_window != 0;
-}
-
-std::list<Window> getTopLevelWindows()
-{
-	std::list<Window> res;
-
-	if (!ewmhIsSupported()) {
-		blog(LOG_WARNING, "Unable to query window list "
-				  "because window manager "
-				  "does not support extended "
-				  "window manager Hints");
-		return res;
-	}
-
-	Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true);
-	Atom actualType;
-	int format;
-	unsigned long num, bytes;
-	Window *data = 0;
-
-	for (int i = 0; i < ScreenCount(disp()); ++i) {
-		Window rootWin = RootWindow(disp(), i);
-
-		int status = XGetWindowProperty(disp(), rootWin, netClList, 0L,
-						~0L, false, AnyPropertyType,
-						&actualType, &format, &num,
-						&bytes, (uint8_t **)&data);
-
-		if (status != Success) {
-			blog(LOG_WARNING, "Failed getting root "
-					  "window properties");
-			continue;
-		}
-
-		for (unsigned long i = 0; i < num; ++i)
-			res.push_back(data[i]);
-
-		XFree(data);
-	}
-
-	return res;
-}
-
-int getRootWindowScreen(Window root)
-{
-	XWindowAttributes attr;
-
-	if (!XGetWindowAttributes(disp(), root, &attr))
-		return DefaultScreen(disp());
-
-	return XScreenNumberOfScreen(attr.screen);
-}
-
-std::string getWindowAtom(Window win, const char *atom)
-{
-	Atom netWmName = XInternAtom(disp(), atom, false);
-	int n;
-	char **list = 0;
-	XTextProperty tp;
-	std::string res = "unknown";
-
-	XGetTextProperty(disp(), win, &tp, netWmName);
-
-	if (!tp.nitems)
-		XGetWMName(disp(), win, &tp);
-
-	if (!tp.nitems)
-		return "error";
-
-	if (tp.encoding == XA_STRING) {
-		res = (char *)tp.value;
-	} else {
-		int ret = XmbTextPropertyToTextList(disp(), &tp, &list, &n);
-
-		if (ret >= Success && n > 0 && *list) {
-			res = *list;
-			XFreeStringList(list);
-		}
-	}
-
-	char *conv = nullptr;
-	if (os_mbs_to_utf8_ptr(res.c_str(), 0, &conv))
-		res = conv;
-	bfree(conv);
-
-	XFree(tp.value);
-
-	return res;
-}
-
-static std::map<XCompcapMain *, Window> windowForSource;
-static std::unordered_set<XCompcapMain *> changedSources;
-static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER;
-
-void registerSource(XCompcapMain *source, Window win)
-{
-	PLock lock(&changeLock);
-
-	blog(LOG_DEBUG, "registerSource(source=%p, win=%ld)", source, win);
-
-	auto it = windowForSource.find(source);
-
-	if (it != windowForSource.end()) {
-		windowForSource.erase(it);
-	}
-
-	// Subscribe to Events
-	XSelectInput(disp(), win,
-		     StructureNotifyMask | ExposureMask | VisibilityChangeMask);
-	XCompositeRedirectWindow(disp(), win, CompositeRedirectAutomatic);
-	XSync(disp(), 0);
-
-	windowForSource.insert(std::make_pair(source, win));
-}
-
-void unregisterSource(XCompcapMain *source)
-{
-	PLock lock(&changeLock);
-
-	blog(LOG_DEBUG, "unregisterSource(source=%p)", source);
-	{
-		auto it = windowForSource.find(source);
-		Window win = it->second;
-
-		if (it != windowForSource.end()) {
-			windowForSource.erase(it);
-		}
-
-		// check if there are still sources listening for the same window
-		it = windowForSource.begin();
-		bool windowInUse = false;
-		while (it != windowForSource.end()) {
-			if (it->second == win) {
-				windowInUse = true;
-				break;
-			}
-			it++;
-		}
-
-		if (!windowInUse) {
-			// Last source released, stop listening for events.
-			XSelectInput(disp(), win, 0);
-			XCompositeUnredirectWindow(disp(), win,
-						   CompositeRedirectAutomatic);
-			XSync(disp(), 0);
-		}
-	}
-
-	{
-		auto it = changedSources.find(source);
-
-		if (it != changedSources.end()) {
-			changedSources.erase(it);
-		}
-	}
-}
-
-void processEvents()
-{
-	PLock lock(&changeLock);
-
-	XLockDisplay(disp());
-
-	while (XEventsQueued(disp(), QueuedAfterReading) > 0) {
-		XEvent ev;
-		Window win = 0;
-
-		XNextEvent(disp(), &ev);
-
-		if (ev.type == ConfigureNotify)
-			win = ev.xconfigure.event;
-
-		else if (ev.type == MapNotify)
-			win = ev.xmap.event;
-
-		else if (ev.type == Expose)
-			win = ev.xexpose.window;
-
-		else if (ev.type == VisibilityNotify)
-			win = ev.xvisibility.window;
-
-		else if (ev.type == DestroyNotify)
-			win = ev.xdestroywindow.event;
-
-		if (win != 0) {
-			blog(LOG_DEBUG, "processEvents(): windowChanged=%ld",
-			     win);
-
-			auto it = windowForSource.begin();
-
-			while (it != windowForSource.end()) {
-				if (it->second == win) {
-					blog(LOG_DEBUG,
-					     "processEvents(): sourceChanged=%p",
-					     it->first);
-					changedSources.insert(it->first);
-				}
-				it++;
-			}
-		}
-	}
-
-	XUnlockDisplay(disp());
-}
-
-bool sourceWasReconfigured(XCompcapMain *source)
-{
-	PLock lock(&changeLock);
-
-	auto it = changedSources.find(source);
-
-	if (it != changedSources.end()) {
-		changedSources.erase(it);
-		blog(LOG_DEBUG, "sourceWasReconfigured(source=%p)=true",
-		     source);
-		return true;
-	}
-
-	return false;
-}
-
-}
-
-PLock::PLock(pthread_mutex_t *mtx, bool trylock) : m(mtx)
-{
-	if (trylock)
-		islock = mtx && pthread_mutex_trylock(mtx) == 0;
-	else
-		islock = mtx && pthread_mutex_lock(mtx) == 0;
-}
-
-PLock::~PLock()
-{
-	if (islock) {
-		pthread_mutex_unlock(m);
-	}
-}
-
-bool PLock::isLocked()
-{
-	return islock;
-}
-
-void PLock::unlock()
-{
-	if (islock) {
-		pthread_mutex_unlock(m);
-		islock = false;
-	}
-}
-
-void PLock::lock()
-{
-	if (!islock) {
-		pthread_mutex_lock(m);
-		islock = true;
-	}
-}
-
-static bool *curErrorTarget = 0;
-static char curErrorText[200];
-static int xerrorlock_handler(Display *disp, XErrorEvent *err)
-{
-
-	if (curErrorTarget)
-		*curErrorTarget = true;
-
-	XGetErrorText(disp, err->error_code, curErrorText, 200);
-
-	return 0;
-}
-
-XErrorLock::XErrorLock()
-{
-	goterr = false;
-	islock = false;
-	prevhandler = 0;
-
-	lock();
-}
-
-XErrorLock::~XErrorLock()
-{
-	unlock();
-}
-
-bool XErrorLock::isLocked()
-{
-	return islock;
-}
-
-void XErrorLock::lock()
-{
-	if (!islock) {
-		XLockDisplay(XCompcap::disp());
-		XSync(XCompcap::disp(), 0);
-
-		curErrorTarget = &goterr;
-		curErrorText[0] = 0;
-		prevhandler = XSetErrorHandler(xerrorlock_handler);
-
-		islock = true;
-	}
-}
-
-void XErrorLock::unlock()
-{
-	if (islock) {
-		XSync(XCompcap::disp(), 0);
-
-		curErrorTarget = 0;
-		XSetErrorHandler(prevhandler);
-		prevhandler = 0;
-		XUnlockDisplay(XCompcap::disp());
-		islock = false;
-	}
-}
-
-bool XErrorLock::gotError()
-{
-	if (!islock)
-		return false;
-
-	XSync(XCompcap::disp(), 0);
-
-	bool res = goterr;
-	goterr = false;
-	return res;
-}
-
-std::string XErrorLock::getErrorText()
-{
-	return curErrorText;
-}
-
-void XErrorLock::resetError()
-{
-	if (islock)
-		XSync(XCompcap::disp(), 0);
-
-	goterr = false;
-	curErrorText[0] = 0;
-}
-
-XDisplayLock::XDisplayLock()
-{
-	islock = false;
-	lock();
-}
-
-XDisplayLock::~XDisplayLock()
-{
-	unlock();
-}
-
-bool XDisplayLock::isLocked()
-{
-	return islock;
-}
-
-void XDisplayLock::lock()
-{
-	if (!islock) {
-		XLockDisplay(XCompcap::disp());
-		islock = true;
-	}
-}
-
-void XDisplayLock::unlock()
-{
-	if (islock) {
-		XSync(XCompcap::disp(), 0);
-		XUnlockDisplay(XCompcap::disp());
-		islock = false;
-	}
-}
-
-ObsGsContextHolder::ObsGsContextHolder()
-{
-	obs_enter_graphics();
-}
-
-ObsGsContextHolder::~ObsGsContextHolder()
-{
-	obs_leave_graphics();
-}

+ 0 - 98
plugins/linux-capture/xcompcap-helper.hpp

@@ -1,98 +0,0 @@
-#pragma once
-
-#include <string>
-#include <list>
-
-#define blog(level, msg, ...) blog(level, "xcompcap: " msg, ##__VA_ARGS__)
-
-class PLock {
-	pthread_mutex_t *m;
-	bool islock;
-
-public:
-	PLock(const PLock &) = delete;
-	PLock &operator=(const PLock &) = delete;
-
-	PLock(pthread_mutex_t *mtx, bool trylock = false);
-
-	~PLock();
-
-	bool isLocked();
-
-	void unlock();
-	void lock();
-};
-
-class XErrorLock {
-	bool islock;
-	bool goterr;
-	XErrorHandler prevhandler;
-
-public:
-	XErrorLock(const XErrorLock &) = delete;
-	XErrorLock &operator=(const XErrorLock &) = delete;
-
-	XErrorLock();
-	~XErrorLock();
-
-	bool isLocked();
-
-	void unlock();
-	void lock();
-
-	bool gotError();
-	std::string getErrorText();
-	void resetError();
-};
-
-class XDisplayLock {
-	bool islock;
-
-public:
-	XDisplayLock(const XDisplayLock &) = delete;
-	XDisplayLock &operator=(const XDisplayLock &) = delete;
-
-	XDisplayLock();
-	~XDisplayLock();
-
-	bool isLocked();
-
-	void unlock();
-	void lock();
-};
-
-class ObsGsContextHolder {
-public:
-	ObsGsContextHolder(const ObsGsContextHolder &) = delete;
-	ObsGsContextHolder &operator=(const ObsGsContextHolder &) = delete;
-
-	ObsGsContextHolder();
-	~ObsGsContextHolder();
-};
-
-class XCompcapMain;
-
-namespace XCompcap {
-Display *disp();
-void cleanupDisplay();
-
-int getRootWindowScreen(Window root);
-std::string getWindowAtom(Window win, const char *atom);
-bool ewmhIsSupported();
-std::list<Window> getTopLevelWindows();
-
-inline std::string getWindowName(Window win)
-{
-	return getWindowAtom(win, "_NET_WM_NAME");
-}
-
-inline std::string getWindowClass(Window win)
-{
-	return getWindowAtom(win, "WM_CLASS");
-}
-
-void registerSource(XCompcapMain *source, Window win);
-void unregisterSource(XCompcapMain *source);
-void processEvents();
-bool sourceWasReconfigured(XCompcapMain *source);
-}

+ 0 - 754
plugins/linux-capture/xcompcap-main.cpp

@@ -1,754 +0,0 @@
-#include <glad/glad.h>
-#include <glad/glad_glx.h>
-#include <X11/Xlib.h>
-#include <X11/extensions/Xcomposite.h>
-#include <pthread.h>
-
-#include <algorithm>
-#include <vector>
-
-#include <obs-module.h>
-#include <graphics/vec4.h>
-#include <util/platform.h>
-
-#include "xcompcap-main.hpp"
-#include "xcompcap-helper.hpp"
-#include "xcursor.h"
-
-#define xdisp (XCompcap::disp())
-#define WIN_STRING_DIV "\r\n"
-
-bool XCompcapMain::init()
-{
-	if (!xdisp) {
-		blog(LOG_ERROR, "failed opening display");
-		return false;
-	}
-
-	XInitThreads();
-
-	int eventBase, errorBase;
-	if (!XCompositeQueryExtension(xdisp, &eventBase, &errorBase)) {
-		blog(LOG_ERROR, "Xcomposite extension not supported");
-		return false;
-	}
-
-	int major = 0, minor = 2;
-	XCompositeQueryVersion(xdisp, &major, &minor);
-
-	if (major == 0 && minor < 2) {
-		blog(LOG_ERROR, "Xcomposite extension is too old: %d.%d < 0.2",
-		     major, minor);
-		return false;
-	}
-
-	return true;
-}
-
-void XCompcapMain::deinit()
-{
-	XCompcap::cleanupDisplay();
-}
-
-obs_properties_t *XCompcapMain::properties()
-{
-	obs_properties_t *props = obs_properties_create();
-
-	obs_property_t *wins = obs_properties_add_list(
-		props, "capture_window", obs_module_text("Window"),
-		OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
-
-	struct WindowInfo {
-		std::string lex_comparable;
-		std::string name;
-		std::string desc;
-	};
-
-	std::vector<WindowInfo> window_strings;
-	for (Window win : XCompcap::getTopLevelWindows()) {
-		std::string wname = XCompcap::getWindowName(win);
-		std::string cls = XCompcap::getWindowClass(win);
-		std::string winid = std::to_string((long long)win);
-		std::string desc =
-			(winid + WIN_STRING_DIV + wname + WIN_STRING_DIV + cls);
-
-		std::string wname_lowercase = wname;
-		std::transform(wname_lowercase.begin(), wname_lowercase.end(),
-			       wname_lowercase.begin(),
-			       [](unsigned char c) { return std::tolower(c); });
-
-		window_strings.push_back({.lex_comparable = wname_lowercase,
-					  .name = wname,
-					  .desc = desc});
-	}
-
-	std::sort(window_strings.begin(), window_strings.end(),
-		  [](const WindowInfo &a, const WindowInfo &b) -> bool {
-			  return std::lexicographical_compare(
-				  a.lex_comparable.begin(),
-				  a.lex_comparable.end(),
-				  b.lex_comparable.begin(),
-				  b.lex_comparable.end());
-		  });
-
-	for (auto s : window_strings) {
-		obs_property_list_add_string(wins, s.name.c_str(),
-					     s.desc.c_str());
-	}
-
-	obs_properties_add_int(props, "cut_top", obs_module_text("CropTop"), 0,
-			       4096, 1);
-	obs_properties_add_int(props, "cut_left", obs_module_text("CropLeft"),
-			       0, 4096, 1);
-	obs_properties_add_int(props, "cut_right", obs_module_text("CropRight"),
-			       0, 4096, 1);
-	obs_properties_add_int(props, "cut_bot", obs_module_text("CropBottom"),
-			       0, 4096, 1);
-
-	obs_properties_add_bool(props, "swap_redblue",
-				obs_module_text("SwapRedBlue"));
-	obs_properties_add_bool(props, "lock_x", obs_module_text("LockX"));
-
-	obs_properties_add_bool(props, "show_cursor",
-				obs_module_text("CaptureCursor"));
-
-	obs_properties_add_bool(props, "include_border",
-				obs_module_text("IncludeXBorder"));
-
-	obs_properties_add_bool(props, "exclude_alpha",
-				obs_module_text("ExcludeAlpha"));
-
-	return props;
-}
-
-void XCompcapMain::defaults(obs_data_t *settings)
-{
-	obs_data_set_default_string(settings, "capture_window", "");
-	obs_data_set_default_int(settings, "cut_top", 0);
-	obs_data_set_default_int(settings, "cut_left", 0);
-	obs_data_set_default_int(settings, "cut_right", 0);
-	obs_data_set_default_int(settings, "cut_bot", 0);
-	obs_data_set_default_bool(settings, "swap_redblue", false);
-	obs_data_set_default_bool(settings, "lock_x", false);
-	obs_data_set_default_bool(settings, "show_cursor", true);
-	obs_data_set_default_bool(settings, "include_border", false);
-	obs_data_set_default_bool(settings, "exclude_alpha", false);
-}
-
-#define FIND_WINDOW_INTERVAL 0.5
-
-struct XCompcapMain_private {
-	XCompcapMain_private()
-		: win(0),
-		  cut_top(0),
-		  cur_cut_top(0),
-		  cut_left(0),
-		  cur_cut_left(0),
-		  cut_right(0),
-		  cur_cut_right(0),
-		  cut_bot(0),
-		  cur_cut_bot(0),
-		  inverted(false),
-		  width(0),
-		  height(0),
-		  pixmap(0),
-		  glxpixmap(0),
-		  tex(0),
-		  gltex(0)
-	{
-		pthread_mutexattr_init(&lockattr);
-		pthread_mutexattr_settype(&lockattr, PTHREAD_MUTEX_RECURSIVE);
-
-		pthread_mutex_init(&lock, &lockattr);
-	}
-
-	~XCompcapMain_private()
-	{
-		pthread_mutex_destroy(&lock);
-		pthread_mutexattr_destroy(&lockattr);
-	}
-
-	obs_source_t *source;
-
-	std::string windowName;
-	Window win = 0;
-	int cut_top, cur_cut_top;
-	int cut_left, cur_cut_left;
-	int cut_right, cur_cut_right;
-	int cut_bot, cur_cut_bot;
-	bool inverted;
-	bool swapRedBlue;
-	bool lockX;
-	bool include_border;
-	bool exclude_alpha;
-	bool draw_opaque;
-
-	double window_check_time = 0.0;
-
-	uint32_t width;
-	uint32_t height;
-	uint32_t border;
-
-	Pixmap pixmap;
-	GLXPixmap glxpixmap;
-	gs_texture_t *tex;
-	gs_texture_t *gltex;
-
-	pthread_mutex_t lock;
-	pthread_mutexattr_t lockattr;
-
-	bool show_cursor = true;
-	bool cursor_outside = false;
-	xcursor_t *cursor = nullptr;
-	bool tick_error_suppressed = false;
-	// Whether to rebind the GLX Pixmap on every tick. This is the correct
-	// mode of operation, according to GLX_EXT_texture_from_pixmap. However
-	// certain drivers exhibits poor performance when this is done, so
-	// setting this to false allows working around it.
-	bool strict_binding = true;
-};
-
-XCompcapMain::XCompcapMain(obs_data_t *settings, obs_source_t *source)
-{
-	p = new XCompcapMain_private;
-	p->source = source;
-
-	obs_enter_graphics();
-	if (strcmp(reinterpret_cast<const char *>(glGetString(GL_VENDOR)),
-		   "NVIDIA Corporation") == 0) {
-		// Pixmap binds are extremely slow on NVIDIA cards (https://github.com/obsproject/obs-studio/issues/5685)
-		p->strict_binding = false;
-	}
-	p->cursor = xcursor_init(xdisp);
-	obs_leave_graphics();
-
-	updateSettings(settings);
-}
-
-static void xcc_cleanup(XCompcapMain_private *p);
-
-XCompcapMain::~XCompcapMain()
-{
-	ObsGsContextHolder obsctx;
-
-	XCompcap::unregisterSource(this);
-
-	if (p->tex) {
-		gs_texture_destroy(p->tex);
-		p->tex = 0;
-	}
-
-	xcc_cleanup(p);
-
-	if (p->cursor) {
-		xcursor_destroy(p->cursor);
-		p->cursor = nullptr;
-	}
-
-	delete p;
-}
-
-static Window getWindowFromString(std::string wstr)
-{
-	XErrorLock xlock;
-
-	if (wstr == "") {
-		return XCompcap::getTopLevelWindows().front();
-	}
-
-	if (wstr.substr(0, 4) == "root") {
-		int i = std::stoi("0" + wstr.substr(4));
-		return RootWindow(xdisp, i);
-	}
-
-	size_t firstMark = wstr.find(WIN_STRING_DIV);
-	size_t lastMark = wstr.rfind(WIN_STRING_DIV);
-	size_t markSize = strlen(WIN_STRING_DIV);
-
-	// wstr only consists of the window-id
-	if (firstMark == std::string::npos)
-		return (Window)std::stol(wstr);
-
-	// wstr also contains window-name and window-class
-	std::string wid = wstr.substr(0, firstMark);
-	std::string wname = wstr.substr(firstMark + markSize,
-					lastMark - firstMark - markSize);
-	std::string wcls = wstr.substr(lastMark + markSize);
-
-	Window winById = (Window)std::stol(wid);
-
-	// first try to find a match by the window-id
-	for (Window cwin : XCompcap::getTopLevelWindows()) {
-		// match by window-id
-		if (cwin == winById) {
-			return cwin;
-		}
-	}
-
-	// then try to find a match by name & class
-	for (Window cwin : XCompcap::getTopLevelWindows()) {
-		std::string cwinname = XCompcap::getWindowName(cwin);
-		std::string ccls = XCompcap::getWindowClass(cwin);
-
-		// match by name and class
-		if (wname == cwinname && wcls == ccls) {
-			return cwin;
-		}
-	}
-
-	// no match
-	blog(LOG_DEBUG, "Did not find Window By ID %s, Name '%s' or Class '%s'",
-	     wid.c_str(), wname.c_str(), wcls.c_str());
-	return 0;
-}
-
-static void xcc_cleanup(XCompcapMain_private *p)
-{
-	PLock lock(&p->lock);
-	XErrorLock xlock;
-
-	if (p->gltex) {
-		GLuint gltex = *(GLuint *)gs_texture_get_obj(p->gltex);
-		glBindTexture(GL_TEXTURE_2D, gltex);
-		if (p->glxpixmap) {
-			glXReleaseTexImageEXT(xdisp, p->glxpixmap,
-					      GLX_FRONT_EXT);
-			if (xlock.gotError()) {
-				blog(LOG_ERROR,
-				     "cleanup glXReleaseTexImageEXT failed: %s",
-				     xlock.getErrorText().c_str());
-				xlock.resetError();
-			}
-			glXDestroyPixmap(xdisp, p->glxpixmap);
-			if (xlock.gotError()) {
-				blog(LOG_ERROR,
-				     "cleanup glXDestroyPixmap failed: %s",
-				     xlock.getErrorText().c_str());
-				xlock.resetError();
-			}
-			p->glxpixmap = 0;
-		}
-		gs_texture_destroy(p->gltex);
-		p->gltex = 0;
-	}
-
-	if (p->pixmap) {
-		XFreePixmap(xdisp, p->pixmap);
-		if (xlock.gotError()) {
-			blog(LOG_ERROR, "cleanup glXDestroyPixmap failed: %s",
-			     xlock.getErrorText().c_str());
-			xlock.resetError();
-		}
-		p->pixmap = 0;
-	}
-
-	if (p->win) {
-		p->win = 0;
-	}
-
-	if (p->tex) {
-		gs_texture_destroy(p->tex);
-		p->tex = 0;
-	}
-}
-
-static gs_color_format gs_format_from_tex()
-{
-	GLint iformat = 0;
-	// consider GL_ARB_internalformat_query
-	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
-				 &iformat);
-
-	// These formats are known to be wrong on Intel platforms. We intentionally
-	// use swapped internal formats here to preserve historic behavior which
-	// swapped colors accidentally and because D3D11 would not support a
-	// GS_RGBX format
-	switch (iformat) {
-	case GL_RGB:
-		return GS_BGRX_UNORM;
-	case GL_RGBA:
-		return GS_RGBA_UNORM;
-	default:
-		return GS_RGBA_UNORM;
-	}
-}
-
-// from libobs-opengl/gl-subsystem.h because we need to handle GLX modifying textures outside libobs.
-struct fb_info;
-
-struct gs_texture {
-	gs_device_t *device;
-	enum gs_texture_type type;
-	enum gs_color_format format;
-	GLenum gl_format;
-	GLenum gl_target;
-	GLenum gl_internal_format;
-	GLenum gl_type;
-	GLuint texture;
-	uint32_t levels;
-	bool is_dynamic;
-	bool is_render_target;
-	bool is_dummy;
-	bool gen_mipmaps;
-
-	gs_samplerstate_t *cur_sampler;
-	struct fbo_info *fbo;
-};
-// End shitty hack.
-
-void XCompcapMain::updateSettings(obs_data_t *settings)
-{
-	ObsGsContextHolder obsctx;
-	XErrorLock xlock;
-
-	PLock lock(&p->lock);
-
-	blog(LOG_DEBUG, "Settings updating");
-
-	Window prevWin = p->win;
-
-	xcc_cleanup(p);
-
-	p->tick_error_suppressed = false;
-
-	if (settings) {
-		/* Settings initialized or changed */
-		const char *windowName =
-			obs_data_get_string(settings, "capture_window");
-
-		p->windowName = windowName;
-		p->win = getWindowFromString(windowName);
-		XCompcap::registerSource(this, p->win);
-
-		p->cut_top = obs_data_get_int(settings, "cut_top");
-		p->cut_left = obs_data_get_int(settings, "cut_left");
-		p->cut_right = obs_data_get_int(settings, "cut_right");
-		p->cut_bot = obs_data_get_int(settings, "cut_bot");
-		p->lockX = obs_data_get_bool(settings, "lock_x");
-		p->swapRedBlue = obs_data_get_bool(settings, "swap_redblue");
-		p->show_cursor = obs_data_get_bool(settings, "show_cursor");
-		p->include_border =
-			obs_data_get_bool(settings, "include_border");
-		p->exclude_alpha = obs_data_get_bool(settings, "exclude_alpha");
-		p->draw_opaque = false;
-	} else {
-		/* New Window found (stored in p->win), just re-initialize GL-Mapping  */
-		p->win = prevWin;
-	}
-
-	if (xlock.gotError()) {
-		blog(LOG_ERROR, "registeringSource failed: %s",
-		     xlock.getErrorText().c_str());
-		return;
-	}
-	XSync(xdisp, 0);
-
-	XWindowAttributes attr;
-	if (!p->win || !XGetWindowAttributes(xdisp, p->win, &attr)) {
-		p->win = 0;
-		p->width = 0;
-		p->height = 0;
-		return;
-	}
-
-	if (p->win && p->cursor && p->show_cursor) {
-		Window child;
-		int x, y;
-
-		XTranslateCoordinates(xdisp, p->win, attr.root, 0, 0, &x, &y,
-				      &child);
-		xcursor_offset(p->cursor, x, y);
-	}
-
-	const int config_attrs[] = {GLX_BIND_TO_TEXTURE_RGBA_EXT,
-				    GL_TRUE,
-				    GLX_DRAWABLE_TYPE,
-				    GLX_PIXMAP_BIT,
-				    GLX_BIND_TO_TEXTURE_TARGETS_EXT,
-				    GLX_TEXTURE_2D_BIT_EXT,
-				    GLX_DOUBLEBUFFER,
-				    GL_FALSE,
-				    None};
-	int nelem = 0;
-	GLXFBConfig *configs = glXChooseFBConfig(
-		xdisp, XCompcap::getRootWindowScreen(attr.root), config_attrs,
-		&nelem);
-
-	bool found = false;
-	GLXFBConfig config;
-	for (int i = 0; i < nelem; i++) {
-		config = configs[i];
-		XVisualInfo *visual = glXGetVisualFromFBConfig(xdisp, config);
-		if (!visual)
-			continue;
-
-		if (attr.depth != visual->depth) {
-			XFree(visual);
-			continue;
-		}
-		XFree(visual);
-		found = true;
-		break;
-	}
-	if (!found) {
-		blog(LOG_ERROR, "no matching fb config found");
-		p->win = 0;
-		p->height = 0;
-		p->width = 0;
-		XFree(configs);
-		return;
-	}
-
-	if (p->exclude_alpha || attr.depth != 32) {
-		p->draw_opaque = true;
-	}
-
-	int inverted;
-	glXGetFBConfigAttrib(xdisp, config, GLX_Y_INVERTED_EXT, &inverted);
-	p->inverted = inverted != 0;
-
-	p->border = attr.border_width;
-
-	if (p->include_border) {
-		p->width = attr.width + p->border * 2;
-		p->height = attr.height + p->border * 2;
-	} else {
-		p->width = attr.width;
-		p->height = attr.height;
-	}
-
-	if (p->cut_top + p->cut_bot < (int)p->height) {
-		p->cur_cut_top = p->cut_top;
-		p->cur_cut_bot = p->cut_bot;
-	} else {
-		p->cur_cut_top = 0;
-		p->cur_cut_bot = 0;
-	}
-
-	if (p->cut_left + p->cut_right < (int)p->width) {
-		p->cur_cut_left = p->cut_left;
-		p->cur_cut_right = p->cut_right;
-	} else {
-		p->cur_cut_left = 0;
-		p->cur_cut_right = 0;
-	}
-
-	// Precautionary since we dont error check every GLX call above.
-	xlock.resetError();
-
-	p->pixmap = XCompositeNameWindowPixmap(xdisp, p->win);
-	if (xlock.gotError()) {
-		blog(LOG_ERROR, "XCompositeNameWindowPixmap failed: %s",
-		     xlock.getErrorText().c_str());
-		p->pixmap = 0;
-		XFree(configs);
-		return;
-	}
-
-	// Should be consistent format with config we are using. Since we searched on RGBA lets use RGBA here.
-	const int pixmap_attrs[] = {GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
-				    GLX_TEXTURE_FORMAT_EXT,
-				    GLX_TEXTURE_FORMAT_RGBA_EXT, None};
-
-	p->glxpixmap = glXCreatePixmap(xdisp, config, p->pixmap, pixmap_attrs);
-	if (xlock.gotError()) {
-		blog(LOG_ERROR, "glXCreatePixmap failed: %s",
-		     xlock.getErrorText().c_str());
-		XFreePixmap(xdisp, p->pixmap);
-		XFree(configs);
-		p->pixmap = 0;
-		p->glxpixmap = 0;
-		return;
-	}
-	XFree(configs);
-
-	// Build an OBS texture to bind the pixmap to.
-	p->gltex = gs_texture_create(p->width, p->height, GS_RGBA_UNORM, 1, 0,
-				     GS_GL_DUMMYTEX);
-	GLuint gltex = *(GLuint *)gs_texture_get_obj(p->gltex);
-	glBindTexture(GL_TEXTURE_2D, gltex);
-	glXBindTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_EXT, nullptr);
-	if (xlock.gotError()) {
-		blog(LOG_ERROR, "glXBindTexImageEXT failed: %s",
-		     xlock.getErrorText().c_str());
-		XFreePixmap(xdisp, p->pixmap);
-		XFree(configs);
-		p->pixmap = 0;
-		p->glxpixmap = 0;
-		return;
-	}
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-	// glxBindTexImageEXT might modify the textures format.
-	gs_color_format format = gs_format_from_tex();
-	glBindTexture(GL_TEXTURE_2D, 0);
-	// sync OBS texture format based on any glxBindTexImageEXT changes
-	p->gltex->format = format;
-
-	// Create a pure OBS texture to use for rendering. Using the same
-	// format so we can copy instead of drawing from the source gltex.
-	if (p->tex)
-		gs_texture_destroy(p->tex);
-	p->tex = gs_texture_create(width(), height(), format, 1, 0,
-				   GS_GL_DUMMYTEX);
-	if (p->swapRedBlue) {
-		GLuint tex = *(GLuint *)gs_texture_get_obj(p->tex);
-		glBindTexture(GL_TEXTURE_2D, tex);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
-		glBindTexture(GL_TEXTURE_2D, 0);
-	}
-
-	if (!p->windowName.empty()) {
-		blog(LOG_INFO,
-		     "[window-capture: '%s'] update settings:\n"
-		     "\ttitle: %s\n"
-		     "\tclass: %s\n"
-		     "\tBit depth: %i\n"
-		     "\tFound proper GLXFBConfig (in %i): %s\n",
-		     obs_source_get_name(p->source),
-		     XCompcap::getWindowName(p->win).c_str(),
-		     XCompcap::getWindowClass(p->win).c_str(), attr.depth,
-		     nelem, found ? "yes" : "no");
-	}
-}
-
-void XCompcapMain::tick(float seconds)
-{
-	if (!obs_source_showing(p->source))
-		return;
-
-	// Must be taken before xlock to prevent deadlock on shutdown
-	ObsGsContextHolder obsctx;
-
-	PLock lock(&p->lock, true);
-
-	if (!lock.isLocked())
-		return;
-
-	XCompcap::processEvents();
-
-	if (p->win && XCompcap::sourceWasReconfigured(this)) {
-		p->window_check_time = FIND_WINDOW_INTERVAL;
-		p->win = 0;
-	}
-
-	XErrorLock xlock;
-	XWindowAttributes attr;
-
-	if (!p->win || !XGetWindowAttributes(xdisp, p->win, &attr)) {
-		p->window_check_time += (double)seconds;
-
-		if (p->window_check_time < FIND_WINDOW_INTERVAL)
-			return;
-
-		Window newWin = getWindowFromString(p->windowName);
-
-		p->window_check_time = 0.0;
-
-		if (newWin && XGetWindowAttributes(xdisp, newWin, &attr)) {
-			p->win = newWin;
-			XCompcap::registerSource(this, p->win);
-			updateSettings(0);
-		} else {
-			return;
-		}
-	}
-
-	if (!p->tex || !p->gltex)
-		return;
-
-	if (p->lockX) {
-		// XDisplayLock is still live so we should already be locked.
-		XLockDisplay(xdisp);
-		XSync(xdisp, 0);
-	}
-
-	glBindTexture(GL_TEXTURE_2D, *(GLuint *)gs_texture_get_obj(p->gltex));
-	if (p->strict_binding) {
-		glXReleaseTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_EXT);
-		if (xlock.gotError() && !p->tick_error_suppressed) {
-			blog(LOG_ERROR, "glXReleaseTexImageEXT failed: %s",
-			     xlock.getErrorText().c_str());
-			p->tick_error_suppressed = true;
-		}
-		glXBindTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_EXT, nullptr);
-		if (xlock.gotError() && !p->tick_error_suppressed) {
-			blog(LOG_ERROR, "glXBindTexImageEXT failed: %s",
-			     xlock.getErrorText().c_str());
-			p->tick_error_suppressed = true;
-		}
-	}
-
-	if (p->include_border) {
-		gs_copy_texture_region(p->tex, 0, 0, p->gltex, p->cur_cut_left,
-				       p->cur_cut_top, width(), height());
-	} else {
-		gs_copy_texture_region(p->tex, 0, 0, p->gltex,
-				       p->cur_cut_left + p->border,
-				       p->cur_cut_top + p->border, width(),
-				       height());
-	}
-	glBindTexture(GL_TEXTURE_2D, 0);
-
-	if (p->cursor && p->show_cursor) {
-		xcursor_tick(p->cursor);
-
-		p->cursor_outside =
-			p->cursor->x < p->cur_cut_left ||
-			p->cursor->y < p->cur_cut_top ||
-			p->cursor->x > int(p->width - p->cur_cut_right) ||
-			p->cursor->y > int(p->height - p->cur_cut_bot);
-	}
-
-	if (p->lockX)
-		XUnlockDisplay(xdisp);
-}
-
-void XCompcapMain::render(gs_effect_t *effect)
-{
-	if (!p->win)
-		return;
-
-	PLock lock(&p->lock, true);
-
-	if (p->draw_opaque)
-		effect = obs_get_base_effect(OBS_EFFECT_OPAQUE);
-	else
-		effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
-
-	if (!lock.isLocked() || !p->tex)
-		return;
-
-	gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
-	gs_effect_set_texture(image, p->tex);
-
-	while (gs_effect_loop(effect, "Draw")) {
-		gs_draw_sprite(p->tex, 0, 0, 0);
-	}
-
-	if (p->cursor && p->gltex && p->show_cursor && !p->cursor_outside) {
-		effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
-
-		while (gs_effect_loop(effect, "Draw")) {
-			xcursor_render(p->cursor, -p->cur_cut_left,
-				       -p->cur_cut_top);
-		}
-	}
-}
-
-uint32_t XCompcapMain::width()
-{
-	if (!p->win)
-		return 0;
-
-	return p->width - p->cur_cut_left - p->cur_cut_right;
-}
-
-uint32_t XCompcapMain::height()
-{
-	if (!p->win)
-		return 0;
-
-	return p->height - p->cur_cut_bot - p->cur_cut_top;
-}

+ 0 - 26
plugins/linux-capture/xcompcap-main.hpp

@@ -1,26 +0,0 @@
-#pragma once
-
-struct XCompcapMain_private;
-
-class XCompcapMain {
-public:
-	static bool init();
-	static void deinit();
-
-	static obs_properties_t *properties();
-	static void defaults(obs_data_t *settings);
-
-	XCompcapMain(obs_data_t *settings, obs_source_t *source);
-	~XCompcapMain();
-
-	void updateSettings(obs_data_t *settings);
-
-	void tick(float seconds);
-	void render(gs_effect_t *effect);
-
-	uint32_t width();
-	uint32_t height();
-
-private:
-	XCompcapMain_private *p;
-};

+ 1061 - 0
plugins/linux-capture/xcomposite-input.c

@@ -0,0 +1,1061 @@
+#include <obs-module.h>
+#include <glad/glad.h>
+#include <glad/glad_glx.h>
+#include <X11/Xlib-xcb.h>
+#include <xcb/xcb.h>
+#include <xcb/composite.h>
+#include <pthread.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "xhelpers.h"
+#include "xcursor-xcb.h"
+#include "xcomposite-input.h"
+#include <util/platform.h>
+#include <util/dstr.h>
+#include <util/darray.h>
+
+#define WIN_STRING_DIV "\r\n"
+#define FIND_WINDOW_INTERVAL 0.5
+
+static Display *disp = NULL;
+static xcb_connection_t *conn = NULL;
+// Atoms used throughout our plugin
+xcb_atom_t ATOM_UTF8_STRING;
+xcb_atom_t ATOM_STRING;
+xcb_atom_t ATOM_TEXT;
+xcb_atom_t ATOM_COMPOUND_TEXT;
+xcb_atom_t ATOM_WM_NAME;
+xcb_atom_t ATOM_WM_CLASS;
+xcb_atom_t ATOM__NET_WM_NAME;
+xcb_atom_t ATOM__NET_SUPPORTING_WM_CHECK;
+xcb_atom_t ATOM__NET_CLIENT_LIST;
+
+struct xcompcap {
+	obs_source_t *source;
+
+	const char *windowName;
+	xcb_window_t win;
+	int crop_top;
+	int crop_left;
+	int crop_right;
+	int crop_bot;
+	bool swapRedBlue;
+	bool include_border;
+	bool exclude_alpha;
+
+	float window_check_time;
+	bool window_changed;
+
+	uint32_t width;
+	uint32_t height;
+	uint32_t border;
+
+	Pixmap pixmap;
+	GLXPixmap glxpixmap;
+	gs_texture_t *tex;
+	gs_texture_t *gltex;
+
+	pthread_mutex_t lock;
+
+	bool show_cursor;
+	bool cursor_outside;
+	xcb_xcursor_t *cursor;
+	// strict_binding determines whether we rebind the GLX Pixmap on every
+	// tick. "Strict binding" is the correct mode of operation, according to
+	// GLX_EXT_texture_from_pixmap. However certain drivers exhibit poor
+	// performance when this is done, so setting this to false allows working
+	// around it.
+	bool strict_binding;
+};
+
+static void xcompcap_update(void *data, obs_data_t *settings);
+
+xcb_atom_t get_atom(xcb_connection_t *conn, const char *name)
+{
+	xcb_intern_atom_cookie_t atom_c =
+		xcb_intern_atom(conn, 1, strlen(name), name);
+	xcb_intern_atom_reply_t *atom_r =
+		xcb_intern_atom_reply(conn, atom_c, NULL);
+	xcb_atom_t a = atom_r->atom;
+	free(atom_r);
+	return a;
+}
+
+void xcomp_gather_atoms(xcb_connection_t *conn)
+{
+	ATOM_UTF8_STRING = get_atom(conn, "UTF8_STRING");
+	ATOM_STRING = get_atom(conn, "STRING");
+	ATOM_TEXT = get_atom(conn, "TEXT");
+	ATOM_COMPOUND_TEXT = get_atom(conn, "COMPOUND_TEXT");
+	ATOM_WM_NAME = get_atom(conn, "WM_NAME");
+	ATOM_WM_CLASS = get_atom(conn, "WM_CLASS");
+	ATOM__NET_WM_NAME = get_atom(conn, "_NET_WM_NAME");
+	ATOM__NET_SUPPORTING_WM_CHECK =
+		get_atom(conn, "_NET_SUPPORTING_WM_CHECK");
+	ATOM__NET_CLIENT_LIST = get_atom(conn, "_NET_CLIENT_LIST");
+}
+
+bool xcomp_window_exists(xcb_connection_t *conn, xcb_window_t win)
+{
+	xcb_generic_error_t *err = NULL;
+	xcb_get_window_attributes_cookie_t attr_cookie =
+		xcb_get_window_attributes(conn, win);
+	xcb_get_window_attributes_reply_t *attr =
+		xcb_get_window_attributes_reply(conn, attr_cookie, &err);
+
+	bool exists = err == NULL && attr->map_state == XCB_MAP_STATE_VIEWABLE;
+	free(attr);
+	return exists;
+}
+
+xcb_get_property_reply_t *xcomp_property_sync(xcb_connection_t *conn,
+					      xcb_window_t win, xcb_atom_t atom)
+{
+	if (atom == XCB_ATOM_NONE)
+		return NULL;
+
+	xcb_generic_error_t *err = NULL;
+	// Read properties up to 4096*4 bytes
+	xcb_get_property_cookie_t prop_cookie =
+		xcb_get_property(conn, 0, win, atom, 0, 0, 4096);
+	xcb_get_property_reply_t *prop =
+		xcb_get_property_reply(conn, prop_cookie, &err);
+	if (err != NULL || xcb_get_property_value_length(prop) == 0) {
+		free(prop);
+		return NULL;
+	}
+
+	return prop;
+}
+
+// See ICCCM https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#text_properties
+// for more info on the galactic brained string types used in Xorg.
+struct dstr xcomp_window_name(xcb_connection_t *conn, Display *disp,
+			      xcb_window_t win)
+{
+	struct dstr ret = {0};
+
+	xcb_get_property_reply_t *name =
+		xcomp_property_sync(conn, win, ATOM__NET_WM_NAME);
+	if (name) {
+		// Guaranteed to be UTF8_STRING.
+		const char *data = (const char *)xcb_get_property_value(name);
+		dstr_ncopy(&ret, data, xcb_get_property_value_length(name));
+		free(name);
+		return ret;
+	}
+
+	name = xcomp_property_sync(conn, win, ATOM_WM_NAME);
+	if (!name) {
+		goto fail;
+	}
+
+	char *data = (char *)xcb_get_property_value(name);
+	if (name->type == ATOM_UTF8_STRING) {
+		dstr_ncopy(&ret, data, xcb_get_property_value_length(name));
+		free(name);
+		return ret;
+	}
+	if (name->type == ATOM_STRING) { // Latin-1, safe enough
+		dstr_ncopy(&ret, data, xcb_get_property_value_length(name));
+		free(name);
+		return ret;
+	}
+	if (name->type == ATOM_TEXT) { // Default charset
+		char *utf8;
+		if (!os_mbs_to_utf8_ptr(
+			    data, xcb_get_property_value_length(name), &utf8)) {
+			free(name);
+			goto fail;
+		}
+		free(name);
+		dstr_init_move_array(&ret, utf8);
+		return ret;
+	}
+	if (name->type ==
+	    ATOM_COMPOUND_TEXT) { // LibX11 is the only decoder for these.
+		XTextProperty xname = {
+			(unsigned char *)data, name->type,
+			8, // 8 by definition.
+			1, // Only decode the first element of string arrays.
+		};
+		char **list;
+		int len = 0;
+		if (XmbTextPropertyToTextList(disp, &xname, &list, &len) <
+			    Success ||
+		    !list || len < 1) {
+			free(name);
+			goto fail;
+		}
+		char *utf8;
+		if (!os_mbs_to_utf8_ptr(list[0], 0, &utf8)) {
+			XFreeStringList(list);
+			free(name);
+			goto fail;
+		}
+		dstr_init_move_array(&ret, utf8);
+		XFreeStringList(list);
+		free(name);
+		return ret;
+	}
+
+fail:
+	dstr_copy(&ret, "unknown");
+	return ret;
+}
+
+struct dstr xcomp_window_class(xcb_connection_t *conn, xcb_window_t win)
+{
+	struct dstr ret = {0};
+	dstr_copy(&ret, "unknown");
+	xcb_get_property_reply_t *cls =
+		xcomp_property_sync(conn, win, ATOM_WM_CLASS);
+	if (!cls)
+		return ret;
+
+	// WM_CLASS is formatted differently from other strings, it's two null terminated strings.
+	// Since we want the first one, let's just let copy run strlen.
+	dstr_copy(&ret, (const char *)xcb_get_property_value(cls));
+	free(cls);
+	return ret;
+}
+
+// Specification for checking for ewmh support at
+// http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472693600
+bool xcomp_check_ewmh(xcb_connection_t *conn, xcb_window_t root)
+{
+	xcb_get_property_reply_t *check =
+		xcomp_property_sync(conn, root, ATOM__NET_SUPPORTING_WM_CHECK);
+	if (!check)
+		return false;
+
+	xcb_window_t ewmh_window =
+		((xcb_window_t *)xcb_get_property_value(check))[0];
+	free(check);
+
+	xcb_get_property_reply_t *check2 = xcomp_property_sync(
+		conn, ewmh_window, ATOM__NET_SUPPORTING_WM_CHECK);
+	if (!check2)
+		return false;
+	free(check2);
+
+	return true;
+}
+
+struct darray xcomp_top_level_windows(xcb_connection_t *conn)
+{
+	DARRAY(xcb_window_t) res = {0};
+
+	// EWMH top level window listing is not supported.
+	if (ATOM__NET_CLIENT_LIST == XCB_ATOM_NONE)
+		return res.da;
+
+	xcb_screen_iterator_t screen_iter =
+		xcb_setup_roots_iterator(xcb_get_setup(conn));
+	for (; screen_iter.rem > 0; xcb_screen_next(&screen_iter)) {
+		xcb_generic_error_t *err = NULL;
+		// Read properties up to 4096*4 bytes
+		xcb_get_property_cookie_t cl_list_cookie =
+			xcb_get_property(conn, 0, screen_iter.data->root,
+					 ATOM__NET_CLIENT_LIST, 0, 0, 4096);
+		xcb_get_property_reply_t *cl_list =
+			xcb_get_property_reply(conn, cl_list_cookie, &err);
+		if (err != NULL) {
+			goto done;
+		}
+
+		uint32_t len = xcb_get_property_value_length(cl_list) /
+			       sizeof(xcb_window_t);
+		for (uint32_t i = 0; i < len; i++)
+			da_push_back(res,
+				     &(((xcb_window_t *)xcb_get_property_value(
+					     cl_list))[i]));
+
+	done:
+		free(cl_list);
+	}
+
+	return res.da;
+}
+
+xcb_window_t xcomp_find_window(xcb_connection_t *conn, Display *disp,
+			       const char *str)
+{
+	xcb_window_t ret = 0;
+	DARRAY(xcb_window_t) tlw = {0};
+	tlw.da = xcomp_top_level_windows(conn);
+	if (!str || strlen(str) == 0) {
+		if (tlw.num > 0)
+			ret = *(xcb_window_t *)darray_item(sizeof(xcb_window_t),
+							   &tlw.da, 0);
+		goto cleanup1;
+	}
+
+	size_t markSize = strlen(WIN_STRING_DIV);
+
+	const char *firstMark = strstr(str, WIN_STRING_DIV);
+	const char *secondMark =
+		firstMark ? strstr(firstMark + markSize, WIN_STRING_DIV) : NULL;
+	const char *strEnd = str + strlen(str);
+
+	const char *secondStr = firstMark + markSize;
+	const char *thirdStr = secondMark + markSize;
+
+	// wstr only consists of the window-id
+	if (!firstMark)
+		return (xcb_window_t)atol(str);
+
+	// wstr also contains window-name and window-class
+	char *wname = bzalloc(secondMark - secondStr + 1);
+	char *wcls = bzalloc(strEnd - thirdStr + 1);
+	memcpy(wname, secondStr, secondMark - secondStr);
+	memcpy(wcls, thirdStr, strEnd - thirdStr);
+	xcb_window_t wid = (xcb_window_t)strtol(str, NULL, 10);
+
+	// first try to find a match by the window-id
+	if (da_find(tlw, &wid, 0) != DARRAY_INVALID) {
+		ret = wid;
+		goto cleanup2;
+	}
+
+	// then try to find a match by name & class
+	for (size_t i = 0; i < tlw.num; i++) {
+		xcb_window_t cwin = *(xcb_window_t *)darray_item(
+			sizeof(xcb_window_t), &tlw.da, i);
+
+		struct dstr cwname = xcomp_window_name(conn, disp, cwin);
+		struct dstr cwcls = xcomp_window_class(conn, cwin);
+		bool found = (strcmp(wname, cwname.array) == 0 &&
+			      strcmp(wcls, cwcls.array));
+
+		dstr_free(&cwname);
+		dstr_free(&cwcls);
+		if (found) {
+			ret = cwin;
+			goto cleanup2;
+		}
+	}
+
+	blog(LOG_DEBUG,
+	     "Did not find new window id for Name '%s' or Class '%s'", wname,
+	     wcls);
+
+cleanup2:
+	bfree(wname);
+	bfree(wcls);
+cleanup1:
+	da_free(tlw);
+	return wid;
+}
+
+void xcomp_cleanup_pixmap(Display *disp, struct xcompcap *s)
+{
+	if (s->gltex) {
+		GLuint gltex = *(GLuint *)gs_texture_get_obj(s->gltex);
+		glBindTexture(GL_TEXTURE_2D, gltex);
+		if (s->glxpixmap) {
+			if (s->strict_binding) {
+				glXReleaseTexImageEXT(disp, s->glxpixmap,
+						      GLX_FRONT_EXT);
+			}
+			glXDestroyPixmap(disp, s->glxpixmap);
+			s->glxpixmap = 0;
+		}
+		gs_texture_destroy(s->gltex);
+		s->gltex = 0;
+	}
+
+	if (s->pixmap) {
+		XFreePixmap(disp, s->pixmap);
+		s->pixmap = 0;
+	}
+
+	if (s->tex) {
+		gs_texture_destroy(s->tex);
+		s->tex = 0;
+	}
+}
+
+static enum gs_color_format gs_format_from_tex()
+{
+	GLint iformat = 0;
+	// consider GL_ARB_internalformat_query
+	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
+				 &iformat);
+
+	// These formats are known to be wrong on Intel platforms. We intentionally
+	// use swapped internal formats here to preserve historic behavior which
+	// swapped colors accidentally and because D3D11 would not support a
+	// GS_RGBX format.
+	switch (iformat) {
+	case GL_RGB:
+		return GS_BGRX_UNORM;
+	case GL_RGBA:
+		return GS_RGBA_UNORM;
+	default:
+		return GS_RGBA_UNORM;
+	}
+}
+
+// These declarations are from libobs-opengl/gl-subsystem.h because we need to
+// handle GLX modifying textures outside libobs.
+struct fb_info;
+
+struct gs_texture {
+	gs_device_t *device;
+	enum gs_texture_type type;
+	enum gs_color_format format;
+	GLenum gl_format;
+	GLenum gl_target;
+	GLenum gl_internal_format;
+	GLenum gl_type;
+	GLuint texture;
+	uint32_t levels;
+	bool is_dynamic;
+	bool is_render_target;
+	bool is_dummy;
+	bool gen_mipmaps;
+
+	gs_samplerstate_t *cur_sampler;
+	struct fbo_info *fbo;
+};
+// End shitty hack.
+
+// XErrorHandler for glXCreatePixmap calls.
+static bool pixmap_err = false;
+static char pixmap_err_text[200];
+static int catch_pixmap_errors(Display *display, XErrorEvent *err)
+{
+	pixmap_err = true;
+	memset(pixmap_err_text, 0, 200);
+	XGetErrorText(display, err->error_code, pixmap_err_text, 200);
+	return 0;
+}
+
+void xcomp_create_pixmap(xcb_connection_t *conn, Display *disp,
+			 struct xcompcap *s, int log_level)
+{
+	if (!s->win)
+		return;
+
+	xcb_generic_error_t *err = NULL;
+	xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(conn, s->win);
+	xcb_get_geometry_reply_t *geom =
+		xcb_get_geometry_reply(conn, geom_cookie, &err);
+	if (err != NULL) {
+		return;
+	}
+
+	s->border = s->include_border ? geom->border_width : 0;
+	s->width = geom->width;
+	s->height = geom->height;
+	// We don't have an alpha channel, but may have garbage in the texture.
+	int32_t depth = geom->depth;
+	if (depth != 32) {
+		s->exclude_alpha = true;
+	}
+	xcb_window_t root = geom->root;
+	free(geom);
+
+	uint32_t vert_borders = s->crop_top + s->crop_bot + 2 * s->border;
+	uint32_t hori_borders = s->crop_left + s->crop_right + 2 * s->border;
+	// Skip 0 sized textures.
+	if (vert_borders > s->height || hori_borders > s->width)
+		return;
+
+	s->pixmap = xcb_generate_id(conn);
+	xcb_void_cookie_t name_cookie =
+		xcb_composite_name_window_pixmap(conn, s->win, s->pixmap);
+	err = NULL;
+	if ((err = xcb_request_check(conn, name_cookie)) != NULL) {
+		blog(log_level, "xcb_composite_name_window_pixmap failed");
+		s->pixmap = 0;
+		return;
+	}
+
+	const int config_attrs[] = {GLX_BIND_TO_TEXTURE_RGBA_EXT,
+				    GL_TRUE,
+				    GLX_DRAWABLE_TYPE,
+				    GLX_PIXMAP_BIT,
+				    GLX_BIND_TO_TEXTURE_TARGETS_EXT,
+				    GLX_TEXTURE_2D_BIT_EXT,
+				    GLX_DOUBLEBUFFER,
+				    GL_FALSE,
+				    None};
+	int nelem = 0;
+	GLXFBConfig *configs =
+		glXChooseFBConfig(disp, xcb_get_screen_for_root(conn, root),
+				  config_attrs, &nelem);
+
+	bool found = false;
+	GLXFBConfig config;
+	for (int i = 0; i < nelem; i++) {
+		config = configs[i];
+		XVisualInfo *visual = glXGetVisualFromFBConfig(disp, config);
+		if (!visual)
+			continue;
+
+		found = depth == visual->depth;
+		XFree(visual);
+		if (found)
+			break;
+	}
+	XFree(configs);
+	if (!found) {
+		blog(log_level, "no matching fb config found");
+		s->pixmap = 0;
+		return;
+	}
+
+	// Should be consistent format with config we are using. Since we searched on RGBA let's use RGBA here.
+	const int pixmap_attrs[] = {GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
+				    GLX_TEXTURE_FORMAT_EXT,
+				    GLX_TEXTURE_FORMAT_RGBA_EXT, None};
+
+	// Try very hard to capture errors in glXCreatePixmap for NVIDIA drivers
+	// where only one pixmap can be bound in GLX at a time.
+	pixmap_err = false;
+	XErrorHandler prev = XSetErrorHandler(catch_pixmap_errors);
+	s->glxpixmap = glXCreatePixmap(disp, config, s->pixmap, pixmap_attrs);
+	XSync(disp, false);
+
+	s->gltex = gs_texture_create(s->width, s->height, GS_RGBA_UNORM, 1, 0,
+				     GS_GL_DUMMYTEX);
+	GLuint gltex = *(GLuint *)gs_texture_get_obj(s->gltex);
+	glBindTexture(GL_TEXTURE_2D, gltex);
+	// Not respecting a captured glXCreatePixmap error will result in Xorg closing our connection.
+	if (!pixmap_err)
+		glXBindTexImageEXT(disp, s->glxpixmap, GLX_FRONT_EXT, NULL);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	// glxBindTexImageEXT might modify the textures format.
+	enum gs_color_format format = gs_format_from_tex();
+	// Check if texture is invalid... because X11 hates us.
+	int w;
+	int h;
+	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
+	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
+	glBindTexture(GL_TEXTURE_2D, 0);
+	// We must sync OBS texture format based on any glxBindTexImageEXT changes.
+	s->gltex->format = format;
+
+	XSync(disp, false);
+	if (pixmap_err || (uint32_t)w < s->width || (uint32_t)h < s->height) {
+		blog(log_level, "glXCreatePixmap failed: %s", pixmap_err_text);
+		glXDestroyPixmap(disp, s->glxpixmap);
+		XFreePixmap(disp, s->pixmap);
+		gs_texture_destroy(s->gltex);
+		s->pixmap = 0;
+		s->glxpixmap = 0;
+		s->gltex = 0;
+		XSetErrorHandler(prev);
+		return;
+	}
+	XSetErrorHandler(prev);
+
+	s->tex = gs_texture_create(
+		s->width - s->crop_left - s->crop_right - 2 * s->border,
+		s->height - s->crop_top - s->crop_bot - 2 * s->border, format,
+		1, 0, GS_GL_DUMMYTEX);
+	if (s->swapRedBlue) {
+		GLuint tex = *(GLuint *)gs_texture_get_obj(s->tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
+		glBindTexture(GL_TEXTURE_2D, 0);
+	}
+}
+
+struct reg_item {
+	struct xcompcap *src;
+	xcb_window_t win;
+};
+static DARRAY(struct reg_item) watcher_registry = {0};
+static pthread_mutex_t watcher_lock = PTHREAD_MUTEX_INITIALIZER;
+
+void watcher_register(xcb_connection_t *conn, struct xcompcap *s)
+{
+	pthread_mutex_lock(&watcher_lock);
+
+	if (xcomp_window_exists(conn, s->win)) {
+		// Subscribe to Events
+		uint32_t vals[1] = {StructureNotifyMask | ExposureMask |
+				    VisibilityChangeMask};
+		xcb_change_window_attributes(conn, s->win, XCB_CW_EVENT_MASK,
+					     vals);
+		xcb_composite_redirect_window(conn, s->win,
+					      XCB_COMPOSITE_REDIRECT_AUTOMATIC);
+
+		da_push_back(watcher_registry, (&(struct reg_item){s, s->win}));
+	}
+
+	pthread_mutex_unlock(&watcher_lock);
+}
+
+void watcher_unregister(xcb_connection_t *conn, struct xcompcap *s)
+{
+	pthread_mutex_lock(&watcher_lock);
+	size_t idx = DARRAY_INVALID;
+	xcb_window_t win = 0;
+	for (size_t i = 0; i < watcher_registry.num; i++) {
+		struct reg_item *item = (struct reg_item *)darray_item(
+			sizeof(struct reg_item), &watcher_registry.da, i);
+
+		if (item->src == s) {
+			win = item->win;
+			break;
+		}
+	}
+
+	da_erase(watcher_registry, idx);
+
+	// Check if there are still sources listening for the same window.
+	bool windowInUse = false;
+	for (size_t i = 0; i < watcher_registry.num; i++) {
+		struct reg_item *item = (struct reg_item *)darray_item(
+			sizeof(struct reg_item), &watcher_registry.da, i);
+
+		if (item->win == win) {
+			windowInUse = true;
+			break;
+		}
+	}
+
+	if (!windowInUse && xcomp_window_exists(conn, s->win)) {
+		// Last source released, stop listening for events.
+		uint32_t vals[1] = {0};
+		xcb_change_window_attributes(conn, win, XCB_CW_EVENT_MASK,
+					     vals);
+	}
+
+	pthread_mutex_unlock(&watcher_lock);
+}
+
+void watcher_process(xcb_generic_event_t *ev)
+{
+	if (!ev)
+		return;
+
+	pthread_mutex_lock(&watcher_lock);
+	xcb_window_t win = 0;
+
+	switch (ev->response_type & ~0x80) {
+	case XCB_CONFIGURE_NOTIFY:
+		win = ((xcb_configure_notify_event_t *)ev)->event;
+		break;
+	case XCB_MAP_NOTIFY:
+		win = ((xcb_map_notify_event_t *)ev)->event;
+		break;
+	case XCB_EXPOSE:
+		win = ((xcb_expose_event_t *)ev)->window;
+		break;
+	case XCB_VISIBILITY_NOTIFY:
+		win = ((xcb_visibility_notify_event_t *)ev)->window;
+		break;
+	case XCB_DESTROY_NOTIFY:
+		win = ((xcb_destroy_notify_event_t *)ev)->event;
+		break;
+	};
+
+	if (win != 0) {
+		for (size_t i = 0; i < watcher_registry.num; i++) {
+			struct reg_item *item = (struct reg_item *)darray_item(
+				sizeof(struct reg_item), &watcher_registry.da,
+				i);
+			if (item->win == win) {
+				item->src->window_changed = true;
+			}
+		}
+	}
+
+	pthread_mutex_unlock(&watcher_lock);
+}
+
+void watcher_unload()
+{
+	da_free(watcher_registry);
+}
+
+static uint32_t xcompcap_get_width(void *data)
+{
+	struct xcompcap *s = (struct xcompcap *)data;
+	if (!s->tex || !s->gltex)
+		return 0;
+
+	int32_t border = s->crop_left + s->crop_right + 2 * s->border;
+	int32_t width = s->width - border;
+	return width < 0 ? 0 : width;
+}
+
+static uint32_t xcompcap_get_height(void *data)
+{
+	struct xcompcap *s = (struct xcompcap *)data;
+	if (!s->tex || !s->gltex)
+		return 0;
+
+	int32_t border = s->crop_bot + s->crop_top + 2 * s->border;
+	int32_t height = s->height - border;
+	return height < 0 ? 0 : height;
+}
+
+static void *xcompcap_create(obs_data_t *settings, obs_source_t *source)
+{
+	struct xcompcap *s =
+		(struct xcompcap *)bzalloc(sizeof(struct xcompcap));
+	pthread_mutex_init(&s->lock, NULL);
+	s->show_cursor = true;
+	s->strict_binding = true;
+	s->source = source;
+
+	obs_enter_graphics();
+	if (strcmp(glGetString(GL_VENDOR), "NVIDIA Corporation") == 0) {
+		// Pixmap binds are extremely slow on NVIDIA cards.
+		// See: https://github.com/obsproject/obs-studio/issues/5685
+		s->strict_binding = false;
+	}
+	s->cursor = xcb_xcursor_init(conn);
+	obs_leave_graphics();
+
+	xcompcap_update(s, settings);
+	return s;
+}
+
+static void xcompcap_destroy(void *data)
+{
+	struct xcompcap *s = (struct xcompcap *)data;
+
+	obs_enter_graphics();
+	pthread_mutex_lock(&s->lock);
+
+	watcher_unregister(conn, s);
+	xcomp_cleanup_pixmap(disp, s);
+
+	if (s->tex)
+		gs_texture_destroy(s->tex);
+
+	if (s->cursor)
+		xcb_xcursor_destroy(s->cursor);
+
+	pthread_mutex_unlock(&s->lock);
+	obs_leave_graphics();
+
+	pthread_mutex_destroy(&s->lock);
+	bfree(s);
+}
+
+static void xcompcap_video_tick(void *data, float seconds)
+{
+	struct xcompcap *s = (struct xcompcap *)data;
+
+	if (!obs_source_showing(s->source))
+		return;
+
+	obs_enter_graphics();
+	pthread_mutex_lock(&s->lock);
+
+	xcb_generic_event_t *event;
+	while ((event = xcb_poll_for_queued_event(conn)))
+		watcher_process(event);
+
+	// Reacquire window after interval or immediately if reconfigured.
+	s->window_check_time += seconds;
+	bool window_lost = !xcomp_window_exists(conn, s->win);
+	if ((window_lost && s->window_check_time > FIND_WINDOW_INTERVAL) ||
+	    s->window_changed) {
+		watcher_unregister(conn, s);
+
+		s->window_changed = false;
+		s->window_check_time = 0.0;
+		s->win = xcomp_find_window(conn, disp, s->windowName);
+
+		watcher_register(conn, s);
+		xcomp_cleanup_pixmap(disp, s);
+		// Avoid excessive logging. We expect this to fail while windows are
+		// minimized or on offscreen workspaces or already captured on NVIDIA.
+		xcomp_create_pixmap(conn, disp, s, LOG_DEBUG);
+		xcb_xcursor_offset_win(conn, s->cursor, s->win);
+		xcb_xcursor_offset(s->cursor, s->cursor->x_org + s->crop_left,
+				   s->cursor->y_org + s->crop_top);
+	}
+
+	if (!s->tex || !s->gltex)
+		goto done;
+
+	if (xcompcap_get_height(s) == 0 || xcompcap_get_width(s) == 0)
+		goto done;
+
+	glBindTexture(GL_TEXTURE_2D, *(GLuint *)gs_texture_get_obj(s->gltex));
+	if (s->strict_binding && s->glxpixmap) {
+		glXReleaseTexImageEXT(disp, s->glxpixmap, GLX_FRONT_EXT);
+		glXBindTexImageEXT(disp, s->glxpixmap, GLX_FRONT_EXT, NULL);
+	}
+	gs_copy_texture_region(s->tex, 0, 0, s->gltex, s->crop_left + s->border,
+			       s->crop_top + s->border, xcompcap_get_width(s),
+			       xcompcap_get_height(s));
+	glBindTexture(GL_TEXTURE_2D, 0);
+
+	if (s->show_cursor) {
+		xcb_xcursor_update(conn, s->cursor);
+
+		s->cursor_outside = s->cursor->x < 0 || s->cursor->y < 0 ||
+				    s->cursor->x > (int)xcompcap_get_width(s) ||
+				    s->cursor->y > (int)xcompcap_get_height(s);
+	}
+
+done:
+	pthread_mutex_unlock(&s->lock);
+	obs_leave_graphics();
+}
+
+static void xcompcap_video_render(void *data, gs_effect_t *effect)
+{
+	gs_eparam_t *image; // Placate C++ goto rules.
+	struct xcompcap *s = (struct xcompcap *)data;
+
+	pthread_mutex_lock(&s->lock);
+
+	if (!s->tex || !s->gltex)
+		goto done;
+
+	if (s->exclude_alpha)
+		effect = obs_get_base_effect(OBS_EFFECT_OPAQUE);
+	else
+		effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
+
+	image = gs_effect_get_param_by_name(effect, "image");
+	gs_effect_set_texture(image, s->tex);
+
+	while (gs_effect_loop(effect, "Draw")) {
+		gs_draw_sprite(s->tex, 0, 0, 0);
+	}
+
+	if (s->gltex && s->show_cursor && !s->cursor_outside) {
+		effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
+
+		while (gs_effect_loop(effect, "Draw")) {
+			xcb_xcursor_render(s->cursor);
+		}
+	}
+
+done:
+	pthread_mutex_unlock(&s->lock);
+}
+
+struct WindowInfo {
+	struct dstr name_lower;
+	struct dstr name;
+	struct dstr desc;
+};
+
+static int cmp_wi(const void *a, const void *b)
+{
+	struct WindowInfo *awi = (struct WindowInfo *)a;
+	struct WindowInfo *bwi = (struct WindowInfo *)b;
+	return strcmp(awi->name_lower.array, bwi->name_lower.array);
+}
+
+static obs_properties_t *xcompcap_props(void *unused)
+{
+	UNUSED_PARAMETER(unused);
+
+	obs_properties_t *props = obs_properties_create();
+
+	obs_property_t *wins = obs_properties_add_list(
+		props, "capture_window", obs_module_text("Window"),
+		OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
+
+	DARRAY(struct WindowInfo) window_strings = {0};
+	struct darray windows = xcomp_top_level_windows(conn);
+	for (size_t w = 0; w < windows.num; w++) {
+		xcb_window_t win = *(xcb_window_t *)darray_item(
+			sizeof(xcb_window_t), &windows, w);
+
+		struct dstr name = xcomp_window_name(conn, disp, win);
+		struct dstr cls = xcomp_window_class(conn, win);
+
+		struct dstr desc = {0};
+		dstr_printf(&desc, "%d" WIN_STRING_DIV "%s" WIN_STRING_DIV "%s",
+			    win, name.array, cls.array);
+		dstr_free(&cls);
+
+		struct dstr name_lower;
+		dstr_init_copy_dstr(&name_lower, &name);
+		dstr_to_lower(&name_lower);
+
+		da_push_back(window_strings,
+			     (&(struct WindowInfo){name_lower, name, desc}));
+	}
+	darray_free(&windows);
+
+	qsort(window_strings.array, window_strings.num,
+	      sizeof(struct WindowInfo), cmp_wi);
+
+	for (size_t i = 0; i < window_strings.num; i++) {
+		struct WindowInfo *s = (struct WindowInfo *)darray_item(
+			sizeof(struct WindowInfo), &window_strings.da, i);
+
+		obs_property_list_add_string(wins, s->name.array,
+					     s->desc.array);
+
+		dstr_free(&s->name_lower);
+		dstr_free(&s->name);
+		dstr_free(&s->desc);
+	}
+	da_free(window_strings);
+
+	obs_properties_add_int(props, "cut_top", obs_module_text("CropTop"), 0,
+			       4096, 1);
+	obs_properties_add_int(props, "cut_left", obs_module_text("CropLeft"),
+			       0, 4096, 1);
+	obs_properties_add_int(props, "cut_right", obs_module_text("CropRight"),
+			       0, 4096, 1);
+	obs_properties_add_int(props, "cut_bot", obs_module_text("CropBottom"),
+			       0, 4096, 1);
+
+	obs_properties_add_bool(props, "swap_redblue",
+				obs_module_text("SwapRedBlue"));
+
+	obs_properties_add_bool(props, "show_cursor",
+				obs_module_text("CaptureCursor"));
+
+	obs_properties_add_bool(props, "include_border",
+				obs_module_text("IncludeXBorder"));
+
+	obs_properties_add_bool(props, "exclude_alpha",
+				obs_module_text("ExcludeAlpha"));
+
+	return props;
+}
+
+static void xcompcap_defaults(obs_data_t *settings)
+{
+	obs_data_set_default_string(settings, "capture_window", "");
+	obs_data_set_default_int(settings, "cut_top", 0);
+	obs_data_set_default_int(settings, "cut_left", 0);
+	obs_data_set_default_int(settings, "cut_right", 0);
+	obs_data_set_default_int(settings, "cut_bot", 0);
+	obs_data_set_default_bool(settings, "swap_redblue", false);
+	obs_data_set_default_bool(settings, "show_cursor", true);
+	obs_data_set_default_bool(settings, "include_border", false);
+	obs_data_set_default_bool(settings, "exclude_alpha", false);
+}
+
+static void xcompcap_update(void *data, obs_data_t *settings)
+{
+	struct xcompcap *s = (struct xcompcap *)data;
+
+	obs_enter_graphics();
+	pthread_mutex_lock(&s->lock);
+
+	s->crop_top = obs_data_get_int(settings, "cut_top");
+	s->crop_left = obs_data_get_int(settings, "cut_left");
+	s->crop_right = obs_data_get_int(settings, "cut_right");
+	s->crop_bot = obs_data_get_int(settings, "cut_bot");
+	s->swapRedBlue = obs_data_get_bool(settings, "swap_redblue");
+	s->show_cursor = obs_data_get_bool(settings, "show_cursor");
+	s->include_border = obs_data_get_bool(settings, "include_border");
+	s->exclude_alpha = obs_data_get_bool(settings, "exclude_alpha");
+
+	s->windowName = obs_data_get_string(settings, "capture_window");
+	s->win = xcomp_find_window(conn, disp, s->windowName);
+	if (s->win && s->windowName) {
+		struct dstr wname = xcomp_window_name(conn, disp, s->win);
+		struct dstr wcls = xcomp_window_class(conn, s->win);
+		blog(LOG_INFO,
+		     "[window-capture: '%s'] update settings:\n"
+		     "\ttitle: %s\n"
+		     "\tclass: %s\n",
+		     obs_source_get_name(s->source), wname.array, wcls.array);
+		dstr_free(&wname);
+		dstr_free(&wcls);
+	}
+
+	watcher_register(conn, s);
+	xcomp_cleanup_pixmap(disp, s);
+	xcomp_create_pixmap(conn, disp, s, LOG_ERROR);
+	xcb_xcursor_offset_win(conn, s->cursor, s->win);
+	xcb_xcursor_offset(s->cursor, s->cursor->x_org + s->crop_left,
+			   s->cursor->y_org + s->crop_top);
+
+	pthread_mutex_unlock(&s->lock);
+	obs_leave_graphics();
+}
+
+static const char *xcompcap_getname(void *data)
+{
+	UNUSED_PARAMETER(data);
+	return obs_module_text("XCCapture");
+}
+
+void xcomposite_load(void)
+{
+	disp = XOpenDisplay(NULL);
+	conn = XGetXCBConnection(disp);
+	if (xcb_connection_has_error(conn)) {
+		blog(LOG_ERROR, "failed opening display");
+		return;
+	}
+
+	const xcb_query_extension_reply_t *xcomp_ext =
+		xcb_get_extension_data(conn, &xcb_composite_id);
+	if (!xcomp_ext->present) {
+		blog(LOG_ERROR, "Xcomposite extension not supported");
+		return;
+	}
+
+	xcb_composite_query_version_cookie_t version_cookie =
+		xcb_composite_query_version(conn, 0, 2);
+	xcb_composite_query_version_reply_t *version =
+		xcb_composite_query_version_reply(conn, version_cookie, NULL);
+	if (version->major_version == 0 && version->minor_version < 2) {
+		blog(LOG_ERROR, "Xcomposite extension is too old: %d.%d < 0.2",
+		     version->major_version, version->minor_version);
+		free(version);
+		return;
+	}
+	free(version);
+
+	// Must be done before other helpers called.
+	xcomp_gather_atoms(conn);
+
+	xcb_screen_t *screen_default =
+		xcb_get_screen(conn, DefaultScreen(disp));
+	if (!screen_default || !xcomp_check_ewmh(conn, screen_default->root)) {
+		blog(LOG_ERROR,
+		     "window manager does not support Extended Window Manager Hints (EWMH).\nXComposite capture disabled.");
+		return;
+	}
+
+	struct obs_source_info sinfo = {
+		.id = "xcomposite_input",
+		.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
+				OBS_SOURCE_DO_NOT_DUPLICATE,
+		.get_name = xcompcap_getname,
+		.create = xcompcap_create,
+		.destroy = xcompcap_destroy,
+		.get_properties = xcompcap_props,
+		.get_defaults = xcompcap_defaults,
+		.update = xcompcap_update,
+		.video_tick = xcompcap_video_tick,
+		.video_render = xcompcap_video_render,
+		.get_width = xcompcap_get_width,
+		.get_height = xcompcap_get_height,
+		.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
+	};
+
+	obs_register_source(&sinfo);
+}
+
+void xcomposite_unload(void)
+{
+	XCloseDisplay(disp);
+	disp = NULL;
+	conn = NULL;
+	watcher_unload();
+}

+ 4 - 0
plugins/linux-capture/xcomposite-input.h

@@ -0,0 +1,4 @@
+#pragma once
+
+void xcomposite_load(void);
+void xcomposite_unload(void);

+ 0 - 93
plugins/linux-capture/xcomposite-main.cpp

@@ -1,93 +0,0 @@
-#include <obs-module.h>
-
-#include "xcompcap-main.hpp"
-
-static void *xcompcap_create(obs_data_t *settings, obs_source_t *source)
-{
-	return new XCompcapMain(settings, source);
-}
-
-static void xcompcap_destroy(void *data)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	delete cc;
-}
-
-static void xcompcap_video_tick(void *data, float seconds)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	cc->tick(seconds);
-}
-
-static void xcompcap_video_render(void *data, gs_effect_t *effect)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	cc->render(effect);
-}
-
-static uint32_t xcompcap_getwidth(void *data)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	return cc->width();
-}
-
-static uint32_t xcompcap_getheight(void *data)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	return cc->height();
-}
-
-static obs_properties_t *xcompcap_props(void *unused)
-{
-	UNUSED_PARAMETER(unused);
-
-	return XCompcapMain::properties();
-}
-
-void xcompcap_defaults(obs_data_t *settings)
-{
-	XCompcapMain::defaults(settings);
-}
-
-void xcompcap_update(void *data, obs_data_t *settings)
-{
-	XCompcapMain *cc = (XCompcapMain *)data;
-	cc->updateSettings(settings);
-}
-
-static const char *xcompcap_getname(void *)
-{
-	return obs_module_text("XCCapture");
-}
-
-extern "C" void xcomposite_load(void)
-{
-	if (!XCompcapMain::init())
-		return;
-
-	obs_source_info sinfo;
-	memset(&sinfo, 0, sizeof(obs_source_info));
-
-	sinfo.id = "xcomposite_input";
-	sinfo.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
-			     OBS_SOURCE_DO_NOT_DUPLICATE;
-
-	sinfo.get_name = xcompcap_getname;
-	sinfo.create = xcompcap_create;
-	sinfo.destroy = xcompcap_destroy;
-	sinfo.get_properties = xcompcap_props;
-	sinfo.get_defaults = xcompcap_defaults;
-	sinfo.update = xcompcap_update;
-	sinfo.video_tick = xcompcap_video_tick;
-	sinfo.video_render = xcompcap_video_render;
-	sinfo.get_width = xcompcap_getwidth;
-	sinfo.get_height = xcompcap_getheight;
-	sinfo.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
-
-	obs_register_source(&sinfo);
-}
-
-extern "C" void xcomposite_unload(void)
-{
-	XCompcapMain::deinit();
-}

+ 34 - 2
plugins/linux-capture/xcursor-xcb.c

@@ -17,6 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include <stdint.h>
 #include <xcb/xfixes.h>
+#include <xcb/xcb.h>
 
 #include <util/bmem.h>
 #include "xcursor-xcb.h"
@@ -73,9 +74,12 @@ void xcb_xcursor_destroy(xcb_xcursor_t *data)
 	bfree(data);
 }
 
-void xcb_xcursor_update(xcb_xcursor_t *data,
-			xcb_xfixes_get_cursor_image_reply_t *xc)
+void xcb_xcursor_update(xcb_connection_t *xcb, xcb_xcursor_t *data)
 {
+	xcb_xfixes_get_cursor_image_cookie_t xc_c =
+		xcb_xfixes_get_cursor_image_unchecked(xcb);
+	xcb_xfixes_get_cursor_image_reply_t *xc =
+		xcb_xfixes_get_cursor_image_reply(xcb, xc_c, NULL);
 	if (!data || !xc)
 		return;
 
@@ -86,6 +90,8 @@ void xcb_xcursor_update(xcb_xcursor_t *data,
 	data->y = xc->y - data->y_org;
 	data->x_render = data->x - xc->xhot;
 	data->y_render = data->y - xc->yhot;
+
+	free(xc);
 }
 
 void xcb_xcursor_render(xcb_xcursor_t *data)
@@ -125,3 +131,29 @@ void xcb_xcursor_offset(xcb_xcursor_t *data, const int x_org, const int y_org)
 	data->x_org = x_org;
 	data->y_org = y_org;
 }
+
+void xcb_xcursor_offset_win(xcb_connection_t *xcb, xcb_xcursor_t *data,
+			    xcb_window_t win)
+{
+	if (!win)
+		return;
+
+	xcb_generic_error_t *err = NULL;
+	xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(xcb, win);
+	xcb_get_geometry_reply_t *geom =
+		xcb_get_geometry_reply(xcb, geom_cookie, &err);
+	if (err) {
+		free(geom);
+		return;
+	}
+
+	xcb_translate_coordinates_cookie_t coords_cookie =
+		xcb_translate_coordinates(xcb, win, geom->root, 0, 0);
+	xcb_translate_coordinates_reply_t *coords =
+		xcb_translate_coordinates_reply(xcb, coords_cookie, &err);
+	if (!err)
+		xcb_xcursor_offset(data, coords->dst_x, coords->dst_y);
+
+	free(coords);
+	free(geom);
+}

+ 9 - 4
plugins/linux-capture/xcursor-xcb.h

@@ -53,14 +53,13 @@ void xcb_xcursor_destroy(xcb_xcursor_t *data);
 
 /**
  * Update the cursor data
+ * @param xcb xcb connection
  * @param data xcursor object
- * @param xc xcb cursor image reply
  *
  * @note This needs to be executed within a valid render context
  *
  */
-void xcb_xcursor_update(xcb_xcursor_t *data,
-			xcb_xfixes_get_cursor_image_reply_t *xc);
+void xcb_xcursor_update(xcb_connection_t *xcb, xcb_xcursor_t *data);
 
 /**
  * Draw the cursor
@@ -70,10 +69,16 @@ void xcb_xcursor_update(xcb_xcursor_t *data,
 void xcb_xcursor_render(xcb_xcursor_t *data);
 
 /**
- * Specify offset for the cursor
+ * Specify a manual offset for the cursor.
  */
 void xcb_xcursor_offset(xcb_xcursor_t *data, const int x_org, const int y_org);
 
+/**
+ * Update the offset to match the window's origin.
+ */
+void xcb_xcursor_offset_win(xcb_connection_t *xcb, xcb_xcursor_t *data,
+			    xcb_window_t win);
+
 #ifdef __cplusplus
 }
 #endif

+ 0 - 143
plugins/linux-capture/xcursor.c

@@ -1,143 +0,0 @@
-/*
-Copyright (C) 2014 by Leonhard Oelke <[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 <stdint.h>
-#include <X11/extensions/Xfixes.h>
-
-#include <util/bmem.h>
-#include "xcursor.h"
-
-/*
- * Get pixel data for the cursor
- *
- * XFixes has the data defined as unsigned long, so we can not use memcpy.
- * Theres a lot of talk about this in other implementation and they tend to
- * be really complicated, but this naive approach seems to work fine ...
- */
-static uint32_t *xcursor_pixels(XFixesCursorImage *xc)
-{
-	uint_fast32_t size = xc->width * xc->height;
-	uint32_t *pixels = bmalloc(size * sizeof(uint32_t));
-
-	for (uint_fast32_t i = 0; i < size; ++i)
-		pixels[i] = (uint32_t)xc->pixels[i];
-
-	return pixels;
-}
-
-/*
- * Create the cursor texture, either by updating if the new cursor has the same
- * size or by creating a new texture if the size is different
- */
-static void xcursor_create(xcursor_t *data, XFixesCursorImage *xc)
-{
-	uint32_t *pixels = xcursor_pixels(xc);
-	if (!pixels)
-		return;
-
-	if (data->tex && data->last_height == xc->width &&
-	    data->last_width == xc->height) {
-		gs_texture_set_image(data->tex, (const uint8_t *)pixels,
-				     xc->width * sizeof(uint32_t), False);
-	} else {
-		if (data->tex)
-			gs_texture_destroy(data->tex);
-
-		data->tex = gs_texture_create(xc->width, xc->height, GS_BGRA, 1,
-					      (const uint8_t **)&pixels,
-					      GS_DYNAMIC);
-	}
-
-	bfree(pixels);
-
-	data->last_serial = xc->cursor_serial;
-	data->last_width = xc->width;
-	data->last_height = xc->height;
-}
-
-xcursor_t *xcursor_init(Display *dpy)
-{
-	xcursor_t *data = bzalloc(sizeof(xcursor_t));
-
-	data->dpy = dpy;
-	xcursor_tick(data);
-
-	return data;
-}
-
-void xcursor_destroy(xcursor_t *data)
-{
-	if (data->tex)
-		gs_texture_destroy(data->tex);
-	bfree(data);
-}
-
-void xcursor_tick(xcursor_t *data)
-{
-	XFixesCursorImage *xc = XFixesGetCursorImage(data->dpy);
-	if (!xc)
-		return;
-
-	if (!data->tex || data->last_serial != xc->cursor_serial)
-		xcursor_create(data, xc);
-
-	data->x = (int_fast32_t)xc->x - (int_fast32_t)data->x_org;
-	data->y = (int_fast32_t)xc->y - (int_fast32_t)data->y_org;
-	data->render_x = xc->x - xc->xhot - data->x_org;
-	data->render_y = xc->y - xc->yhot - data->y_org;
-
-	XFree(xc);
-}
-
-void xcursor_render(xcursor_t *data, int x_offset, int y_offset)
-{
-	if (!data->tex)
-		return;
-
-	const bool linear_srgb = gs_get_linear_srgb();
-
-	const bool previous = gs_framebuffer_srgb_enabled();
-	gs_enable_framebuffer_srgb(linear_srgb);
-
-	gs_effect_t *effect = gs_get_effect();
-	gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
-	if (linear_srgb)
-		gs_effect_set_texture_srgb(image, data->tex);
-	else
-		gs_effect_set_texture(image, data->tex);
-
-	gs_blend_state_push();
-	gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA);
-	gs_enable_color(true, true, true, false);
-
-	gs_matrix_push();
-	gs_matrix_translate3f(data->render_x + x_offset,
-			      data->render_y + y_offset, 0.0f);
-	gs_draw_sprite(data->tex, 0, 0, 0);
-	gs_matrix_pop();
-
-	gs_enable_color(true, true, true, true);
-	gs_blend_state_pop();
-
-	gs_enable_framebuffer_srgb(previous);
-}
-
-void xcursor_offset(xcursor_t *data, int_fast32_t x_org, int_fast32_t y_org)
-{
-	data->x_org = x_org;
-	data->y_org = y_org;
-}

+ 0 - 73
plugins/linux-capture/xcursor.h

@@ -1,73 +0,0 @@
-/*
-Copyright (C) 2014 by Leonhard Oelke <[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>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct {
-	Display *dpy;
-	float render_x;
-	float render_y;
-	unsigned long last_serial;
-	uint_fast32_t last_width;
-	uint_fast32_t last_height;
-	gs_texture_t *tex;
-
-	int_fast32_t x, y;
-	int_fast32_t x_org;
-	int_fast32_t y_org;
-} xcursor_t;
-
-/**
- * Initializes the xcursor object
- *
- * This needs to be executed within a valid render context
- */
-xcursor_t *xcursor_init(Display *dpy);
-
-/**
- * Destroys the xcursor object
- */
-void xcursor_destroy(xcursor_t *data);
-
-/**
- * Update the cursor texture
- *
- * This needs to be executed within a valid render context
- */
-void xcursor_tick(xcursor_t *data);
-
-/**
- * Draw the cursor
- *
- * This needs to be executed within a valid render context
- */
-void xcursor_render(xcursor_t *data, int x_offset, int y_offset);
-
-/**
- * Specify offset for the cursor
- */
-void xcursor_offset(xcursor_t *data, int_fast32_t x_org, int_fast32_t y_org);
-
-#ifdef __cplusplus
-}
-#endif

+ 12 - 0
plugins/linux-capture/xhelpers.c

@@ -319,3 +319,15 @@ xcb_screen_t *xcb_get_screen(xcb_connection_t *xcb, int screen)
 
 	return NULL;
 }
+
+int xcb_get_screen_for_root(xcb_connection_t *conn, xcb_window_t root)
+{
+	xcb_screen_iterator_t iter =
+		xcb_setup_roots_iterator(xcb_get_setup(conn));
+	for (int i = 0; iter.rem > 0; xcb_screen_next(&iter)) {
+		if (iter.data->root == root)
+			return i;
+		i++;
+	}
+	return 0;
+}

+ 8 - 0
plugins/linux-capture/xhelpers.h

@@ -136,6 +136,14 @@ void xshm_xcb_detach(xcb_shm_t *shm);
  */
 xcb_screen_t *xcb_get_screen(xcb_connection_t *xcb, int screen);
 
+/**
+ * Get the screen id for the given root window
+ *
+ * @param conn xcb connection
+ * @param root window id for the root window
+ * @return screen id
+ */
+int xcb_get_screen_for_root(xcb_connection_t *conn, xcb_window_t root);
 #ifdef __cplusplus
 }
 #endif

+ 1 - 6
plugins/linux-capture/xshm-input.c

@@ -481,18 +481,14 @@ static void xshm_video_tick(void *vptr, float seconds)
 
 	xcb_shm_get_image_cookie_t img_c;
 	xcb_shm_get_image_reply_t *img_r;
-	xcb_xfixes_get_cursor_image_cookie_t cur_c;
-	xcb_xfixes_get_cursor_image_reply_t *cur_r;
 
 	img_c = xcb_shm_get_image_unchecked(data->xcb, data->xcb_screen->root,
 					    data->adj_x_org, data->adj_y_org,
 					    data->adj_width, data->adj_height,
 					    ~0, XCB_IMAGE_FORMAT_Z_PIXMAP,
 					    data->xshm->seg, 0);
-	cur_c = xcb_xfixes_get_cursor_image_unchecked(data->xcb);
 
 	img_r = xcb_shm_get_image_reply(data->xcb, img_c, NULL);
-	cur_r = xcb_xfixes_get_cursor_image_reply(data->xcb, cur_c, NULL);
 
 	if (!img_r)
 		goto exit;
@@ -501,13 +497,12 @@ static void xshm_video_tick(void *vptr, float seconds)
 
 	gs_texture_set_image(data->texture, (void *)data->xshm->data,
 			     data->adj_width * 4, false);
-	xcb_xcursor_update(data->cursor, cur_r);
+	xcb_xcursor_update(data->xcb, data->cursor);
 
 	obs_leave_graphics();
 
 exit:
 	free(img_r);
-	free(cur_r);
 }
 
 /**