Window Capture hotkey for OBS
OBS Studio allows you to configure a hotkey to change the captured window for a Game Capture
source. However this ability to use a hotkey is missing from the Window Capture
source.
The Window Capture
source works by looking for a window matching a
selected window title, window class, and executable. A raw window
handle (HWND
) can't be used as these can't be saved and reused later
on.
Thankfully OBS is really scriptable, and uses LuaJIT which ships with an FFI module. Using this I could write a Lua script to find the foreground window when a hotkey is triggered, and then update the Window Capture source with the window title and class (ignoring the executable). It works!
So here it is, save the following somewhere as hotkey_window_capture.lua
:
--[[
obs-hotkey-window-capture v0.1
Allows using a hotkey to change capture window for Window Capture
sources.
Partly derived from https://obsproject.com/forum/resources/bounce.947/
MIT Licensed
]]--
local obs = obslua
local bit = require('bit')
local ffi = require('ffi')
--- name of source to modify
local source_name = ''
--- scene item to be adjusted
local scene_item = nil
--- the hotkey assigned to window_capture_foreground in OBS's hotkey config
local hotkey_id = obs.OBS_INVALID_HOTKEY_ID
ffi.cdef[[
void* GetAncestor(void *hwnd, unsigned int flags);
void* GetForegroundWindow();
int GetWindowTextLengthA(void* hwnd);
int GetWindowTextA(void* hwnd, char* str, int count);
int GetClassNameA(void* hwnd, char* str, int count);
]]
local function get_active_window_title()
local hwnd = ffi.C.GetAncestor(ffi.C.GetForegroundWindow(), 3)
local sz = ffi.C.GetWindowTextLengthA(hwnd)
local ws = ffi.new('char[?]', sz)
ffi.C.GetWindowTextA(hwnd, ws, sz+1)
return ffi.string(ws)
end
local function get_active_window_class()
local hwnd = ffi.C.GetAncestor(ffi.C.GetForegroundWindow(), 3)
local ws = ffi.new('char[?]', 64)
ffi.C.GetClassNameA(hwnd, ws, 64)
return ffi.string(ws)
end
--- get a list of source names, sorted alphabetically
local function get_source_names()
local sources = obs.obs_enum_sources()
local source_names = {}
if sources then
for _, source in ipairs(sources) do
-- exclude Desktop Audio and Mic/Aux by their capabilities
local capability_flags = obs.obs_source_get_output_flags(source)
if bit.band(capability_flags, obs.OBS_SOURCE_DO_NOT_SELF_MONITOR) == 0 and
capability_flags ~= bit.bor(obs.OBS_SOURCE_AUDIO, obs.OBS_SOURCE_DO_NOT_DUPLICATE) then
table.insert(source_names, obs.obs_source_get_name(source))
end
end
end
obs.source_list_release(sources)
table.sort(source_names, function(a, b)
return string.lower(a) < string.lower(b)
end)
return source_names
end
function find_scene_item()
if not source_name or source_name == '' then
obs.script_log(obs.LOG_INFO, 'no source name')
return false
end
local source = obs.obs_frontend_get_current_scene()
if not source then
obs.script_log(obs.LOG_INFO, 'no current scene')
return false
end
local scene = obs.obs_scene_from_source(source)
obs.obs_source_release(source)
scene_item = obs.obs_scene_find_source(scene, source_name)
scene_item = obs.obs_sceneitem_get_source(scene_item)
if scene_item then
return true
end
obs.script_log(obs.LOG_INFO, source_name .. ' not found')
return false
end
function script_description()
return 'Allows using a hotkey to change capture window for Window Capture sources.'
end
function script_properties()
local props = obs.obs_properties_create()
local source = obs.obs_properties_add_list(
props,
'source',
'Source:',
obs.OBS_COMBO_TYPE_EDITABLE,
obs.OBS_COMBO_FORMAT_STRING)
for _, name in ipairs(get_source_names()) do
obs.obs_property_list_add_string(source, name, name)
end
return props
end
function script_update(settings)
source_name = obs.obs_data_get_string(settings, 'source')
find_scene_item()
end
function script_load(settings)
hotkey_id = obs.obs_hotkey_register_frontend('window_capture_foreground', 'Set Window Capture to foreground', window_capture_foreground)
local hotkey_save_array = obs.obs_data_get_array(settings, 'window_capture_foreground')
obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
obs.obs_data_array_release(hotkey_save_array)
obs.obs_frontend_add_event_callback(on_event)
end
function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_SCENE_CHANGED then
find_scene_item()
end
end
function script_save(settings)
local hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
obs.obs_data_set_array(settings, 'window_capture_foreground', hotkey_save_array)
obs.obs_data_array_release(hotkey_save_array)
end
function window_capture_foreground(pressed)
if not pressed then
return
end
if not scene_item then
obs.script_log(obs.LOG_INFO, 'no window capture source')
return
end
local window_title = get_active_window_title()
local window_class = get_active_window_class()
local data = obs.obs_source_get_settings(scene_item)
obs.obs_data_set_string(data, 'window', window_title .. ':' .. window_class .. ': ')
obs.obs_source_update(scene_item, data)
obs.obs_data_release(data)
end
To use, add the script under Tools -> Scripts
, and select a Window Capture
source, I named mine Window Capture (hotkey)
to make it obvious.
Once that's added, go to File -> Settings -> Hotkeys
and look for Set Window Capture to foreground
and bind this to the hotkey of your choice.
That's it, hopefully now when pressing the hotkey you'll see your Window Capture source update immediately.