4b8c490ed5
Track all windows corresponding to sources and ensure that we only disable XSelectInput events once all sources for a given window have been removed. Previously we may have stopped listening for events if multiple sources captured the same window and one was removed. We also move window redirection into the helper to avoid similar issues.
515 lines
9.0 KiB
C++
515 lines
9.0 KiB
C++
#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;
|
|
}
|
|
|
|
static void getAllWindows(Window parent, std::list<Window> &windows)
|
|
{
|
|
UNUSED_PARAMETER(parent);
|
|
UNUSED_PARAMETER(windows);
|
|
}
|
|
|
|
std::list<Window> getAllWindows()
|
|
{
|
|
std::list<Window> res;
|
|
|
|
for (int i = 0; i < ScreenCount(disp()); ++i)
|
|
getAllWindows(RootWindow(disp(), i), res);
|
|
|
|
return res;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
std::string getWindowCommand(Window win)
|
|
{
|
|
Atom xi = XInternAtom(disp(), "WM_COMMAND", false);
|
|
int n;
|
|
char **list = 0;
|
|
XTextProperty tp;
|
|
std::string res = "error";
|
|
|
|
XGetTextProperty(disp(), win, &tp, xi);
|
|
|
|
if (!tp.nitems)
|
|
return std::string();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
XFree(tp.value);
|
|
|
|
return res;
|
|
}
|
|
|
|
int getWindowPid(Window win)
|
|
{
|
|
UNUSED_PARAMETER(win);
|
|
return 1234; //TODO
|
|
}
|
|
|
|
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();
|
|
}
|