Browse Source

Merge pull request #3047 from MaZderMind/bugfix/3040

linux-capture: Fix Problems with the Window-Selection of the XComposite Source
Jim 4 years ago
parent
commit
40a47b3af4

+ 89 - 53
plugins/linux-capture/xcompcap-helper.cpp

@@ -4,6 +4,7 @@
 #include <X11/extensions/Xcomposite.h>
 
 #include <unordered_set>
+#include <map>
 #include <pthread.h>
 
 #include <obs-module.h>
@@ -31,22 +32,6 @@ void cleanupDisplay()
 	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
 
@@ -181,42 +166,73 @@ std::string getWindowAtom(Window win, const char *atom)
 	return res;
 }
 
-std::string getWindowCommand(Window win)
+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)
 {
-	Atom xi = XInternAtom(disp(), "WM_COMMAND", false);
-	int n;
-	char **list = 0;
-	XTextProperty tp;
-	std::string res = "error";
+	PLock lock(&changeLock);
 
-	XGetTextProperty(disp(), win, &tp, xi);
+	blog(LOG_DEBUG, "registerSource(source=%p, win=%ld)", source, win);
 
-	if (!tp.nitems)
-		return std::string();
+	auto it = windowForSource.find(source);
 
-	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);
-		}
+	if (it != windowForSource.end()) {
+		windowForSource.erase(it);
 	}
 
-	XFree(tp.value);
+	// Subscribe to Events
+	XSelectInput(disp(), win,
+		     StructureNotifyMask | ExposureMask | VisibilityChangeMask);
+	XCompositeRedirectWindow(disp(), win, CompositeRedirectAutomatic);
+	XSync(disp(), 0);
 
-	return res;
+	windowForSource.insert(std::make_pair(source, win));
 }
 
-int getWindowPid(Window win)
+void unregisterSource(XCompcapMain *source)
 {
-	UNUSED_PARAMETER(win);
-	return 1234; //TODO
+	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);
+		}
+	}
 }
 
-static std::unordered_set<Window> changedWindows;
-static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER;
 void processEvents()
 {
 	PLock lock(&changeLock);
@@ -225,36 +241,56 @@ void processEvents()
 
 	while (XEventsQueued(disp(), QueuedAfterReading) > 0) {
 		XEvent ev;
+		Window win = 0;
 
 		XNextEvent(disp(), &ev);
 
 		if (ev.type == ConfigureNotify)
-			changedWindows.insert(ev.xconfigure.event);
+			win = ev.xconfigure.event;
+
+		else if (ev.type == MapNotify)
+			win = ev.xmap.event;
+
+		else if (ev.type == Expose)
+			win = ev.xexpose.window;
 
-		if (ev.type == MapNotify)
-			changedWindows.insert(ev.xmap.event);
+		else if (ev.type == VisibilityNotify)
+			win = ev.xvisibility.window;
 
-		if (ev.type == Expose)
-			changedWindows.insert(ev.xexpose.window);
+		else if (ev.type == DestroyNotify)
+			win = ev.xdestroywindow.event;
 
-		if (ev.type == VisibilityNotify)
-			changedWindows.insert(ev.xvisibility.window);
+		if (win != 0) {
+			blog(LOG_DEBUG, "processEvents(): windowChanged=%ld",
+			     win);
 
-		if (ev.type == DestroyNotify)
-			changedWindows.insert(ev.xdestroywindow.event);
+			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 windowWasReconfigured(Window win)
+bool sourceWasReconfigured(XCompcapMain *source)
 {
 	PLock lock(&changeLock);
 
-	auto it = changedWindows.find(win);
+	auto it = changedSources.find(source);
 
-	if (it != changedWindows.end()) {
-		changedWindows.erase(it);
+	if (it != changedSources.end()) {
+		changedSources.erase(it);
+		blog(LOG_DEBUG, "sourceWasReconfigured(source=%p)=true",
+		     source);
 		return true;
 	}
 

+ 5 - 4
plugins/linux-capture/xcompcap-helper.hpp

@@ -70,17 +70,16 @@ public:
 	~ObsGsContextHolder();
 };
 
+class XCompcapMain;
+
 namespace XCompcap {
 Display *disp();
 void cleanupDisplay();
 
-std::string getWindowCommand(Window win);
 int getRootWindowScreen(Window root);
 std::string getWindowAtom(Window win, const char *atom);
-int getWindowPid(Window win);
 bool ewmhIsSupported();
 std::list<Window> getTopLevelWindows();
-std::list<Window> getAllWindows();
 
 inline std::string getWindowName(Window win)
 {
@@ -92,6 +91,8 @@ inline std::string getWindowClass(Window win)
 	return getWindowAtom(win, "WM_CLASS");
 }
 
+void registerSource(XCompcapMain *source, Window win);
+void unregisterSource(XCompcapMain *source);
 void processEvents();
-bool windowWasReconfigured(Window win);
+bool sourceWasReconfigured(XCompcapMain *source);
 }

+ 34 - 28
plugins/linux-capture/xcompcap-main.cpp

@@ -106,7 +106,7 @@ void XCompcapMain::defaults(obs_data_t *settings)
 	obs_data_set_default_bool(settings, "exclude_alpha", false);
 }
 
-#define FIND_WINDOW_INTERVAL 2.0
+#define FIND_WINDOW_INTERVAL 0.5
 
 struct XCompcapMain_private {
 	XCompcapMain_private()
@@ -191,6 +191,8 @@ XCompcapMain::~XCompcapMain()
 {
 	ObsGsContextHolder obsctx;
 
+	XCompcap::unregisterSource(this);
+
 	if (p->tex) {
 		gs_texture_destroy(p->tex);
 		p->tex = 0;
@@ -220,33 +222,44 @@ static Window getWindowFromString(std::string wstr)
 	}
 
 	size_t firstMark = wstr.find(WIN_STRING_DIV);
+	size_t lastMark = wstr.rfind(WIN_STRING_DIV);
 	size_t markSize = strlen(WIN_STRING_DIV);
 
+	// wstr only consists of the window-id
 	if (firstMark == std::string::npos)
 		return (Window)std::stol(wstr);
 
-	Window wid = 0;
+	// wstr also contains window-name and window-class
+	std::string wid = wstr.substr(0, firstMark);
+	std::string wname = wstr.substr(firstMark + markSize,
+					lastMark - firstMark - markSize);
+	std::string wcls = wstr.substr(lastMark + markSize);
 
-	wstr = wstr.substr(firstMark + markSize);
+	Window winById = (Window)std::stol(wid);
 
-	size_t lastMark = wstr.rfind(WIN_STRING_DIV);
-	std::string wname = wstr.substr(0, lastMark);
-	std::string wcls = wstr.substr(lastMark + markSize);
+	// first try to find a match by the window-id
+	for (Window cwin : XCompcap::getTopLevelWindows()) {
+		// match by window-id
+		if (cwin == winById) {
+			return cwin;
+		}
+	}
 
-	Window matchedNameWin = wid;
+	// then try to find a match by name & class
 	for (Window cwin : XCompcap::getTopLevelWindows()) {
 		std::string cwinname = XCompcap::getWindowName(cwin);
 		std::string ccls = XCompcap::getWindowClass(cwin);
 
-		if (cwin == wid && wname == cwinname && wcls == ccls)
-			return wid;
-
-		if (wname == cwinname ||
-		    (!matchedNameWin && !wcls.empty() && wcls == ccls))
-			matchedNameWin = cwin;
+		// match by name and class
+		if (wname == cwinname && wcls == ccls) {
+			return cwin;
+		}
 	}
 
-	return matchedNameWin;
+	// no match
+	blog(LOG_DEBUG, "Did not find Window By ID %s, Name '%s' or Class '%s'",
+	     wid.c_str(), wname.c_str(), wcls.c_str());
+	return 0;
 }
 
 static void xcc_cleanup(XCompcapMain_private *p)
@@ -290,9 +303,6 @@ static void xcc_cleanup(XCompcapMain_private *p)
 	}
 
 	if (p->win) {
-		XCompositeUnredirectWindow(xdisp, p->win,
-					   CompositeRedirectAutomatic);
-		XSelectInput(xdisp, p->win, 0);
 		p->win = 0;
 	}
 
@@ -349,6 +359,7 @@ struct gs_texture {
 void XCompcapMain::updateSettings(obs_data_t *settings)
 {
 	ObsGsContextHolder obsctx;
+	XErrorLock xlock;
 
 	PLock lock(&p->lock);
 
@@ -359,11 +370,13 @@ void XCompcapMain::updateSettings(obs_data_t *settings)
 	xcc_cleanup(p);
 
 	if (settings) {
+		/* Settings initialized or changed */
 		const char *windowName =
 			obs_data_get_string(settings, "capture_window");
 
 		p->windowName = windowName;
 		p->win = getWindowFromString(windowName);
+		XCompcap::registerSource(this, p->win);
 
 		p->cut_top = obs_data_get_int(settings, "cut_top");
 		p->cut_left = obs_data_get_int(settings, "cut_left");
@@ -377,23 +390,15 @@ void XCompcapMain::updateSettings(obs_data_t *settings)
 		p->exclude_alpha = obs_data_get_bool(settings, "exclude_alpha");
 		p->draw_opaque = false;
 	} else {
+		/* New Window found (stored in p->win), just re-initialize GL-Mapping  */
 		p->win = prevWin;
 	}
 
-	XErrorLock xlock;
-	if (p->win)
-		XCompositeRedirectWindow(xdisp, p->win,
-					 CompositeRedirectAutomatic);
 	if (xlock.gotError()) {
-		blog(LOG_ERROR, "XCompositeRedirectWindow failed: %s",
+		blog(LOG_ERROR, "registeringSource failed: %s",
 		     xlock.getErrorText().c_str());
 		return;
 	}
-
-	if (p->win)
-		XSelectInput(xdisp, p->win,
-			     StructureNotifyMask | ExposureMask |
-				     VisibilityChangeMask);
 	XSync(xdisp, 0);
 
 	XWindowAttributes attr;
@@ -581,7 +586,7 @@ void XCompcapMain::tick(float seconds)
 
 	XCompcap::processEvents();
 
-	if (p->win && XCompcap::windowWasReconfigured(p->win)) {
+	if (p->win && XCompcap::sourceWasReconfigured(this)) {
 		p->window_check_time = FIND_WINDOW_INTERVAL;
 		p->win = 0;
 	}
@@ -601,6 +606,7 @@ void XCompcapMain::tick(float seconds)
 
 		if (newWin && XGetWindowAttributes(xdisp, newWin, &attr)) {
 			p->win = newWin;
+			XCompcap::registerSource(this, p->win);
 			updateSettings(0);
 		} else {
 			return;