diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b5350407c..551ad0976c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: libmad0-dev libpulse-dev libudev-dev + libusb-dev libxinerama-dev libx11-dev libxrandr-dev diff --git a/CMake/Modules/FindLibusb.cmake b/CMake/Modules/FindLibusb.cmake new file mode 100644 index 0000000000..6a9190cf94 --- /dev/null +++ b/CMake/Modules/FindLibusb.cmake @@ -0,0 +1,105 @@ +# - Find libusb for portable USB support +# This module will find libusb as published by +# http://libusb.sf.net and +# http://libusb-win32.sf.net +# +# It will use PkgConfig if present and supported, else search +# it on its own. If the LibUSB_ROOT_DIR environment variable +# is defined, it will be used as base path. +# The following standard variables get defined: +# LIBUSB_FOUND: true if LibUSB was found +# LIBUSB_INCLUDE_DIRS: the directory that contains the include file +# LIBUSB_LIBRARIES: the library +if( WIN32 ) + return() +endif() + +include ( CheckLibraryExists ) +include ( CheckIncludeFile ) + +find_package ( PkgConfig ) +if ( PKG_CONFIG_FOUND ) + pkg_check_modules ( PKGCONFIG_LIBUSB libusb ) +endif ( PKG_CONFIG_FOUND ) + +if ( PKGCONFIG_LIBUSB_FOUND ) + set ( LIBUSB_FOUND ${PKGCONFIG_LIBUSB_FOUND} ) + set ( LIBUSB_INCLUDE_DIRS ${PKGCONFIG_LIBUSB_INCLUDE_DIRS} ) + foreach ( i ${PKGCONFIG_LIBUSB_LIBRARIES} ) + find_library ( ${i}_LIBRARY + NAMES ${i} + PATHS ${PKGCONFIG_LIBUSB_LIBRARY_DIRS} + ) + if ( ${i}_LIBRARY ) + list ( APPEND LIBUSB_LIBRARIES ${${i}_LIBRARY} ) + endif ( ${i}_LIBRARY ) + mark_as_advanced ( ${i}_LIBRARY ) + endforeach ( i ) + +else ( PKGCONFIG_LIBUSB_FOUND ) + find_path ( LIBUSB_INCLUDE_DIRS + NAMES + usb.h + PATHS + $ENV{ProgramFiles}/LibUSB-Win32 + $ENV{LibUSB_ROOT_DIR} + PATH_SUFFIXES + include + ) + mark_as_advanced ( LIBUSB_INCLUDE_DIRS ) +# message ( STATUS "LibUSB include dir: ${LIBUSB_INCLUDE_DIRS}" ) + + if ( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + # LibUSB-Win32 binary distribution contains several libs. + # Use the lib that got compiled with the same compiler. + if ( MSVC ) + if ( WIN32 ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/msvc ) + else ( WIN32 ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/msvc_x64 ) + endif ( WIN32 ) + elseif ( BORLAND ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/bcc ) + elseif ( CMAKE_COMPILER_IS_GNUCC ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/gcc ) + endif ( MSVC ) + endif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + + find_library ( usb_LIBRARY + NAMES + libusb usb + PATHS + $ENV{ProgramFiles}/LibUSB-Win32 + $ENV{LibUSB_ROOT_DIR} + PATH_SUFFIXES + ${LibUSB_LIBRARY_PATH_SUFFIX} + ) + mark_as_advanced ( usb_LIBRARY ) + if ( usb_LIBRARY ) + set ( LIBUSB_LIBRARIES ${usb_LIBRARY} ) + endif ( usb_LIBRARY ) + + if ( LIBUSB_INCLUDE_DIRS AND LIBUSB_LIBRARIES ) + set ( LIBUSB_FOUND true ) + endif ( LIBUSB_INCLUDE_DIRS AND LIBUSB_LIBRARIES ) +endif ( PKGCONFIG_LIBUSB_FOUND ) + +if ( LIBUSB_FOUND ) + set ( CMAKE_REQUIRED_INCLUDES "${LIBUSB_INCLUDE_DIRS}" ) + check_include_file ( usb.h LIBUSB_FOUND ) +# message ( STATUS "LibUSB: usb.h is usable: ${LIBUSB_FOUND}" ) +endif ( LIBUSB_FOUND ) +if ( LIBUSB_FOUND ) + check_library_exists ( "${LIBUSB_LIBRARIES}" usb_open "" LIBUSB_FOUND ) +# message ( STATUS "LibUSB: library is usable: ${LIBUSB_FOUND}" ) +endif ( LIBUSB_FOUND ) + +if ( NOT LIBUSB_FOUND ) + if ( NOT LibUSB_FIND_QUIETLY ) + message ( STATUS "LibUSB not found, try setting LibUSB_ROOT_DIR environment variable." ) + endif ( NOT LibUSB_FIND_QUIETLY ) + if ( LibUSB_FIND_REQUIRED ) + message ( FATAL_ERROR "" ) + endif ( LibUSB_FIND_REQUIRED ) +endif ( NOT LIBUSB_FOUND ) +#message ( STATUS "LibUSB: ${LIBUSB_FOUND}" ) \ No newline at end of file diff --git a/StepmaniaCore.cmake b/StepmaniaCore.cmake index e656e6350e..c14804dfd5 100644 --- a/StepmaniaCore.cmake +++ b/StepmaniaCore.cmake @@ -370,6 +370,11 @@ elseif(LINUX) set(OpenGL_GL_PREFERENCE GLVND) find_package(OpenGL REQUIRED) + + find_package(Libusb) + if(NOT LIBUSB_FOUND) + message(FATAL_ERROR "libusb was not found.") + endif() endif(WIN32) # LINUX, APPLE configure_file("${SM_SRC_DIR}/config.in.hpp" diff --git a/src/CMakeData-arch.cmake b/src/CMakeData-arch.cmake index 92be2262fe..2dffeffb20 100644 --- a/src/CMakeData-arch.cmake +++ b/src/CMakeData-arch.cmake @@ -209,6 +209,7 @@ if(NOT APPLE) "arch/Lights/LightsDriver_Linux_PIUIOBTN_Leds.cpp" "arch/Lights/LightsDriver_Linux_ITGIO.cpp" "arch/Lights/LightsDriver_Linux_stac.cpp" + "arch/Lights/LightsDriver_Linux_PacDrive.cpp" "arch/Lights/LightsDriver_LinuxMinimaid.cpp" "arch/Lights/LightsDriver_LinuxWeedTech.cpp") list(APPEND SMDATA_ARCH_LIGHTS_HPP @@ -218,6 +219,7 @@ if(NOT APPLE) "arch/Lights/LightsDriver_Linux_PIUIOBTN_Leds.h" "arch/Lights/LightsDriver_Linux_ITGIO.h" "arch/Lights/LightsDriver_Linux_stac.h" + "arch/Lights/LightsDriver_Linux_PacDrive.h" "arch/Lights/LightsDriver_LinuxMinimaid.h" "arch/Lights/LightsDriver_LinuxWeedTech.h") if(WITH_PARALLEL_PORT) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 145565f638..14ae5f1f0e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -441,6 +441,8 @@ else() # Unix / Linux TODO: Remember to find and locate the zip archive files. list(APPEND SMDATA_LINK_LIB ${LIBXTST_LIBRARY}) endif() + list(APPEND SMDATA_LINK_LIB ${LIBUSB_LIBRARIES}) + list(APPEND SMDATA_LINK_LIB ${XRANDR_LIBRARIES} ${XINERAMA_LIBRARIES}) list(REMOVE_DUPLICATES SMDATA_LINK_LIB) @@ -466,6 +468,8 @@ else() if(X11_FOUND) list(APPEND SM_INCLUDE_DIRS "${X11_INCLUDE_DIR}") endif() + + list(APPEND SM_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}") endif() endif() diff --git a/src/arch/Lights/LightsDriver_Linux_PacDrive.cpp b/src/arch/Lights/LightsDriver_Linux_PacDrive.cpp new file mode 100644 index 0000000000..76042badf5 --- /dev/null +++ b/src/arch/Lights/LightsDriver_Linux_PacDrive.cpp @@ -0,0 +1,234 @@ +// Cross-platform, libusb-based driver for outputting lights +// via a PacDrive (http://www.ultimarc.com/pacdrive.html) + +#include "global.h" +#include "RageLog.h" +#include "LightsDriver_Linux_PacDrive.h" + +extern "C" { +#include +} + +REGISTER_LIGHTS_DRIVER_CLASS( Linux_PacDrive ); + +#define USB_DIR_OUT 0x00 +#define USB_DIR_IN 0x80 + +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#define HID_GET_REPORT 0x01 +#define HID_SET_REPORT 0x09 +#define HID_IFACE_IN 256 +#define HID_IFACE_OUT 512 + + +/* PacDrives have PIDs 1500 - 1507, but we'll handle that later. */ +const int PACDRIVE_VENDOR_ID = 0xD209; +const int PACDRIVE_PRODUCT_ID = 0x1500; + +/* I/O request timeout, in microseconds (so, 10 ms) */ +const unsigned PACDRIVE_TIMEOUT = 10000; + +/* static struct to ensure the USB subsystem is initialized on start */ +struct USBInit +{ + USBInit() { usb_init(); usb_find_busses(); usb_find_devices(); } +}; + +static struct USBInit g_USBInit; + +LightsDriver_Linux_PacDrive::LightsDriver_Linux_PacDrive() +{ + Device = NULL; + DeviceHandle = NULL; + + FindDevice(); + OpenDevice(); + + // clear all lights + WriteDevice( 0 ); +} + +LightsDriver_Linux_PacDrive::~LightsDriver_Linux_PacDrive() +{ + // clear all lights and close the connection + WriteDevice( 0 ); + CloseDevice(); +} + +void LightsDriver_Linux_PacDrive::Set( const LightsState *ls ) +{ + if ( !DeviceHandle ) return; + + uint16_t outb = 0; + + // This is original order from openitg-beta-2 + if (ls->m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT]) outb|=BIT(0); + if (ls->m_bCabinetLights[LIGHT_MARQUEE_UP_RIGHT]) outb|=BIT(1); + if (ls->m_bCabinetLights[LIGHT_MARQUEE_LR_LEFT]) outb|=BIT(2); + if (ls->m_bCabinetLights[LIGHT_MARQUEE_LR_RIGHT]) outb|=BIT(3); + + if (ls->m_bGameButtonLights[GameController_1][GAME_BUTTON_START]) outb|=BIT(4); + if (ls->m_bGameButtonLights[GameController_2][GAME_BUTTON_START]) outb|=BIT(5); + + if (ls->m_bCabinetLights[LIGHT_BASS_LEFT]) outb|=BIT(6); + if (ls->m_bCabinetLights[LIGHT_BASS_RIGHT]) outb|=BIT(7); + + if (ls->m_bGameButtonLights[GameController_1][DANCE_BUTTON_LEFT]) outb|=BIT(8); + if (ls->m_bGameButtonLights[GameController_1][DANCE_BUTTON_RIGHT]) outb|=BIT(9); + if (ls->m_bGameButtonLights[GameController_1][DANCE_BUTTON_UP]) outb|=BIT(10); + if (ls->m_bGameButtonLights[GameController_1][DANCE_BUTTON_DOWN]) outb|=BIT(11); + if (ls->m_bGameButtonLights[GameController_2][DANCE_BUTTON_LEFT]) outb|=BIT(12); + if (ls->m_bGameButtonLights[GameController_2][DANCE_BUTTON_RIGHT]) outb|=BIT(13); + if (ls->m_bGameButtonLights[GameController_2][DANCE_BUTTON_UP]) outb|=BIT(14); + if (ls->m_bGameButtonLights[GameController_2][DANCE_BUTTON_DOWN]) outb|=BIT(15); + + WriteDevice(outb); +} + +void LightsDriver_Linux_PacDrive::FindDevice() +{ + if ( usb_find_busses() < 0 ) + { + LOG->Warn( "libusb: usb_find_busses: %s", usb_strerror() ); + return; + } + + if ( usb_find_devices() < 0 ) + { + LOG->Warn( "libusb: usb_find_devices: %s", usb_strerror() ); + return; + } + + for ( usb_bus *bus = usb_get_busses(); bus; bus = bus->next ) + for ( struct usb_device *dev = bus->devices; dev; dev = dev->next ) + if ( PACDRIVE_VENDOR_ID == dev->descriptor.idVendor && + PACDRIVE_PRODUCT_ID <= dev->descriptor.idProduct && + PACDRIVE_PRODUCT_ID + 8 > dev->descriptor.idProduct ) { + Device = dev; + LOG->Info( "PacDrive device was found vid: 0x%04x pid: 0x%04x", dev->descriptor.idVendor, dev->descriptor.idProduct ); + return; + } + + LOG->Warn( "PacDrive was not found!" ); + Device = NULL; +} + +void LightsDriver_Linux_PacDrive::OpenDevice() +{ + CloseDevice(); + + if ( !Device ) return; + + DeviceHandle = usb_open( Device ); + + if ( DeviceHandle == NULL ) { + LOG->Warn( "libusb: usb_open: %s", usb_strerror() ); + return; + } + + // The device may be claimed by a kernel driver. Attempt to reclaim it. + for ( unsigned iface = 0; iface < Device->config->bNumInterfaces; iface++ ) + { + int result = usb_detach_kernel_driver_np( DeviceHandle, iface ); + + // device doesn't understand message, no attached driver, no error -- ignore these + if( result == -EINVAL || result == -ENODATA || result == 0 ) + continue; + + /* we have an error we can't handle; try and get more info. */ + LOG->Warn( "usb_detach_kernel_driver_np: %s\n", usb_strerror() ); + + // on EPERM, a driver exists and we can't detach - report which one + if ( result == -EPERM ) + { + char szDriverName[16]; + strcpy( szDriverName, "(unknown)" ); + usb_get_driver_np(DeviceHandle, iface, szDriverName, 16); + + LOG->Warn( "(cannot detach kernel driver \"%s\")", szDriverName ); + } + + CloseDevice(); + return; + } + + if ( usb_set_configuration( DeviceHandle, Device->config->bConfigurationValue) ) { + LOG->Warn( "libusb: usb_set_configuration: %s", usb_strerror() ); + CloseDevice(); + return; + } + + // attempt to claim all interfaces for this device + for ( unsigned i = 0; i < Device->config->bNumInterfaces; i++ ) + { + if ( usb_claim_interface( DeviceHandle, i ) ) { + LOG->Warn( "Libusb: usb_claim_interface(%i): %s", i, usb_strerror() ); + CloseDevice(); + return; + } + } +} + +void LightsDriver_Linux_PacDrive::WriteDevice(uint16_t out) +{ + if ( !DeviceHandle ) return; + + // output is within the first 16 bits - accept a + // 16-bit arg and cast it, for simplicity's sake. + uint32_t data = (out << 16); + int expected = sizeof(data); + + int result = usb_control_msg( DeviceHandle, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + HID_SET_REPORT, HID_IFACE_OUT, 0, (char *)&data, expected, + PACDRIVE_TIMEOUT ); + + if( result != expected ) { + LOG->Warn( "PacDrive writing failed: %i (%s)\n", result, usb_strerror() ); + CloseDevice(); + } +} + +void LightsDriver_Linux_PacDrive::CloseDevice() +{ + if ( !DeviceHandle ) + return; + + usb_set_altinterface( DeviceHandle, 0 ); + usb_reset( DeviceHandle ); + usb_close( DeviceHandle ); + DeviceHandle = NULL; +} + +/* + * Copyright (c) 2008 BoXoRRoXoRs + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ \ No newline at end of file diff --git a/src/arch/Lights/LightsDriver_Linux_PacDrive.h b/src/arch/Lights/LightsDriver_Linux_PacDrive.h new file mode 100644 index 0000000000..92bab9e189 --- /dev/null +++ b/src/arch/Lights/LightsDriver_Linux_PacDrive.h @@ -0,0 +1,55 @@ +#ifndef LIGHTSDRIVER_LINUX_PACDRIVE_H +#define LIGHTSDRIVER_LINUX_PACDRIVE_H + +#include "LightsDriver.h" + +extern "C" { +#include +} + +#define BIT(i) (1<<(i)) + +class LightsDriver_Linux_PacDrive: public LightsDriver +{ +public: + LightsDriver_Linux_PacDrive(); + ~LightsDriver_Linux_PacDrive(); + + void Set( const LightsState *ls ); +private: + + void FindDevice(); + void OpenDevice(); + void WriteDevice(uint16_t out); + void CloseDevice(); + + struct usb_device *Device; + usb_dev_handle *DeviceHandle; +}; + +#endif // LIGHTSDRIVER_LINUX_PACDRIVE_H + +/* + * Copyright (c) 2008 BoXoRRoXoRs + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ \ No newline at end of file