|
@@ -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
|
|
|
|
+};
|