diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt index da747ee60..b3954bf1a 100644 --- a/plugins/obs-outputs/CMakeLists.txt +++ b/plugins/obs-outputs/CMakeLists.txt @@ -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 diff --git a/plugins/win-capture/CMakeLists.txt b/plugins/win-capture/CMakeLists.txt index c1bdec931..c1f951a03 100644 --- a/plugins/win-capture/CMakeLists.txt +++ b/plugins/win-capture/CMakeLists.txt @@ -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) diff --git a/plugins/win-capture/monitor-capture.c b/plugins/win-capture/monitor-capture.c index 6363950f1..719a60d3c 100644 --- a/plugins/win-capture/monitor-capture.c +++ b/plugins/win-capture/monitor-capture.c @@ -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) diff --git a/plugins/win-capture/plugin-main.c b/plugins/win-capture/plugin-main.c index 28f12e0a9..c02afc88e 100644 --- a/plugins/win-capture/plugin-main.c +++ b/plugins/win-capture/plugin-main.c @@ -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; diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c new file mode 100644 index 000000000..22e66e73a --- /dev/null +++ b/plugins/win-capture/window-capture.c @@ -0,0 +1,459 @@ +#include +#include +#include "dc-capture.h" +#include + +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 +}; diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj b/vs/2013/obs-outputs/obs-outputs.vcxproj index 5f5396ec9..0f4b79bcb 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj @@ -132,7 +132,7 @@ true true true - pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) + winmm.lib;pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) @@ -155,7 +155,7 @@ true true true - pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) + winmm.lib;pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) diff --git a/vs/2013/win-capture/win-capture.vcxproj b/vs/2013/win-capture/win-capture.vcxproj index 3363ce9f3..8e6fb76ba 100644 --- a/vs/2013/win-capture/win-capture.vcxproj +++ b/vs/2013/win-capture/win-capture.vcxproj @@ -91,7 +91,7 @@ Windows true $(OutDir);%(AdditionalLibraryDirectories) - libobs.lib;%(AdditionalDependencies) + psapi.lib;libobs.lib;%(AdditionalDependencies) copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" @@ -110,7 +110,7 @@ Windows true $(OutDir);%(AdditionalLibraryDirectories) - libobs.lib;%(AdditionalDependencies) + psapi.lib;libobs.lib;%(AdditionalDependencies) copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" @@ -133,7 +133,7 @@ true true $(OutDir);%(AdditionalLibraryDirectories) - libobs.lib;%(AdditionalDependencies) + psapi.lib;libobs.lib;%(AdditionalDependencies) copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" @@ -156,7 +156,7 @@ true true $(OutDir);%(AdditionalLibraryDirectories) - libobs.lib;%(AdditionalDependencies) + psapi.lib;libobs.lib;%(AdditionalDependencies) copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" @@ -166,6 +166,7 @@ + diff --git a/vs/2013/win-capture/win-capture.vcxproj.filters b/vs/2013/win-capture/win-capture.vcxproj.filters index 6d3099809..0ef775459 100644 --- a/vs/2013/win-capture/win-capture.vcxproj.filters +++ b/vs/2013/win-capture/win-capture.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files +