ciface/win32: use CM_Register_Notification instead of wnd msgs

This commit is contained in:
Shawn Hoffman
2022-08-07 17:28:41 -07:00
parent b6ac63dc47
commit 76d2e47444

View File

@ -3,35 +3,38 @@
#include "InputCommon/ControllerInterface/Win32/Win32.h" #include "InputCommon/ControllerInterface/Win32/Win32.h"
#include <windows.h> #include <Windows.h>
#include <cfgmgr32.h>
// must be before hidclass
#include <initguid.h>
#include <hidclass.h>
#include <array>
#include <future>
#include <mutex> #include <mutex>
#include <thread>
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/HRWrap.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/Thread.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/DInput/DInput.h"
#include "InputCommon/ControllerInterface/WGInput/WGInput.h" #include "InputCommon/ControllerInterface/WGInput/WGInput.h"
#include "InputCommon/ControllerInterface/XInput/XInput.h" #include "InputCommon/ControllerInterface/XInput/XInput.h"
constexpr UINT WM_DOLPHIN_STOP = WM_USER; #pragma comment(lib, "OneCoreUAP.Lib")
// Dolphin's render window // Dolphin's render window
static HWND s_hwnd; static HWND s_hwnd;
// Windows messaging window (hidden)
static HWND s_message_window;
static std::thread s_thread;
static std::mutex s_populate_mutex; static std::mutex s_populate_mutex;
// TODO is this really needed?
static Common::Flag s_first_populate_devices_asked; static Common::Flag s_first_populate_devices_asked;
static HCMNOTIFICATION s_notify_handle;
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) _Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALLBACK
OnDevicesChanged(_In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context,
_In_ CM_NOTIFY_ACTION Action,
_In_reads_bytes_(EventDataSize) PCM_NOTIFY_EVENT_DATA EventData,
_In_ DWORD EventDataSize)
{ {
if (message == WM_INPUT_DEVICE_CHANGE) if (Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)
{ {
// Windows automatically sends this message before we ask for it and before we are "ready" to // Windows automatically sends this message before we ask for it and before we are "ready" to
// listen for it. // listen for it.
@ -47,143 +50,60 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARA
}); });
} }
} }
return ERROR_SUCCESS;
return DefWindowProc(hwnd, message, wparam, lparam);
} }
void ciface::Win32::Init(void* hwnd) void ciface::Win32::Init(void* hwnd)
{ {
s_hwnd = static_cast<HWND>(hwnd); s_hwnd = static_cast<HWND>(hwnd);
XInput::Init(); XInput::Init();
WGInput::Init(); WGInput::Init();
std::promise<HWND> message_window_promise; CM_NOTIFY_FILTER notify_filter{.cbSize = sizeof(notify_filter),
.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE,
s_thread = std::thread([&message_window_promise] { .u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}};
Common::SetCurrentThreadName("ciface::Win32 Message Loop"); const CONFIGRET cfg_rv =
CM_Register_Notification(&notify_filter, nullptr, OnDevicesChanged, &s_notify_handle);
HWND message_window = nullptr; if (cfg_rv != CR_SUCCESS)
Common::ScopeGuard promise_guard([&] { message_window_promise.set_value(message_window); });
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{ {
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CoInitializeEx failed: {}", Common::HRWrap(hr)); ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv);
return;
} }
Common::ScopeGuard uninit([] { CoUninitialize(); });
const auto window_name = TEXT("DolphinWin32ControllerInterface");
WNDCLASSEX window_class_info{};
window_class_info.cbSize = sizeof(window_class_info);
window_class_info.lpfnWndProc = WindowProc;
window_class_info.hInstance = GetModuleHandle(nullptr);
window_class_info.lpszClassName = window_name;
ATOM window_class = RegisterClassEx(&window_class_info);
if (!window_class)
{
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "RegisterClassEx failed: {}",
Common::HRWrap(GetLastError()));
return;
}
Common::ScopeGuard unregister([&window_class] {
if (!UnregisterClass(MAKEINTATOM(window_class), GetModuleHandle(nullptr)))
ERROR_LOG_FMT(CONTROLLERINTERFACE, "UnregisterClass failed: {}",
Common::HRWrap(GetLastError()));
});
message_window = CreateWindowEx(0, window_name, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr,
nullptr, nullptr);
promise_guard.Exit();
if (!message_window)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CreateWindowEx failed: {}",
Common::HRWrap(GetLastError()));
return;
}
Common::ScopeGuard destroy([&] {
if (!DestroyWindow(message_window))
ERROR_LOG_FMT(CONTROLLERINTERFACE, "DestroyWindow failed: {}",
Common::HRWrap(GetLastError()));
});
std::array<RAWINPUTDEVICE, 2> devices;
// game pad devices
devices[0].usUsagePage = 0x01;
devices[0].usUsage = 0x05;
devices[0].dwFlags = RIDEV_DEVNOTIFY;
devices[0].hwndTarget = message_window;
// joystick devices
devices[1].usUsagePage = 0x01;
devices[1].usUsage = 0x04;
devices[1].dwFlags = RIDEV_DEVNOTIFY;
devices[1].hwndTarget = message_window;
if (!RegisterRawInputDevices(devices.data(), static_cast<UINT>(devices.size()),
static_cast<UINT>(sizeof(decltype(devices)::value_type))))
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "RegisterRawInputDevices failed: {}", GetLastError());
return;
}
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_DOLPHIN_STOP)
break;
}
});
s_message_window = message_window_promise.get_future().get();
} }
void ciface::Win32::PopulateDevices(void* hwnd) void ciface::Win32::PopulateDevices(void* hwnd)
{
if (s_thread.joinable())
{ {
s_hwnd = static_cast<HWND>(hwnd); s_hwnd = static_cast<HWND>(hwnd);
// To avoid blocking this thread until the async population has finished, directly do it here
// (we need the DInput Keyboard and Mouse "default" device to always be added without any wait).
std::lock_guard lk_population(s_populate_mutex); std::lock_guard lk_population(s_populate_mutex);
s_first_populate_devices_asked.Set(); s_first_populate_devices_asked.Set();
ciface::DInput::PopulateDevices(s_hwnd); ciface::DInput::PopulateDevices(s_hwnd);
ciface::XInput::PopulateDevices(); ciface::XInput::PopulateDevices();
ciface::WGInput::PopulateDevices(); ciface::WGInput::PopulateDevices();
} }
else
{
ERROR_LOG_FMT(CONTROLLERINTERFACE,
"win32 asked to populate devices, but device thread isn't running");
}
}
void ciface::Win32::ChangeWindow(void* hwnd) void ciface::Win32::ChangeWindow(void* hwnd)
{
if (s_thread.joinable()) // "Has init?"
{ {
s_hwnd = static_cast<HWND>(hwnd); s_hwnd = static_cast<HWND>(hwnd);
std::lock_guard lk_population(s_populate_mutex); std::lock_guard lk_population(s_populate_mutex);
ciface::DInput::ChangeWindow(s_hwnd); ciface::DInput::ChangeWindow(s_hwnd);
} }
}
void ciface::Win32::DeInit() void ciface::Win32::DeInit()
{ {
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "win32 DeInit");
if (s_thread.joinable())
{
PostMessage(s_message_window, WM_DOLPHIN_STOP, 0, 0);
s_thread.join();
s_message_window = nullptr;
s_first_populate_devices_asked.Clear(); s_first_populate_devices_asked.Clear();
DInput::DeInit(); DInput::DeInit();
}
s_hwnd = nullptr; s_hwnd = nullptr;
if (s_notify_handle)
{
const CONFIGRET cfg_rv = CM_Unregister_Notification(s_notify_handle);
if (cfg_rv != CR_SUCCESS)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Unregister_Notification failed: {:x}", cfg_rv);
}
s_notify_handle = nullptr;
}
XInput::DeInit(); XInput::DeInit();
WGInput::DeInit(); WGInput::DeInit();
} }