├── debian ├── compat ├── source │ └── format ├── kodi-peripheral-steamcontroller.install ├── changelog.in ├── rules ├── control └── copyright ├── depends └── common │ └── libusb │ ├── libusb.txt │ └── CMakeLists.txt ├── peripheral.steamcontroller ├── icon.png └── addon.xml.in ├── src ├── steamcontroller │ ├── SteamControllerTranslator.h │ ├── SteamControllerTranslator.cpp │ ├── interfaces │ │ └── IFeedbackCallback.h │ ├── SteamControllerInput.cpp │ ├── SteamControllerManager.h │ ├── SteamControllerTypes.h │ ├── SteamControllerInput.h │ ├── SteamControllerManager.cpp │ ├── SteamController.h │ └── SteamController.cpp ├── usb │ ├── interfaces │ │ ├── ISendMessageCallback.h │ │ ├── ITransferCallback.h │ │ ├── IMessageCallback.h │ │ └── ITransferObserver.h │ ├── USBInterface.h │ ├── USBInterfaceSetting.h │ ├── USBInterface.cpp │ ├── USBInterfaceSetting.cpp │ ├── USBConfiguration.h │ ├── USBConfiguration.cpp │ ├── USBDevice.h │ ├── USBTypes.h │ ├── USBContext.h │ ├── USBThread.h │ ├── USBDevice.cpp │ ├── USBContext.cpp │ ├── USBThread.cpp │ ├── USBTransfer.h │ ├── USBDeviceHandle.h │ ├── USBTransfer.cpp │ └── USBDeviceHandle.cpp ├── util │ ├── Log.h │ └── Log.cpp ├── addon.cpp └── addon.h ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── cmake └── Findlibusb-1.0.cmake ├── README.md └── LICENSE.md /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/kodi-peripheral-steamcontroller.install: -------------------------------------------------------------------------------- 1 | usr/lib/* 2 | usr/share/* 3 | -------------------------------------------------------------------------------- /depends/common/libusb/libusb.txt: -------------------------------------------------------------------------------- 1 | libusb https://github.com/libusb/libusb v1.0.22 2 | -------------------------------------------------------------------------------- /peripheral.steamcontroller/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodi-game/peripheral.steamcontroller/HEAD/peripheral.steamcontroller/icon.png -------------------------------------------------------------------------------- /debian/changelog.in: -------------------------------------------------------------------------------- 1 | kodi-peripheral-steamcontroller (#PACKAGEVERSION#-#TAGREV#~#DIST#) #DIST#; urgency=low 2 | 3 | [ kodi ] 4 | * autogenerated dummy changelog 5 | 6 | -- Team Kodi Sat, 01 Jun 2013 00:59:22 +0200 7 | 8 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerTranslator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "SteamControllerTypes.h" 12 | 13 | namespace STEAMCONTROLLER 14 | { 15 | class CSteamControllerTranslator 16 | { 17 | public: 18 | static HapticPosition GetPosition(unsigned int motorIndex); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/usb/interfaces/ISendMessageCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | namespace STEAMCONTROLLER 14 | { 15 | class ISendMessageCallback 16 | { 17 | public: 18 | virtual ~ISendMessageCallback() = default; 19 | 20 | virtual void SendControl(const uint8_t* data, size_t length, unsigned int timeout = 0) = 0; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/usb/interfaces/ITransferCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace STEAMCONTROLLER 12 | { 13 | class CUSBTransfer; 14 | 15 | /*! 16 | * \brief USB async Rx function 17 | */ 18 | class ITransferCallback 19 | { 20 | public: 21 | virtual ~ITransferCallback() = default; 22 | 23 | virtual void OnTransferComplete(CUSBTransfer* transfer) = 0; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/usb/interfaces/IMessageCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | namespace STEAMCONTROLLER 15 | { 16 | /*! 17 | * \brief USB async Rx function 18 | */ 19 | class IMessageCallback 20 | { 21 | public: 22 | virtual ~IMessageCallback() = default; 23 | 24 | virtual void OnTransferComplete(const std::vector& message) = 0; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/usb/interfaces/ITransferObserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace STEAMCONTROLLER 12 | { 13 | class CUSBTransfer; 14 | 15 | /*! 16 | * \brief libusb callbacks 17 | */ 18 | class ITransferObserver 19 | { 20 | public: 21 | virtual ~ITransferObserver() = default; 22 | 23 | virtual void BeforeSubmit(CUSBTransfer* transfer) = 0; 24 | virtual void AfterCompletion(CUSBTransfer* transfer) = 0; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/usb/USBInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "USBInterfaceSetting.h" 12 | 13 | struct libusb_interface; 14 | 15 | namespace STEAMCONTROLLER 16 | { 17 | class CUSBInterface 18 | { 19 | public: 20 | CUSBInterface(const libusb_interface& interface); 21 | 22 | unsigned int SettingCount() const; 23 | 24 | CUSBInterfaceSetting GetSetting(unsigned int settingNumber); 25 | 26 | private: 27 | const libusb_interface& m_interface; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/usb/USBInterfaceSetting.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | struct libusb_interface_descriptor; 14 | 15 | namespace STEAMCONTROLLER 16 | { 17 | class CUSBInterfaceSetting 18 | { 19 | public: 20 | CUSBInterfaceSetting(const libusb_interface_descriptor& interDesc); 21 | 22 | uint8_t Number() const; 23 | uint8_t Class() const; 24 | uint8_t SubClass() const; 25 | uint8_t Protocol() const; 26 | 27 | private: 28 | const libusb_interface_descriptor& m_interDesc; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ --parallel 14 | 15 | override_dh_auto_configure: 16 | dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=1 -DUSE_LTO=1 17 | 18 | override_dh_installdocs: 19 | dh_installdocs --link-doc=kodi-peripheral-steamcontroller 20 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerTranslator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "SteamControllerTranslator.h" 10 | 11 | using namespace STEAMCONTROLLER; 12 | 13 | #define MOTOR_RIGHT_INDEX 0 14 | #define MOTOR_LEFT_INDEX 1 15 | 16 | HapticPosition CSteamControllerTranslator::GetPosition(unsigned int motorIndex) 17 | { 18 | switch (motorIndex) 19 | { 20 | case MOTOR_RIGHT_INDEX: return HapticPosition::RIGHT; 21 | case MOTOR_LEFT_INDEX: return HapticPosition::LEFT; 22 | default: 23 | break; 24 | } 25 | 26 | return HapticPosition::RIGHT; 27 | } 28 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: kodi-peripheral-steamcontroller 2 | Priority: extra 3 | Maintainer: Nobody 4 | Build-Depends: debhelper (>= 9.0.0), cmake, 5 | kodi-addon-dev, pkg-config, libusb-1.0-0-dev 6 | Standards-Version: 4.1.2 7 | Section: libs 8 | Homepage: http://kodi.tv 9 | 10 | Package: kodi-peripheral-steamcontroller 11 | Section: libs 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Steam controller driver for Kodi 15 | Steam controller driver for Kodi 16 | 17 | Package: kodi-peripheral-steamcontroller-dbg 18 | Section: libs 19 | Architecture: any 20 | Depends: ${shlibs:Depends}, ${misc:Depends} 21 | Description: Steam controller driver for Kodi, debug symbols 22 | debug symbols for Steam controller driver for Kodi 23 | -------------------------------------------------------------------------------- /src/usb/USBInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBInterface.h" 10 | 11 | #include 12 | 13 | using namespace STEAMCONTROLLER; 14 | 15 | CUSBInterface::CUSBInterface(const libusb_interface& interface) : 16 | m_interface(interface) 17 | { 18 | } 19 | 20 | unsigned int CUSBInterface::SettingCount() const 21 | { 22 | return m_interface.num_altsetting; 23 | } 24 | 25 | CUSBInterfaceSetting CUSBInterface::GetSetting(unsigned int settingNumber) 26 | { 27 | const libusb_interface_descriptor& interDesc = m_interface.altsetting[settingNumber]; 28 | return CUSBInterfaceSetting(interDesc); 29 | } 30 | -------------------------------------------------------------------------------- /depends/common/libusb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(libusb) 3 | 4 | include(ExternalProject) 5 | 6 | ExternalProject_Add( 7 | libusb 8 | SOURCE_DIR ${CMAKE_SOURCE_DIR} 9 | CONFIGURE_COMMAND /bootstrap.sh 10 | COMMAND /configure 11 | --prefix=${CMAKE_INSTALL_PREFIX} 12 | --enable-static 13 | --disable-shared 14 | --with-pic 15 | --disable-examples-build 16 | --disable-tests-build 17 | INSTALL_COMMAND "" 18 | BUILD_IN_SOURCE 1 19 | ) 20 | 21 | install(CODE "execute_process(COMMAND make install WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})") 22 | -------------------------------------------------------------------------------- /src/steamcontroller/interfaces/IFeedbackCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | namespace STEAMCONTROLLER 15 | { 16 | class CUSBDeviceHandle; 17 | class ISendMessageCallback; 18 | 19 | class IFeedbackCallback 20 | { 21 | public: 22 | virtual ~IFeedbackCallback() = default; 23 | 24 | virtual void RegisterDeviceHandle(CUSBDeviceHandle* deviceHandle) = 0; 25 | virtual void UnregisterDeviceHandle(CUSBDeviceHandle* deviceHandle) = 0; 26 | virtual void AddMessage(ISendMessageCallback* callback, std::vector&& message) = 0; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | build/ 3 | peripheral.*/addon.xml 4 | 5 | # Debian build files 6 | debian/changelog 7 | debian/files 8 | debian/*.log 9 | debian/*.substvars 10 | debian/.debhelper/ 11 | debian/tmp/ 12 | debian/kodi-peripheral-*/ 13 | obj-x86_64-linux-gnu/ 14 | 15 | # commonly used editors 16 | # vim 17 | *.swp 18 | 19 | # Eclipse 20 | *.project 21 | *.cproject 22 | .classpath 23 | *.sublime-* 24 | .settings/ 25 | 26 | # KDevelop 4 27 | *.kdev4 28 | 29 | # gedit 30 | *~ 31 | 32 | # CLion 33 | /.idea 34 | 35 | # clion 36 | .idea/ 37 | 38 | # to prevent add after a "git format-patch VALUE" and "git add ." call 39 | /*.patch 40 | 41 | # Visual Studio Code 42 | .vscode 43 | 44 | # to prevent add if project code opened by Visual Studio over CMake file 45 | .vs/ 46 | 47 | # General MacOS 48 | .DS_Store 49 | .AppleDouble 50 | .LSOverride 51 | -------------------------------------------------------------------------------- /peripheral.steamcontroller/addon.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 7 | @ADDON_DEPENDS@ 8 | 13 | 14 | Steam controller driver 15 | This driver enables input from the Steam controller. It is a work-in-progress, and controllers do not work yet. 16 | GPL-2.0-or-later 17 | https://github.com/kodi-game/peripheral.steamcontroller 18 | @PLATFORM@ 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerInput.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "SteamControllerInput.h" 10 | 11 | #include 12 | 13 | using namespace STEAMCONTROLLER; 14 | 15 | #define PACKET_SIZE 64 16 | 17 | CSteamControllerInput::CSteamControllerInput() 18 | { 19 | static_assert(sizeof(m_state) == PACKET_SIZE, "Unexpected packet size!"); 20 | 21 | Reset(); 22 | } 23 | 24 | void CSteamControllerInput::Reset() 25 | { 26 | std::memset(&m_state, 0x00, sizeof(m_state)); 27 | } 28 | 29 | bool CSteamControllerInput::Deserialize(const std::vector& packet) 30 | { 31 | if (packet.size() != sizeof(m_state)) 32 | return false; 33 | 34 | std::memcpy(&m_state, packet.data(), sizeof(m_state)); 35 | 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/usb/USBInterfaceSetting.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBInterfaceSetting.h" 10 | 11 | #include 12 | 13 | using namespace STEAMCONTROLLER; 14 | 15 | CUSBInterfaceSetting::CUSBInterfaceSetting(const libusb_interface_descriptor& interDesc) : 16 | m_interDesc(interDesc) 17 | { 18 | } 19 | 20 | uint8_t CUSBInterfaceSetting::Number() const 21 | { 22 | return m_interDesc.bInterfaceNumber; 23 | } 24 | 25 | uint8_t CUSBInterfaceSetting::Class() const 26 | { 27 | return m_interDesc.bInterfaceClass; 28 | } 29 | 30 | uint8_t CUSBInterfaceSetting::SubClass() const 31 | { 32 | return m_interDesc.bInterfaceSubClass; 33 | } 34 | 35 | uint8_t CUSBInterfaceSetting::Protocol() const 36 | { 37 | return m_interDesc.bInterfaceProtocol; 38 | } 39 | -------------------------------------------------------------------------------- /src/util/Log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef dsyslog 12 | #define dsyslog(...) STEAMCONTROLLER::CLog::Get().Log(STEAMCONTROLLER::LOG_DEBUG, __VA_ARGS__) 13 | #endif 14 | 15 | #ifndef isyslog 16 | #define isyslog(...) STEAMCONTROLLER::CLog::Get().Log(STEAMCONTROLLER::LOG_INFO, __VA_ARGS__) 17 | #endif 18 | 19 | #ifndef esyslog 20 | #define esyslog(...) STEAMCONTROLLER::CLog::Get().Log(STEAMCONTROLLER::LOG_ERROR, __VA_ARGS__) 21 | #endif 22 | 23 | namespace STEAMCONTROLLER 24 | { 25 | enum LogLevel 26 | { 27 | LOG_DEBUG, 28 | LOG_INFO, 29 | LOG_ERROR, 30 | }; 31 | 32 | class CLog 33 | { 34 | CLog(); 35 | 36 | public: 37 | static CLog& Get(); 38 | 39 | void Log(LogLevel level, const char* format, ...); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/usb/USBConfiguration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBInterface.h" 10 | 11 | struct libusb_config_descriptor; 12 | 13 | namespace STEAMCONTROLLER 14 | { 15 | class CUSBConfiguration 16 | { 17 | public: 18 | CUSBConfiguration(libusb_config_descriptor* config); 19 | ~CUSBConfiguration(); 20 | 21 | /*! 22 | * \brief Returns device's power consumption in mW 23 | * 24 | * Beware of unit: USB descriptor uses 2mW increments, this method converts 25 | * it to mW units. 26 | */ 27 | unsigned int GetMaxPower() const; 28 | 29 | unsigned int InterfaceCount() const; 30 | 31 | CUSBInterface GetInterface(unsigned int interfaceNumber) const; 32 | 33 | private: 34 | // Construction params 35 | libusb_config_descriptor* const m_config; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: peripheral.steamcontroller 3 | Source: https://github.com/kodi-game/peripheral.steamcontroller 4 | 5 | Files: * 6 | Copyright: 2005-2021 Team Kodi 7 | License: GPL-2+ 8 | This package is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | . 13 | This package is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | . 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see 20 | . 21 | On Debian systems, the complete text of the GNU General 22 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 23 | -------------------------------------------------------------------------------- /src/usb/USBConfiguration.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBConfiguration.h" 10 | 11 | #include 12 | #include 13 | 14 | using namespace STEAMCONTROLLER; 15 | 16 | CUSBConfiguration::CUSBConfiguration(libusb_config_descriptor* config) : 17 | m_config(config) 18 | { 19 | assert(m_config != nullptr); 20 | } 21 | 22 | CUSBConfiguration::~CUSBConfiguration() 23 | { 24 | libusb_free_config_descriptor(m_config); 25 | } 26 | 27 | unsigned int CUSBConfiguration::GetMaxPower() const 28 | { 29 | return m_config->MaxPower * 2; 30 | } 31 | 32 | unsigned int CUSBConfiguration::InterfaceCount() const 33 | { 34 | return m_config->bNumInterfaces; 35 | } 36 | 37 | CUSBInterface CUSBConfiguration::GetInterface(unsigned int interfaceNumber) const 38 | { 39 | const libusb_interface& interface = m_config->interface[interfaceNumber]; 40 | return CUSBInterface(interface); 41 | } 42 | -------------------------------------------------------------------------------- /src/util/Log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "Log.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | using namespace STEAMCONTROLLER; 17 | 18 | #define MAXSYSLOGBUF 1024 19 | 20 | CLog::CLog() 21 | { 22 | } 23 | 24 | CLog& CLog::Get() 25 | { 26 | static CLog instance; 27 | return instance; 28 | } 29 | 30 | void CLog::Log(LogLevel level, const char* format, ...) 31 | { 32 | char buffer[MAXSYSLOGBUF]; 33 | 34 | va_list args; 35 | va_start(args, format); 36 | vsnprintf(buffer, sizeof(buffer) - 1, format, args); 37 | va_end(args); 38 | 39 | ADDON_LOG addonLevel = ADDON_LOG_DEBUG; 40 | 41 | switch (level) 42 | { 43 | case LOG_DEBUG: 44 | addonLevel = ADDON_LOG_DEBUG; 45 | break; 46 | case LOG_INFO: 47 | addonLevel = ADDON_LOG_INFO; 48 | break; 49 | case LOG_ERROR: 50 | addonLevel = ADDON_LOG_ERROR; 51 | break; 52 | default: 53 | break; 54 | } 55 | 56 | kodi::Log(addonLevel, buffer); 57 | } 58 | -------------------------------------------------------------------------------- /src/usb/USBDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBTypes.h" 10 | 11 | struct libusb_device; 12 | struct libusb_device_descriptor; 13 | 14 | namespace STEAMCONTROLLER 15 | { 16 | class CUSBConfiguration; 17 | class CUSBContext; 18 | 19 | /*! 20 | * \brief Represents a USB device 21 | */ 22 | class CUSBDevice 23 | { 24 | public: 25 | CUSBDevice(libusb_device* device, CUSBContext& context); 26 | 27 | ~CUSBDevice(); 28 | 29 | bool LoadDescriptor(); 30 | 31 | uint16_t VendorID() const; 32 | uint16_t ProductID() const; 33 | 34 | bool LoadConfiguration(); 35 | 36 | const CUSBConfiguration& GetConfiguration() { return *m_configuration; } 37 | 38 | DeviceHandlePtr Open(); 39 | void Close(); 40 | 41 | private: 42 | // Construction params 43 | CUSBContext& m_context; 44 | libusb_device* const m_device; 45 | 46 | std::unique_ptr m_descriptor; 47 | std::unique_ptr m_configuration; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "SteamControllerTypes.h" 12 | #include "usb/USBTypes.h" 13 | 14 | #include 15 | 16 | namespace kodi 17 | { 18 | namespace addon 19 | { 20 | class PeripheralEvent; 21 | } 22 | } 23 | 24 | namespace STEAMCONTROLLER 25 | { 26 | class CUSBContext; 27 | 28 | class CSteamControllerManager 29 | { 30 | CSteamControllerManager(); 31 | 32 | public: 33 | static CSteamControllerManager& Get(); 34 | 35 | bool Initialize(); 36 | void Deinitialize(); 37 | 38 | void GetControllers(ControllerVector& controllers); 39 | 40 | ControllerPtr GetController(unsigned int index); 41 | 42 | ControllerPtr GetController(const DevicePtr& device); 43 | 44 | void GetEvents(std::vector& events); 45 | 46 | bool SendEvent(const kodi::addon::PeripheralEvent& events); 47 | 48 | private: 49 | ControllerVector m_controllers; 50 | std::unique_ptr m_context; 51 | 52 | static unsigned int m_nextControllerIndex; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/usb/USBTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | // Default string length 16 | // From a comment in libusb-1.0: "Some devices choke on size > 255" 17 | #define STRING_LENGTH 255 18 | 19 | // As of v3 of USB specs, there cannot be more than 7 hubs from controller to device 20 | #define PATH_MAX_DEPTH 7 21 | 22 | namespace STEAMCONTROLLER 23 | { 24 | class CUSBDevice; 25 | typedef std::shared_ptr DevicePtr; 26 | typedef std::vector DeviceVector; 27 | 28 | class CUSBDeviceHandle; 29 | typedef std::shared_ptr DeviceHandlePtr; 30 | typedef std::vector DeviceHandleVector; 31 | 32 | class CUSBTransfer; 33 | typedef std::shared_ptr TransferPtr; 34 | typedef std::vector TransferVector; 35 | 36 | struct USB_ID 37 | { 38 | uint16_t vid; 39 | uint16_t pid; 40 | }; 41 | 42 | inline bool operator==(const USB_ID& lhs, const USB_ID& rhs) 43 | { 44 | return lhs.vid == rhs.vid && 45 | lhs.pid == rhs.pid; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #define STEAM_CONTROLLER_NAME "Steam Controller" // TODO: Get from USB device 15 | #define STEAM_CONTROLLER_PROVIDER "steam" 16 | 17 | #define STEAM_CONTROLLER_VID 0x28de 18 | #define STEAM_CONTROLLER_PID_WIRELESS 0x1142 19 | #define STEAM_CONTROLLER_PID_WIRED 0x1102 20 | 21 | #define STEAM_CONTROLLER_ENDPOINT_WIRELESS 2 22 | #define STEAM_CONTROLLER_ENDPOINT_WIRED 3 23 | 24 | #define STEAM_CONTROLLER_CONTROLIDX_WIRELESS 2 25 | #define STEAM_CONTROLLER_CONTROLIDX_WIRED 1 26 | 27 | #define STEAM_CONTROLLER_HPERIOD 0.02f 28 | #define STEAM_CONTROLLER_LPERIOD 0.5f 29 | #define STEAM_CONTROLLER_DURATION 1.0f 30 | 31 | namespace STEAMCONTROLLER 32 | { 33 | class CSteamController; 34 | typedef std::shared_ptr ControllerPtr; 35 | typedef std::vector ControllerVector; 36 | 37 | /*! 38 | * \brief Specify which pad or trigger is used 39 | */ 40 | enum class HapticPosition 41 | { 42 | RIGHT, 43 | LEFT, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/usb/USBContext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "USBTypes.h" 12 | 13 | #include 14 | 15 | struct libusb_context; 16 | 17 | namespace STEAMCONTROLLER 18 | { 19 | class CUSBThread; 20 | class IFeedbackCallback; 21 | 22 | /*! 23 | * \brief libusb1 USB context 24 | * 25 | * This class provides methods to enumerate and look up USB devices. It also 26 | * provides access to global (device-independent) libusb1 functions (TODO). 27 | */ 28 | class CUSBContext 29 | { 30 | public: 31 | CUSBContext(); 32 | 33 | ~CUSBContext(); 34 | 35 | bool Initialize(); 36 | void Deinitialize(); 37 | 38 | void GetDevicesByID(const std::vector& ids, DeviceVector& result); 39 | 40 | /*! 41 | * \brief Handle any pending event in blocking mode 42 | * 43 | * See libusb1 documentation for details (there is a timeout, so it's 44 | * not "really" blocking). 45 | */ 46 | void HandleEvents(); 47 | 48 | IFeedbackCallback* GetFeedbackCallback(); 49 | 50 | private: 51 | libusb_context* m_context; 52 | std::unique_ptr m_thread; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | # 4 | # Define the build matrix 5 | # 6 | # Travis defaults to building on Ubuntu Trusty when building on 7 | # Linux. We need Xenial in order to get up to date versions of 8 | # cmake and g++. 9 | # 10 | env: 11 | global: 12 | - app_id=peripheral.steamcontroller 13 | 14 | matrix: 15 | include: 16 | - os: linux 17 | dist: xenial 18 | sudo: required 19 | compiler: gcc 20 | - os: linux 21 | dist: xenial 22 | sudo: required 23 | compiler: clang 24 | 25 | before_install: 26 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi 27 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y libudev-dev; fi 28 | 29 | # 30 | # The addon source is automatically checked out in $TRAVIS_BUILD_DIR, 31 | # we'll put the Kodi source on the same level 32 | # 33 | before_script: 34 | - cd $TRAVIS_BUILD_DIR/.. 35 | - git clone --depth=1 https://github.com/xbmc/xbmc.git 36 | - cd ${app_id} && mkdir build && cd build 37 | - mkdir -p definition/${app_id} 38 | - echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt 39 | - cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons 40 | 41 | script: make 42 | -------------------------------------------------------------------------------- /src/usb/USBThread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "steamcontroller/interfaces/IFeedbackCallback.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace STEAMCONTROLLER 20 | { 21 | class CUSBContext; 22 | class CUSBDeviceHandle; 23 | class ISendMessageCallback; 24 | 25 | /*! 26 | * \brief Thread to run in order to process USB events 27 | */ 28 | class CUSBThread : public IFeedbackCallback 29 | { 30 | public: 31 | CUSBThread(CUSBContext* context); 32 | ~CUSBThread() override; 33 | 34 | void Initialize() { } 35 | void Deinitialize(); 36 | 37 | // implementation of IFeedbackCallback 38 | void RegisterDeviceHandle(CUSBDeviceHandle* deviceHandle) override; 39 | void UnregisterDeviceHandle(CUSBDeviceHandle* deviceHandle) override; 40 | void AddMessage(ISendMessageCallback* callback, std::vector&& message) override; 41 | 42 | protected: 43 | // implementation of CThread 44 | void Process(); 45 | 46 | private: 47 | struct FeedbackMessage 48 | { 49 | ISendMessageCallback* callback; 50 | std::vector data; 51 | }; 52 | 53 | void SendMessage(const FeedbackMessage& message); 54 | 55 | // Construction parameter 56 | CUSBContext* const m_context; 57 | 58 | std::vector m_deviceHandles; 59 | 60 | std::list m_messagesIn; 61 | 62 | std::recursive_mutex m_mutex; 63 | std::thread* m_thread = nullptr; 64 | bool m_isStopped = true; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerInput.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace STEAMCONTROLLER 16 | { 17 | enum class InputStatus 18 | { 19 | INPUT = 0x01, 20 | HOTPLUG = 0x03, 21 | IDLE = 0x04, 22 | }; 23 | 24 | class CSteamControllerInput 25 | { 26 | public: 27 | CSteamControllerInput(); 28 | 29 | void Reset(); 30 | 31 | bool Deserialize(const std::vector& packet); 32 | 33 | InputStatus Status() const { return static_cast(m_state.status); } 34 | uint8_t Seq() const { return m_state.seq; } 35 | uint32_t Buttons() const { return m_state.buttons; } 36 | uint8_t LeftTrigger() const { return m_state.ltrig; } 37 | uint8_t RightTrigger() const { return m_state.rtrig; } 38 | int16_t LeftPadX() const { return m_state.lpad_x; } 39 | int16_t LeftPadY() const { return m_state.lpad_y; } 40 | int16_t RightPadX() const { return m_state.rpad_x; } 41 | int16_t RightPadY() const { return m_state.rpad_y; } 42 | 43 | private: 44 | struct PacketFormat 45 | { 46 | uint8_t unk_00; 47 | uint8_t unk_01; 48 | uint8_t status; 49 | uint8_t unk_02; 50 | uint16_t seq; 51 | uint8_t unk_03; 52 | uint32_t buttons; 53 | uint8_t ltrig; 54 | uint8_t rtrig; 55 | uint8_t unk_04; 56 | uint8_t unk_05; 57 | uint8_t unk_06; 58 | int16_t lpad_x; 59 | int16_t lpad_y; 60 | int16_t rpad_x; 61 | int16_t rpad_y; 62 | uint8_t unk_07[10]; 63 | int16_t gpitch; 64 | int16_t groll; 65 | int16_t gyaw; 66 | int16_t q1; 67 | int16_t q2; 68 | int16_t q3; 69 | int16_t q4; 70 | uint8_t unk_08[16]; 71 | } __attribute__((__packed__)); 72 | 73 | PacketFormat m_state; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /src/usb/USBDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBDevice.h" 10 | #include "USBConfiguration.h" 11 | #include "USBContext.h" 12 | #include "USBDeviceHandle.h" 13 | #include "util/Log.h" 14 | 15 | #include 16 | 17 | using namespace STEAMCONTROLLER; 18 | 19 | CUSBDevice::CUSBDevice(libusb_device* device, CUSBContext& context) : 20 | m_context(context), 21 | m_device(device), 22 | m_descriptor(new libusb_device_descriptor) 23 | { 24 | libusb_ref_device(m_device); 25 | } 26 | 27 | CUSBDevice::~CUSBDevice() 28 | { 29 | libusb_unref_device(m_device); 30 | } 31 | 32 | bool CUSBDevice::LoadDescriptor() 33 | { 34 | int res = libusb_get_device_descriptor(m_device, m_descriptor.get()); 35 | if (res < 0) 36 | { 37 | esyslog("Failed to get device descriptor (error = %d)", res); 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | uint16_t CUSBDevice::VendorID() const 45 | { 46 | return m_descriptor->idVendor; 47 | } 48 | 49 | uint16_t CUSBDevice::ProductID() const 50 | { 51 | return m_descriptor->idProduct; 52 | } 53 | 54 | bool CUSBDevice::LoadConfiguration() 55 | { 56 | // We only care about configuration 0 57 | const unsigned int configurationNumber = 0; 58 | 59 | libusb_config_descriptor* config = nullptr; 60 | int res = libusb_get_config_descriptor(m_device, 0, &config); 61 | if (res == LIBUSB_ERROR_NOT_FOUND) 62 | { 63 | // Some devices (ex windows' root hubs) tell they have one configuration, 64 | // but they have no configuration descriptor 65 | // REF: python-libusb1 66 | return false; 67 | } 68 | else if (res < 0) 69 | { 70 | esyslog("Failed to get config descriptor (error = %d)", res); 71 | return false; 72 | } 73 | 74 | m_configuration.reset(new CUSBConfiguration(config)); 75 | 76 | return true; 77 | } 78 | 79 | DeviceHandlePtr CUSBDevice::Open() 80 | { 81 | DeviceHandlePtr deviceHandle; 82 | 83 | libusb_device_handle* handle; 84 | int res = libusb_open(m_device, &handle); 85 | if (res < 0) 86 | { 87 | esyslog("Failed to open device (error = %d)", res); 88 | } 89 | else 90 | { 91 | deviceHandle.reset(new CUSBDeviceHandle(handle, m_context)); 92 | } 93 | 94 | return deviceHandle; 95 | } 96 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(peripheral.steamcontroller) 3 | 4 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 5 | 6 | # --- Add-on Dependencies ------------------------------------------------------ 7 | 8 | find_package(Kodi REQUIRED) 9 | find_package(libusb-1.0 REQUIRED) 10 | 11 | # Fix linking on OSX 12 | IF (APPLE) 13 | FIND_LIBRARY(COCOA_LIBRARY Cocoa) 14 | FIND_LIBRARY(FRAMEWORK_IOKIT IOKit) 15 | ENDIF (APPLE) 16 | 17 | include_directories(${INCLUDES} 18 | ${PROJECT_SOURCE_DIR}/src 19 | ${KODI_INCLUDE_DIR}/.. # HACK (see https://github.com/xbmc/peripheral.joystick/pull/112) 20 | ${LIBUSB_1_INCLUDE_DIRS}) 21 | 22 | set(STEAMCONTROLLER_SOURCES 23 | src/steamcontroller/SteamController.cpp 24 | src/steamcontroller/SteamControllerInput.cpp 25 | src/steamcontroller/SteamControllerManager.cpp 26 | src/steamcontroller/SteamControllerTranslator.cpp 27 | src/usb/USBConfiguration.cpp 28 | src/usb/USBContext.cpp 29 | src/usb/USBDevice.cpp 30 | src/usb/USBDeviceHandle.cpp 31 | src/usb/USBInterface.cpp 32 | src/usb/USBInterfaceSetting.cpp 33 | src/usb/USBThread.cpp 34 | src/usb/USBTransfer.cpp 35 | src/util/Log.cpp 36 | src/addon.cpp 37 | ) 38 | 39 | set(STEAMCONTROLLER_HEADERS 40 | src/steamcontroller/interfaces/IFeedbackCallback.h 41 | src/steamcontroller/SteamController.h 42 | src/steamcontroller/SteamControllerInput.h 43 | src/steamcontroller/SteamControllerManager.h 44 | src/steamcontroller/SteamControllerTranslator.h 45 | src/steamcontroller/SteamControllerTypes.h 46 | src/usb/interfaces/IMessageCallback.h 47 | src/usb/interfaces/ISendMessageCallback.h 48 | src/usb/interfaces/ITransferCallback.h 49 | src/usb/interfaces/ITransferObserver.h 50 | src/usb/USBConfiguration.h 51 | src/usb/USBContext.h 52 | src/usb/USBDevice.h 53 | src/usb/USBDeviceHandle.h 54 | src/usb/USBInterface.h 55 | src/usb/USBInterfaceSetting.h 56 | src/usb/USBThread.h 57 | src/usb/USBTransfer.h 58 | src/usb/USBTypes.h 59 | src/util/Log.h 60 | src/addon.h 61 | ) 62 | 63 | list(APPEND DEPLIBS 64 | ${LIBUSB_1_LIBRARIES} 65 | ${COCOA_LIBRARY} 66 | ${FRAMEWORK_IOKIT} 67 | ) 68 | 69 | # ------------------------------------------------------------------------------ 70 | 71 | build_addon(${PROJECT_NAME} STEAMCONTROLLER DEPLIBS) 72 | 73 | include(CPack) 74 | -------------------------------------------------------------------------------- /src/usb/USBContext.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBContext.h" 10 | #include "USBDevice.h" 11 | #include "USBThread.h" 12 | #include "util/Log.h" 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | using namespace STEAMCONTROLLER; 20 | 21 | CUSBContext::CUSBContext() : 22 | m_context(nullptr), 23 | m_thread(new CUSBThread(this)) 24 | { 25 | } 26 | 27 | CUSBContext::~CUSBContext() 28 | { 29 | Deinitialize(); 30 | } 31 | 32 | bool CUSBContext::Initialize() 33 | { 34 | int res = libusb_init(&m_context); 35 | if (res < 0) 36 | { 37 | esyslog("Failed to initialize libusb (error = %d)", res); 38 | return false; 39 | } 40 | 41 | // Set verbosity level to 3, as suggested in the documentation 42 | libusb_set_debug(m_context, 3); 43 | 44 | m_thread->Initialize(); 45 | 46 | return true; 47 | } 48 | 49 | void CUSBContext::Deinitialize() 50 | { 51 | m_thread->Deinitialize(); 52 | 53 | if (m_context) 54 | { 55 | libusb_exit(m_context); 56 | m_context = nullptr; 57 | } 58 | } 59 | 60 | void CUSBContext::GetDevicesByID(const std::vector& ids, DeviceVector& result) 61 | { 62 | if (m_context == nullptr) 63 | return; 64 | 65 | libusb_device** devices; 66 | ssize_t count = libusb_get_device_list(m_context, &devices); 67 | 68 | if (count < 0) 69 | { 70 | esyslog("Failed to get list of USB devices"); 71 | return; 72 | } 73 | 74 | for (ssize_t i = 0; i < count; i++) 75 | { 76 | libusb_device* dev = devices[i]; 77 | DevicePtr device = std::make_shared(dev, *this); 78 | 79 | if (device->LoadDescriptor()) 80 | { 81 | USB_ID deviceId = { device->VendorID(), device->ProductID() }; 82 | 83 | // Check if device's USB ID appears in the list parameter 84 | auto it = std::find(ids.begin(), ids.end(), deviceId); 85 | 86 | if (it != ids.end()) 87 | { 88 | if (device->LoadConfiguration()) 89 | result.emplace_back(std::move(device)); 90 | } 91 | } 92 | } 93 | 94 | libusb_free_device_list(devices, 1); 95 | } 96 | 97 | void CUSBContext::HandleEvents() 98 | { 99 | if (m_context == nullptr) 100 | return; 101 | 102 | int res = libusb_handle_events(m_context); 103 | if (res < 0) 104 | esyslog("Failed to handle USB events (error = %d)", res); 105 | } 106 | 107 | IFeedbackCallback* CUSBContext::GetFeedbackCallback() 108 | { 109 | return m_thread.get(); 110 | } 111 | -------------------------------------------------------------------------------- /src/usb/USBThread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBThread.h" 10 | #include "USBContext.h" 11 | #include "USBDeviceHandle.h" 12 | #include "USBTransfer.h" 13 | #include "usb/interfaces/ISendMessageCallback.h" 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace STEAMCONTROLLER; 23 | 24 | CUSBThread::CUSBThread(CUSBContext* context) : 25 | m_context(context) 26 | { 27 | assert(m_context != nullptr); 28 | } 29 | 30 | CUSBThread::~CUSBThread() 31 | { 32 | Deinitialize(); 33 | } 34 | 35 | void CUSBThread::Deinitialize() 36 | { 37 | m_isStopped = true; 38 | if (m_thread) 39 | { 40 | if (m_thread->joinable()) 41 | m_thread->join(); 42 | 43 | delete m_thread; 44 | m_thread = nullptr; 45 | } 46 | } 47 | 48 | void CUSBThread::RegisterDeviceHandle(CUSBDeviceHandle* deviceHandle) 49 | { 50 | std::unique_lock lock(m_mutex); 51 | 52 | m_deviceHandles.push_back(deviceHandle); 53 | 54 | m_isStopped = false; 55 | m_thread = new std::thread(&CUSBThread::Process, this); 56 | } 57 | 58 | void CUSBThread::UnregisterDeviceHandle(CUSBDeviceHandle* deviceHandle) 59 | { 60 | std::unique_lock lock(m_mutex); 61 | 62 | m_deviceHandles.erase(std::remove(m_deviceHandles.begin(), m_deviceHandles.end(), deviceHandle), m_deviceHandles.end()); 63 | 64 | if (m_deviceHandles.empty()) 65 | { 66 | lock.unlock(); 67 | Deinitialize(); 68 | } 69 | } 70 | 71 | void CUSBThread::AddMessage(ISendMessageCallback* callback, std::vector&& message) 72 | { 73 | std::unique_lock lock(m_mutex); 74 | 75 | m_messagesIn.push_front({ callback, message }); 76 | } 77 | 78 | void CUSBThread::SendMessage(const FeedbackMessage& message) 79 | { 80 | message.callback->SendControl(message.data.data(), message.data.size()); 81 | } 82 | 83 | void CUSBThread::Process() 84 | { 85 | while (!m_isStopped) 86 | { 87 | bool bIsAnySubmitted = true; // TODO 88 | 89 | { 90 | std::unique_lock lock(m_mutex); 91 | 92 | // Check if any device handles have transfers submitted 93 | auto itDeviceHandle = std::find_if(m_deviceHandles.begin(), m_deviceHandles.end(), 94 | [](CUSBDeviceHandle* deviceHandle) 95 | { 96 | return deviceHandle->HasSubmittedTransfers(); 97 | }); 98 | 99 | bIsAnySubmitted = (itDeviceHandle != m_deviceHandles.end()); 100 | } 101 | 102 | if (bIsAnySubmitted) 103 | { 104 | m_context->HandleEvents(); 105 | 106 | std::unique_lock lock(m_mutex); 107 | 108 | // Handle haptic feedback 109 | if (!m_messagesIn.empty()) 110 | { 111 | SendMessage(m_messagesIn.back()); 112 | m_messagesIn.pop_back(); 113 | } 114 | } 115 | else 116 | std::this_thread::sleep_for(std::chrono::microseconds(100)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/usb/USBTransfer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct libusb_device_handle; 16 | struct libusb_transfer; 17 | 18 | namespace STEAMCONTROLLER 19 | { 20 | class ITransferCallback; 21 | class ITransferObserver; 22 | 23 | /*! 24 | * \brief USB asynchronous transfer control & data 25 | */ 26 | class CUSBTransfer 27 | { 28 | public: 29 | CUSBTransfer(libusb_device_handle* handle, unsigned int isoPackets, ITransferObserver* observer); 30 | 31 | ~CUSBTransfer(); 32 | 33 | void Close(); 34 | 35 | /*! 36 | * \brief Prevent transfer from being submitted again 37 | */ 38 | void Doom(); 39 | 40 | /*! 41 | * \brief Setup transfer for interrupt use 42 | * 43 | * param endpoint Endpoint to submit transfer to. Defines transfer direction 44 | * (see LIBUSB_ENDPOINT_OUT and LIBUSB_ENDPOINT_IN) 45 | * 46 | * \param bufferLength Expected data length (when receiving data) 47 | * \param buffer A string (when sending data) 48 | * 49 | * \param callback Callback function to be invoked on transfer completion 50 | * 51 | * \param timeoutMs Transfer timeout in milliseconds, or 0 to disable 52 | */ 53 | bool SetInterrupt(uint8_t endpoint, unsigned int bufferLength, ITransferCallback* callback, unsigned int timeoutMs = 0); 54 | bool SetInterrupt(uint8_t endpoint, const std::string& buffer, ITransferCallback* callback, unsigned int timeoutMs = 0); 55 | 56 | /*! 57 | * \brief Get transfer status 58 | * 59 | * Should not be called on a submitted transfer. 60 | */ 61 | int Status() const; 62 | 63 | /*! 64 | * \brief Get actually transfered data length 65 | * 66 | * Should not be called on a submitted transfer. 67 | */ 68 | int ActualLength() const; 69 | 70 | /*! 71 | * \brief Get data buffer content 72 | * 73 | * Should not be called on a submitted transfer. 74 | */ 75 | std::vector Buffer() const; 76 | 77 | /*! 78 | * \brief Tells if this transfer is submitted and still pending 79 | */ 80 | bool IsSubmitted() const; 81 | 82 | /*! 83 | * \brief Submit transfer for asynchronous handling 84 | */ 85 | bool Submit(); 86 | 87 | /*! 88 | * \brief Cancel transfer 89 | * 90 | * Note: cancellation happens asynchronously, so you must wait for 91 | * LIBUSB_TRANSFER_CANCELLED. 92 | */ 93 | void Cancel(); 94 | 95 | private: 96 | /*! 97 | * \brief Makes it possible for user-provided callback to alter transfer 98 | * when fired (ie, mark transfer as not submitted upon call) 99 | */ 100 | void TransferCallback(libusb_transfer* transfer); 101 | 102 | static void transfer_cb_fn(libusb_transfer* transfer); 103 | 104 | // Construction params 105 | libusb_device_handle* const m_handle; 106 | ITransferObserver* const m_observer; 107 | 108 | bool m_bInitailized; 109 | bool m_bSubmitted; 110 | bool m_bDoomed; 111 | std::string m_transferBuffer; 112 | ITransferCallback* m_callback; 113 | libusb_transfer* m_transfer; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamControllerManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "SteamControllerManager.h" 10 | #include "SteamController.h" 11 | #include "usb/USBContext.h" 12 | #include "usb/USBDevice.h" 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | using namespace STEAMCONTROLLER; 20 | 21 | unsigned int CSteamControllerManager::m_nextControllerIndex = 0; 22 | 23 | CSteamControllerManager::CSteamControllerManager() : 24 | m_context(new CUSBContext) 25 | { 26 | } 27 | 28 | CSteamControllerManager& CSteamControllerManager::Get() 29 | { 30 | static CSteamControllerManager instance; 31 | return instance; 32 | } 33 | 34 | bool CSteamControllerManager::Initialize() 35 | { 36 | if (!m_context->Initialize()) 37 | return false; 38 | 39 | return true; 40 | } 41 | 42 | void CSteamControllerManager::Deinitialize() 43 | { 44 | m_controllers.clear(); 45 | m_context->Deinitialize(); 46 | } 47 | 48 | void CSteamControllerManager::GetControllers(ControllerVector& controllers) 49 | { 50 | USB_ID wirelessId = { STEAM_CONTROLLER_VID, STEAM_CONTROLLER_PID_WIRELESS }; 51 | USB_ID wiredId = { STEAM_CONTROLLER_VID, STEAM_CONTROLLER_PID_WIRED }; 52 | 53 | DeviceVector devices; 54 | m_context->GetDevicesByID({ wirelessId, wiredId }, devices); 55 | 56 | for (auto& device : devices) 57 | { 58 | // TODO: Need a better uniqueness test 59 | if (!GetController(device)) 60 | { 61 | ControllerPtr controller(std::make_shared(device, m_nextControllerIndex++, m_context->GetFeedbackCallback())); 62 | if (controller->Initialize()) 63 | { 64 | // Disable haptic auto feedback 65 | m_context->HandleEvents(); 66 | controller->SendControl({ 0x81000000 }); 67 | m_context->HandleEvents(); 68 | controller->SendControl({ 0x87153284, 0x03180000, 0x31020008, 0x07000707, 0x00300000, 0x2f010000 }); 69 | m_context->HandleEvents(); 70 | 71 | m_controllers.emplace_back(std::move(controller)); 72 | } 73 | } 74 | } 75 | 76 | // TODO: Handle removed devices 77 | 78 | controllers = m_controllers; 79 | } 80 | 81 | ControllerPtr CSteamControllerManager::GetController(unsigned int index) 82 | { 83 | ControllerPtr controller; 84 | 85 | auto it = std::find_if(m_controllers.begin(), m_controllers.end(), 86 | [index](const ControllerPtr& controller) 87 | { 88 | return controller->Index() == index; 89 | }); 90 | 91 | if (it != m_controllers.end()) 92 | controller = *it; 93 | 94 | return controller; 95 | } 96 | 97 | ControllerPtr CSteamControllerManager::GetController(const DevicePtr& device) 98 | { 99 | ControllerPtr controller; 100 | 101 | auto it = std::find_if(m_controllers.begin(), m_controllers.end(), 102 | [&device](const ControllerPtr& controller) 103 | { 104 | return controller->Device().VendorID() == device->VendorID() && 105 | controller->Device().ProductID() == device->ProductID(); 106 | }); 107 | 108 | if (it != m_controllers.end()) 109 | controller = *it; 110 | 111 | return controller; 112 | } 113 | 114 | void CSteamControllerManager::GetEvents(std::vector& events) 115 | { 116 | // TODO 117 | } 118 | 119 | bool CSteamControllerManager::SendEvent(const kodi::addon::PeripheralEvent& events) 120 | { 121 | return false; // TODO 122 | } 123 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "SteamControllerTypes.h" 12 | #include "usb/USBTypes.h" 13 | #include "usb/interfaces/IMessageCallback.h" 14 | #include "usb/interfaces/ISendMessageCallback.h" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace kodi 21 | { 22 | namespace addon 23 | { 24 | class Joystick; 25 | class Peripheral; 26 | class PeripheralEvent; 27 | } 28 | } 29 | 30 | namespace STEAMCONTROLLER 31 | { 32 | class CSteamControllerInput; 33 | class IFeedbackCallback; 34 | 35 | class CSteamController : public IMessageCallback, 36 | public ISendMessageCallback 37 | { 38 | public: 39 | CSteamController(const DevicePtr& device, unsigned int index, IFeedbackCallback* feedbackCallback); 40 | 41 | ~CSteamController(); 42 | 43 | bool Initialize(); 44 | void Deinitialize(); 45 | 46 | unsigned int Index() const { return m_index; } 47 | CUSBDevice& Device() const { return *m_device; } 48 | 49 | // implementation of IMessageCallback 50 | void OnTransferComplete(const std::vector& message) override; 51 | 52 | // implementation of ISendMessageCallback 53 | void SendControl(const uint8_t* data, size_t length, unsigned int timeout = 0) override; 54 | 55 | // Peripheral interface 56 | void GetPeripheralInfo(kodi::addon::Peripheral& peripheralInfo); 57 | void GetJoystickInfo(kodi::addon::Joystick& joystickInfo); 58 | bool SendEvent(const kodi::addon::PeripheralEvent& event); 59 | 60 | void SendControl(const std::vector& data, unsigned int timeout = 0); 61 | 62 | private: 63 | struct FeedbackMessage 64 | { 65 | uint8_t byte1; 66 | uint8_t byte2; 67 | uint8_t position; 68 | uint16_t amplitude; 69 | uint16_t period; 70 | uint16_t count; 71 | } __attribute__((__packed__)); 72 | 73 | void ClaimInterfaces(); 74 | 75 | /*! 76 | * \brief Process SteamController inputs to generate events 77 | */ 78 | void ProcessInput(); 79 | 80 | /*! 81 | * \brief Add haptic feedback to be send on next USB tick 82 | * 83 | * \param int position haptic to use 84 | * \param int magnitude signal amplitude from 0.0 to 1.0 85 | */ 86 | void AddFeedback(HapticPosition position, float magnitude); 87 | 88 | /*! 89 | * \brief Add haptic feedback to be send on next USB tick 90 | * 91 | * \param position haptic to use 92 | * \param amplitude signal amplitude from 0 to 65535 93 | * \param period signal period from 0 to 65535 94 | * \param count number of periods to play 95 | */ 96 | void AddFeedback(HapticPosition position, 97 | uint16_t amplitude = 128, 98 | uint16_t period = 0, 99 | uint16_t count = 1); 100 | 101 | // Utility functions 102 | uint8_t GetEndpoint() const; 103 | uint8_t GetControlIdx() const; 104 | 105 | // Construction parameters 106 | const DevicePtr m_device; 107 | const unsigned int m_index; 108 | IFeedbackCallback* const m_feedbackCallback; 109 | 110 | DeviceHandlePtr m_deviceHandle; 111 | 112 | std::unique_ptr m_input; 113 | std::unique_ptr m_previousInput; 114 | 115 | double m_lastUsbTimeMs; 116 | float m_period; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /cmake/Findlibusb-1.0.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find libusb-1.0 2 | # Once done this will define 3 | # 4 | # LIBUSB_1_FOUND - system has libusb 5 | # LIBUSB_1_INCLUDE_DIRS - the libusb include directory 6 | # LIBUSB_1_LIBRARIES - Link these to use libusb 7 | # LIBUSB_1_DEFINITIONS - Compiler switches required for using libusb 8 | # 9 | # Adapted from cmake-modules Google Code project 10 | # 11 | # Copyright (c) 2006 Andreas Schneider 12 | # 13 | # (Changes for libusb) Copyright (c) 2008 Kyle Machulis 14 | # 15 | # Redistribution and use is allowed according to the terms of the New BSD license. 16 | # 17 | # CMake-Modules Project New BSD License 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # * Redistributions of source code must retain the above copyright notice, this 23 | # list of conditions and the following disclaimer. 24 | # 25 | # * Redistributions in binary form must reproduce the above copyright notice, 26 | # this list of conditions and the following disclaimer in the 27 | # documentation and/or other materials provided with the distribution. 28 | # 29 | # * Neither the name of the CMake-Modules Project nor the names of its 30 | # contributors may be used to endorse or promote products derived from this 31 | # software without specific prior written permission. 32 | # 33 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 34 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 35 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 37 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 38 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 39 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 40 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 41 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 42 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | # 44 | 45 | 46 | if (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) 47 | # in cache already 48 | set(LIBUSB_FOUND TRUE) 49 | else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) 50 | find_path(LIBUSB_1_INCLUDE_DIR 51 | NAMES 52 | libusb.h 53 | PATHS 54 | /usr/include 55 | /usr/local/include 56 | /opt/local/include 57 | /sw/include 58 | PATH_SUFFIXES 59 | libusb-1.0 60 | ) 61 | 62 | find_library(LIBUSB_1_LIBRARY 63 | NAMES 64 | usb-1.0 usb 65 | PATHS 66 | /usr/lib 67 | /usr/local/lib 68 | /opt/local/lib 69 | /sw/lib 70 | ) 71 | 72 | set(LIBUSB_1_INCLUDE_DIRS 73 | ${LIBUSB_1_INCLUDE_DIR} 74 | ) 75 | set(LIBUSB_1_LIBRARIES 76 | ${LIBUSB_1_LIBRARY} 77 | ) 78 | 79 | if (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES) 80 | set(LIBUSB_1_FOUND TRUE) 81 | endif (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES) 82 | 83 | if (LIBUSB_1_FOUND) 84 | if (NOT libusb_1_FIND_QUIETLY) 85 | message(STATUS "Found libusb-1.0:") 86 | message(STATUS " - Includes: ${LIBUSB_1_INCLUDE_DIRS}") 87 | message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}") 88 | endif (NOT libusb_1_FIND_QUIETLY) 89 | else (LIBUSB_1_FOUND) 90 | if (libusb_1_FIND_REQUIRED) 91 | message(FATAL_ERROR "Could not find libusb") 92 | endif (libusb_1_FIND_REQUIRED) 93 | endif (LIBUSB_1_FOUND) 94 | 95 | # show the LIBUSB_1_INCLUDE_DIRS and LIBUSB_1_LIBRARIES variables only in the advanced view 96 | mark_as_advanced(LIBUSB_1_INCLUDE_DIRS LIBUSB_1_LIBRARIES) 97 | 98 | endif (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: GPL-2.0-or-later](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](LICENSE.md) 2 | 3 | # Steam controller driver for Kodi 4 | 5 | ![Steam controller](https://raw.githubusercontent.com/kodi-game/peripheral.steamcontroller/master/peripheral.steamcontroller/icon.png) 6 | 7 | This is a peripheral add-on for Kodi that enables input from Steam controllers. It is based on the Standalone Steam Controller Driver: https://github.com/ynsta/steamcontroller. 8 | 9 | ## Building 10 | 11 | Building this add-on requires Kodi's internal CMake-based build system for binary add-ons. If you are cross-compiling or just packaging the add-on, it is recommended that you use the Makefile provided with Kodi. 12 | 13 | The Makefile will download, build and install the add-on and its dependencies. There is no need to manually clone the add-on if Kodi's source is available. 14 | 15 | ### Building on Linux 16 | 17 | Make sure Kodi's add-on build system is installed somewhere. See https://github.com/xbmc/xbmc/blob/master/docs/README.md for instructions on building Kodi, which will create the binary add-on build system. 18 | 19 | Run the Makefile with the path to the build system: 20 | 21 | ```shell 22 | cd tools/depends/target/binary-addons 23 | make PREFIX=$HOME/kodi ADDONS="peripheral.steamcontroller" 24 | ``` 25 | 26 | You can specify multiple add-ons, and wildcards are accepted too. For example, `ADDONS="pvr.*"` will build all pvr add-ons. 27 | 28 | On Linux this performs a cross-compile install, so to package the add-on you'll need to copy the library and add-on files manually: 29 | 30 | ```shell 31 | cd $HOME/workspace/kodi/addons 32 | mkdir -p peripheral.steamcontroller 33 | cp -r $HOME/kodi/share/kodi/addons/peripheral.steamcontroller/ . 34 | cp -r $HOME/kodi/lib/kodi/addons/peripheral.steamcontroller/ . 35 | ``` 36 | 37 | To rebuild the add-on or compile a different one, clean the build directory: 38 | 39 | ```shell 40 | make clean 41 | ``` 42 | 43 | ### Building on OSX 44 | 45 | Building on OSX is similar to Linux, but all the paths are determined for you. This command will download, build and install the add-on to the `addons/` directory in your Kodi repo: 46 | 47 | ```shell 48 | cd tools/depends/target/binary-addons 49 | make ADDONS="peripheral.steamcontroller" 50 | ``` 51 | 52 | ### Building on Windows 53 | 54 | First, download and install [CMake](http://www.cmake.org/download/). 55 | 56 | To compile on windows, open a command prompt at `tools\buildsteps\win32` and run the script: 57 | 58 | ``` 59 | make-addons.bat install peripheral.steamcontroller 60 | ``` 61 | 62 | ## Developing 63 | 64 | When developing, compiling from a git repo is more convenient than repeatedly pushing changes to a remote one for Kodi's Makefile. 65 | 66 | ### Developing on Linux 67 | 68 | ```shell 69 | git clone https://github.com/xbmc/peripheral.steamcontroller.git 70 | cd peripheral.steamcontroller 71 | mkdir build 72 | cd build 73 | cmake -DCMAKE_BUILD_TYPE=Debug \ 74 | -DCMAKE_INSTALL_PREFIX=$HOME/workspace/kodi/addons \ 75 | -DPACKAGE_ZIP=1 \ 76 | .. 77 | make 78 | make install 79 | ``` 80 | 81 | where `$HOME/workspace/kodi` symlinks to the directory you cloned Kodi into. 82 | 83 | ### Developing on Windows 84 | 85 | This instructions here came from this helpful [forum post](http://forum.kodi.tv/showthread.php?tid=173361&pid=2097898#pid2097898). 86 | 87 | First, open `tools\windows\prepare-binary-addons-dev.bat` and change `-DCMAKE_BUILD_TYPE=Debug ^` to `-DCMAKE_BUILD_TYPE=Release ^`. 88 | 89 | Open a command prompt at `tools\windows` and run the script: 90 | 91 | ```shell 92 | prepare-binary-addons-dev.bat peripheral.steamcontroller 93 | ``` 94 | 95 | Open `cmake\addons\build\kodi-addons.sln` and build the solution. This downloads the add-on from the version specified in its text file (see above) and creates a Visual Studio project for it. If the build fails, try running it twice. 96 | 97 | This should package and copy the add-on to the `addons/` directory. If not, you can try opening the solution `cmake\addons\build\-prefix\src\-build\.sln` and building the INSTALL project or, worse case, copy by hand. 98 | -------------------------------------------------------------------------------- /src/addon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2021 Garrett Brown 3 | * Copyright (C) 2015-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "addon.h" 10 | #include "steamcontroller/SteamController.h" 11 | #include "steamcontroller/SteamControllerManager.h" 12 | #include "util/Log.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace STEAMCONTROLLER; 20 | 21 | ADDON_STATUS CPeripheralSteamController::Create() 22 | { 23 | //! @TODO Fix hang in USB thread 24 | #if 0 25 | if (!CSteamControllerManager::Get().Initialize()) 26 | { 27 | CLog::Get().Deinitailize(); 28 | return ADDON_STATUS_PERMANENT_FAILURE; 29 | } 30 | #endif 31 | 32 | return ADDON_STATUS_OK; 33 | } 34 | 35 | CPeripheralSteamController::~CPeripheralSteamController() 36 | { 37 | CSteamControllerManager::Get().Deinitialize(); 38 | } 39 | 40 | ADDON_STATUS CPeripheralSteamController::SetSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue) 41 | { 42 | return ADDON_STATUS_OK; 43 | } 44 | 45 | void CPeripheralSteamController::GetCapabilities(kodi::addon::PeripheralCapabilities& capabilities) 46 | { 47 | capabilities.SetProvidesJoysticks(true); 48 | } 49 | 50 | bool SupportsDevice(const char* device_name) 51 | { 52 | return false; 53 | } 54 | 55 | PERIPHERAL_ERROR CPeripheralSteamController::PerformDeviceScan(std::vector>& scan_results) 56 | { 57 | //! @todo Fix permission error when opening usb device 58 | #if 0 59 | CSteamControllerManager::Get().GetControllers(scan_results); 60 | #endif 61 | 62 | return PERIPHERAL_NO_ERROR; 63 | } 64 | 65 | PERIPHERAL_ERROR CPeripheralSteamController::GetEvents(std::vector& events) 66 | { 67 | //! @todo Fix bug causing infinite loop 68 | #if 0 69 | CSteamControllerManager::Get().GetEvents(events); 70 | #endif 71 | 72 | return PERIPHERAL_NO_ERROR; 73 | } 74 | 75 | bool CPeripheralSteamController::SendEvent(const kodi::addon::PeripheralEvent& event) 76 | { 77 | return CSteamControllerManager::Get().SendEvent(event); 78 | } 79 | 80 | PERIPHERAL_ERROR CPeripheralSteamController::GetJoystickInfo(unsigned int index, kodi::addon::Joystick& info) 81 | { 82 | ControllerPtr controller = CSteamControllerManager::Get().GetController(index); 83 | if (controller) 84 | { 85 | controller->GetJoystickInfo(info); 86 | return PERIPHERAL_NO_ERROR; 87 | } 88 | 89 | return PERIPHERAL_ERROR_FAILED; 90 | } 91 | 92 | PERIPHERAL_ERROR CPeripheralSteamController::GetFeatures(const kodi::addon::Joystick& joystick, 93 | const std::string& controller_id, 94 | std::vector& features) 95 | { 96 | return PERIPHERAL_ERROR_FAILED; 97 | } 98 | 99 | PERIPHERAL_ERROR CPeripheralSteamController::MapFeatures(const kodi::addon::Joystick& joystick, 100 | const std::string& controller_id, 101 | const std::vector& features) 102 | { 103 | return PERIPHERAL_ERROR_FAILED; 104 | } 105 | 106 | PERIPHERAL_ERROR CPeripheralSteamController::GetIgnoredPrimitives(const kodi::addon::Joystick& joystick, 107 | std::vector& primitives) 108 | { 109 | return PERIPHERAL_ERROR_FAILED; 110 | } 111 | 112 | PERIPHERAL_ERROR CPeripheralSteamController::SetIgnoredPrimitives(const kodi::addon::Joystick& joystick, 113 | const std::vector& primitives) 114 | { 115 | return PERIPHERAL_ERROR_FAILED; 116 | } 117 | 118 | void CPeripheralSteamController::SaveButtonMap(const kodi::addon::Joystick& joystick) 119 | { 120 | 121 | } 122 | 123 | void CPeripheralSteamController::RevertButtonMap(const kodi::addon::Joystick& joystick) 124 | { 125 | 126 | } 127 | 128 | void CPeripheralSteamController::ResetButtonMap(const kodi::addon::Joystick& joystick, const std::string& controller_id) 129 | { 130 | 131 | } 132 | 133 | void CPeripheralSteamController::PowerOffJoystick(unsigned int index) 134 | { 135 | } 136 | 137 | ADDONCREATOR(CPeripheralSteamController) // Don't touch this! 138 | -------------------------------------------------------------------------------- /src/usb/USBDeviceHandle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "USBTypes.h" 12 | #include "usb/interfaces/ITransferCallback.h" 13 | #include "usb/interfaces/ITransferObserver.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct libusb_device_handle; 21 | 22 | namespace STEAMCONTROLLER 23 | { 24 | class CUSBContext; 25 | class IMessageCallback; 26 | 27 | /*! 28 | * \brief Represents an opened USB device 29 | */ 30 | class CUSBDeviceHandle : public ITransferObserver, 31 | public ITransferCallback 32 | { 33 | public: 34 | CUSBDeviceHandle(libusb_device_handle* handle, CUSBContext& context); 35 | 36 | ~CUSBDeviceHandle(); 37 | 38 | /*! 39 | * \brief Close this handle 40 | * 41 | * This method cancels any in-flight transfer when it is called. As 42 | * cancellation is not immediate, this method needs to let libusb handle 43 | * events until transfers are actually cancelled. 44 | * 45 | * In multi-threaded programs, this can lead to stalls. To avoid this, 46 | * do not close nor let GC collect a USBDeviceHandle which has in-flight 47 | * transfers. 48 | */ 49 | void Close(); 50 | 51 | // implementation of ITransferObserver 52 | void BeforeSubmit(CUSBTransfer* transfer) override; 53 | void AfterCompletion(CUSBTransfer* transfer) override; 54 | 55 | // implementation of ITransferCallback 56 | void OnTransferComplete(CUSBTransfer* transfer) override; 57 | 58 | /*! 59 | * \brief Claim (get exclusive access to) given interface number 60 | * 61 | * Required to receive/send data. 62 | */ 63 | bool ClaimInterface(unsigned int interfaceNumber); 64 | 65 | /*! 66 | * \brief Release interface, allowing another process to use it 67 | */ 68 | void ReleaseInterface(unsigned int interfaceNumber); 69 | 70 | /*! 71 | * \brief Tell whether a kernel driver is active on given interface number 72 | */ 73 | bool IsKernelDriverActive(unsigned int interfaceNumber); 74 | 75 | /*! 76 | * \brief Ask kernel driver to detach from given interface number 77 | */ 78 | bool DetachKernelDriver(unsigned int interfaceNumber); 79 | 80 | /*! 81 | * \brief Initialize a USB transfer instance for asynchronous use 82 | * 83 | * \param endpoint The USB endpoint 84 | * \param iso_packets The number of isochronous transfer descriptors to allocate 85 | */ 86 | void InitializeTransfer(IMessageCallback* callback, uint8_t endpoint, unsigned int isoPackets = 0); 87 | 88 | /*! 89 | * \brief Return true if any transfers are in flight 90 | */ 91 | bool HasSubmittedTransfers(); 92 | 93 | /*! 94 | * \brief Synchronous control read 95 | * 96 | * \sa ControlTransfer() 97 | */ 98 | int ControlRead(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs = 0); 99 | 100 | /*! 101 | * \brief Synchronous control write 102 | * 103 | * \sa ControlTransfer() 104 | */ 105 | int ControlWrite(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs = 0); 106 | 107 | private: 108 | /*! 109 | * \brief Synchronous control transfer 110 | * 111 | * \param requestType Request byte bitmask, see constants TYPE_* and RECIPIENT_* 112 | * \param request Request ID (some values are standard) 113 | * \param value meaning is request-dependent 114 | * \param index meaning is request-dependent 115 | * \param data meaning is request-dependent 116 | * \param timeoutMs How long to wait for device acknowledgement, or 0 to disable 117 | * 118 | * \return Number of bytes actually sent 119 | */ 120 | int ControlTransfer(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs); 121 | 122 | std::map m_transfers; 123 | 124 | // In-flight transfers 125 | std::set m_inflightTransfers; 126 | 127 | // Construction parameters 128 | CUSBContext& m_context; 129 | libusb_device_handle* m_handle; 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /src/addon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2021 Garrett Brown 3 | * Copyright (C) 2015-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | /* 10 | * Derived from __init__.py of the Standalone Steam Controller Driver by ynsta 11 | * 12 | * 13 | * Copyright (c) 2015 Stany MARCEL 14 | * 15 | * Permission is hereby granted, free of charge, to any person obtaining a copy 16 | * of this software and associated documentation files (the "Software"), to deal 17 | * in the Software without restriction, including without limitation the rights 18 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | * copies of the Software, and to permit persons to whom the Software is 20 | * furnished to do so, subject to the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be included in 23 | * all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | * THE SOFTWARE. 32 | * 33 | * 34 | * Also derived from usb1.py of the python-libusb1 project 35 | * 36 | * 37 | * Copyright (C) 2010-2016 Vincent Pelletier 38 | * 39 | * This library is free software; you can redistribute it and/or 40 | * modify it under the terms of the GNU Lesser General Public 41 | * License as published by the Free Software Foundation; either 42 | * version 2.1 of the License, or (at your option) any later version. 43 | * 44 | * This library is distributed in the hope that it will be useful, 45 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 47 | * Lesser General Public License for more details. 48 | * 49 | * You should have received a copy of the GNU Lesser General Public 50 | * License along with this library; if not, write to the Free Software 51 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 52 | * 53 | */ 54 | 55 | #pragma once 56 | 57 | #include 58 | 59 | #include 60 | 61 | class ATTR_DLL_LOCAL CPeripheralSteamController : 62 | public kodi::addon::CAddonBase, 63 | public kodi::addon::CInstancePeripheral 64 | { 65 | public: 66 | CPeripheralSteamController() { } 67 | ~CPeripheralSteamController() override; 68 | 69 | ADDON_STATUS Create() override; 70 | ADDON_STATUS SetSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue) override; 71 | 72 | void GetCapabilities(kodi::addon::PeripheralCapabilities& capabilities) override; 73 | PERIPHERAL_ERROR PerformDeviceScan(std::vector>& scan_results) override; 74 | PERIPHERAL_ERROR GetEvents(std::vector& events) override; 75 | bool SendEvent(const kodi::addon::PeripheralEvent& event) override; 76 | PERIPHERAL_ERROR GetJoystickInfo(unsigned int index, kodi::addon::Joystick& info) override; 77 | PERIPHERAL_ERROR GetFeatures(const kodi::addon::Joystick& joystick, 78 | const std::string& controller_id, 79 | std::vector& features) override; 80 | PERIPHERAL_ERROR MapFeatures(const kodi::addon::Joystick& joystick, 81 | const std::string& controller_id, 82 | const std::vector& features) override; 83 | PERIPHERAL_ERROR GetIgnoredPrimitives(const kodi::addon::Joystick& joystick, 84 | std::vector& primitives) override; 85 | PERIPHERAL_ERROR SetIgnoredPrimitives(const kodi::addon::Joystick& joystick, 86 | const std::vector& primitives) override; 87 | void SaveButtonMap(const kodi::addon::Joystick& joystick) override; 88 | void RevertButtonMap(const kodi::addon::Joystick& joystick) override; 89 | void ResetButtonMap(const kodi::addon::Joystick& joystick, const std::string& controller_id) override; 90 | void PowerOffJoystick(unsigned int index) override; 91 | }; 92 | -------------------------------------------------------------------------------- /src/usb/USBTransfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBTransfer.h" 10 | #include "usb/interfaces/ITransferCallback.h" 11 | #include "usb/interfaces/ITransferObserver.h" 12 | #include "util/Log.h" 13 | 14 | #include 15 | #include 16 | 17 | using namespace STEAMCONTROLLER; 18 | 19 | CUSBTransfer::CUSBTransfer(libusb_device_handle* handle, unsigned int isoPackets, ITransferObserver* observer) : 20 | m_handle(handle), 21 | m_observer(observer), 22 | m_bInitailized(false), 23 | m_bSubmitted(false), 24 | m_bDoomed(false), 25 | m_callback(nullptr), 26 | m_transfer(libusb_alloc_transfer(isoPackets)) 27 | { 28 | assert(m_handle != nullptr); 29 | assert(m_observer != nullptr); 30 | } 31 | 32 | CUSBTransfer::~CUSBTransfer() 33 | { 34 | Cancel(); 35 | libusb_free_transfer(m_transfer); 36 | } 37 | 38 | void CUSBTransfer::Close() 39 | { 40 | if (m_bSubmitted) 41 | { 42 | esyslog("Can't close a submitted transfer"); 43 | return; 44 | } 45 | 46 | Doom(); 47 | 48 | m_bInitailized = false; 49 | m_callback = nullptr; 50 | } 51 | 52 | void CUSBTransfer::Doom() 53 | { 54 | m_bDoomed = true; 55 | } 56 | 57 | bool CUSBTransfer::SetInterrupt(uint8_t endpoint, unsigned int bufferLength, ITransferCallback* callback, unsigned int timeoutMs /* = 0 */) 58 | { 59 | std::string buffer; 60 | buffer.resize(bufferLength); 61 | return SetInterrupt(endpoint, buffer, callback, timeoutMs); 62 | } 63 | 64 | bool CUSBTransfer::SetInterrupt(uint8_t endpoint, const std::string& buffer, ITransferCallback* callback, unsigned int timeoutMs /* = 0 */) 65 | { 66 | if (m_bSubmitted) 67 | { 68 | esyslog("Cannot alter a submitted transfer"); 69 | return false; 70 | } 71 | 72 | if (m_bDoomed) 73 | { 74 | esyslog("Cannot reuse a doomed transfer"); 75 | return false; 76 | } 77 | 78 | m_bInitailized = false; 79 | m_transferBuffer = buffer; 80 | m_callback = callback; 81 | 82 | // TODO 83 | uint8_t* data = reinterpret_cast(const_cast(buffer.c_str())); 84 | 85 | libusb_fill_interrupt_transfer(m_transfer, m_handle, endpoint, data, buffer.length(), transfer_cb_fn, this, timeoutMs); 86 | 87 | m_bInitailized = true; 88 | 89 | return true; 90 | } 91 | 92 | int CUSBTransfer::Status() const 93 | { 94 | return static_cast(m_transfer->status); 95 | } 96 | 97 | int CUSBTransfer::ActualLength() const 98 | { 99 | return m_transfer->actual_length; 100 | } 101 | 102 | std::vector CUSBTransfer::Buffer() const 103 | { 104 | size_t offset = 0; 105 | 106 | if (static_cast(m_transfer->type) == LIBUSB_TRANSFER_TYPE_CONTROL) 107 | { 108 | // Get the data section of a control transfer. The data does not start 109 | // until 8 bytes into the actual buffer, as the setup packet comes first. 110 | offset = libusb_control_transfer_get_data(m_transfer) - m_transfer->buffer; 111 | } 112 | 113 | std::vector buffer(m_transfer->buffer + offset, m_transfer->buffer + m_transfer->length - offset); 114 | 115 | return buffer; 116 | } 117 | 118 | bool CUSBTransfer::IsSubmitted() const 119 | { 120 | return m_bSubmitted; 121 | } 122 | 123 | bool CUSBTransfer::Submit() 124 | { 125 | if (m_bSubmitted) 126 | { 127 | esyslog("Cannot submit a submitted transfer"); 128 | return false; 129 | } 130 | 131 | if (!m_bInitailized) 132 | { 133 | esyslog("Cannot submit a transfer until it has been initialized"); 134 | return false; 135 | } 136 | 137 | if (m_bDoomed) 138 | { 139 | esyslog("Cannot submit doomed transfer"); 140 | return false; 141 | } 142 | 143 | m_observer->BeforeSubmit(this); 144 | m_bSubmitted = true; 145 | 146 | int res = libusb_submit_transfer(m_transfer); 147 | if (res < 0) 148 | { 149 | m_bSubmitted = false; 150 | m_observer->AfterCompletion(this); 151 | return false; 152 | } 153 | 154 | return true; 155 | } 156 | 157 | void CUSBTransfer::Cancel() 158 | { 159 | if (!m_bSubmitted) 160 | { 161 | // XXX: Workaround for a bug reported on libusb 1.0.8: calling 162 | // libusb_cancel_transfer on a non-submitted transfer might 163 | // trigger a segfault. 164 | return; 165 | } 166 | 167 | int res = libusb_cancel_transfer(m_transfer); 168 | if (res < 0) 169 | esyslog("Failed to cancel submitted transfer (error = %d)", res); 170 | } 171 | 172 | void CUSBTransfer::TransferCallback(libusb_transfer* transfer) 173 | { 174 | m_bSubmitted = false; 175 | m_observer->AfterCompletion(this); 176 | 177 | if (m_callback) 178 | m_callback->OnTransferComplete(this); 179 | 180 | if (m_bDoomed) 181 | Close(); 182 | } 183 | 184 | void CUSBTransfer::transfer_cb_fn(libusb_transfer* transfer) 185 | { 186 | if (transfer && transfer->user_data) 187 | static_cast(transfer->user_data)->TransferCallback(transfer); 188 | } 189 | -------------------------------------------------------------------------------- /src/usb/USBDeviceHandle.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2021 Garrett Brown 3 | * Copyright (C) 2016-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "USBDeviceHandle.h" 10 | #include "USBContext.h" 11 | #include "USBTransfer.h" 12 | #include "usb/interfaces/IMessageCallback.h" 13 | #include "util/Log.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace STEAMCONTROLLER; 21 | 22 | CUSBDeviceHandle::CUSBDeviceHandle(libusb_device_handle* handle, CUSBContext& context) : 23 | m_context(context), 24 | m_handle(handle) 25 | { 26 | } 27 | 28 | CUSBDeviceHandle::~CUSBDeviceHandle() 29 | { 30 | Close(); 31 | } 32 | 33 | void CUSBDeviceHandle::Close() 34 | { 35 | for (auto it = m_transfers.begin(); it != m_transfers.end(); ++it) 36 | it->first->Doom(); 37 | 38 | for (auto& transfer : m_inflightTransfers) 39 | transfer->Cancel(); 40 | 41 | while (!m_inflightTransfers.empty()) 42 | m_context.HandleEvents(); 43 | 44 | for (auto it = m_transfers.begin(); it != m_transfers.end(); ++it) 45 | it->first->Close(); 46 | 47 | m_transfers.clear(); 48 | 49 | libusb_close(m_handle); 50 | m_handle = nullptr; 51 | } 52 | 53 | void CUSBDeviceHandle::BeforeSubmit(CUSBTransfer* transfer) 54 | { 55 | m_inflightTransfers.insert(transfer); 56 | } 57 | 58 | void CUSBDeviceHandle::AfterCompletion(CUSBTransfer* transfer) 59 | { 60 | auto it1 = m_inflightTransfers.find(transfer); 61 | if (it1 != m_inflightTransfers.end()) 62 | m_inflightTransfers.erase(it1); 63 | 64 | for (auto it2 = m_transfers.begin(); it2 != m_transfers.end(); ++it2) 65 | { 66 | if (it2->first.get() == transfer) 67 | { 68 | m_transfers.erase(it2); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | void CUSBDeviceHandle::OnTransferComplete(CUSBTransfer* transfer) 75 | { 76 | if (transfer->Status() != LIBUSB_TRANSFER_COMPLETED) 77 | return; 78 | 79 | for (auto it = m_transfers.begin(); it != m_transfers.end(); ++it) 80 | { 81 | if (it->first.get() == transfer) 82 | { 83 | it->second->OnTransferComplete(transfer->Buffer()); 84 | break; 85 | } 86 | } 87 | 88 | transfer->Submit(); // TODO 89 | } 90 | 91 | bool CUSBDeviceHandle::ClaimInterface(unsigned int interfaceNumber) 92 | { 93 | if (m_handle == nullptr) 94 | { 95 | esyslog("Cannot claim interface on closed handle"); 96 | return false; 97 | } 98 | 99 | int res = libusb_claim_interface(m_handle, interfaceNumber); 100 | if (res < 0) 101 | { 102 | esyslog("Failed to claim interface (error = %d)", res); 103 | return false; 104 | } 105 | 106 | return true; 107 | } 108 | 109 | void CUSBDeviceHandle::ReleaseInterface(unsigned int interfaceNumber) 110 | { 111 | if (m_handle == nullptr) 112 | { 113 | esyslog("Cannot release interface on closed handle"); 114 | return; 115 | } 116 | 117 | int res = libusb_release_interface(m_handle, interfaceNumber); 118 | if (res < 0) 119 | esyslog("Failed to release interface (error = %d)", res); 120 | } 121 | 122 | bool CUSBDeviceHandle::IsKernelDriverActive(unsigned int interfaceNumber) 123 | { 124 | if (m_handle == nullptr) 125 | return false; 126 | 127 | return libusb_kernel_driver_active(m_handle, interfaceNumber) == 1; 128 | } 129 | 130 | bool CUSBDeviceHandle::DetachKernelDriver(unsigned int interfaceNumber) 131 | { 132 | if (m_handle == nullptr) 133 | { 134 | esyslog("Cannot detach kernel driver from closed handle"); 135 | return false; 136 | } 137 | 138 | int res = libusb_detach_kernel_driver(m_handle, interfaceNumber); 139 | if (res < 0) 140 | { 141 | esyslog("Failed to detach kernel driver (error = %d)", res); 142 | return false; 143 | } 144 | 145 | return true; 146 | } 147 | 148 | void CUSBDeviceHandle::InitializeTransfer(IMessageCallback* callback, uint8_t endpoint, unsigned int isoPackets /* = 0 */) 149 | { 150 | if (m_handle == nullptr) 151 | { 152 | esyslog("Cannot initialize transfer on closed handle"); 153 | return; 154 | } 155 | 156 | TransferPtr transfer = std::make_shared(m_handle, isoPackets, this); 157 | transfer->SetInterrupt(LIBUSB_ENDPOINT_IN | endpoint, 64, this); 158 | transfer->Submit(); 159 | m_transfers.insert(std::make_pair(transfer, callback)); 160 | } 161 | 162 | bool CUSBDeviceHandle::HasSubmittedTransfers() 163 | { 164 | return !m_inflightTransfers.empty(); 165 | } 166 | 167 | int CUSBDeviceHandle::ControlRead(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs) 168 | { 169 | // Read 170 | requestType = (requestType & ~LIBUSB_ENDPOINT_DIR_MASK) | LIBUSB_ENDPOINT_IN; 171 | 172 | return ControlTransfer(requestType, request, value, index, data, timeoutMs); 173 | } 174 | 175 | int CUSBDeviceHandle::ControlWrite(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs) 176 | { 177 | // Write 178 | requestType = (requestType & ~LIBUSB_ENDPOINT_DIR_MASK) | LIBUSB_ENDPOINT_OUT; 179 | 180 | return ControlTransfer(requestType, request, value, index, data, timeoutMs); 181 | } 182 | 183 | int CUSBDeviceHandle::ControlTransfer(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, const std::vector& data, unsigned int timeoutMs) 184 | { 185 | if (m_handle == nullptr) 186 | { 187 | esyslog("Cannot perform transfer on closed handle"); 188 | return -1; 189 | } 190 | 191 | return libusb_control_transfer(m_handle, requestType, request, value, index, const_cast(data.data()), data.size(), timeoutMs); 192 | } 193 | -------------------------------------------------------------------------------- /src/steamcontroller/SteamController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2021 Garrett Brown 3 | * Copyright (C) 2015-2021 Team Kodi 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "SteamController.h" 10 | #include "SteamControllerInput.h" 11 | #include "SteamControllerTranslator.h" 12 | #include "SteamControllerTypes.h" 13 | #include "steamcontroller/interfaces/IFeedbackCallback.h" 14 | #include "usb/USBConfiguration.h" 15 | #include "usb/USBDevice.h" 16 | #include "usb/USBDeviceHandle.h" 17 | #include "usb/USBInterface.h" 18 | #include "usb/USBInterfaceSetting.h" 19 | #include "usb/USBTransfer.h" 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace STEAMCONTROLLER; 29 | 30 | namespace STEAMCONTROLLER 31 | { 32 | enum class Button 33 | { 34 | RPADTOUCH, 35 | LPADTOUCH, 36 | RPAD, 37 | LPAD, 38 | RGRIP, 39 | LGRIP, 40 | START, 41 | STEAM, 42 | BACK, 43 | A, 44 | X, 45 | B, 46 | Y, 47 | LB, 48 | RB, 49 | LT, 50 | RT, 51 | }; 52 | 53 | struct ButtonMask 54 | { 55 | Button button; 56 | uint32_t mask; 57 | }; 58 | 59 | std::vector buttonMap = { 60 | { Button::RPADTOUCH, 0b00010000000000000000000000000000 }, 61 | { Button::LPADTOUCH, 0b00001000000000000000000000000000 }, 62 | { Button::RPAD, 0b00000100000000000000000000000000 }, 63 | { Button::LPAD, 0b00000010000000000000000000000000 }, // Same for stick but without LPadTouch 64 | { Button::RGRIP, 0b00000001000000000000000000000000 }, 65 | { Button::LGRIP, 0b00000000100000000000000000000000 }, 66 | { Button::START, 0b00000000010000000000000000000000 }, 67 | { Button::STEAM, 0b00000000001000000000000000000000 }, 68 | { Button::BACK, 0b00000000000100000000000000000000 }, 69 | { Button::A, 0b00000000000000001000000000000000 }, 70 | { Button::X, 0b00000000000000000100000000000000 }, 71 | { Button::B, 0b00000000000000000010000000000000 }, 72 | { Button::Y, 0b00000000000000000001000000000000 }, 73 | { Button::LB, 0b00000000000000000000100000000000 }, 74 | { Button::RB, 0b00000000000000000000010000000000 }, 75 | { Button::LT, 0b00000000000000000000001000000000 }, 76 | { Button::RT, 0b00000000000000000000000100000000 }, 77 | }; 78 | } 79 | 80 | CSteamController::CSteamController(const DevicePtr& device, unsigned int index, IFeedbackCallback* feedbackCallback) : 81 | m_device(device), 82 | m_index(index), 83 | m_feedbackCallback(feedbackCallback), 84 | m_input(new CSteamControllerInput), 85 | m_previousInput(new CSteamControllerInput), 86 | m_lastUsbTimeMs(0.0), 87 | m_period(0.0f) 88 | { 89 | assert(m_device.get() != nullptr); 90 | assert(m_feedbackCallback != nullptr); 91 | } 92 | 93 | CSteamController::~CSteamController() 94 | { 95 | Deinitialize(); 96 | } 97 | 98 | bool CSteamController::Initialize() 99 | { 100 | m_deviceHandle = m_device->Open(); 101 | 102 | if (!m_deviceHandle) 103 | return false; 104 | 105 | m_feedbackCallback->RegisterDeviceHandle(m_deviceHandle.get()); 106 | 107 | ClaimInterfaces(); 108 | 109 | m_deviceHandle->InitializeTransfer(this, GetEndpoint()); 110 | 111 | m_period = STEAM_CONTROLLER_LPERIOD; 112 | 113 | if (m_device->ProductID() == STEAM_CONTROLLER_PID_WIRED) 114 | { 115 | // TODO: Timer 116 | } 117 | 118 | m_input->Reset(); 119 | m_previousInput->Reset(); 120 | 121 | m_lastUsbTimeMs = std::chrono::duration(std::chrono::system_clock::now().time_since_epoch()).count(); 122 | 123 | return true; 124 | } 125 | 126 | void CSteamController::Deinitialize() 127 | { 128 | m_feedbackCallback->UnregisterDeviceHandle(m_deviceHandle.get()); 129 | m_deviceHandle.reset(); 130 | } 131 | 132 | void CSteamController::OnTransferComplete(const std::vector& message) 133 | { 134 | if (!m_input->Deserialize(message)) 135 | return; 136 | 137 | ProcessInput(); 138 | } 139 | 140 | void CSteamController::ClaimInterfaces() 141 | { 142 | const CUSBConfiguration& config = m_device->GetConfiguration(); 143 | 144 | for (unsigned int i = 0; i < config.InterfaceCount(); i++) 145 | { 146 | CUSBInterface interface = config.GetInterface(i); 147 | 148 | for (unsigned int j = 0; j < interface.SettingCount(); j++) 149 | { 150 | CUSBInterfaceSetting setting = interface.GetSetting(j); 151 | uint8_t interfaceNumber = setting.Number(); 152 | 153 | if (m_deviceHandle->IsKernelDriverActive(interfaceNumber)) 154 | { 155 | if (!m_deviceHandle->DetachKernelDriver(interfaceNumber)) 156 | continue; 157 | } 158 | 159 | if (setting.Class() == 3 && 160 | setting.SubClass() == 0 && 161 | setting.Protocol() == 0) 162 | { 163 | m_deviceHandle->ClaimInterface(interfaceNumber); 164 | } 165 | } 166 | } 167 | } 168 | 169 | void CSteamController::ProcessInput() 170 | { 171 | if (m_input->Status() != InputStatus::INPUT) 172 | return; 173 | 174 | // TODO 175 | uint32_t xorButtons = m_input->Buttons() ^ m_previousInput->Buttons(); 176 | uint32_t pressed = xorButtons & m_input->Buttons(); 177 | uint32_t unpressed = xorButtons & m_previousInput->Buttons(); 178 | 179 | for (auto& buttonMask : buttonMap) 180 | { 181 | if (buttonMask.mask & pressed) 182 | { 183 | // TODO: OnPress(buttonMask.button); 184 | } 185 | else if (buttonMask.mask & unpressed) 186 | { 187 | // TODO: OnUnpress(buttonMask.button); 188 | } 189 | } 190 | 191 | m_input.swap(m_previousInput); 192 | } 193 | 194 | void CSteamController::GetPeripheralInfo(kodi::addon::Peripheral& peripheralInfo) 195 | { 196 | peripheralInfo.SetType(PERIPHERAL_TYPE_JOYSTICK); 197 | peripheralInfo.SetName(STEAM_CONTROLLER_NAME); 198 | peripheralInfo.SetVendorID(m_device->VendorID()); 199 | peripheralInfo.SetProductID(m_device->ProductID()); 200 | peripheralInfo.SetIndex(m_index); 201 | } 202 | 203 | void CSteamController::GetJoystickInfo(kodi::addon::Joystick& joystickInfo) 204 | { 205 | GetPeripheralInfo(joystickInfo); 206 | 207 | joystickInfo.SetProvider(STEAM_CONTROLLER_PROVIDER); 208 | joystickInfo.SetButtonCount(0); // TODO 209 | joystickInfo.SetAxisCount(0); // TODO 210 | joystickInfo.SetMotorCount(2); 211 | } 212 | 213 | bool CSteamController::SendEvent(const kodi::addon::PeripheralEvent& event) 214 | { 215 | switch (event.Type()) 216 | { 217 | case PERIPHERAL_EVENT_TYPE_SET_MOTOR: 218 | { 219 | HapticPosition position = CSteamControllerTranslator::GetPosition(event.DriverIndex()); 220 | 221 | AddFeedback(position, event.MotorState()); 222 | 223 | return true; 224 | } 225 | default: 226 | break; 227 | } 228 | 229 | return false; 230 | } 231 | 232 | void CSteamController::AddFeedback(HapticPosition position, float magnitude) 233 | { 234 | if (magnitude < 0.0f) 235 | magnitude = 0.0f; 236 | 237 | uint16_t amplitude = std::min(static_cast(magnitude * 0xffff), static_cast(0xffff)); 238 | 239 | AddFeedback(position, amplitude); 240 | } 241 | 242 | void CSteamController::AddFeedback(HapticPosition position, 243 | uint16_t amplitude /* = 128 */, 244 | uint16_t period /* = 0 */, 245 | uint16_t count /* = 1 */) 246 | { 247 | FeedbackMessage msg; 248 | msg.byte1 = 0x8f; 249 | msg.byte2 = 0x07; 250 | msg.position = static_cast(position); 251 | msg.amplitude = amplitude; 252 | msg.amplitude = amplitude; 253 | msg.count = count; 254 | 255 | const uint8_t* data = reinterpret_cast(&msg); 256 | 257 | std::vector message(data, data + sizeof(msg)); 258 | 259 | m_feedbackCallback->AddMessage(this, std::move(message)); 260 | } 261 | 262 | void CSteamController::SendControl(const std::vector& data, unsigned int timeout /* = 0 */) 263 | { 264 | SendControl(reinterpret_cast(data.data()), data.size(), timeout); 265 | } 266 | 267 | void CSteamController::SendControl(const uint8_t* data, size_t length, unsigned int timeout /* = 0 */) 268 | { 269 | std::vector buffer(64, 0); 270 | std::memcpy(const_cast(buffer.data()), data, std::min(length, buffer.size())); 271 | 272 | const uint8_t requestType = 0x21; 273 | const uint8_t request = 0x09; 274 | const uint16_t value = 0x0300; 275 | const uint16_t index = GetControlIdx(); 276 | 277 | int transfered = m_deviceHandle->ControlWrite(requestType, request, value, index, buffer, timeout); 278 | } 279 | 280 | uint8_t CSteamController::GetEndpoint() const 281 | { 282 | switch (m_device->ProductID()) 283 | { 284 | case STEAM_CONTROLLER_PID_WIRELESS: return STEAM_CONTROLLER_ENDPOINT_WIRELESS; 285 | case STEAM_CONTROLLER_PID_WIRED: return STEAM_CONTROLLER_ENDPOINT_WIRED; 286 | default: 287 | break; 288 | } 289 | 290 | return 0; 291 | } 292 | 293 | uint8_t CSteamController::GetControlIdx() const 294 | { 295 | switch (m_device->ProductID()) 296 | { 297 | case STEAM_CONTROLLER_PID_WIRELESS: return STEAM_CONTROLLER_CONTROLIDX_WIRELESS; 298 | case STEAM_CONTROLLER_PID_WIRED: return STEAM_CONTROLLER_CONTROLIDX_WIRED; 299 | default: 300 | break; 301 | } 302 | 303 | return 0; 304 | } 305 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The GNU General Public License, Version 2, June 1991 (GPLv2) 2 | ============================================================ 3 | 4 | > Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your freedom to share 15 | and change it. By contrast, the GNU General Public License is intended to 16 | guarantee your freedom to share and change free software--to make sure the 17 | software is free for all its users. This General Public License applies to most 18 | of the Free Software Foundation's software and to any other program whose 19 | authors commit to using it. (Some other Free Software Foundation software is 20 | covered by the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our 24 | General Public Licenses are designed to make sure that you have the freedom to 25 | distribute copies of free software (and charge for this service if you wish), 26 | that you receive source code or can get it if you want it, that you can change 27 | the software or use pieces of it in new free programs; and that you know you can 28 | do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid anyone to deny 31 | you these rights or to ask you to surrender the rights. These restrictions 32 | translate to certain responsibilities for you if you distribute copies of the 33 | software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a 36 | fee, you must give the recipients all the rights that you have. You must make 37 | sure that they, too, receive or can get the source code. And you must show them 38 | these terms so they know their rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and (2) offer 41 | you this license which gives you legal permission to copy, distribute and/or 42 | modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain that 45 | everyone understands that there is no warranty for this free software. If the 46 | software is modified by someone else and passed on, we want its recipients to 47 | know that what they have is not the original, so that any problems introduced by 48 | others will not reflect on the original authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software patents. We wish 51 | to avoid the danger that redistributors of a free program will individually 52 | obtain patent licenses, in effect making the program proprietary. To prevent 53 | this, we have made it clear that any patent must be licensed for everyone's free 54 | use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and modification 57 | follow. 58 | 59 | 60 | Terms And Conditions For Copying, Distribution And Modification 61 | --------------------------------------------------------------- 62 | 63 | **0.** This License applies to any program or other work which contains a notice 64 | placed by the copyright holder saying it may be distributed under the terms of 65 | this General Public License. The "Program", below, refers to any such program or 66 | work, and a "work based on the Program" means either the Program or any 67 | derivative work under copyright law: that is to say, a work containing the 68 | Program or a portion of it, either verbatim or with modifications and/or 69 | translated into another language. (Hereinafter, translation is included without 70 | limitation in the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not covered by 73 | this License; they are outside its scope. The act of running the Program is not 74 | restricted, and the output from the Program is covered only if its contents 75 | constitute a work based on the Program (independent of having been made by 76 | running the Program). Whether that is true depends on what the Program does. 77 | 78 | **1.** You may copy and distribute verbatim copies of the Program's source code 79 | as you receive it, in any medium, provided that you conspicuously and 80 | appropriately publish on each copy an appropriate copyright notice and 81 | disclaimer of warranty; keep intact all the notices that refer to this License 82 | and to the absence of any warranty; and give any other recipients of the Program 83 | a copy of this License along with the Program. 84 | 85 | You may charge a fee for the physical act of transferring a copy, and you may at 86 | your option offer warranty protection in exchange for a fee. 87 | 88 | **2.** You may modify your copy or copies of the Program or any portion of it, 89 | thus forming a work based on the Program, and copy and distribute such 90 | modifications or work under the terms of Section 1 above, provided that you also 91 | meet all of these conditions: 92 | 93 | * **a)** You must cause the modified files to carry prominent notices stating 94 | that you changed the files and the date of any change. 95 | 96 | * **b)** You must cause any work that you distribute or publish, that in whole 97 | or in part contains or is derived from the Program or any part thereof, to 98 | be licensed as a whole at no charge to all third parties under the terms of 99 | this License. 100 | 101 | * **c)** If the modified program normally reads commands interactively when 102 | run, you must cause it, when started running for such interactive use in the 103 | most ordinary way, to print or display an announcement including an 104 | appropriate copyright notice and a notice that there is no warranty (or 105 | else, saying that you provide a warranty) and that users may redistribute 106 | the program under these conditions, and telling the user how to view a copy 107 | of this License. (Exception: if the Program itself is interactive but does 108 | not normally print such an announcement, your work based on the Program is 109 | not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If identifiable 112 | sections of that work are not derived from the Program, and can be reasonably 113 | considered independent and separate works in themselves, then this License, and 114 | its terms, do not apply to those sections when you distribute them as separate 115 | works. But when you distribute the same sections as part of a whole which is a 116 | work based on the Program, the distribution of the whole must be on the terms of 117 | this License, whose permissions for other licensees extend to the entire whole, 118 | and thus to each and every part regardless of who wrote it. 119 | 120 | Thus, it is not the intent of this section to claim rights or contest your 121 | rights to work written entirely by you; rather, the intent is to exercise the 122 | right to control the distribution of derivative or collective works based on the 123 | Program. 124 | 125 | In addition, mere aggregation of another work not based on the Program with the 126 | Program (or with a work based on the Program) on a volume of a storage or 127 | distribution medium does not bring the other work under the scope of this 128 | License. 129 | 130 | **3.** You may copy and distribute the Program (or a work based on it, under 131 | Section 2) in object code or executable form under the terms of Sections 1 and 2 132 | above provided that you also do one of the following: 133 | 134 | * **a)** Accompany it with the complete corresponding machine-readable source 135 | code, which must be distributed under the terms of Sections 1 and 2 above on 136 | a medium customarily used for software interchange; or, 137 | 138 | * **b)** Accompany it with a written offer, valid for at least three years, to 139 | give any third party, for a charge no more than your cost of physically 140 | performing source distribution, a complete machine-readable copy of the 141 | corresponding source code, to be distributed under the terms of Sections 1 142 | and 2 above on a medium customarily used for software interchange; or, 143 | 144 | * **c)** Accompany it with the information you received as to the offer to 145 | distribute corresponding source code. (This alternative is allowed only for 146 | noncommercial distribution and only if you received the program in object 147 | code or executable form with such an offer, in accord with Subsection b 148 | above.) 149 | 150 | The source code for a work means the preferred form of the work for making 151 | modifications to it. For an executable work, complete source code means all the 152 | source code for all modules it contains, plus any associated interface 153 | definition files, plus the scripts used to control compilation and installation 154 | of the executable. However, as a special exception, the source code distributed 155 | need not include anything that is normally distributed (in either source or 156 | binary form) with the major components (compiler, kernel, and so on) of the 157 | operating system on which the executable runs, unless that component itself 158 | accompanies the executable. 159 | 160 | If distribution of executable or object code is made by offering access to copy 161 | from a designated place, then offering equivalent access to copy the source code 162 | from the same place counts as distribution of the source code, even though third 163 | parties are not compelled to copy the source along with the object code. 164 | 165 | **4.** You may not copy, modify, sublicense, or distribute the Program except as 166 | expressly provided under this License. Any attempt otherwise to copy, modify, 167 | sublicense or distribute the Program is void, and will automatically terminate 168 | your rights under this License. However, parties who have received copies, or 169 | rights, from you under this License will not have their licenses terminated so 170 | long as such parties remain in full compliance. 171 | 172 | **5.** You are not required to accept this License, since you have not signed 173 | it. However, nothing else grants you permission to modify or distribute the 174 | Program or its derivative works. These actions are prohibited by law if you do 175 | not accept this License. Therefore, by modifying or distributing the Program (or 176 | any work based on the Program), you indicate your acceptance of this License to 177 | do so, and all its terms and conditions for copying, distributing or modifying 178 | the Program or works based on it. 179 | 180 | **6.** Each time you redistribute the Program (or any work based on the 181 | Program), the recipient automatically receives a license from the original 182 | licensor to copy, distribute or modify the Program subject to these terms and 183 | conditions. You may not impose any further restrictions on the recipients' 184 | exercise of the rights granted herein. You are not responsible for enforcing 185 | compliance by third parties to this License. 186 | 187 | **7.** If, as a consequence of a court judgment or allegation of patent 188 | infringement or for any other reason (not limited to patent issues), conditions 189 | are imposed on you (whether by court order, agreement or otherwise) that 190 | contradict the conditions of this License, they do not excuse you from the 191 | conditions of this License. If you cannot distribute so as to satisfy 192 | simultaneously your obligations under this License and any other pertinent 193 | obligations, then as a consequence you may not distribute the Program at all. 194 | For example, if a patent license would not permit royalty-free redistribution of 195 | the Program by all those who receive copies directly or indirectly through you, 196 | then the only way you could satisfy both it and this License would be to refrain 197 | entirely from distribution of the Program. 198 | 199 | If any portion of this section is held invalid or unenforceable under any 200 | particular circumstance, the balance of the section is intended to apply and the 201 | section as a whole is intended to apply in other circumstances. 202 | 203 | It is not the purpose of this section to induce you to infringe any patents or 204 | other property right claims or to contest validity of any such claims; this 205 | section has the sole purpose of protecting the integrity of the free software 206 | distribution system, which is implemented by public license practices. Many 207 | people have made generous contributions to the wide range of software 208 | distributed through that system in reliance on consistent application of that 209 | system; it is up to the author/donor to decide if he or she is willing to 210 | distribute software through any other system and a licensee cannot impose that 211 | choice. 212 | 213 | This section is intended to make thoroughly clear what is believed to be a 214 | consequence of the rest of this License. 215 | 216 | **8.** If the distribution and/or use of the Program is restricted in certain 217 | countries either by patents or by copyrighted interfaces, the original copyright 218 | holder who places the Program under this License may add an explicit 219 | geographical distribution limitation excluding those countries, so that 220 | distribution is permitted only in or among countries not thus excluded. In such 221 | case, this License incorporates the limitation as if written in the body of this 222 | License. 223 | 224 | **9.** The Free Software Foundation may publish revised and/or new versions of 225 | the General Public License from time to time. Such new versions will be similar 226 | in spirit to the present version, but may differ in detail to address new 227 | problems or concerns. 228 | 229 | Each version is given a distinguishing version number. If the Program specifies 230 | a version number of this License which applies to it and "any later version", 231 | you have the option of following the terms and conditions either of that version 232 | or of any later version published by the Free Software Foundation. If the 233 | Program does not specify a version number of this License, you may choose any 234 | version ever published by the Free Software Foundation. 235 | 236 | **10.** If you wish to incorporate parts of the Program into other free programs 237 | whose distribution conditions are different, write to the author to ask for 238 | permission. For software which is copyrighted by the Free Software Foundation, 239 | write to the Free Software Foundation; we sometimes make exceptions for this. 240 | Our decision will be guided by the two goals of preserving the free status of 241 | all derivatives of our free software and of promoting the sharing and reuse of 242 | software generally. 243 | 244 | 245 | No Warranty 246 | ----------- 247 | 248 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 249 | THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 250 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 251 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 252 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 253 | PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 254 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 255 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 256 | 257 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 258 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 259 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 260 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR 261 | INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA 262 | BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 263 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER 264 | OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 265 | --------------------------------------------------------------------------------