Add window capture

Now that we have the priorties window in we can finally be able to
select windows for capture source such as window capture.  Only took
about an hour or two to write.

Also, fixed some depednency issues on winmm.lib with obs-outputs
This commit is contained in:
jp9000 2014-04-07 01:20:36 -07:00
parent e78dc5aa51
commit 906535022f
8 changed files with 478 additions and 8 deletions

View File

@ -2,7 +2,8 @@ project(obs-outputs)
if(WIN32)
set(obs-outputs_PLATFORM_DEPS
ws2_32.lib)
ws2_32.lib
winmm.lib)
endif()
set(obs-outputs_librtmp_HEADERS

View File

@ -6,13 +6,15 @@ set(win-capture_HEADERS
set(win-capture_SOURCES
dc-capture.c
monitor-capture.c
window-capture.c
plugin-main.c)
add_library(win-capture MODULE
${win-capture_SOURCES}
${win-capture_HEADERS})
target_link_libraries(win-capture
libobs)
libobs
psapi.lib)
install_obs_plugin(win-capture)
install_obs_plugin_data(win-capture ../../build/data/obs-plugins/win-capture)

View File

@ -130,6 +130,8 @@ static void monitor_capture_tick(void *data, float seconds)
gs_entercontext(obs_graphics());
dc_capture_capture(&capture->data, NULL);
gs_leavecontext();
UNUSED_PARAMETER(seconds);
}
static void monitor_capture_render(void *data, effect_t effect)

View File

@ -3,10 +3,12 @@
OBS_DECLARE_MODULE()
extern struct obs_source_info monitor_capture_info;
extern struct obs_source_info window_capture_info;
bool obs_module_load(uint32_t libobs_ver)
{
obs_register_source(&monitor_capture_info);
obs_register_source(&window_capture_info);
UNUSED_PARAMETER(libobs_ver);
return true;

View File

@ -0,0 +1,459 @@
#include <stdlib.h>
#include <util/dstr.h>
#include "dc-capture.h"
#include <psapi.h>
enum window_priority {
WINDOW_PRIORITY_CLASS,
WINDOW_PRIORITY_TITLE,
WINDOW_PRIORITY_EXE,
};
struct window_capture {
obs_source_t source;
char *title;
char *class;
char *executable;
enum window_priority priority;
bool cursor;
bool compatibility;
bool use_wildcards; /* TODO */
struct dc_capture capture;
float resize_timer;
effect_t opaque_effect;
HWND window;
RECT last_rect;
};
void encode_dstr(struct dstr *str)
{
dstr_replace(str, "#", "#22");
dstr_replace(str, ":", "#3A");
}
char *decode_str(const char *src)
{
struct dstr str = {0};
dstr_copy(&str, src);
dstr_replace(&str, "#3A", ":");
dstr_replace(&str, "#22", "#");
return str.array;
}
static void update_settings(struct window_capture *wc, obs_data_t s)
{
const char *window = obs_data_getstring(s, "window");
int priority = (int)obs_data_getint(s, "priority");
bfree(wc->title);
bfree(wc->class);
bfree(wc->executable);
wc->title = NULL;
wc->class = NULL;
wc->executable = NULL;
if (window) {
char **strlist = strlist_split(window, ':', true);
if (strlist[0] && strlist[1] && strlist[2]) {
wc->title = decode_str(strlist[0]);
wc->class = decode_str(strlist[1]);
wc->executable = decode_str(strlist[2]);
}
strlist_free(strlist);
}
wc->priority = (enum window_priority)priority;
wc->cursor = obs_data_getbool(s, "cursor");
wc->use_wildcards = obs_data_getbool(s, "use_wildcards");
}
static bool get_exe_name(struct dstr *name, HWND window)
{
wchar_t wname[MAX_PATH];
struct dstr temp = {0};
bool success = false;
HANDLE process = NULL;
char *slash;
DWORD id;
GetWindowThreadProcessId(window, &id);
if (id == GetCurrentProcessId())
return false;
process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, id);
if (!process)
goto fail;
if (!GetProcessImageFileNameW(process, wname, MAX_PATH))
goto fail;
dstr_from_wcs(&temp, wname);
slash = strrchr(temp.array, '\\');
if (!slash)
goto fail;
dstr_copy(name, slash+1);
success = true;
fail:
if (!success)
dstr_copy(name, "unknown");
dstr_free(&temp);
CloseHandle(process);
return true;
}
static void get_window_title(struct dstr *name, HWND hwnd)
{
wchar_t *temp;
int len;
len = GetWindowTextLengthW(hwnd);
if (!len)
return;
temp = malloc(sizeof(wchar_t) * (len+1));
GetWindowTextW(hwnd, temp, len+1);
dstr_from_wcs(name, temp);
free(temp);
}
static void get_window_class(struct dstr *class, HWND hwnd)
{
wchar_t temp[256];
temp[0] = 0;
GetClassNameW(hwnd, temp, sizeof(temp));
dstr_from_wcs(class, temp);
}
static void add_window(obs_property_t p, HWND hwnd,
struct dstr *title,
struct dstr *class,
struct dstr *executable)
{
struct dstr encoded = {0};
struct dstr desc = {0};
if (!get_exe_name(executable, hwnd))
return;
get_window_title(title, hwnd);
get_window_class(class, hwnd);
dstr_printf(&desc, "[%s]: %s", executable->array, title->array);
encode_dstr(title);
encode_dstr(class);
encode_dstr(executable);
dstr_cat_dstr(&encoded, title);
dstr_cat(&encoded, ":");
dstr_cat_dstr(&encoded, class);
dstr_cat(&encoded, ":");
dstr_cat_dstr(&encoded, executable);
obs_property_list_add_string(p, desc.array, encoded.array);
dstr_free(&encoded);
dstr_free(&desc);
}
static bool check_window_valid(HWND window,
struct dstr *title,
struct dstr *class,
struct dstr *executable)
{
DWORD styles, ex_styles;
RECT rect;
if (!IsWindowVisible(window) || IsIconic(window))
return false;
GetClientRect(window, &rect);
styles = (DWORD)GetWindowLongPtr(window, GWL_STYLE);
ex_styles = (DWORD)GetWindowLongPtr(window, GWL_EXSTYLE);
if (ex_styles & WS_EX_TOOLWINDOW)
return false;
if (styles & WS_CHILD)
return false;
if (rect.bottom == 0 || rect.right == 0)
return false;
if (!get_exe_name(executable, window))
return false;
get_window_title(title, window);
get_window_class(class, window);
return true;
}
static inline HWND next_window(HWND window,
struct dstr *title,
struct dstr *class,
struct dstr *exe)
{
while (true) {
window = GetNextWindow(window, GW_HWNDNEXT);
if (!window || check_window_valid(window, title, class, exe))
break;
}
return window;
}
static inline HWND first_window(
struct dstr *title,
struct dstr *class,
struct dstr *executable)
{
HWND window = GetWindow(GetDesktopWindow(), GW_CHILD);
if (!check_window_valid(window, title, class, executable))
window = next_window(window, title, class, executable);
return window;
}
static void fill_window_list(obs_property_t p)
{
struct dstr title = {0};
struct dstr class = {0};
struct dstr executable = {0};
HWND window = first_window(&title, &class, &executable);
while (window) {
add_window(p, window, &title, &class, &executable);
window = next_window(window, &title, &class, &executable);
}
dstr_free(&title);
dstr_free(&class);
dstr_free(&executable);
}
static int window_rating(struct window_capture *wc,
struct dstr *title,
struct dstr *class,
struct dstr *executable)
{
int class_val = 1;
int title_val = 1;
int exe_val = 0;
int total = 0;
if (wc->priority == WINDOW_PRIORITY_CLASS)
class_val += 3;
else if (wc->priority == WINDOW_PRIORITY_TITLE)
title_val += 3;
else
exe_val += 3;
if (dstr_cmpi(class, wc->class) == 0)
total += class_val;
if (dstr_cmpi(title, wc->title) == 0)
total += title_val;
if (dstr_cmpi(executable, wc->executable) == 0)
total += exe_val;
return total;
}
static HWND find_window(struct window_capture *wc)
{
struct dstr title = {0};
struct dstr class = {0};
struct dstr exe = {0};
HWND window = first_window(&title, &class, &exe);
HWND best_window = NULL;
int best_rating = 0;
while (window) {
int rating = window_rating(wc, &title, &class, &exe);
if (rating > best_rating) {
best_rating = rating;
best_window = window;
}
window = next_window(window, &title, &class, &exe);
}
dstr_free(&title);
dstr_free(&class);
dstr_free(&exe);
return best_window;
}
/* ------------------------------------------------------------------------- */
static const char *wc_getname(const char *locale)
{
/* TODO: locale */
UNUSED_PARAMETER(locale);
return "Window capture";
}
static void *wc_create(obs_data_t settings, obs_source_t source)
{
struct window_capture *wc;
effect_t opaque_effect = create_opaque_effect();
if (!opaque_effect)
return NULL;
wc = bzalloc(sizeof(struct window_capture));
wc->source = source;
wc->opaque_effect = opaque_effect;
update_settings(wc, settings);
return wc;
}
static void wc_destroy(void *data)
{
struct window_capture *wc = data;
if (wc) {
dc_capture_free(&wc->capture);
bfree(wc->title);
bfree(wc->class);
bfree(wc->executable);
gs_entercontext(obs_graphics());
effect_destroy(wc->opaque_effect);
gs_leavecontext();
bfree(wc);
}
}
static void wc_update(void *data, obs_data_t settings)
{
struct window_capture *wc = data;
update_settings(wc, settings);
/* forces a reset */
wc->window = NULL;
}
static uint32_t wc_width(void *data)
{
struct window_capture *wc = data;
return wc->capture.width;
}
static uint32_t wc_height(void *data)
{
struct window_capture *wc = data;
return wc->capture.height;
}
static void wc_defaults(obs_data_t defaults)
{
obs_data_setbool(defaults, "cursor", true);
obs_data_setbool(defaults, "compatibility", false);
}
static obs_properties_t wc_properties(const char *locale)
{
obs_properties_t ppts = obs_properties_create(locale);
obs_property_t p;
/* TODO: locale */
p = obs_properties_add_list(ppts, "window", "Window",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
fill_window_list(p);
p = obs_properties_add_list(ppts, "priority", "Window Match Priority",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(p, "Window Title", WINDOW_PRIORITY_TITLE);
obs_property_list_add_int(p, "Window Class", WINDOW_PRIORITY_CLASS);
obs_property_list_add_int(p, "Executable", WINDOW_PRIORITY_EXE);
obs_properties_add_bool(ppts, "cursor", "Capture Cursor");
obs_properties_add_bool(ppts, "compatibility",
"Laptop Compatibility Mode");
return ppts;
}
#define RESIZE_CHECK_TIME 0.2f
static void wc_tick(void *data, float seconds)
{
struct window_capture *wc = data;
RECT rect;
bool reset_capture = false;
if (!wc->window || !IsWindow(wc->window)) {
if (!wc->title && !wc->class)
return;
wc->window = find_window(wc);
if (!wc->window)
return;
reset_capture = true;
} else if (IsIconic(wc->window)) {
return;
}
gs_entercontext(obs_graphics());
GetClientRect(wc->window, &rect);
if (!reset_capture) {
wc->resize_timer += seconds;
if (wc->resize_timer >= RESIZE_CHECK_TIME) {
if (rect.bottom != wc->last_rect.bottom ||
rect.right != wc->last_rect.right)
reset_capture = true;
wc->resize_timer = 0.0f;
}
}
if (reset_capture) {
wc->resize_timer = 0.0f;
wc->last_rect = rect;
dc_capture_free(&wc->capture);
dc_capture_init(&wc->capture, 0, 0, rect.right, rect.bottom,
wc->cursor, wc->compatibility);
}
dc_capture_capture(&wc->capture, wc->window);
gs_leavecontext();
}
static void wc_render(void *data, effect_t effect)
{
struct window_capture *wc = data;
dc_capture_render(&wc->capture, wc->opaque_effect);
}
struct obs_source_info window_capture_info = {
.id = "window_capture",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW,
.getname = wc_getname,
.create = wc_create,
.destroy = wc_destroy,
.update = wc_update,
.getwidth = wc_width,
.getheight = wc_height,
.defaults = wc_defaults,
.properties = wc_properties,
.video_render = wc_render,
.video_tick = wc_tick
};

View File

@ -132,7 +132,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>winmm.lib;pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
@ -155,7 +155,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>winmm.lib;pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>

View File

@ -91,7 +91,7 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
@ -110,7 +110,7 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
@ -133,7 +133,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
@ -156,7 +156,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>psapi.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
@ -166,6 +166,7 @@
<ClCompile Include="..\..\..\plugins\win-capture\dc-capture.c" />
<ClCompile Include="..\..\..\plugins\win-capture\monitor-capture.c" />
<ClCompile Include="..\..\..\plugins\win-capture\plugin-main.c" />
<ClCompile Include="..\..\..\plugins\win-capture\window-capture.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\plugins\win-capture\dc-capture.h" />

View File

@ -24,6 +24,9 @@
<ClCompile Include="..\..\..\plugins\win-capture\dc-capture.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\win-capture\window-capture.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\plugins\win-capture\dc-capture.h">