// Copyright 2017 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "InputCommon/ControllerInterface/Win32/Win32.h" #include #include #include #include #include "Common/Event.h" #include "Common/Logging/Log.h" #include "Common/ScopeGuard.h" #include "Common/Thread.h" #include "InputCommon/ControllerInterface/DInput/DInput.h" #include "InputCommon/ControllerInterface/XInput/XInput.h" constexpr UINT WM_DOLPHIN_STOP = WM_USER; static Common::Event s_done_populating; static std::atomic s_hwnd; static HWND s_message_window; static std::thread s_thread; static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { if (message == WM_INPUT_DEVICE_CHANGE) { g_controller_interface.PlatformPopulateDevices([] { ciface::DInput::PopulateDevices(s_hwnd); ciface::XInput::PopulateDevices(); }); s_done_populating.Set(); } return DefWindowProc(hwnd, message, wparam, lparam); } void ciface::Win32::Init(void* hwnd) { s_hwnd = static_cast(hwnd); XInput::Init(); std::promise message_window_promise; s_thread = std::thread([&message_window_promise] { Common::SetCurrentThreadName("ciface::Win32 Message Loop"); HWND message_window = nullptr; Common::ScopeGuard promise_guard([&] { message_window_promise.set_value(message_window); }); if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) { ERROR_LOG_FMT(SERIALINTERFACE, "CoInitializeEx failed: {}", GetLastError()); return; } Common::ScopeGuard uninit([] { CoUninitialize(); }); 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 = L"Message"; ATOM window_class = RegisterClassEx(&window_class_info); if (!window_class) { NOTICE_LOG_FMT(SERIALINTERFACE, "RegisterClassEx failed: {}", GetLastError()); return; } Common::ScopeGuard unregister([&window_class] { if (!UnregisterClass(MAKEINTATOM(window_class), GetModuleHandle(nullptr))) ERROR_LOG_FMT(SERIALINTERFACE, "UnregisterClass failed: {}", GetLastError()); }); message_window = CreateWindowEx(0, L"Message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr); promise_guard.Exit(); if (!message_window) { ERROR_LOG_FMT(SERIALINTERFACE, "CreateWindowEx failed: {}", GetLastError()); return; } Common::ScopeGuard destroy([&] { if (!DestroyWindow(message_window)) ERROR_LOG_FMT(SERIALINTERFACE, "DestroyWindow failed: {}", GetLastError()); }); std::array 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(devices.size()), static_cast(sizeof(decltype(devices)::value_type)))) { ERROR_LOG_FMT(SERIALINTERFACE, "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) { if (s_thread.joinable()) { s_hwnd = static_cast(hwnd); s_done_populating.Reset(); PostMessage(s_message_window, WM_INPUT_DEVICE_CHANGE, 0, 0); if (!s_done_populating.WaitFor(std::chrono::seconds(10))) ERROR_LOG_FMT(SERIALINTERFACE, "win32 timed out when trying to populate devices"); } else { ERROR_LOG_FMT(SERIALINTERFACE, "win32 asked to populate devices, but device thread isn't running"); } } void ciface::Win32::DeInit() { NOTICE_LOG_FMT(SERIALINTERFACE, "win32 DeInit"); if (s_thread.joinable()) { PostMessage(s_message_window, WM_DOLPHIN_STOP, 0, 0); s_thread.join(); s_message_window = nullptr; } XInput::DeInit(); }