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:
Tillmann Karras 2015-01-10 18:10:45 +01:00
parent 1c26d41c9a
commit ca0c2efe7a
19 changed files with 245 additions and 1703 deletions

View File

@ -2,6 +2,7 @@
# General setup # General setup
# #
cmake_minimum_required(VERSION 2.8.8) cmake_minimum_required(VERSION 2.8.8)
project(dolphin-emu)
option(USE_EGL "Enables EGL OpenGL Interface" OFF) option(USE_EGL "Enables EGL OpenGL Interface" OFF)
option(TRY_X11 "Enables X11 Support" ON) 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) option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF)
if(APPLE) if(APPLE)
option(OSX_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF) 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() endif()
option(ENCODE_FRAMEDUMPS "Encode framedumps in AVI format" ON) 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(OPROFILING "Enable profiling" OFF)
option(GDBSTUB "Enable gdb stub for remote debugging." 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 # Optional Targets
# TODO: Add DSPSpy # TODO: Add DSPSpy
@ -45,7 +46,6 @@ if (APPLE)
endif() endif()
endif() endif()
endif() endif()
project(dolphin-emu)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMakeTests) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMakeTests)
set(DOLPHIN_IS_STABLE FALSE) set(DOLPHIN_IS_STABLE FALSE)
# Libraries to link # Libraries to link
@ -292,7 +292,6 @@ if(APPLE)
find_library(COREFUND_LIBRARY CoreFoundation) find_library(COREFUND_LIBRARY CoreFoundation)
find_library(CORESERV_LIBRARY CoreServices) find_library(CORESERV_LIBRARY CoreServices)
find_library(FOUNDATION_LIBRARY foundation) find_library(FOUNDATION_LIBRARY foundation)
find_library(IOB_LIBRARY IOBluetooth)
find_library(IOK_LIBRARY IOKit) find_library(IOK_LIBRARY IOKit)
find_library(QUICKTIME_LIBRARY QuickTime) find_library(QUICKTIME_LIBRARY QuickTime)
find_library(WEBKIT_LIBRARY WebKit) find_library(WEBKIT_LIBRARY WebKit)
@ -379,15 +378,6 @@ if(NOT ANDROID)
message("ao NOT found, disabling ao sound backend") message("ao NOT found, disabling ao sound backend")
endif(AO_FOUND) 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) check_lib(PULSEAUDIO libpulse QUIET)
if(PULSEAUDIO_FOUND) if(PULSEAUDIO_FOUND)
add_definitions(-DHAVE_PULSEAUDIO=1) add_definitions(-DHAVE_PULSEAUDIO=1)
@ -595,10 +585,19 @@ endif()
include(FindLibUSB OPTIONAL) include(FindLibUSB OPTIONAL)
if(LIBUSB_FOUND) if(LIBUSB_FOUND)
message("Using shared LibUSB") message("Using shared libusb")
add_definitions(-D__LIBUSB__) add_definitions(-D__LIBUSB__)
include_directories(${LIBUSB_INCLUDE_DIR}) 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) set(SFML_REQD_VERSION 2.1)
if(NOT APPLE AND NOT ANDROID) if(NOT APPLE AND NOT ANDROID)
@ -665,6 +664,32 @@ else()
mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES) mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES)
endif() 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) if(ENABLE_QT)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
message("Found Qt version ${Qt5Core_VERSION}, enabling the Qt backend") message("Found Qt version ${Qt5Core_VERSION}, enabling the Qt backend")

View File

@ -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)

View File

@ -0,0 +1 @@
add_library(hidapi-libusb hid.c)

1
Externals/hidapi/linux/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi-hidraw hid.c)

1
Externals/hidapi/mac/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi hid.c)

17
Externals/libusb/libusb/CMakeLists.txt vendored Normal file
View File

@ -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)

View File

@ -0,0 +1,7 @@
include_directories(..)
add_library(usb-os STATIC
linux_netlink.c
linux_usbfs.c
poll_posix.c
threads_posix.c
)

View File

@ -27,7 +27,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v120</PlatformToolset> <PlatformToolset>v120</PlatformToolset>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -38,7 +37,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v120</PlatformToolset> <PlatformToolset>v120</PlatformToolset>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

View File

@ -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"

View File

@ -116,6 +116,7 @@ set(SRCS ActionReplay.cpp
HW/SI_DeviceGBA.cpp HW/SI_DeviceGBA.cpp
HW/SI_DeviceGCController.cpp HW/SI_DeviceGCController.cpp
HW/SI_DeviceGCSteeringWheel.cpp HW/SI_DeviceGCSteeringWheel.cpp
HW/SI_GCAdapter.cpp
HW/Sram.cpp HW/Sram.cpp
HW/StreamADPCM.cpp HW/StreamADPCM.cpp
HW/SystemTimers.cpp HW/SystemTimers.cpp
@ -132,6 +133,7 @@ set(SRCS ActionReplay.cpp
HW/WiimoteEmu/EmuSubroutines.cpp HW/WiimoteEmu/EmuSubroutines.cpp
HW/WiimoteEmu/Encryption.cpp HW/WiimoteEmu/Encryption.cpp
HW/WiimoteEmu/Speaker.cpp HW/WiimoteEmu/Speaker.cpp
HW/WiimoteReal/IOhidapi.cpp
HW/WiimoteReal/WiimoteReal.cpp HW/WiimoteReal/WiimoteReal.cpp
HW/WiiSaveCrypted.cpp HW/WiiSaveCrypted.cpp
IPC_HLE/ICMPLin.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_es.cpp
IPC_HLE/WII_IPC_HLE_Device_FileIO.cpp IPC_HLE/WII_IPC_HLE_Device_FileIO.cpp
IPC_HLE/WII_IPC_HLE_Device_fs.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_Socket.cpp
IPC_HLE/WII_IPC_HLE_Device_net.cpp IPC_HLE/WII_IPC_HLE_Device_net.cpp
IPC_HLE/WII_IPC_HLE_Device_net_ssl.cpp IPC_HLE/WII_IPC_HLE_Device_net_ssl.cpp
@ -245,29 +248,15 @@ set(LIBS
z 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}) set(LIBS ${LIBS} ${POLARSSL_LIBRARY})
if(WIN32) 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) elseif(APPLE)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp HW/WiimoteReal/IOdarwin.mm) set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp)
set(LIBS ${LIBS}
${IOB_LIBRARY})
elseif(UNIX) elseif(UNIX)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Unix.cpp) 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() endif()
if(PORTAUDIO_FOUND) if(PORTAUDIO_FOUND)

View File

@ -169,7 +169,7 @@
<ClCompile Include="HW\WiimoteEmu\Encryption.cpp" /> <ClCompile Include="HW\WiimoteEmu\Encryption.cpp" />
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp" /> <ClCompile Include="HW\WiimoteEmu\Speaker.cpp" />
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.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\WiimoteReal\WiimoteReal.cpp" />
<ClCompile Include="HW\WII_IPC.cpp" /> <ClCompile Include="HW\WII_IPC.cpp" />
<ClCompile Include="HW\WiiSaveCrypted.cpp" /> <ClCompile Include="HW\WiiSaveCrypted.cpp" />
@ -470,6 +470,9 @@
<ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj"> <ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj">
<Project>{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}</Project> <Project>{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}</Project>
</ProjectReference> </ProjectReference>
<ProjectReference Include="$(ExternalsDir)hidapi\hidapi.vcxproj">
<Project>{549d32d8-1640-46f9-9d78-bae6eb0d723d}</Project>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">

View File

@ -484,7 +484,7 @@
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp"> <ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter> <Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="HW\WiimoteReal\IOWin.cpp"> <ClCompile Include="HW\WiimoteReal\IOhidapi.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Real</Filter> <Filter>HW %28Flipper/Hollywood%29\Wiimote\Real</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp"> <ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp">

View File

@ -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;
}
};

View File

@ -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

View File

@ -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;
}
};

View File

@ -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

View File

@ -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

View File

@ -135,13 +135,6 @@ private:
volatile bool m_run_thread; volatile bool m_run_thread;
volatile bool m_want_wiimotes; volatile bool m_want_wiimotes;
volatile bool m_want_bb; 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; extern std::recursive_mutex g_refresh_lock;

View File

@ -4,22 +4,6 @@
#pragma once #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 // Wiimote internal codes
// Communication channels // Communication channels