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.