Files
obs-studio/plugins/win-capture/window-capture.c
jp9000 0b4a259e56 Remove 'locale' parameter from all callbacks
The locale parameter was a mistake, because it puts extra needless
burden upon the module developer to have to handle this variable for
each and every single callback function.  The parameter is being removed
in favor of a single centralized module callback function that
specifically updates locale information for a module only when needed.
2014-06-25 12:36:26 -07:00

459 lines
9.8 KiB
C

#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 && 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(void)
{
/* TODO: 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(void)
{
obs_properties_t ppts = obs_properties_create();
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
};