2014-04-28 17:59:53 -07:00
|
|
|
#include <X11/Xlib.h>
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
#include <X11/extensions/Xcomposite.h>
|
|
|
|
|
|
|
|
#include <unordered_set>
|
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
#include <obs-module.h>
|
2016-06-05 13:50:53 -07:00
|
|
|
#include <util/platform.h>
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2014-08-29 17:05:32 -07:00
|
|
|
#include "xcompcap-helper.hpp"
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
namespace XCompcap {
|
|
|
|
static Display *xdisplay = 0;
|
|
|
|
|
|
|
|
Display *disp()
|
2014-04-28 17:59:53 -07:00
|
|
|
{
|
2019-06-22 22:13:45 -07:00
|
|
|
if (!xdisplay)
|
|
|
|
xdisplay = XOpenDisplay(NULL);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return xdisplay;
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
void cleanupDisplay()
|
|
|
|
{
|
|
|
|
if (!xdisplay)
|
|
|
|
return;
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XCloseDisplay(xdisplay);
|
|
|
|
xdisplay = 0;
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
static void getAllWindows(Window parent, std::list<Window> &windows)
|
|
|
|
{
|
|
|
|
UNUSED_PARAMETER(parent);
|
|
|
|
UNUSED_PARAMETER(windows);
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
std::list<Window> getAllWindows()
|
|
|
|
{
|
|
|
|
std::list<Window> res;
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
for (int i = 0; i < ScreenCount(disp()); ++i)
|
|
|
|
getAllWindows(RootWindow(disp(), i), res);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return res;
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
// Specification for checking for ewmh support at
|
|
|
|
// http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472693600
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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];
|
2015-11-14 00:34:32 -08:00
|
|
|
}
|
2019-06-22 22:13:45 -07:00
|
|
|
if (data) {
|
|
|
|
XFree(data);
|
|
|
|
data = NULL;
|
2015-11-14 00:34:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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;
|
2015-11-14 00:34:32 -08:00
|
|
|
}
|
2019-06-22 22:13:45 -07:00
|
|
|
if (status == Success && data) {
|
2014-04-28 17:59:53 -07:00
|
|
|
XFree(data);
|
|
|
|
}
|
2019-06-22 22:13:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return ewmh_window != 0;
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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");
|
2014-04-28 17:59:53 -07:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true);
|
|
|
|
Atom actualType;
|
|
|
|
int format;
|
|
|
|
unsigned long num, bytes;
|
|
|
|
Window *data = 0;
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
for (int i = 0; i < ScreenCount(disp()); ++i) {
|
|
|
|
Window rootWin = RootWindow(disp(), i);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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);
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getRootWindowScreen(Window root)
|
|
|
|
{
|
|
|
|
XWindowAttributes attr;
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (!XGetWindowAttributes(disp(), root, &attr))
|
|
|
|
return DefaultScreen(disp());
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return XScreenNumberOfScreen(attr.screen);
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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";
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XGetTextProperty(disp(), win, &tp, netWmName);
|
2014-05-15 02:12:22 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (!tp.nitems)
|
|
|
|
XGetWMName(disp(), win, &tp);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (!tp.nitems)
|
|
|
|
return "error";
|
2016-06-05 13:50:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (tp.encoding == XA_STRING) {
|
|
|
|
res = (char *)tp.value;
|
|
|
|
} else {
|
|
|
|
int ret = XmbTextPropertyToTextList(disp(), &tp, &list, &n);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (ret >= Success && n > 0 && *list) {
|
|
|
|
res = *list;
|
|
|
|
XFreeStringList(list);
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
char *conv = nullptr;
|
|
|
|
if (os_mbs_to_utf8_ptr(res.c_str(), 0, &conv))
|
|
|
|
res = conv;
|
|
|
|
bfree(conv);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XFree(tp.value);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return res;
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
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);
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XFree(tp.value);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getWindowPid(Window win)
|
|
|
|
{
|
|
|
|
UNUSED_PARAMETER(win);
|
|
|
|
return 1234; //TODO
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
static std::unordered_set<Window> changedWindows;
|
|
|
|
static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
void processEvents()
|
|
|
|
{
|
|
|
|
PLock lock(&changeLock);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XLockDisplay(disp());
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
while (XEventsQueued(disp(), QueuedAfterReading) > 0) {
|
|
|
|
XEvent ev;
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XNextEvent(disp(), &ev);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (ev.type == ConfigureNotify)
|
|
|
|
changedWindows.insert(ev.xconfigure.event);
|
2014-12-24 12:08:02 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (ev.type == MapNotify)
|
|
|
|
changedWindows.insert(ev.xmap.event);
|
2017-03-28 15:57:14 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (ev.type == Expose)
|
|
|
|
changedWindows.insert(ev.xexpose.window);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (ev.type == VisibilityNotify)
|
|
|
|
changedWindows.insert(ev.xvisibility.window);
|
|
|
|
|
|
|
|
if (ev.type == DestroyNotify)
|
|
|
|
changedWindows.insert(ev.xdestroywindow.event);
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
XUnlockDisplay(disp());
|
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
bool windowWasReconfigured(Window win)
|
|
|
|
{
|
|
|
|
PLock lock(&changeLock);
|
2014-05-15 02:12:22 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
auto it = changedWindows.find(win);
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
if (it != changedWindows.end()) {
|
|
|
|
changedWindows.erase(it);
|
|
|
|
return true;
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
return false;
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
}
|
2014-04-28 17:59:53 -07:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
PLock::PLock(pthread_mutex_t *mtx, bool trylock) : m(mtx)
|
2014-04-28 17:59:53 -07:00
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (trylock)
|
2014-04-28 17:59:53 -07:00
|
|
|
islock = mtx && pthread_mutex_trylock(mtx) == 0;
|
|
|
|
else
|
|
|
|
islock = mtx && pthread_mutex_lock(mtx) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
PLock::~PLock()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (islock) {
|
2014-04-28 17:59:53 -07:00
|
|
|
pthread_mutex_unlock(m);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PLock::isLocked()
|
|
|
|
{
|
|
|
|
return islock;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PLock::unlock()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (islock) {
|
2014-04-28 17:59:53 -07:00
|
|
|
pthread_mutex_unlock(m);
|
|
|
|
islock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PLock::lock()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (!islock) {
|
2014-04-28 17:59:53 -07:00
|
|
|
pthread_mutex_lock(m);
|
|
|
|
islock = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
static bool *curErrorTarget = 0;
|
2014-04-28 17:59:53 -07:00
|
|
|
static char curErrorText[200];
|
2019-06-22 22:13:45 -07:00
|
|
|
static int xerrorlock_handler(Display *disp, XErrorEvent *err)
|
2014-04-28 17:59:53 -07:00
|
|
|
{
|
|
|
|
|
2014-05-15 02:12:22 -07:00
|
|
|
if (curErrorTarget)
|
2014-04-28 17:59:53 -07:00
|
|
|
*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()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (!islock) {
|
2014-04-28 17:59:53 -07:00
|
|
|
XLockDisplay(XCompcap::disp());
|
|
|
|
XSync(XCompcap::disp(), 0);
|
|
|
|
|
|
|
|
curErrorTarget = &goterr;
|
|
|
|
curErrorText[0] = 0;
|
|
|
|
prevhandler = XSetErrorHandler(xerrorlock_handler);
|
|
|
|
|
|
|
|
islock = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void XErrorLock::unlock()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (islock) {
|
2016-01-25 08:43:32 -08:00
|
|
|
XSync(XCompcap::disp(), 0);
|
|
|
|
|
2014-04-28 17:59:53 -07:00
|
|
|
curErrorTarget = 0;
|
|
|
|
XSetErrorHandler(prevhandler);
|
|
|
|
prevhandler = 0;
|
|
|
|
XUnlockDisplay(XCompcap::disp());
|
|
|
|
islock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool XErrorLock::gotError()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (!islock)
|
2014-04-28 17:59:53 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
XSync(XCompcap::disp(), 0);
|
|
|
|
|
|
|
|
bool res = goterr;
|
|
|
|
goterr = false;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string XErrorLock::getErrorText()
|
|
|
|
{
|
|
|
|
return curErrorText;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XErrorLock::resetError()
|
|
|
|
{
|
2014-05-15 02:12:22 -07:00
|
|
|
if (islock)
|
2014-04-28 17:59:53 -07:00
|
|
|
XSync(XCompcap::disp(), 0);
|
|
|
|
|
|
|
|
goterr = false;
|
|
|
|
curErrorText[0] = 0;
|
|
|
|
}
|
|
|
|
|
linux-capture: Fix window capture crashes
The xcomposite window capture crashes were due to a few factors:
-------------------------------
1.) The source's X error handler was possibly being overwritten by
another part of the program despite us locking the display, presumably
something in Qt which isn't locking the display when pushing/popping its
own error handler (though this is not yet certain). The source's calls
to X functions happen in the graphics thread, which is separate from the
UI thread, and it was noticed that somehow the error handler would be
overwritten almost seemingly at random, indicating that something else
in the program outside of OBS code was not locking the display while
pushing/popping the error handler.
To replicate this, make it so that the source cannot find the target
window and so it continually searches for it each video_tick call, then
resize the main OBS window continually (which causes Qt to push/pop its
own error handlers). A crash will almost always occur due to BadWindow
despite our error handling.
2.) Calling X functions with a window ID that no longer exists,
particularly XGetWindowAttributes, in conjunction the unknown error
handler set in case #1 would cause the program to outright crash because
that error handler is programmed to crash on BadWindow for whatever
reason. The source would call X functions without even checking if
'win' was 0.
3.) The source stored window IDs (in JSON, even if they've long since
become invalid/pointless, such as system restarts). This is a bad
practice and will result in more cases of BadWindow.
Fixing the problem (reducing the possibility of getting BadWindow):
-------------------------------
Step 1.) Deprecate and ignore window IDs in stored settings. Instead of
using window IDs to find the window, we now must always search the
windows and find the target window via the window name exclusively.
This helps ensure that we actually consistently have a working window
ID.
Step 2.) Do not call any X functions if the window ID is 0.
Step 3.) Reset the window ID to 0 any time the window has updated, and
make the source find the window again to ensure it still exists before
attempting to use any X functions on the window ID again.
2016-06-04 14:20:41 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-28 17:59:53 -07:00
|
|
|
ObsGsContextHolder::ObsGsContextHolder()
|
|
|
|
{
|
2014-08-04 05:48:58 -07:00
|
|
|
obs_enter_graphics();
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
ObsGsContextHolder::~ObsGsContextHolder()
|
|
|
|
{
|
2014-08-04 05:48:58 -07:00
|
|
|
obs_leave_graphics();
|
2014-04-28 17:59:53 -07:00
|
|
|
}
|