obs-studio/plugins/linux-capture/xcompcap-helper.cpp

429 lines
7.2 KiB
C++

#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>
#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::unordered_set<Window> changedWindows;
static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER;
void processEvents()
{
PLock lock(&changeLock);
XLockDisplay(disp());
while (XEventsQueued(disp(), QueuedAfterReading) > 0) {
XEvent ev;
XNextEvent(disp(), &ev);
if (ev.type == ConfigureNotify)
changedWindows.insert(ev.xconfigure.event);
if (ev.type == MapNotify)
changedWindows.insert(ev.xmap.event);
if (ev.type == Expose)
changedWindows.insert(ev.xexpose.window);
if (ev.type == VisibilityNotify)
changedWindows.insert(ev.xvisibility.window);
if (ev.type == DestroyNotify)
changedWindows.insert(ev.xdestroywindow.event);
}
XUnlockDisplay(disp());
}
bool windowWasReconfigured(Window win)
{
PLock lock(&changeLock);
auto it = changedWindows.find(win);
if (it != changedWindows.end()) {
changedWindows.erase(it);
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();
}