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.
master
Kurt Kartaltepe 2022-02-20 17:37:49 -08:00 committed by Georges Basile Stavracas Neto
parent f2ea473373
commit 1604400e48
17 changed files with 1138 additions and 1680 deletions

View File

@ -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}}"

View File

@ -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")

View File

@ -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();

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
};

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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

View File

@ -1,143 +0,0 @@
/*
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
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;
}

View File

@ -1,73 +0,0 @@
/*
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
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

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
/**