xcompcap-helper.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #include <X11/Xlib.h>
  2. #include <X11/Xatom.h>
  3. #include <X11/Xutil.h>
  4. #include <X11/extensions/Xcomposite.h>
  5. #include <unordered_set>
  6. #include <map>
  7. #include <pthread.h>
  8. #include <obs-module.h>
  9. #include <util/platform.h>
  10. #include "xcompcap-helper.hpp"
  11. namespace XCompcap {
  12. static Display *xdisplay = 0;
  13. Display *disp()
  14. {
  15. if (!xdisplay)
  16. xdisplay = XOpenDisplay(NULL);
  17. return xdisplay;
  18. }
  19. void cleanupDisplay()
  20. {
  21. if (!xdisplay)
  22. return;
  23. XCloseDisplay(xdisplay);
  24. xdisplay = 0;
  25. }
  26. // Specification for checking for ewmh support at
  27. // http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472693600
  28. bool ewmhIsSupported()
  29. {
  30. Display *display = disp();
  31. Atom netSupportingWmCheck =
  32. XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", true);
  33. Atom actualType;
  34. int format = 0;
  35. unsigned long num = 0, bytes = 0;
  36. unsigned char *data = NULL;
  37. Window ewmh_window = 0;
  38. int status = XGetWindowProperty(display, DefaultRootWindow(display),
  39. netSupportingWmCheck, 0L, 1L, false,
  40. XA_WINDOW, &actualType, &format, &num,
  41. &bytes, &data);
  42. if (status == Success) {
  43. if (num > 0) {
  44. ewmh_window = ((Window *)data)[0];
  45. }
  46. if (data) {
  47. XFree(data);
  48. data = NULL;
  49. }
  50. }
  51. if (ewmh_window) {
  52. status = XGetWindowProperty(display, ewmh_window,
  53. netSupportingWmCheck, 0L, 1L, false,
  54. XA_WINDOW, &actualType, &format,
  55. &num, &bytes, &data);
  56. if (status != Success || num == 0 ||
  57. ewmh_window != ((Window *)data)[0]) {
  58. ewmh_window = 0;
  59. }
  60. if (status == Success && data) {
  61. XFree(data);
  62. }
  63. }
  64. return ewmh_window != 0;
  65. }
  66. std::list<Window> getTopLevelWindows()
  67. {
  68. std::list<Window> res;
  69. if (!ewmhIsSupported()) {
  70. blog(LOG_WARNING, "Unable to query window list "
  71. "because window manager "
  72. "does not support extended "
  73. "window manager Hints");
  74. return res;
  75. }
  76. Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true);
  77. Atom actualType;
  78. int format;
  79. unsigned long num, bytes;
  80. Window *data = 0;
  81. for (int i = 0; i < ScreenCount(disp()); ++i) {
  82. Window rootWin = RootWindow(disp(), i);
  83. int status = XGetWindowProperty(disp(), rootWin, netClList, 0L,
  84. ~0L, false, AnyPropertyType,
  85. &actualType, &format, &num,
  86. &bytes, (uint8_t **)&data);
  87. if (status != Success) {
  88. blog(LOG_WARNING, "Failed getting root "
  89. "window properties");
  90. continue;
  91. }
  92. for (unsigned long i = 0; i < num; ++i)
  93. res.push_back(data[i]);
  94. XFree(data);
  95. }
  96. return res;
  97. }
  98. int getRootWindowScreen(Window root)
  99. {
  100. XWindowAttributes attr;
  101. if (!XGetWindowAttributes(disp(), root, &attr))
  102. return DefaultScreen(disp());
  103. return XScreenNumberOfScreen(attr.screen);
  104. }
  105. std::string getWindowAtom(Window win, const char *atom)
  106. {
  107. Atom netWmName = XInternAtom(disp(), atom, false);
  108. int n;
  109. char **list = 0;
  110. XTextProperty tp;
  111. std::string res = "unknown";
  112. XGetTextProperty(disp(), win, &tp, netWmName);
  113. if (!tp.nitems)
  114. XGetWMName(disp(), win, &tp);
  115. if (!tp.nitems)
  116. return "error";
  117. if (tp.encoding == XA_STRING) {
  118. res = (char *)tp.value;
  119. } else {
  120. int ret = XmbTextPropertyToTextList(disp(), &tp, &list, &n);
  121. if (ret >= Success && n > 0 && *list) {
  122. res = *list;
  123. XFreeStringList(list);
  124. }
  125. }
  126. char *conv = nullptr;
  127. if (os_mbs_to_utf8_ptr(res.c_str(), 0, &conv))
  128. res = conv;
  129. bfree(conv);
  130. XFree(tp.value);
  131. return res;
  132. }
  133. static std::map<XCompcapMain *, Window> windowForSource;
  134. static std::unordered_set<XCompcapMain *> changedSources;
  135. static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER;
  136. void registerSource(XCompcapMain *source, Window win)
  137. {
  138. PLock lock(&changeLock);
  139. blog(LOG_DEBUG, "registerSource(source=%p, win=%ld)", source, win);
  140. auto it = windowForSource.find(source);
  141. if (it != windowForSource.end()) {
  142. windowForSource.erase(it);
  143. }
  144. // Subscribe to Events
  145. XSelectInput(disp(), win,
  146. StructureNotifyMask | ExposureMask | VisibilityChangeMask);
  147. XCompositeRedirectWindow(disp(), win, CompositeRedirectAutomatic);
  148. XSync(disp(), 0);
  149. windowForSource.insert(std::make_pair(source, win));
  150. }
  151. void unregisterSource(XCompcapMain *source)
  152. {
  153. PLock lock(&changeLock);
  154. blog(LOG_DEBUG, "unregisterSource(source=%p)", source);
  155. {
  156. auto it = windowForSource.find(source);
  157. Window win = it->second;
  158. if (it != windowForSource.end()) {
  159. windowForSource.erase(it);
  160. }
  161. // check if there are still sources listening for the same window
  162. it = windowForSource.begin();
  163. bool windowInUse = false;
  164. while (it != windowForSource.end()) {
  165. if (it->second == win) {
  166. windowInUse = true;
  167. break;
  168. }
  169. it++;
  170. }
  171. if (!windowInUse) {
  172. // Last source released, stop listening for events.
  173. XSelectInput(disp(), win, 0);
  174. XCompositeUnredirectWindow(disp(), win,
  175. CompositeRedirectAutomatic);
  176. XSync(disp(), 0);
  177. }
  178. }
  179. {
  180. auto it = changedSources.find(source);
  181. if (it != changedSources.end()) {
  182. changedSources.erase(it);
  183. }
  184. }
  185. }
  186. void processEvents()
  187. {
  188. PLock lock(&changeLock);
  189. XLockDisplay(disp());
  190. while (XEventsQueued(disp(), QueuedAfterReading) > 0) {
  191. XEvent ev;
  192. Window win = 0;
  193. XNextEvent(disp(), &ev);
  194. if (ev.type == ConfigureNotify)
  195. win = ev.xconfigure.event;
  196. else if (ev.type == MapNotify)
  197. win = ev.xmap.event;
  198. else if (ev.type == Expose)
  199. win = ev.xexpose.window;
  200. else if (ev.type == VisibilityNotify)
  201. win = ev.xvisibility.window;
  202. else if (ev.type == DestroyNotify)
  203. win = ev.xdestroywindow.event;
  204. if (win != 0) {
  205. blog(LOG_DEBUG, "processEvents(): windowChanged=%ld",
  206. win);
  207. auto it = windowForSource.begin();
  208. while (it != windowForSource.end()) {
  209. if (it->second == win) {
  210. blog(LOG_DEBUG,
  211. "processEvents(): sourceChanged=%p",
  212. it->first);
  213. changedSources.insert(it->first);
  214. }
  215. it++;
  216. }
  217. }
  218. }
  219. XUnlockDisplay(disp());
  220. }
  221. bool sourceWasReconfigured(XCompcapMain *source)
  222. {
  223. PLock lock(&changeLock);
  224. auto it = changedSources.find(source);
  225. if (it != changedSources.end()) {
  226. changedSources.erase(it);
  227. blog(LOG_DEBUG, "sourceWasReconfigured(source=%p)=true",
  228. source);
  229. return true;
  230. }
  231. return false;
  232. }
  233. }
  234. PLock::PLock(pthread_mutex_t *mtx, bool trylock) : m(mtx)
  235. {
  236. if (trylock)
  237. islock = mtx && pthread_mutex_trylock(mtx) == 0;
  238. else
  239. islock = mtx && pthread_mutex_lock(mtx) == 0;
  240. }
  241. PLock::~PLock()
  242. {
  243. if (islock) {
  244. pthread_mutex_unlock(m);
  245. }
  246. }
  247. bool PLock::isLocked()
  248. {
  249. return islock;
  250. }
  251. void PLock::unlock()
  252. {
  253. if (islock) {
  254. pthread_mutex_unlock(m);
  255. islock = false;
  256. }
  257. }
  258. void PLock::lock()
  259. {
  260. if (!islock) {
  261. pthread_mutex_lock(m);
  262. islock = true;
  263. }
  264. }
  265. static bool *curErrorTarget = 0;
  266. static char curErrorText[200];
  267. static int xerrorlock_handler(Display *disp, XErrorEvent *err)
  268. {
  269. if (curErrorTarget)
  270. *curErrorTarget = true;
  271. XGetErrorText(disp, err->error_code, curErrorText, 200);
  272. return 0;
  273. }
  274. XErrorLock::XErrorLock()
  275. {
  276. goterr = false;
  277. islock = false;
  278. prevhandler = 0;
  279. lock();
  280. }
  281. XErrorLock::~XErrorLock()
  282. {
  283. unlock();
  284. }
  285. bool XErrorLock::isLocked()
  286. {
  287. return islock;
  288. }
  289. void XErrorLock::lock()
  290. {
  291. if (!islock) {
  292. XLockDisplay(XCompcap::disp());
  293. XSync(XCompcap::disp(), 0);
  294. curErrorTarget = &goterr;
  295. curErrorText[0] = 0;
  296. prevhandler = XSetErrorHandler(xerrorlock_handler);
  297. islock = true;
  298. }
  299. }
  300. void XErrorLock::unlock()
  301. {
  302. if (islock) {
  303. XSync(XCompcap::disp(), 0);
  304. curErrorTarget = 0;
  305. XSetErrorHandler(prevhandler);
  306. prevhandler = 0;
  307. XUnlockDisplay(XCompcap::disp());
  308. islock = false;
  309. }
  310. }
  311. bool XErrorLock::gotError()
  312. {
  313. if (!islock)
  314. return false;
  315. XSync(XCompcap::disp(), 0);
  316. bool res = goterr;
  317. goterr = false;
  318. return res;
  319. }
  320. std::string XErrorLock::getErrorText()
  321. {
  322. return curErrorText;
  323. }
  324. void XErrorLock::resetError()
  325. {
  326. if (islock)
  327. XSync(XCompcap::disp(), 0);
  328. goterr = false;
  329. curErrorText[0] = 0;
  330. }
  331. XDisplayLock::XDisplayLock()
  332. {
  333. islock = false;
  334. lock();
  335. }
  336. XDisplayLock::~XDisplayLock()
  337. {
  338. unlock();
  339. }
  340. bool XDisplayLock::isLocked()
  341. {
  342. return islock;
  343. }
  344. void XDisplayLock::lock()
  345. {
  346. if (!islock) {
  347. XLockDisplay(XCompcap::disp());
  348. islock = true;
  349. }
  350. }
  351. void XDisplayLock::unlock()
  352. {
  353. if (islock) {
  354. XSync(XCompcap::disp(), 0);
  355. XUnlockDisplay(XCompcap::disp());
  356. islock = false;
  357. }
  358. }
  359. ObsGsContextHolder::ObsGsContextHolder()
  360. {
  361. obs_enter_graphics();
  362. }
  363. ObsGsContextHolder::~ObsGsContextHolder()
  364. {
  365. obs_leave_graphics();
  366. }