f4a8137412
Some window managers like i3 don't issue Expose events when switching between workspaces, but send VisibilityNotify events. This causes the stream to freeze even when the workspace is switched back. Related issue: #863
464 lines
7.5 KiB
C++
464 lines
7.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 <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();
|
|
}
|