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