190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * Modern effects for a modern Streamer
 | |
|  * Copyright (C) 2018 Michael Fabian Dirks
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation; either version 2 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software
 | |
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 | |
|  */
 | |
| 
 | |
| #include "obs-tools.hpp"
 | |
| #include <map>
 | |
| #include <stdexcept>
 | |
| #include "plugin.hpp"
 | |
| 
 | |
| struct scs_searchdata {
 | |
| 	obs_source_t*                 source;
 | |
| 	bool                          found = false;
 | |
| 	std::map<obs_source_t*, bool> visited;
 | |
| };
 | |
| 
 | |
| static bool scs_contains(scs_searchdata& sd, obs_source_t* source);
 | |
| 
 | |
| static void scs_enum_active_cb(obs_source_t*, obs_source_t* child, void* searchdata) noexcept
 | |
| try {
 | |
| 	scs_searchdata& sd = reinterpret_cast<scs_searchdata&>(*reinterpret_cast<scs_searchdata*>(searchdata));
 | |
| 	scs_contains(sd, child);
 | |
| } catch (...) {
 | |
| 	DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
 | |
| }
 | |
| 
 | |
| static bool scs_enum_items_cb(obs_scene_t*, obs_sceneitem_t* item, void* searchdata) noexcept
 | |
| try {
 | |
| 	scs_searchdata& sd     = reinterpret_cast<scs_searchdata&>(*reinterpret_cast<scs_searchdata*>(searchdata));
 | |
| 	obs_source_t*   source = obs_sceneitem_get_source(item);
 | |
| 	return scs_contains(sd, source);
 | |
| } catch (...) {
 | |
| 	DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static bool scs_contains(scs_searchdata& sd, obs_source_t* source)
 | |
| {
 | |
| 	if (sd.visited.find(source) != sd.visited.end()) {
 | |
| 		return false;
 | |
| 	} else {
 | |
| 		sd.visited.insert({source, true});
 | |
| 	}
 | |
| 
 | |
| 	if (source == sd.source) {
 | |
| 		sd.found = true;
 | |
| 		return true;
 | |
| 	} else {
 | |
| 		if (strcmp(obs_source_get_id(source), "scene")) {
 | |
| 			obs_scene_t* nscene = obs_scene_from_source(source);
 | |
| 			obs_scene_enum_items(nscene, scs_enum_items_cb, &sd);
 | |
| 		} else {
 | |
| 			obs_source_enum_active_sources(source, scs_enum_active_cb, &sd);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (sd.found) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool obs::tools::scene_contains_source(obs_scene_t* scene, obs_source_t* source)
 | |
| {
 | |
| 	scs_searchdata sd;
 | |
| 	sd.source = source;
 | |
| 	obs_scene_enum_items(scene, scs_enum_items_cb, &sd);
 | |
| 	return sd.found;
 | |
| }
 | |
| 
 | |
| extern "C" {
 | |
| struct _hack_obs_properties;
 | |
| 
 | |
| struct _hack_obs_property {
 | |
| 	char*                  name;
 | |
| 	char*                  desc;
 | |
| 	char*                  long_desc;
 | |
| 	void*                  priv;
 | |
| 	enum obs_property_type type;
 | |
| 	bool                   visible;
 | |
| 	bool                   enabled;
 | |
| 
 | |
| 	struct _hack_obs_properties* parent;
 | |
| 
 | |
| 	obs_property_modified_t  modified;
 | |
| 	obs_property_modified2_t modified2;
 | |
| 
 | |
| 	struct _hack_obs_property* next;
 | |
| };
 | |
| 
 | |
| struct _hack_obs_properties {
 | |
| 	void* param;
 | |
| 	void (*destroy)(void* param);
 | |
| 	uint32_t flags;
 | |
| 
 | |
| 	struct _hack_obs_property*  first_property;
 | |
| 	struct _hack_obs_property** last;
 | |
| 	struct _hack_obs_property*  parent;
 | |
| };
 | |
| }
 | |
| 
 | |
| bool obs::tools::obs_properties_remove_by_name(obs_properties_t* props, const char* name)
 | |
| {
 | |
| 	// Due to a bug in obs_properties_remove_by_name, calling it on the first or last element of a group corrupts the
 | |
| 	// obs_properties_t's first and last pointers, which now point at nonsense.
 | |
| 	//
 | |
| 	// There are two ways to work around this issue for now:
 | |
| 	// 1. Add some invisible properties to the beginning and end of the list, ensuring that you never hit the first or
 | |
| 	//    last element with a obs_properties_remove_by_name.
 | |
| 	// 2. Manually adjust the pointers using a dirty hack like in gs::mipmapper.
 | |
| 	// I've opted for the 2nd way, at it is way simpler to implement.
 | |
| 
 | |
| 	// Assume that this is fixed in libobs 24.0.7 or newer.
 | |
| 	if (obs_get_version() >= MAKE_SEMANTIC_VERSION(24, 0, 7)) {
 | |
| 		::obs_properties_remove_by_name(props, name);
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	auto rprops = reinterpret_cast<_hack_obs_properties*>(props);
 | |
| 
 | |
| 	for (_hack_obs_property *el_prev = rprops->first_property, *el_cur = el_prev; el_cur != nullptr;
 | |
| 		 el_prev = el_cur, el_cur = el_cur->next) {
 | |
| 		if (strcmp(el_cur->name, name) == 0) {
 | |
| 			// Store some information.
 | |
| 			_hack_obs_property* next     = el_cur->next;
 | |
| 			bool                is_first = (rprops->first_property == el_cur);
 | |
| 			bool                is_last  = (rprops->last == &el_cur->next);
 | |
| 			bool                is_solo  = (el_cur == el_prev);
 | |
| 
 | |
| 			// Call the real one which fixes the element pointer and deallocates the element.
 | |
| 			::obs_properties_remove_by_name(props, name);
 | |
| 
 | |
| 			// Fix up the memory pointers after the element was deleted.
 | |
| 			if (is_last) {
 | |
| 				if (is_solo) {
 | |
| 					rprops->last = &rprops->first_property;
 | |
| 				} else {
 | |
| 					rprops->last = &el_prev->next;
 | |
| 				}
 | |
| 			}
 | |
| 			if (is_first) {
 | |
| 				rprops->first_property = next;
 | |
| 			}
 | |
| 
 | |
| 			// Finally break out as we no longer have to process the properties list.
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		if (el_cur->type == OBS_PROPERTY_GROUP) {
 | |
| 			if (obs::tools::obs_properties_remove_by_name(
 | |
| 					obs_property_group_content(reinterpret_cast<obs_property_t*>(el_cur)), name))
 | |
| 				return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| obs::tools::child_source::child_source(obs_source_t* parent, std::shared_ptr<obs_source_t> child)
 | |
| 	: _parent(parent), _child(child)
 | |
| {
 | |
| 	if (!obs_source_add_active_child(_parent, _child.get())) {
 | |
| 		throw std::runtime_error("recursion detected");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| obs::tools::child_source::~child_source()
 | |
| {
 | |
| 	obs_source_remove_active_child(_parent, _child.get());
 | |
| }
 | |
| 
 | |
| std::shared_ptr<obs_source_t> obs::tools::child_source::get()
 | |
| {
 | |
| 	return _child;
 | |
| }
 |