summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp
blob: 79584913cf771ed638ad836b1085a6806ab2319e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "InputCommon/ControllerInterface/Win32/Win32.h"

#include <Windows.h>
#include <cfgmgr32.h>
// must be before hidclass
#include <initguid.h>

#include <hidclass.h>

#include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h"
#include "InputCommon/ControllerInterface/WGInput/WGInput.h"
#include "InputCommon/ControllerInterface/XInput/XInput.h"

#pragma comment(lib, "OneCoreUAP.Lib")

// TODO is this really needed?
static Common::Flag s_first_populate_devices_asked;
static HCMNOTIFICATION s_notify_handle;

namespace ciface::Win32
{
class InputBackend final : public ciface::InputBackend
{
public:
  InputBackend(ControllerInterface* controller_interface);
  ~InputBackend() override;

  void PopulateDevices() override;
  void HandleWindowChange() override;
  HWND GetHWND();
};
}  // namespace ciface::Win32

_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 (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
    // listen for it.
    if (s_first_populate_devices_asked.IsSet())
    {
      // TODO: we could easily use the message passed alongside this event, which tells
      // whether a device was added or removed, to avoid removing old, still connected, devices
      g_controller_interface.PlatformPopulateDevices([&] {
        ciface::DInput::PopulateDevices(
            static_cast<ciface::Win32::InputBackend*>(Context)->GetHWND());
        ciface::XInput::PopulateDevices();
      });
    }
  }
  return ERROR_SUCCESS;
}

namespace ciface::Win32
{
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
  return std::make_unique<InputBackend>(controller_interface);
}

HWND InputBackend::GetHWND()
{
  return static_cast<HWND>(GetControllerInterface().GetWindowSystemInfo().render_window);
}

InputBackend::InputBackend(ControllerInterface* controller_interface)
    : ciface::InputBackend(controller_interface)
{
  XInput::Init();
  WGInput::Init();

  CM_NOTIFY_FILTER notify_filter{.cbSize = sizeof(notify_filter),
                                 .FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE,
                                 .u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}};
  const CONFIGRET cfg_rv =
      CM_Register_Notification(&notify_filter, this, OnDevicesChanged, &s_notify_handle);
  if (cfg_rv != CR_SUCCESS)
  {
    ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv);
  }
}

void InputBackend::PopulateDevices()
{
  g_controller_interface.PlatformPopulateDevices([this] {
    s_first_populate_devices_asked.Set();
    ciface::DInput::PopulateDevices(GetHWND());
    ciface::XInput::PopulateDevices();
    ciface::WGInput::PopulateDevices();
  });
}

void InputBackend::HandleWindowChange()
{
  g_controller_interface.PlatformPopulateDevices(
      [this] { ciface::DInput::ChangeWindow(GetHWND()); });
}

InputBackend::~InputBackend()
{
  s_first_populate_devices_asked.Clear();
  DInput::DeInit();

  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();
  WGInput::DeInit();
}

}  // namespace ciface::Win32