1 // Copyright 2017 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "InputCommon/ControllerInterface/Win32/Win32.h"
6
7 #include <windows.h>
8
9 #include <array>
10 #include <future>
11 #include <thread>
12
13 #include "Common/Event.h"
14 #include "Common/Logging/Log.h"
15 #include "Common/ScopeGuard.h"
16 #include "Common/Thread.h"
17 #include "InputCommon/ControllerInterface/DInput/DInput.h"
18 #include "InputCommon/ControllerInterface/XInput/XInput.h"
19
20 constexpr UINT WM_DOLPHIN_STOP = WM_USER;
21
22 static Common::Event s_done_populating;
23 static std::atomic<HWND> s_hwnd;
24 static HWND s_message_window;
25 static std::thread s_thread;
26
WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)27 static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
28 {
29 if (message == WM_INPUT_DEVICE_CHANGE)
30 {
31 ciface::DInput::PopulateDevices(s_hwnd);
32 ciface::XInput::PopulateDevices();
33 s_done_populating.Set();
34 }
35
36 return DefWindowProc(hwnd, message, wparam, lparam);
37 }
38
Init(void * hwnd)39 void ciface::Win32::Init(void* hwnd)
40 {
41 s_hwnd = static_cast<HWND>(hwnd);
42 XInput::Init();
43
44 std::promise<HWND> message_window_promise;
45
46 s_thread = std::thread([&message_window_promise] {
47 Common::SetCurrentThreadName("ciface::Win32 Message Loop");
48
49 HWND message_window = nullptr;
50 Common::ScopeGuard promise_guard([&] { message_window_promise.set_value(message_window); });
51
52 if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
53 {
54 ERROR_LOG(SERIALINTERFACE, "CoInitializeEx failed: %i", GetLastError());
55 return;
56 }
57 Common::ScopeGuard uninit([] { CoUninitialize(); });
58
59 WNDCLASSEX window_class_info{};
60 window_class_info.cbSize = sizeof(window_class_info);
61 window_class_info.lpfnWndProc = WindowProc;
62 window_class_info.hInstance = GetModuleHandle(nullptr);
63 window_class_info.lpszClassName = L"Message";
64
65 ATOM window_class = RegisterClassEx(&window_class_info);
66 if (!window_class)
67 {
68 NOTICE_LOG(SERIALINTERFACE, "RegisterClassEx failed: %i", GetLastError());
69 return;
70 }
71 Common::ScopeGuard unregister([&window_class] {
72 if (!UnregisterClass(MAKEINTATOM(window_class), GetModuleHandle(nullptr)))
73 ERROR_LOG(SERIALINTERFACE, "UnregisterClass failed: %i", GetLastError());
74 });
75
76 message_window = CreateWindowEx(0, L"Message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr,
77 nullptr, nullptr);
78 promise_guard.Exit();
79 if (!message_window)
80 {
81 ERROR_LOG(SERIALINTERFACE, "CreateWindowEx failed: %i", GetLastError());
82 return;
83 }
84 Common::ScopeGuard destroy([&] {
85 if (!DestroyWindow(message_window))
86 ERROR_LOG(SERIALINTERFACE, "DestroyWindow failed: %i", GetLastError());
87 });
88
89 std::array<RAWINPUTDEVICE, 2> devices;
90 // game pad devices
91 devices[0].usUsagePage = 0x01;
92 devices[0].usUsage = 0x05;
93 devices[0].dwFlags = RIDEV_DEVNOTIFY;
94 devices[0].hwndTarget = message_window;
95 // joystick devices
96 devices[1].usUsagePage = 0x01;
97 devices[1].usUsage = 0x04;
98 devices[1].dwFlags = RIDEV_DEVNOTIFY;
99 devices[1].hwndTarget = message_window;
100
101 if (!RegisterRawInputDevices(devices.data(), static_cast<UINT>(devices.size()),
102 static_cast<UINT>(sizeof(decltype(devices)::value_type))))
103 {
104 ERROR_LOG(SERIALINTERFACE, "RegisterRawInputDevices failed: %i", GetLastError());
105 return;
106 }
107
108 MSG msg;
109 while (GetMessage(&msg, nullptr, 0, 0) > 0)
110 {
111 TranslateMessage(&msg);
112 DispatchMessage(&msg);
113 if (msg.message == WM_DOLPHIN_STOP)
114 break;
115 }
116 });
117
118 s_message_window = message_window_promise.get_future().get();
119 }
120
PopulateDevices(void * hwnd)121 void ciface::Win32::PopulateDevices(void* hwnd)
122 {
123 if (s_thread.joinable())
124 {
125 s_hwnd = static_cast<HWND>(hwnd);
126 s_done_populating.Reset();
127 PostMessage(s_message_window, WM_INPUT_DEVICE_CHANGE, 0, 0);
128 if (!s_done_populating.WaitFor(std::chrono::seconds(10)))
129 ERROR_LOG(SERIALINTERFACE, "win32 timed out when trying to populate devices");
130 }
131 else
132 {
133 ERROR_LOG(SERIALINTERFACE, "win32 asked to populate devices, but device thread isn't running");
134 }
135 }
136
DeInit()137 void ciface::Win32::DeInit()
138 {
139 NOTICE_LOG(SERIALINTERFACE, "win32 DeInit");
140 if (s_thread.joinable())
141 {
142 PostMessage(s_message_window, WM_DOLPHIN_STOP, 0, 0);
143 s_thread.join();
144 s_message_window = nullptr;
145 }
146
147 XInput::DeInit();
148 }
149