01491a96d3
On i3wm, windows aren't unmapped when switching away from a window's workspace, but it does cause OBS to lose the capture. Because switching back will not trigger a MapNotify, the capture fails to restart unless you resize or move the window (ConfigureNotify). An Expose event is fired by the wm, however, so catching this correctly restarts the capture.
348 lines
5.5 KiB
C++
348 lines
5.5 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 "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;
|
|
}
|
|
|
|
std::list<Window> getTopLevelWindows()
|
|
{
|
|
std::list<Window> 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 getWindowName(Window win)
|
|
{
|
|
Atom netWmName = XInternAtom(disp(), "_NET_WM_NAME", 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);
|
|
}
|
|
}
|
|
|
|
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 == 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) {
|
|
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;
|
|
}
|
|
|
|
|
|
ObsGsContextHolder::ObsGsContextHolder()
|
|
{
|
|
obs_enter_graphics();
|
|
}
|
|
|
|
ObsGsContextHolder::~ObsGsContextHolder()
|
|
{
|
|
obs_leave_graphics();
|
|
}
|