WiimoteReal: reimplement using HIDAPI
Using the HIDAPI library has the advantage that we get Wiimote and DolphinBar support on all platforms with only one IO implementation.
This commit is contained in:
parent
1c26d41c9a
commit
ca0c2efe7a
|
@ -2,6 +2,7 @@
|
|||
# General setup
|
||||
#
|
||||
cmake_minimum_required(VERSION 2.8.8)
|
||||
project(dolphin-emu)
|
||||
|
||||
option(USE_EGL "Enables EGL OpenGL Interface" OFF)
|
||||
option(TRY_X11 "Enables X11 Support" ON)
|
||||
|
@ -13,6 +14,9 @@ option(ENABLE_LTO "Enables Link Time Optimization" OFF)
|
|||
option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF)
|
||||
if(APPLE)
|
||||
option(OSX_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF)
|
||||
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
option(INSTALL_UDEV_RULES "Install udev rules for hidraw/usb devices" OFF)
|
||||
endif()
|
||||
|
||||
option(ENCODE_FRAMEDUMPS "Encode framedumps in AVI format" ON)
|
||||
|
@ -21,9 +25,6 @@ option(FASTLOG "Enable all logs" OFF)
|
|||
option(OPROFILING "Enable profiling" OFF)
|
||||
option(GDBSTUB "Enable gdb stub for remote debugging." OFF)
|
||||
|
||||
if(APPLE)
|
||||
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
|
||||
endif()
|
||||
########################################
|
||||
# Optional Targets
|
||||
# TODO: Add DSPSpy
|
||||
|
@ -45,7 +46,6 @@ if (APPLE)
|
|||
endif()
|
||||
endif()
|
||||
endif()
|
||||
project(dolphin-emu)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMakeTests)
|
||||
set(DOLPHIN_IS_STABLE FALSE)
|
||||
# Libraries to link
|
||||
|
@ -292,7 +292,6 @@ if(APPLE)
|
|||
find_library(COREFUND_LIBRARY CoreFoundation)
|
||||
find_library(CORESERV_LIBRARY CoreServices)
|
||||
find_library(FOUNDATION_LIBRARY foundation)
|
||||
find_library(IOB_LIBRARY IOBluetooth)
|
||||
find_library(IOK_LIBRARY IOKit)
|
||||
find_library(QUICKTIME_LIBRARY QuickTime)
|
||||
find_library(WEBKIT_LIBRARY WebKit)
|
||||
|
@ -379,15 +378,6 @@ if(NOT ANDROID)
|
|||
message("ao NOT found, disabling ao sound backend")
|
||||
endif(AO_FOUND)
|
||||
|
||||
check_lib(BLUEZ bluez QUIET)
|
||||
if(BLUEZ_FOUND)
|
||||
add_definitions(-DHAVE_BLUEZ=1)
|
||||
message("bluez found, enabling bluetooth support")
|
||||
else()
|
||||
add_definitions(-DHAVE_BLUEZ=0)
|
||||
message("bluez NOT found, disabling bluetooth support")
|
||||
endif(BLUEZ_FOUND)
|
||||
|
||||
check_lib(PULSEAUDIO libpulse QUIET)
|
||||
if(PULSEAUDIO_FOUND)
|
||||
add_definitions(-DHAVE_PULSEAUDIO=1)
|
||||
|
@ -595,10 +585,19 @@ endif()
|
|||
|
||||
include(FindLibUSB OPTIONAL)
|
||||
if(LIBUSB_FOUND)
|
||||
message("Using shared LibUSB")
|
||||
message("Using shared libusb")
|
||||
add_definitions(-D__LIBUSB__)
|
||||
include_directories(${LIBUSB_INCLUDE_DIR})
|
||||
endif(LIBUSB_FOUND)
|
||||
list(APPEND LIBS ${LIBUSB_LIBRARIES})
|
||||
elseif(ANDROID)
|
||||
message("Using static libusb from Externals")
|
||||
add_subdirectory(Externals/libusb/libusb)
|
||||
include_directories(Externals/libusb/libusb)
|
||||
list(APPEND LIBS usb-1.0)
|
||||
else()
|
||||
message(FATAL_ERROR "libusb is required. Either extend the CMakeLists.txt file to support your OS"
|
||||
" or just install libusb as a shared library.")
|
||||
endif()
|
||||
|
||||
set(SFML_REQD_VERSION 2.1)
|
||||
if(NOT APPLE AND NOT ANDROID)
|
||||
|
@ -665,6 +664,32 @@ else()
|
|||
mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES)
|
||||
endif()
|
||||
|
||||
include(FindHIDAPI OPTIONAL)
|
||||
find_package(HIDAPI)
|
||||
if(HIDAPI_FOUND)
|
||||
message("Using shared ${HIDAPI_LIBRARIES} ${HIDAPI_VERSION}")
|
||||
include_directories(${HIDAPI_INCLUDE_DIRS})
|
||||
list(APPEND LIBS ${HIDAPI_LIBRARIES})
|
||||
else()
|
||||
include_directories(Externals/hidapi/hidapi)
|
||||
if(APPLE)
|
||||
message("Using static hidapi from Externals")
|
||||
add_subdirectory(Externals/hidapi/mac)
|
||||
list(APPEND LIBS hidapi)
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND NOT ANDROID)
|
||||
message("Using static hidapi-hidraw from Externals")
|
||||
add_subdirectory(Externals/hidapi/linux)
|
||||
list(APPEND LIBS hidapi-hidraw udev)
|
||||
else()
|
||||
message("Using static hidapi-libusb from Externals")
|
||||
add_subdirectory(Externals/hidapi/libusb)
|
||||
list(APPEND LIBS hidapi-libusb)
|
||||
endif()
|
||||
endif()
|
||||
if(INSTALL_UDEV_RULES)
|
||||
install(FILES Installer/90-wiimote.rules /etc/udev/rules.d/)
|
||||
endif()
|
||||
|
||||
if(ENABLE_QT)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
message("Found Qt version ${Qt5Core_VERSION}, enabling the Qt backend")
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
find_path(HIDAPI_INCLUDE_DIR NAMES hidapi.h PATH_SUFFIXES hidapi)
|
||||
find_library(HIDAPI_LIBRARY NAMES hidapi hidapi-hidraw hidapi-libusb)
|
||||
set(HIDAPI_LIBRARIES ${HIDAPI_LIBRARY})
|
||||
set(HIDAPI_INCLUDE_DIRS ${HIDAPI_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(HIDAPI DEFAULT_MSG HIDAPI_LIBRARY HIDAPI_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(HIDAPI_INCLUDE_DIR HIDAPI_LIBRARY)
|
|
@ -0,0 +1 @@
|
|||
add_library(hidapi-libusb hid.c)
|
|
@ -0,0 +1 @@
|
|||
add_library(hidapi-hidraw hid.c)
|
|
@ -0,0 +1 @@
|
|||
add_library(hidapi hid.c)
|
|
@ -0,0 +1,17 @@
|
|||
# This file is only used for Android (because I'm lazy).
|
||||
# We don't use CMake on Windows and just require libusb to be installed everywhere else.
|
||||
|
||||
set(SRCS
|
||||
core.c
|
||||
descriptor.c
|
||||
hotplug.c
|
||||
io.c
|
||||
strerror.c
|
||||
sync.c
|
||||
)
|
||||
|
||||
include_directories(BEFORE ../android)
|
||||
|
||||
add_subdirectory(os)
|
||||
add_library(usb-1.0 STATIC ${SRCS})
|
||||
target_link_libraries(usb-1.0 usb-os)
|
|
@ -0,0 +1,7 @@
|
|||
include_directories(..)
|
||||
add_library(usb-os STATIC
|
||||
linux_netlink.c
|
||||
linux_usbfs.c
|
||||
poll_posix.c
|
||||
threads_posix.c
|
||||
)
|
|
@ -27,7 +27,6 @@
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v120</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
|
@ -38,7 +37,6 @@
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v120</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# This is how you can match Wiimotes (and Wiimote-emulating devices like the DolphinBar) in udev.
|
||||
# If you are a package maintainer you'll probably want to restrict permissions to a group: GROUP="plugdev", MODE="0660".
|
||||
|
||||
# Wiimote or DolphinBar
|
||||
SUBSYSTEMS=="hid", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0306", MODE="0666"
|
||||
|
||||
# newer Wiimotes (RVL-CNT-01-TR)
|
||||
SUBSYSTEMS=="hid", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0330", MODE="0666"
|
|
@ -116,6 +116,7 @@ set(SRCS ActionReplay.cpp
|
|||
HW/SI_DeviceGBA.cpp
|
||||
HW/SI_DeviceGCController.cpp
|
||||
HW/SI_DeviceGCSteeringWheel.cpp
|
||||
HW/SI_GCAdapter.cpp
|
||||
HW/Sram.cpp
|
||||
HW/StreamADPCM.cpp
|
||||
HW/SystemTimers.cpp
|
||||
|
@ -132,6 +133,7 @@ set(SRCS ActionReplay.cpp
|
|||
HW/WiimoteEmu/EmuSubroutines.cpp
|
||||
HW/WiimoteEmu/Encryption.cpp
|
||||
HW/WiimoteEmu/Speaker.cpp
|
||||
HW/WiimoteReal/IOhidapi.cpp
|
||||
HW/WiimoteReal/WiimoteReal.cpp
|
||||
HW/WiiSaveCrypted.cpp
|
||||
IPC_HLE/ICMPLin.cpp
|
||||
|
@ -140,6 +142,7 @@ set(SRCS ActionReplay.cpp
|
|||
IPC_HLE/WII_IPC_HLE_Device_es.cpp
|
||||
IPC_HLE/WII_IPC_HLE_Device_FileIO.cpp
|
||||
IPC_HLE/WII_IPC_HLE_Device_fs.cpp
|
||||
IPC_HLE/WII_IPC_HLE_Device_hid.cpp
|
||||
IPC_HLE/WII_Socket.cpp
|
||||
IPC_HLE/WII_IPC_HLE_Device_net.cpp
|
||||
IPC_HLE/WII_IPC_HLE_Device_net_ssl.cpp
|
||||
|
@ -245,29 +248,15 @@ set(LIBS
|
|||
z
|
||||
)
|
||||
|
||||
if(LIBUSB_FOUND)
|
||||
# Using shared LibUSB
|
||||
set(LIBS ${LIBS} ${LIBUSB_LIBRARIES})
|
||||
set(SRCS ${SRCS} IPC_HLE/WII_IPC_HLE_Device_hid.cpp
|
||||
HW/SI_GCAdapter.cpp)
|
||||
endif(LIBUSB_FOUND)
|
||||
|
||||
set(LIBS ${LIBS} ${POLARSSL_LIBRARY})
|
||||
|
||||
if(WIN32)
|
||||
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Win32.cpp HW/WiimoteReal/IOWin.cpp)
|
||||
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Win32.cpp)
|
||||
elseif(APPLE)
|
||||
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp HW/WiimoteReal/IOdarwin.mm)
|
||||
set(LIBS ${LIBS}
|
||||
${IOB_LIBRARY})
|
||||
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp)
|
||||
elseif(UNIX)
|
||||
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Unix.cpp)
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND BLUEZ_FOUND)
|
||||
set(SRCS ${SRCS} HW/WiimoteReal/IONix.cpp)
|
||||
set(LIBS ${LIBS} bluetooth)
|
||||
else()
|
||||
set(SRCS ${SRCS} HW/WiimoteReal/IODummy.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(PORTAUDIO_FOUND)
|
||||
|
|
|
@ -169,7 +169,7 @@
|
|||
<ClCompile Include="HW\WiimoteEmu\Encryption.cpp" />
|
||||
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp" />
|
||||
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp" />
|
||||
<ClCompile Include="HW\WiimoteReal\IOWin.cpp" />
|
||||
<ClCompile Include="HW\WiimoteReal\IOhidapi.cpp" />
|
||||
<ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp" />
|
||||
<ClCompile Include="HW\WII_IPC.cpp" />
|
||||
<ClCompile Include="HW\WiiSaveCrypted.cpp" />
|
||||
|
@ -470,6 +470,9 @@
|
|||
<ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj">
|
||||
<Project>{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(ExternalsDir)hidapi\hidapi.vcxproj">
|
||||
<Project>{549d32d8-1640-46f9-9d78-bae6eb0d723d}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
|
|
@ -484,7 +484,7 @@
|
|||
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\WiimoteReal\IOWin.cpp">
|
||||
<ClCompile Include="HW\WiimoteReal\IOhidapi.cpp">
|
||||
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Real</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp">
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
{}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
|
||||
{
|
||||
found_wiimotes.clear();
|
||||
found_board = nullptr;
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,279 +0,0 @@
|
|||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_lib.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
class WiimoteLinux final : public Wiimote
|
||||
{
|
||||
public:
|
||||
WiimoteLinux(bdaddr_t bdaddr);
|
||||
~WiimoteLinux() override;
|
||||
|
||||
protected:
|
||||
bool ConnectInternal() override;
|
||||
void DisconnectInternal() override;
|
||||
bool IsConnected() const override;
|
||||
void IOWakeup() override;
|
||||
int IORead(u8* buf) override;
|
||||
int IOWrite(u8 const* buf, size_t len) override;
|
||||
|
||||
private:
|
||||
bdaddr_t m_bdaddr; // Bluetooth address
|
||||
int m_cmd_sock; // Command socket
|
||||
int m_int_sock; // Interrupt socket
|
||||
int m_wakeup_pipe_w;
|
||||
int m_wakeup_pipe_r;
|
||||
};
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
: m_want_wiimotes()
|
||||
, device_id(-1)
|
||||
, device_sock(-1)
|
||||
{
|
||||
// Get the id of the first bluetooth device.
|
||||
device_id = hci_get_route(nullptr);
|
||||
if (device_id < 0)
|
||||
{
|
||||
NOTICE_LOG(WIIMOTE, "Bluetooth not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a socket to the device
|
||||
device_sock = hci_open_dev(device_id);
|
||||
if (device_sock < 0)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to open bluetooth.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
return device_sock > 0;
|
||||
}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{
|
||||
if (IsReady())
|
||||
close(device_sock);
|
||||
}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
|
||||
{
|
||||
// supposedly 1.28 seconds
|
||||
int const wait_len = 1;
|
||||
|
||||
int const max_infos = 255;
|
||||
inquiry_info scan_infos[max_infos] = {};
|
||||
auto* scan_infos_ptr = scan_infos;
|
||||
found_board = nullptr;
|
||||
|
||||
// Scan for bluetooth devices
|
||||
int const found_devices = hci_inquiry(device_id, wait_len, max_infos, nullptr, &scan_infos_ptr, IREQ_CACHE_FLUSH);
|
||||
if (found_devices < 0)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Error searching for bluetooth devices.");
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(WIIMOTE, "Found %i bluetooth device(s).", found_devices);
|
||||
|
||||
// Display discovered devices
|
||||
for (int i = 0; i < found_devices; ++i)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "found a device...");
|
||||
|
||||
// BT names are a maximum of 248 bytes apparently
|
||||
char name[255] = {};
|
||||
if (hci_read_remote_name(device_sock, &scan_infos[i].bdaddr, sizeof(name), name, 1000) < 0)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "name request failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
ERROR_LOG(WIIMOTE, "device name %s", name);
|
||||
if (IsValidBluetoothName(name))
|
||||
{
|
||||
bool new_wiimote = true;
|
||||
|
||||
// TODO: do this
|
||||
|
||||
// Determine if this wiimote has already been found.
|
||||
//for (int j = 0; j < MAX_WIIMOTES && new_wiimote; ++j)
|
||||
//{
|
||||
// if (wm[j] && bacmp(&scan_infos[i].bdaddr,&wm[j]->bdaddr) == 0)
|
||||
// new_wiimote = false;
|
||||
//}
|
||||
|
||||
if (new_wiimote)
|
||||
{
|
||||
// Found a new device
|
||||
char bdaddr_str[18] = {};
|
||||
ba2str(&scan_infos[i].bdaddr, bdaddr_str);
|
||||
|
||||
Wiimote* wm = new WiimoteLinux(scan_infos[i].bdaddr);
|
||||
if (IsBalanceBoardName(name))
|
||||
{
|
||||
found_board = wm;
|
||||
NOTICE_LOG(WIIMOTE, "Found balance board (%s).", bdaddr_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
found_wiimotes.push_back(wm);
|
||||
NOTICE_LOG(WIIMOTE, "Found wiimote (%s).", bdaddr_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WiimoteLinux::WiimoteLinux(bdaddr_t bdaddr) : Wiimote(), m_bdaddr(bdaddr)
|
||||
{
|
||||
m_cmd_sock = -1;
|
||||
m_int_sock = -1;
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds))
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "pipe failed");
|
||||
abort();
|
||||
}
|
||||
m_wakeup_pipe_w = fds[1];
|
||||
m_wakeup_pipe_r = fds[0];
|
||||
}
|
||||
|
||||
WiimoteLinux::~WiimoteLinux()
|
||||
{
|
||||
Shutdown();
|
||||
close(m_wakeup_pipe_w);
|
||||
close(m_wakeup_pipe_r);
|
||||
}
|
||||
|
||||
// Connect to a wiimote with a known address.
|
||||
bool WiimoteLinux::ConnectInternal()
|
||||
{
|
||||
sockaddr_l2 addr = {};
|
||||
addr.l2_family = AF_BLUETOOTH;
|
||||
addr.l2_bdaddr = m_bdaddr;
|
||||
addr.l2_cid = 0;
|
||||
|
||||
// Output channel
|
||||
addr.l2_psm = htobs(WM_OUTPUT_CHANNEL);
|
||||
if ((m_cmd_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) == -1 ||
|
||||
connect(m_cmd_sock, (sockaddr*)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "Unable to open output socket to wiimote: %s", strerror(errno));
|
||||
close(m_cmd_sock);
|
||||
m_cmd_sock = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Input channel
|
||||
addr.l2_psm = htobs(WM_INPUT_CHANNEL);
|
||||
if ((m_int_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) == -1 ||
|
||||
connect(m_int_sock, (sockaddr*)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "Unable to open input socket from wiimote: %s", strerror(errno));
|
||||
close(m_int_sock);
|
||||
close(m_cmd_sock);
|
||||
m_int_sock = m_cmd_sock = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiimoteLinux::DisconnectInternal()
|
||||
{
|
||||
close(m_cmd_sock);
|
||||
close(m_int_sock);
|
||||
|
||||
m_cmd_sock = -1;
|
||||
m_int_sock = -1;
|
||||
}
|
||||
|
||||
bool WiimoteLinux::IsConnected() const
|
||||
{
|
||||
return m_cmd_sock != -1;// && int_sock != -1;
|
||||
}
|
||||
|
||||
void WiimoteLinux::IOWakeup()
|
||||
{
|
||||
char c = 0;
|
||||
if (write(m_wakeup_pipe_w, &c, 1) != 1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to write to wakeup pipe.");
|
||||
}
|
||||
}
|
||||
|
||||
// positive = read packet
|
||||
// negative = didn't read packet
|
||||
// zero = error
|
||||
int WiimoteLinux::IORead(u8* buf)
|
||||
{
|
||||
// Block select for 1/2000th of a second
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(m_int_sock, &fds);
|
||||
FD_SET(m_wakeup_pipe_r, &fds);
|
||||
|
||||
if (select(m_int_sock + 1, &fds, nullptr, nullptr, nullptr) == -1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to select wiimote %i input socket.", m_index + 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (FD_ISSET(m_wakeup_pipe_r, &fds))
|
||||
{
|
||||
char c;
|
||||
if (read(m_wakeup_pipe_r, &c, 1) != 1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to read from wakeup pipe.");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!FD_ISSET(m_int_sock, &fds))
|
||||
return -1;
|
||||
|
||||
// Read the pending message into the buffer
|
||||
int r = read(m_int_sock, buf, MAX_PAYLOAD);
|
||||
if (r == -1)
|
||||
{
|
||||
// Error reading data
|
||||
ERROR_LOG(WIIMOTE, "Receiving data from wiimote %i.", m_index + 1);
|
||||
|
||||
if (errno == ENOTCONN)
|
||||
{
|
||||
// This can happen if the bluetooth dongle is disconnected
|
||||
ERROR_LOG(WIIMOTE, "Bluetooth appears to be disconnected. "
|
||||
"Wiimote %i will be disconnected.", m_index + 1);
|
||||
}
|
||||
|
||||
r = 0;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int WiimoteLinux::IOWrite(u8 const* buf, size_t len)
|
||||
{
|
||||
return write(m_int_sock, buf, (int)len);
|
||||
}
|
||||
|
||||
}; // WiimoteReal
|
|
@ -1,954 +0,0 @@
|
|||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <hidsdi.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <windows.h>
|
||||
// The following Windows headers must be included AFTER windows.h.
|
||||
#include <BluetoothAPIs.h> //NOLINT
|
||||
#include <dbt.h> //NOLINT
|
||||
#include <setupapi.h> //NOLINT
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
//#define AUTHENTICATE_WIIMOTES
|
||||
#define SHARE_WRITE_WIIMOTES
|
||||
|
||||
// Create func_t function pointer type and declare a nullptr-initialized static variable of that
|
||||
// type named "pfunc".
|
||||
#define DYN_FUNC_DECLARE(func) \
|
||||
typedef decltype(&func) func ## _t; \
|
||||
static func ## _t p ## func = nullptr;
|
||||
|
||||
DYN_FUNC_DECLARE(HidD_GetHidGuid);
|
||||
DYN_FUNC_DECLARE(HidD_GetAttributes);
|
||||
DYN_FUNC_DECLARE(HidD_SetOutputReport);
|
||||
DYN_FUNC_DECLARE(HidD_GetProductString);
|
||||
|
||||
DYN_FUNC_DECLARE(BluetoothFindDeviceClose);
|
||||
DYN_FUNC_DECLARE(BluetoothFindFirstDevice);
|
||||
DYN_FUNC_DECLARE(BluetoothFindFirstRadio);
|
||||
DYN_FUNC_DECLARE(BluetoothFindNextDevice);
|
||||
DYN_FUNC_DECLARE(BluetoothFindNextRadio);
|
||||
DYN_FUNC_DECLARE(BluetoothFindRadioClose);
|
||||
DYN_FUNC_DECLARE(BluetoothGetRadioInfo);
|
||||
DYN_FUNC_DECLARE(BluetoothRemoveDevice);
|
||||
DYN_FUNC_DECLARE(BluetoothSetServiceState);
|
||||
DYN_FUNC_DECLARE(BluetoothAuthenticateDeviceEx);
|
||||
DYN_FUNC_DECLARE(BluetoothEnumerateInstalledServices);
|
||||
|
||||
#undef DYN_FUNC_DECLARE
|
||||
|
||||
static HINSTANCE s_hid_lib = nullptr;
|
||||
static HINSTANCE s_bthprops_lib = nullptr;
|
||||
|
||||
static bool s_loaded_ok = false;
|
||||
|
||||
std::unordered_map<BTH_ADDR, std::time_t> g_connect_times;
|
||||
|
||||
#ifdef SHARE_WRITE_WIIMOTES
|
||||
std::unordered_set<std::basic_string<TCHAR>> g_connected_wiimotes;
|
||||
std::mutex g_connected_wiimotes_lock;
|
||||
#endif
|
||||
|
||||
#define DYN_FUNC_UNLOAD(func) \
|
||||
p ## func = nullptr;
|
||||
|
||||
// Attempt to load the function from the given module handle.
|
||||
#define DYN_FUNC_LOAD(module, func) \
|
||||
p ## func = ( func ## _t)::GetProcAddress(module, # func ); \
|
||||
if (! p ## func ) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool load_hid()
|
||||
{
|
||||
auto loader = [&]()
|
||||
{
|
||||
s_hid_lib = ::LoadLibrary(_T("hid.dll"));
|
||||
if (!s_hid_lib)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DYN_FUNC_LOAD(s_hid_lib, HidD_GetHidGuid);
|
||||
DYN_FUNC_LOAD(s_hid_lib, HidD_GetAttributes);
|
||||
DYN_FUNC_LOAD(s_hid_lib, HidD_SetOutputReport);
|
||||
DYN_FUNC_LOAD(s_hid_lib, HidD_GetProductString);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
bool loaded_ok = loader();
|
||||
|
||||
if (!loaded_ok)
|
||||
{
|
||||
DYN_FUNC_UNLOAD(HidD_GetHidGuid);
|
||||
DYN_FUNC_UNLOAD(HidD_GetAttributes);
|
||||
DYN_FUNC_UNLOAD(HidD_SetOutputReport);
|
||||
DYN_FUNC_UNLOAD(HidD_GetProductString);
|
||||
|
||||
if (s_hid_lib)
|
||||
{
|
||||
::FreeLibrary(s_hid_lib);
|
||||
s_hid_lib = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return loaded_ok;
|
||||
}
|
||||
|
||||
bool load_bthprops()
|
||||
{
|
||||
auto loader = [&]()
|
||||
{
|
||||
s_bthprops_lib = ::LoadLibrary(_T("bthprops.cpl"));
|
||||
if (!s_bthprops_lib)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindDeviceClose);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindFirstDevice);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindFirstRadio);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindNextDevice);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindNextRadio);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindRadioClose);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothGetRadioInfo);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothRemoveDevice);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothSetServiceState);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothAuthenticateDeviceEx);
|
||||
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothEnumerateInstalledServices);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
bool loaded_ok = loader();
|
||||
|
||||
if (!loaded_ok)
|
||||
{
|
||||
DYN_FUNC_UNLOAD(BluetoothFindDeviceClose);
|
||||
DYN_FUNC_UNLOAD(BluetoothFindFirstDevice);
|
||||
DYN_FUNC_UNLOAD(BluetoothFindFirstRadio);
|
||||
DYN_FUNC_UNLOAD(BluetoothFindNextDevice);
|
||||
DYN_FUNC_UNLOAD(BluetoothFindNextRadio);
|
||||
DYN_FUNC_UNLOAD(BluetoothFindRadioClose);
|
||||
DYN_FUNC_UNLOAD(BluetoothGetRadioInfo);
|
||||
DYN_FUNC_UNLOAD(BluetoothRemoveDevice);
|
||||
DYN_FUNC_UNLOAD(BluetoothSetServiceState);
|
||||
DYN_FUNC_UNLOAD(BluetoothAuthenticateDeviceEx);
|
||||
DYN_FUNC_UNLOAD(BluetoothEnumerateInstalledServices);
|
||||
|
||||
if (s_bthprops_lib)
|
||||
{
|
||||
::FreeLibrary(s_bthprops_lib);
|
||||
s_bthprops_lib = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return loaded_ok;
|
||||
}
|
||||
|
||||
#undef DYN_FUNC_LOAD
|
||||
#undef DYN_FUNC_UNLOAD
|
||||
|
||||
inline void init_lib()
|
||||
{
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
// Only try once
|
||||
initialized = true;
|
||||
|
||||
// After these calls, we know all dynamically loaded APIs will either all be valid or
|
||||
// all nullptr.
|
||||
if (!load_hid() || !load_bthprops())
|
||||
{
|
||||
NOTICE_LOG(WIIMOTE,
|
||||
"Failed to load bluetooth support libraries, wiimotes will not function");
|
||||
return;
|
||||
}
|
||||
|
||||
s_loaded_ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
class WiimoteWindows final : public Wiimote
|
||||
{
|
||||
public:
|
||||
WiimoteWindows(const std::basic_string<TCHAR>& path);
|
||||
~WiimoteWindows() override;
|
||||
|
||||
protected:
|
||||
bool ConnectInternal() override;
|
||||
void DisconnectInternal() override;
|
||||
bool IsConnected() const override;
|
||||
void IOWakeup() override;
|
||||
int IORead(u8* buf) override;
|
||||
int IOWrite(u8 const* buf, size_t len) override;
|
||||
|
||||
private:
|
||||
std::basic_string<TCHAR> m_devicepath; // Unique wiimote reference
|
||||
HANDLE m_dev_handle; // HID handle
|
||||
OVERLAPPED m_hid_overlap_read; // Overlap handles
|
||||
OVERLAPPED m_hid_overlap_write;
|
||||
enum win_bt_stack_t m_stack; // Type of bluetooth stack to use
|
||||
};
|
||||
|
||||
int _IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum win_bt_stack_t &stack, const u8* buf, size_t len, DWORD* written);
|
||||
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index);
|
||||
|
||||
template <typename T>
|
||||
void ProcessWiimotes(bool new_scan, T& callback);
|
||||
|
||||
bool AttachWiimote(HANDLE hRadio, const BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT&);
|
||||
void RemoveWiimote(BLUETOOTH_DEVICE_INFO_STRUCT&);
|
||||
bool ForgetWiimote(BLUETOOTH_DEVICE_INFO_STRUCT&);
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
: m_run_thread()
|
||||
, m_want_wiimotes()
|
||||
{
|
||||
init_lib();
|
||||
}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{
|
||||
// TODO: what do we want here?
|
||||
#if 0
|
||||
ProcessWiimotes(false, [](HANDLE, BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
RemoveWiimote(btdi);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{
|
||||
if (!s_loaded_ok)
|
||||
return;
|
||||
|
||||
bool forgot_some = false;
|
||||
|
||||
ProcessWiimotes(false, [&](HANDLE, BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
forgot_some |= ForgetWiimote(btdi);
|
||||
});
|
||||
|
||||
// Some hacks that allows disconnects to be detected before connections are handled
|
||||
// workaround for wiimote 1 moving to slot 2 on temporary disconnect
|
||||
if (forgot_some)
|
||||
SLEEP(100);
|
||||
}
|
||||
|
||||
// Find and connect wiimotes.
|
||||
// Does not replace already found wiimotes even if they are disconnected.
|
||||
// wm is an array of max_wiimotes wiimotes
|
||||
// Returns the total number of found and connected wiimotes.
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
|
||||
{
|
||||
if (!s_loaded_ok)
|
||||
return;
|
||||
|
||||
ProcessWiimotes(true, [](HANDLE hRadio, const BLUETOOTH_RADIO_INFO& rinfo, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
ForgetWiimote(btdi);
|
||||
AttachWiimote(hRadio, rinfo, btdi);
|
||||
});
|
||||
|
||||
// Get the device id
|
||||
GUID device_id;
|
||||
pHidD_GetHidGuid(&device_id);
|
||||
|
||||
// Get all hid devices connected
|
||||
HDEVINFO const device_info = SetupDiGetClassDevs(&device_id, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA device_data;
|
||||
device_data.cbSize = sizeof(device_data);
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = nullptr;
|
||||
|
||||
for (int index = 0; SetupDiEnumDeviceInterfaces(device_info, nullptr, &device_id, index, &device_data); ++index)
|
||||
{
|
||||
// Get the size of the data block required
|
||||
DWORD len;
|
||||
SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &len, nullptr);
|
||||
detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(len);
|
||||
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
// Query the data for this device
|
||||
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, len, nullptr, nullptr))
|
||||
{
|
||||
std::basic_string<TCHAR> device_path(detail_data->DevicePath);
|
||||
Wiimote* wm = new WiimoteWindows(device_path);
|
||||
bool real_wiimote = false, is_bb = false;
|
||||
|
||||
CheckDeviceType(device_path, real_wiimote, is_bb);
|
||||
if (is_bb)
|
||||
{
|
||||
found_board = wm;
|
||||
}
|
||||
else if (real_wiimote)
|
||||
{
|
||||
found_wiimotes.push_back(wm);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete wm;
|
||||
}
|
||||
}
|
||||
|
||||
free(detail_data);
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(device_info);
|
||||
|
||||
// Don't mind me, just a random sleep to fix stuff on Windows
|
||||
//if (!wiimotes.empty())
|
||||
// SLEEP(2000);
|
||||
|
||||
}
|
||||
int CheckDeviceType_Write(HANDLE &dev_handle, const u8* buf, size_t size, int attempts)
|
||||
{
|
||||
OVERLAPPED hid_overlap_write = OVERLAPPED();
|
||||
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
enum win_bt_stack_t stack = MSBT_STACK_UNKNOWN;
|
||||
|
||||
DWORD written = 0;
|
||||
|
||||
for (; attempts>0; --attempts)
|
||||
{
|
||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, size, &written))
|
||||
break;
|
||||
}
|
||||
|
||||
CloseHandle(hid_overlap_write.hEvent);
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
int CheckDeviceType_Read(HANDLE &dev_handle, u8* buf, int attempts)
|
||||
{
|
||||
OVERLAPPED hid_overlap_read = OVERLAPPED();
|
||||
hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
int read = 0;
|
||||
for (; attempts>0; --attempts)
|
||||
{
|
||||
read = _IORead(dev_handle, hid_overlap_read, buf, 1);
|
||||
if (read > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
CloseHandle(hid_overlap_read.hEvent);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
// A convoluted way of checking if a device is a Wii Balance Board and if it is a connectible Wiimote.
|
||||
// Because nothing on Windows should be easy.
|
||||
// (We can't seem to easily identify the bluetooth device an HID device belongs to...)
|
||||
void WiimoteScanner::CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool &real_wiimote, bool &is_bb)
|
||||
{
|
||||
real_wiimote = false;
|
||||
is_bb = false;
|
||||
|
||||
#ifdef SHARE_WRITE_WIIMOTES
|
||||
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
|
||||
if (g_connected_wiimotes.count(devicepath) != 0)
|
||||
return;
|
||||
#endif
|
||||
|
||||
HANDLE dev_handle = CreateFile(devicepath.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
if (dev_handle == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
// enable to only check for official nintendo wiimotes/bb's
|
||||
bool check_vidpid = false;
|
||||
HIDD_ATTRIBUTES attrib;
|
||||
attrib.Size = sizeof(attrib);
|
||||
if (!check_vidpid ||
|
||||
(pHidD_GetAttributes(dev_handle, &attrib) &&
|
||||
(attrib.VendorID == 0x057e) &&
|
||||
(attrib.ProductID == 0x0306)))
|
||||
{
|
||||
// max_cycles insures we are never stuck here due to bad coding...
|
||||
int max_cycles = 20;
|
||||
u8 buf[MAX_PAYLOAD] = {0};
|
||||
|
||||
u8 const req_status_report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
|
||||
// The new way to initialize the extension is by writing 0x55 to 0x(4)A400F0, then writing 0x00 to 0x(4)A400FB
|
||||
// 52 16 04 A4 00 F0 01 55
|
||||
// 52 16 04 A4 00 FB 01 00
|
||||
u8 const disable_enc_pt1_report[MAX_PAYLOAD] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xf0, 0x01, 0x55};
|
||||
u8 const disable_enc_pt2_report[MAX_PAYLOAD] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
|
||||
|
||||
CheckDeviceType_Write(dev_handle,
|
||||
disable_enc_pt1_report,
|
||||
sizeof(disable_enc_pt1_report),
|
||||
1);
|
||||
CheckDeviceType_Write(dev_handle,
|
||||
disable_enc_pt2_report,
|
||||
sizeof(disable_enc_pt2_report),
|
||||
1);
|
||||
|
||||
int rc = CheckDeviceType_Write(dev_handle,
|
||||
req_status_report,
|
||||
sizeof(req_status_report),
|
||||
1);
|
||||
|
||||
while (rc > 0 && --max_cycles > 0)
|
||||
{
|
||||
if ((rc = CheckDeviceType_Read(dev_handle, buf, 1)) <= 0)
|
||||
{
|
||||
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Read failed...");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (buf[1])
|
||||
{
|
||||
case WM_STATUS_REPORT:
|
||||
{
|
||||
real_wiimote = true;
|
||||
|
||||
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Status Report");
|
||||
wm_status_report * wsr = (wm_status_report*)&buf[2];
|
||||
if (wsr->extension)
|
||||
{
|
||||
// Wiimote with extension, we ask it what kind.
|
||||
u8 read_ext[MAX_PAYLOAD] = {0};
|
||||
read_ext[0] = WM_SET_REPORT | WM_BT_OUTPUT;
|
||||
read_ext[1] = WM_READ_DATA;
|
||||
// Extension type register.
|
||||
*(u32*)&read_ext[2] = Common::swap32(0x4a400fa);
|
||||
// Size.
|
||||
*(u16*)&read_ext[6] = Common::swap16(6);
|
||||
rc = CheckDeviceType_Write(dev_handle, read_ext, 8, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal Wiimote, exit while and be happy.
|
||||
rc = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_ACK_DATA:
|
||||
{
|
||||
real_wiimote = true;
|
||||
//wm_acknowledge * wm = (wm_acknowledge*)&buf[2];
|
||||
//DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Ack Error: %X ReportID: %X", wm->errorID, wm->reportID);
|
||||
break;
|
||||
}
|
||||
case WM_READ_DATA_REPLY:
|
||||
{
|
||||
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Data Reply");
|
||||
wm_read_data_reply * wrdr
|
||||
= (wm_read_data_reply*)&buf[2];
|
||||
// Check if it has returned what we asked.
|
||||
if (Common::swap16(wrdr->address) == 0x00fa)
|
||||
{
|
||||
real_wiimote = true;
|
||||
// 0x020420A40000ULL means balance board.
|
||||
u64 ext_type = (*(u64*)&wrdr->data[0]);
|
||||
// DEBUG_LOG(WIIMOTE,
|
||||
// "CheckDeviceType: GOT EXT TYPE %llX",
|
||||
// ext_type);
|
||||
is_bb = (ext_type == 0x020420A40000ULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(WIIMOTE,
|
||||
"CheckDeviceType: GOT UNREQUESTED ADDRESS %X",
|
||||
Common::swap16(wrdr->address));
|
||||
}
|
||||
// force end
|
||||
rc = -1;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// We let read try again incase there is another packet waiting.
|
||||
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: GOT UNKNOWN REPLY: %X", buf[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CloseHandle(dev_handle);
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
if (!s_loaded_ok)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: don't search for a radio each time
|
||||
|
||||
BLUETOOTH_FIND_RADIO_PARAMS radioParam;
|
||||
radioParam.dwSize = sizeof(radioParam);
|
||||
|
||||
HANDLE hRadio;
|
||||
HBLUETOOTH_RADIO_FIND hFindRadio = pBluetoothFindFirstRadio(&radioParam, &hRadio);
|
||||
|
||||
if (nullptr != hFindRadio)
|
||||
{
|
||||
pBluetoothFindRadioClose(hFindRadio);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to a wiimote with a known device path.
|
||||
bool WiimoteWindows::ConnectInternal()
|
||||
{
|
||||
if (IsConnected())
|
||||
return false;
|
||||
|
||||
#ifdef SHARE_WRITE_WIIMOTES
|
||||
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
|
||||
if (g_connected_wiimotes.count(m_devicepath) != 0)
|
||||
return false;
|
||||
|
||||
auto const open_flags = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||
#else
|
||||
// Having no FILE_SHARE_WRITE disallows us from connecting to the same wiimote twice.
|
||||
// (And disallows using wiimotes in use by other programs)
|
||||
// This is what "WiiYourself" does.
|
||||
// Apparently this doesn't work for everyone. It might be their fault.
|
||||
auto const open_flags = FILE_SHARE_READ;
|
||||
#endif
|
||||
|
||||
m_dev_handle = CreateFile(m_devicepath.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE, open_flags,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
|
||||
|
||||
if (m_dev_handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_dev_handle = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TCHAR name[128] = {};
|
||||
pHidD_GetProductString(dev_handle, name, 128);
|
||||
|
||||
//ERROR_LOG(WIIMOTE, "Product string: %s", TStrToUTF8(name).c_str());
|
||||
|
||||
if (!IsValidBluetoothName(TStrToUTF8(name)))
|
||||
{
|
||||
CloseHandle(dev_handle);
|
||||
dev_handle = 0;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
HIDD_ATTRIBUTES attr;
|
||||
attr.Size = sizeof(attr);
|
||||
if (!pHidD_GetAttributes(dev_handle, &attr))
|
||||
{
|
||||
CloseHandle(dev_handle);
|
||||
dev_handle = 0;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: thread isn't started here now, do this elsewhere
|
||||
// This isn't as drastic as it sounds, since the process in which the threads
|
||||
// reside is normal priority. Needed for keeping audio reports at a decent rate
|
||||
/*
|
||||
if (!SetThreadPriority(m_wiimote_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL))
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Failed to set Wiimote thread priority");
|
||||
}
|
||||
*/
|
||||
#ifdef SHARE_WRITE_WIIMOTES
|
||||
g_connected_wiimotes.insert(m_devicepath);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiimoteWindows::DisconnectInternal()
|
||||
{
|
||||
if (!IsConnected())
|
||||
return;
|
||||
|
||||
CloseHandle(m_dev_handle);
|
||||
m_dev_handle = 0;
|
||||
|
||||
#ifdef SHARE_WRITE_WIIMOTES
|
||||
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
|
||||
g_connected_wiimotes.erase(m_devicepath);
|
||||
#endif
|
||||
}
|
||||
|
||||
WiimoteWindows::WiimoteWindows(const std::basic_string<TCHAR>& path) : m_devicepath(path)
|
||||
{
|
||||
m_dev_handle = 0;
|
||||
m_stack = MSBT_STACK_UNKNOWN;
|
||||
|
||||
m_hid_overlap_read = OVERLAPPED();
|
||||
m_hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
|
||||
m_hid_overlap_write = OVERLAPPED();
|
||||
m_hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
}
|
||||
|
||||
WiimoteWindows::~WiimoteWindows()
|
||||
{
|
||||
Shutdown();
|
||||
CloseHandle(m_hid_overlap_read.hEvent);
|
||||
CloseHandle(m_hid_overlap_write.hEvent);
|
||||
}
|
||||
|
||||
bool WiimoteWindows::IsConnected() const
|
||||
{
|
||||
return m_dev_handle != 0;
|
||||
}
|
||||
|
||||
// positive = read packet
|
||||
// negative = didn't read packet
|
||||
// zero = error
|
||||
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index)
|
||||
{
|
||||
// Add data report indicator byte (here, 0xa1)
|
||||
buf[0] = 0xa1;
|
||||
// Used below for a warning
|
||||
buf[1] = 0;
|
||||
|
||||
DWORD bytes = 0;
|
||||
ResetEvent(hid_overlap_read.hEvent);
|
||||
if (!ReadFile(dev_handle, buf + 1, MAX_PAYLOAD - 1, &bytes, &hid_overlap_read))
|
||||
{
|
||||
auto const read_err = GetLastError();
|
||||
|
||||
if (ERROR_IO_PENDING == read_err)
|
||||
{
|
||||
auto const wait_result = WaitForSingleObject(hid_overlap_read.hEvent, INFINITE);
|
||||
|
||||
// In case the event was signalled by IOWakeup before the read completed, cancel it.
|
||||
CancelIo(dev_handle);
|
||||
|
||||
if (WAIT_FAILED == wait_result)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "A wait error occurred on reading from Wiimote %i.", index + 1);
|
||||
}
|
||||
|
||||
if (!GetOverlappedResult(dev_handle, &hid_overlap_read, &bytes, FALSE))
|
||||
{
|
||||
auto const overlapped_err = GetLastError();
|
||||
|
||||
if (ERROR_OPERATION_ABORTED == overlapped_err)
|
||||
{
|
||||
// It was.
|
||||
return -1;
|
||||
}
|
||||
|
||||
WARN_LOG(WIIMOTE, "GetOverlappedResult error %d on Wiimote %i.", overlapped_err, index + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "ReadFile error %d on Wiimote %i.", read_err, index + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes + 1;
|
||||
}
|
||||
|
||||
void WiimoteWindows::IOWakeup()
|
||||
{
|
||||
SetEvent(m_hid_overlap_read.hEvent);
|
||||
}
|
||||
|
||||
|
||||
// positive = read packet
|
||||
// negative = didn't read packet
|
||||
// zero = error
|
||||
int WiimoteWindows::IORead(u8* buf)
|
||||
{
|
||||
return _IORead(m_dev_handle, m_hid_overlap_read, buf, m_index);
|
||||
}
|
||||
|
||||
|
||||
int _IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum win_bt_stack_t &stack, const u8* buf, size_t len, DWORD* written)
|
||||
{
|
||||
switch (stack)
|
||||
{
|
||||
case MSBT_STACK_UNKNOWN:
|
||||
{
|
||||
// Try to auto-detect the stack type
|
||||
stack = MSBT_STACK_BLUESOLEIL;
|
||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
|
||||
return 1;
|
||||
|
||||
stack = MSBT_STACK_MS;
|
||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
|
||||
return 1;
|
||||
|
||||
stack = MSBT_STACK_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
case MSBT_STACK_MS:
|
||||
{
|
||||
auto result = pHidD_SetOutputReport(dev_handle, const_cast<u8*>(buf) + 1, (ULONG)(len - 1));
|
||||
//FlushFileBuffers(dev_handle);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
auto err = GetLastError();
|
||||
if (err == 121)
|
||||
{
|
||||
// Semaphore timeout
|
||||
NOTICE_LOG(WIIMOTE, "WiimoteIOWrite[MSBT_STACK_MS]: Unable to send data to the Wiimote");
|
||||
}
|
||||
else if (err != 0x1F) // Some third-party adapters (DolphinBar) use this
|
||||
// error code to signal the absence of a WiiMote
|
||||
// linked to the HID device.
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "IOWrite[MSBT_STACK_MS]: ERROR: %08x", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (written)
|
||||
*written = (result ? (DWORD)len : 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
case MSBT_STACK_BLUESOLEIL:
|
||||
{
|
||||
u8 big_buf[MAX_PAYLOAD];
|
||||
if (len < MAX_PAYLOAD)
|
||||
{
|
||||
std::copy(buf, buf + len, big_buf);
|
||||
std::fill(big_buf + len, big_buf + MAX_PAYLOAD, 0);
|
||||
buf = big_buf;
|
||||
}
|
||||
|
||||
ResetEvent(hid_overlap_write.hEvent);
|
||||
DWORD bytes = 0;
|
||||
if (WriteFile(dev_handle, buf + 1, MAX_PAYLOAD - 1, &bytes, &hid_overlap_write))
|
||||
{
|
||||
// If the number of written bytes is requested, block until we can provide
|
||||
// this information to the called.
|
||||
if (written)
|
||||
{
|
||||
auto const wait_result = WaitForSingleObject(hid_overlap_write.hEvent, WIIMOTE_DEFAULT_TIMEOUT);
|
||||
if (WAIT_TIMEOUT == wait_result)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "_IOWrite: A timeout occurred on writing to Wiimote.");
|
||||
CancelIo(dev_handle);
|
||||
*written = 0;
|
||||
}
|
||||
else if (WAIT_FAILED == wait_result)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "_IOWrite: A wait error occurred on writing to Wiimote.");
|
||||
CancelIo(dev_handle);
|
||||
*written = 0;
|
||||
}
|
||||
else if (!GetOverlappedResult(dev_handle, &hid_overlap_write, written, TRUE))
|
||||
*written = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const err = GetLastError();
|
||||
if (ERROR_IO_PENDING == err)
|
||||
{
|
||||
CancelIo(dev_handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WiimoteWindows::IOWrite(const u8* buf, size_t len)
|
||||
{
|
||||
return _IOWrite(m_dev_handle, m_hid_overlap_write, m_stack, buf, len, nullptr);
|
||||
}
|
||||
|
||||
// invokes callback for each found wiimote bluetooth device
|
||||
template <typename T>
|
||||
void ProcessWiimotes(bool new_scan, T& callback)
|
||||
{
|
||||
BLUETOOTH_DEVICE_SEARCH_PARAMS srch;
|
||||
srch.dwSize = sizeof(srch);
|
||||
srch.fReturnAuthenticated = true;
|
||||
srch.fReturnRemembered = true;
|
||||
// Does not filter properly somehow, so we need to do an additional check on
|
||||
// fConnected BT Devices
|
||||
srch.fReturnConnected = true;
|
||||
srch.fReturnUnknown = true;
|
||||
srch.fIssueInquiry = new_scan;
|
||||
// multiple of 1.28 seconds
|
||||
srch.cTimeoutMultiplier = 2;
|
||||
|
||||
BLUETOOTH_FIND_RADIO_PARAMS radioParam;
|
||||
radioParam.dwSize = sizeof(radioParam);
|
||||
|
||||
HANDLE hRadio;
|
||||
|
||||
// TODO: save radio(s) in the WiimoteScanner constructor?
|
||||
|
||||
// Enumerate BT radios
|
||||
HBLUETOOTH_RADIO_FIND hFindRadio = pBluetoothFindFirstRadio(&radioParam, &hRadio);
|
||||
while (hFindRadio)
|
||||
{
|
||||
BLUETOOTH_RADIO_INFO radioInfo;
|
||||
radioInfo.dwSize = sizeof(radioInfo);
|
||||
|
||||
auto const rinfo_result = pBluetoothGetRadioInfo(hRadio, &radioInfo);
|
||||
if (ERROR_SUCCESS == rinfo_result)
|
||||
{
|
||||
srch.hRadio = hRadio;
|
||||
|
||||
BLUETOOTH_DEVICE_INFO btdi;
|
||||
btdi.dwSize = sizeof(btdi);
|
||||
|
||||
// Enumerate BT devices
|
||||
HBLUETOOTH_DEVICE_FIND hFindDevice = pBluetoothFindFirstDevice(&srch, &btdi);
|
||||
while (hFindDevice)
|
||||
{
|
||||
// btdi.szName is sometimes missing it's content - it's a bt feature..
|
||||
DEBUG_LOG(WIIMOTE, "Authenticated %i connected %i remembered %i ",
|
||||
btdi.fAuthenticated, btdi.fConnected, btdi.fRemembered);
|
||||
|
||||
if (IsValidBluetoothName(UTF16ToUTF8(btdi.szName)))
|
||||
{
|
||||
callback(hRadio, radioInfo, btdi);
|
||||
}
|
||||
|
||||
if (false == pBluetoothFindNextDevice(hFindDevice, &btdi))
|
||||
{
|
||||
pBluetoothFindDeviceClose(hFindDevice);
|
||||
hFindDevice = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false == pBluetoothFindNextRadio(hFindRadio, &hRadio))
|
||||
{
|
||||
pBluetoothFindRadioClose(hFindRadio);
|
||||
hFindRadio = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveWiimote(BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
//if (btdi.fConnected)
|
||||
{
|
||||
if (SUCCEEDED(pBluetoothRemoveDevice(&btdi.Address)))
|
||||
{
|
||||
NOTICE_LOG(WIIMOTE, "Removed BT Device", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AttachWiimote(HANDLE hRadio, const BLUETOOTH_RADIO_INFO& radio_info, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
// We don't want "remembered" devices.
|
||||
// SetServiceState will just fail with them..
|
||||
if (!btdi.fConnected && !btdi.fRemembered)
|
||||
{
|
||||
auto const& wm_addr = btdi.Address.rgBytes;
|
||||
|
||||
NOTICE_LOG(WIIMOTE, "Found Wiimote (%02x:%02x:%02x:%02x:%02x:%02x). Enabling HID service.",
|
||||
wm_addr[0], wm_addr[1], wm_addr[2], wm_addr[3], wm_addr[4], wm_addr[5]);
|
||||
|
||||
#if defined(AUTHENTICATE_WIIMOTES)
|
||||
// Authenticate
|
||||
auto const& radio_addr = radio_info.address.rgBytes;
|
||||
// FIXME Not sure this usage of OOB_DATA_INFO is correct...
|
||||
BLUETOOTH_OOB_DATA_INFO oob_data_info = { 0 };
|
||||
memcpy(&oob_data_info.C[0], &radio_addr[0], sizeof(WCHAR) * 6);
|
||||
const DWORD auth_result = pBluetoothAuthenticateDeviceEx(nullptr, hRadio, &btdi,
|
||||
&oob_data_info, MITMProtectionNotDefined);
|
||||
|
||||
if (ERROR_SUCCESS != auth_result)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothAuthenticateDeviceEx returned %08x", auth_result);
|
||||
}
|
||||
|
||||
DWORD pcServices = 16;
|
||||
GUID guids[16];
|
||||
// If this is not done, the Wii device will not remember the pairing
|
||||
const DWORD srv_result = pBluetoothEnumerateInstalledServices(hRadio, &btdi, &pcServices, guids);
|
||||
|
||||
if (ERROR_SUCCESS != srv_result)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothEnumerateInstalledServices returned %08x", srv_result);
|
||||
}
|
||||
#endif
|
||||
// Activate service
|
||||
const DWORD hr = pBluetoothSetServiceState(hRadio, &btdi,
|
||||
&HumanInterfaceDeviceServiceClass_UUID, BLUETOOTH_SERVICE_ENABLE);
|
||||
|
||||
g_connect_times[btdi.Address.ullLong] = std::time(nullptr);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothSetServiceState returned %08x", hr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removes remembered non-connected devices
|
||||
bool ForgetWiimote(BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
|
||||
{
|
||||
if (!btdi.fConnected && btdi.fRemembered)
|
||||
{
|
||||
// Time to avoid RemoveDevice after SetServiceState.
|
||||
// Sometimes SetServiceState takes a while..
|
||||
auto const avoid_forget_seconds = 5.0;
|
||||
|
||||
auto pair_time = g_connect_times.find(btdi.Address.ullLong);
|
||||
if (pair_time == g_connect_times.end() ||
|
||||
std::difftime(time(nullptr), pair_time->second) >= avoid_forget_seconds)
|
||||
{
|
||||
// Make Windows forget about device so it will re-find it if visible.
|
||||
// This is also required to detect a disconnect for some reason..
|
||||
NOTICE_LOG(WIIMOTE, "Removing remembered Wiimote.");
|
||||
pBluetoothRemoveDevice(&btdi.Address);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,380 +0,0 @@
|
|||
#define BLUETOOTH_VERSION_USE_CURRENT
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
@interface SearchBT: NSObject {
|
||||
@public
|
||||
unsigned int maxDevices;
|
||||
bool done;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ConnectBT: NSObject {}
|
||||
@end
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
class WiimoteDarwin final : public Wiimote
|
||||
{
|
||||
public:
|
||||
WiimoteDarwin(IOBluetoothDevice* device);
|
||||
~WiimoteDarwin() override;
|
||||
|
||||
// These are not protected/private because ConnectBT needs them.
|
||||
void DisconnectInternal() override;
|
||||
IOBluetoothDevice* m_btd;
|
||||
unsigned char* m_input;
|
||||
int m_inputlen;
|
||||
|
||||
protected:
|
||||
bool ConnectInternal() override;
|
||||
bool IsConnected() const override;
|
||||
void IOWakeup() override;
|
||||
int IORead(u8* buf) override;
|
||||
int IOWrite(u8 const* buf, size_t len) override;
|
||||
void EnablePowerAssertionInternal() override;
|
||||
void DisablePowerAssertionInternal() override;
|
||||
|
||||
private:
|
||||
IOBluetoothL2CAPChannel* m_ichan;
|
||||
IOBluetoothL2CAPChannel* m_cchan;
|
||||
bool m_connected;
|
||||
CFRunLoopRef m_wiimote_thread_run_loop;
|
||||
IOPMAssertionID m_pm_assertion;
|
||||
};
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
: m_run_thread()
|
||||
, m_want_wiimotes()
|
||||
{}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
|
||||
{
|
||||
// TODO: find the device in the constructor and save it for later
|
||||
IOBluetoothHostController *bth;
|
||||
IOBluetoothDeviceInquiry *bti;
|
||||
SearchBT *sbt;
|
||||
NSEnumerator *en;
|
||||
found_board = nullptr;
|
||||
|
||||
bth = [[IOBluetoothHostController alloc] init];
|
||||
if ([bth addressAsString] == nil)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "No bluetooth host controller");
|
||||
[bth release];
|
||||
return;
|
||||
}
|
||||
|
||||
sbt = [[SearchBT alloc] init];
|
||||
sbt->maxDevices = 32;
|
||||
bti = [[IOBluetoothDeviceInquiry alloc] init];
|
||||
[bti setDelegate: sbt];
|
||||
[bti setInquiryLength: 2];
|
||||
|
||||
if ([bti start] != kIOReturnSuccess)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to do bluetooth discovery");
|
||||
[bth release];
|
||||
[sbt release];
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
CFRunLoopRun();
|
||||
}
|
||||
while (!sbt->done);
|
||||
|
||||
int found_devices = [[bti foundDevices] count];
|
||||
|
||||
if (found_devices)
|
||||
NOTICE_LOG(WIIMOTE, "Found %i bluetooth devices", found_devices);
|
||||
|
||||
en = [[bti foundDevices] objectEnumerator];
|
||||
for (int i = 0; i < found_devices; i++)
|
||||
{
|
||||
IOBluetoothDevice *dev = [en nextObject];
|
||||
if (!IsValidBluetoothName([[dev name] UTF8String]))
|
||||
continue;
|
||||
|
||||
Wiimote* wm = new WiimoteDarwin([dev retain]);
|
||||
|
||||
if (IsBalanceBoardName([[dev name] UTF8String]))
|
||||
{
|
||||
found_board = wm;
|
||||
}
|
||||
else
|
||||
{
|
||||
found_wiimotes.push_back(wm);
|
||||
}
|
||||
}
|
||||
|
||||
[bth release];
|
||||
[bti release];
|
||||
[sbt release];
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
// TODO: only return true when a BT device is present
|
||||
return true;
|
||||
}
|
||||
|
||||
WiimoteDarwin::WiimoteDarwin(IOBluetoothDevice* device) : m_btd(device)
|
||||
{
|
||||
m_inputlen = 0;
|
||||
m_connected = false;
|
||||
m_wiimote_thread_run_loop = nullptr;
|
||||
m_pm_assertion = kIOPMNullAssertionID;
|
||||
}
|
||||
|
||||
WiimoteDarwin::~WiimoteDarwin()
|
||||
{
|
||||
Shutdown();
|
||||
if (m_wiimote_thread_run_loop)
|
||||
{
|
||||
CFRelease(m_wiimote_thread_run_loop);
|
||||
m_wiimote_thread_run_loop = nullptr;
|
||||
}
|
||||
[m_btd release];
|
||||
m_btd = nil;
|
||||
DisablePowerAssertionInternal();
|
||||
}
|
||||
|
||||
// Connect to a wiimote with a known address.
|
||||
bool WiimoteDarwin::ConnectInternal()
|
||||
{
|
||||
if (IsConnected())
|
||||
return false;
|
||||
|
||||
ConnectBT *cbt = [[ConnectBT alloc] init];
|
||||
|
||||
m_cchan = m_ichan = nil;
|
||||
|
||||
IOReturn ret = [m_btd openConnection];
|
||||
if (ret)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to open Bluetooth connection to wiimote %i: %x",
|
||||
m_index + 1, ret);
|
||||
[cbt release];
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = [m_btd openL2CAPChannelSync: &m_cchan
|
||||
withPSM: kBluetoothL2CAPPSMHIDControl delegate: cbt];
|
||||
if (ret)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Unable to open control channel for wiimote %i: %x",
|
||||
m_index + 1, ret);
|
||||
goto bad;
|
||||
}
|
||||
// Apple docs claim:
|
||||
// "The L2CAP channel object is already retained when this function returns
|
||||
// success; the channel must be released when the caller is done with it."
|
||||
// But without this, the channels get over-autoreleased, even though the
|
||||
// refcounting behavior here is clearly correct.
|
||||
[m_cchan retain];
|
||||
|
||||
ret = [m_btd openL2CAPChannelSync: &m_ichan
|
||||
withPSM: kBluetoothL2CAPPSMHIDInterrupt delegate: cbt];
|
||||
if (ret)
|
||||
{
|
||||
WARN_LOG(WIIMOTE, "Unable to open interrupt channel for wiimote %i: %x",
|
||||
m_index + 1, ret);
|
||||
goto bad;
|
||||
}
|
||||
[m_ichan retain];
|
||||
|
||||
NOTICE_LOG(WIIMOTE, "Connected to wiimote %i at %s",
|
||||
m_index + 1, [[m_btd addressString] UTF8String]);
|
||||
|
||||
m_connected = true;
|
||||
|
||||
[cbt release];
|
||||
|
||||
m_wiimote_thread_run_loop = (CFRunLoopRef) CFRetain(CFRunLoopGetCurrent());
|
||||
|
||||
return true;
|
||||
|
||||
bad:
|
||||
DisconnectInternal();
|
||||
[cbt release];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disconnect a wiimote.
|
||||
void WiimoteDarwin::DisconnectInternal()
|
||||
{
|
||||
[m_ichan closeChannel];
|
||||
[m_ichan release];
|
||||
m_ichan = nil;
|
||||
|
||||
[m_cchan closeChannel];
|
||||
[m_cchan release];
|
||||
m_cchan = nil;
|
||||
|
||||
[m_btd closeConnection];
|
||||
|
||||
if (!IsConnected())
|
||||
return;
|
||||
|
||||
NOTICE_LOG(WIIMOTE, "Disconnecting wiimote %i", m_index + 1);
|
||||
|
||||
m_connected = false;
|
||||
}
|
||||
|
||||
bool WiimoteDarwin::IsConnected() const
|
||||
{
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
void WiimoteDarwin::IOWakeup()
|
||||
{
|
||||
if (m_wiimote_thread_run_loop)
|
||||
{
|
||||
CFRunLoopStop(m_wiimote_thread_run_loop);
|
||||
}
|
||||
}
|
||||
|
||||
int WiimoteDarwin::IORead(unsigned char *buf)
|
||||
{
|
||||
m_input = buf;
|
||||
m_inputlen = -1;
|
||||
|
||||
CFRunLoopRun();
|
||||
|
||||
return m_inputlen;
|
||||
}
|
||||
|
||||
int WiimoteDarwin::IOWrite(const unsigned char *buf, size_t len)
|
||||
{
|
||||
IOReturn ret;
|
||||
|
||||
if (!IsConnected())
|
||||
return 0;
|
||||
|
||||
ret = [m_ichan writeAsync: const_cast<void*>((void *)buf) length: (int)len refcon: nil];
|
||||
|
||||
if (ret == kIOReturnSuccess)
|
||||
return len;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WiimoteDarwin::EnablePowerAssertionInternal()
|
||||
{
|
||||
if (m_pm_assertion == kIOPMNullAssertionID)
|
||||
{
|
||||
if (IOReturn ret = IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, CFSTR("Dolphin Wiimote activity"), &m_pm_assertion))
|
||||
ERROR_LOG(WIIMOTE, "Could not create power management assertion: %08x", ret);
|
||||
}
|
||||
}
|
||||
|
||||
void WiimoteDarwin::DisablePowerAssertionInternal()
|
||||
{
|
||||
if (m_pm_assertion != kIOPMNullAssertionID)
|
||||
{
|
||||
if (IOReturn ret = IOPMAssertionRelease(m_pm_assertion))
|
||||
ERROR_LOG(WIIMOTE, "Could not release power management assertion: %08x", ret);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@implementation SearchBT
|
||||
- (void) deviceInquiryComplete: (IOBluetoothDeviceInquiry *) sender
|
||||
error: (IOReturn) error
|
||||
aborted: (BOOL) aborted
|
||||
{
|
||||
done = true;
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
- (void) deviceInquiryDeviceFound: (IOBluetoothDeviceInquiry *) sender
|
||||
device: (IOBluetoothDevice *) device
|
||||
{
|
||||
NOTICE_LOG(WIIMOTE, "Discovered bluetooth device at %s: %s",
|
||||
[[device addressString] UTF8String],
|
||||
[[device name] UTF8String]);
|
||||
|
||||
if ([[sender foundDevices] count] == maxDevices)
|
||||
[sender stop];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation ConnectBT
|
||||
- (void) l2capChannelData: (IOBluetoothL2CAPChannel *) l2capChannel
|
||||
data: (unsigned char *) data
|
||||
length: (NSUInteger) length
|
||||
{
|
||||
IOBluetoothDevice *device = [l2capChannel device];
|
||||
WiimoteReal::WiimoteDarwin *wm = nullptr;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lk(WiimoteReal::g_refresh_lock);
|
||||
|
||||
for (int i = 0; i < MAX_WIIMOTES; i++)
|
||||
{
|
||||
if (WiimoteReal::g_wiimotes[i] == nullptr)
|
||||
continue;
|
||||
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
|
||||
if ([device isEqual: wm->m_btd] != TRUE)
|
||||
wm = nullptr;
|
||||
}
|
||||
|
||||
if (wm == nullptr) {
|
||||
ERROR_LOG(WIIMOTE, "Received packet for unknown wiimote");
|
||||
return;
|
||||
}
|
||||
|
||||
if (length > MAX_PAYLOAD) {
|
||||
WARN_LOG(WIIMOTE, "Dropping packet for wiimote %i, too large",
|
||||
wm->m_index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wm->m_inputlen != -1) {
|
||||
WARN_LOG(WIIMOTE, "Dropping packet for wiimote %i, queue full",
|
||||
wm->m_index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(wm->m_input, data, length);
|
||||
wm->m_inputlen = length;
|
||||
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
- (void) l2capChannelClosed: (IOBluetoothL2CAPChannel *) l2capChannel
|
||||
{
|
||||
IOBluetoothDevice *device = [l2capChannel device];
|
||||
WiimoteReal::WiimoteDarwin *wm = nullptr;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lk(WiimoteReal::g_refresh_lock);
|
||||
|
||||
for (int i = 0; i < MAX_WIIMOTES; i++)
|
||||
{
|
||||
if (WiimoteReal::g_wiimotes[i] == nullptr)
|
||||
continue;
|
||||
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
|
||||
if ([device isEqual: wm->m_btd] != TRUE)
|
||||
wm = nullptr;
|
||||
}
|
||||
|
||||
if (wm == nullptr) {
|
||||
ERROR_LOG(WIIMOTE, "Channel for unknown wiimote was closed");
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_LOG(WIIMOTE, "Lost channel to wiimote %i", wm->m_index + 1);
|
||||
|
||||
wm->DisconnectInternal();
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <hidapi.h>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
class WiimoteHidapi final : public Wiimote
|
||||
{
|
||||
public:
|
||||
WiimoteHidapi(char* device_path);
|
||||
~WiimoteHidapi() override;
|
||||
|
||||
protected:
|
||||
bool ConnectInternal() override;
|
||||
void DisconnectInternal() override;
|
||||
bool IsConnected() const override;
|
||||
void IOWakeup() override;
|
||||
int IORead(u8* buf) override;
|
||||
int IOWrite(u8 const* buf, size_t len) override;
|
||||
|
||||
private:
|
||||
std::string m_device_path;
|
||||
hid_device* m_handle;
|
||||
};
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
: m_want_wiimotes()
|
||||
{
|
||||
if (hid_init() == -1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Failed to initialize hidapi.");
|
||||
}
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{
|
||||
if (hid_exit() == -1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Failed to clean up hidapi.");
|
||||
}
|
||||
}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wiimote*& found_board)
|
||||
{
|
||||
// Search for both old and new Wiimotes.
|
||||
for (uint16_t product_id : {0x0306, 0x0330})
|
||||
{
|
||||
hid_device_info* list = hid_enumerate(0x057e, product_id);
|
||||
hid_device_info* item = list;
|
||||
while (item)
|
||||
{
|
||||
NOTICE_LOG(WIIMOTE, "Found Wiimote at %s: %ls %ls", item->path, item->manufacturer_string, item->product_string);
|
||||
Wiimote* wiimote = new WiimoteHidapi(item->path);
|
||||
found_wiimotes.push_back(wiimote);
|
||||
item = item->next;
|
||||
}
|
||||
hid_free_enumeration(list);
|
||||
}
|
||||
}
|
||||
|
||||
WiimoteHidapi::WiimoteHidapi(char* device_path) : m_device_path(device_path), m_handle(nullptr)
|
||||
{}
|
||||
|
||||
WiimoteHidapi::~WiimoteHidapi()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
// Connect to a wiimote with a known address.
|
||||
bool WiimoteHidapi::ConnectInternal()
|
||||
{
|
||||
m_handle = hid_open_path(m_device_path.c_str());
|
||||
if (!m_handle)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Could not connect to Wiimote at \"%s\". "
|
||||
"Do you have permission to access the device?", m_device_path.c_str());
|
||||
}
|
||||
return !!m_handle;
|
||||
}
|
||||
|
||||
void WiimoteHidapi::DisconnectInternal()
|
||||
{
|
||||
hid_close(m_handle);
|
||||
m_handle = nullptr;
|
||||
}
|
||||
|
||||
bool WiimoteHidapi::IsConnected() const
|
||||
{
|
||||
return !!m_handle;
|
||||
}
|
||||
|
||||
void WiimoteHidapi::IOWakeup()
|
||||
{}
|
||||
|
||||
// positive = read packet
|
||||
// negative = didn't read packet
|
||||
// zero = error
|
||||
int WiimoteHidapi::IORead(u8* buf)
|
||||
{
|
||||
int timeout = 200; // ms
|
||||
int result = hid_read_timeout(m_handle, buf + 1, MAX_PAYLOAD - 1, timeout);
|
||||
// TODO: If and once we use hidapi across plaforms, change our internal API to clean up this mess.
|
||||
if (result == -1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Failed to read from %s.", m_device_path.c_str());
|
||||
result = 0;
|
||||
}
|
||||
else if (result == 0)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[0] = WM_SET_REPORT | WM_BT_INPUT;
|
||||
result += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int WiimoteHidapi::IOWrite(u8 const* buf, size_t len)
|
||||
{
|
||||
_dbg_assert_(WIIMOTE, buf[0] == (WM_SET_REPORT | WM_BT_OUTPUT));
|
||||
int result = hid_write(m_handle, buf + 1, len - 1);
|
||||
if (result == -1)
|
||||
{
|
||||
ERROR_LOG(WIIMOTE, "Failed to write to %s.", m_device_path.c_str());
|
||||
result = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}; // WiimoteReal
|
|
@ -135,13 +135,6 @@ private:
|
|||
volatile bool m_run_thread;
|
||||
volatile bool m_want_wiimotes;
|
||||
volatile bool m_want_bb;
|
||||
|
||||
#if defined(_WIN32)
|
||||
void CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool &real_wiimote, bool &is_bb);
|
||||
#elif defined(__linux__) && HAVE_BLUEZ
|
||||
int device_id;
|
||||
int device_sock;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern std::recursive_mutex g_refresh_lock;
|
||||
|
|
|
@ -4,22 +4,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
// Work around an Apple bug: for some reason, IOBluetooth.h errors on
|
||||
// inclusion in Mavericks, but only in Objective-C++ C++11 mode. I filed
|
||||
// this as <rdar://15312520>; in the meantime...
|
||||
#import <Foundation/Foundation.h>
|
||||
#undef NS_ENUM_AVAILABLE
|
||||
#define NS_ENUM_AVAILABLE(...)
|
||||
// end hack
|
||||
#import <IOBluetooth/IOBluetooth.h>
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif defined(__linux__) && HAVE_BLUEZ
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#endif
|
||||
|
||||
// Wiimote internal codes
|
||||
|
||||
// Communication channels
|
||||
|
|
Loading…
Reference in New Issue