diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/CMakeLists.txt b/CMakeLists.txt index c668c3a..ed0ad30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,14 @@ -project(moonlight-embedded C) cmake_minimum_required(VERSION 3.1) +project(moonlight-embedded VERSION 2.4.0 LANGUAGES C) SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(${CMAKE_ROOT}/Modules/GNUInstallDirs.cmake) -set(MOONLIGHT_MAJOR_VERSION 2) -set(MOONLIGHT_MINOR_VERSION 3) -set(MOONLIGHT_PATCH_VERSION 0) -set(MOONLIGHT_VERSION ${MOONLIGHT_MAJOR_VERSION}.${MOONLIGHT_MINOR_VERSION}.${MOONLIGHT_PATCH_VERSION}) - aux_source_directory(./src SRC_LIST) -aux_source_directory(./src/input SRC_LIST) +list(APPEND SRC_LIST ./src/input/evdev.c ./src/input/mapping.c ./src/input/udev.c) set(MOONLIGHT_DEFINITIONS) -find_package(ALSA REQUIRED) +find_package(ALSA) find_package(Opus REQUIRED) find_package(Broadcom) find_package(Freescale) @@ -25,36 +20,47 @@ pkg_check_modules(UDEV REQUIRED libudev) pkg_check_modules(SDL sdl2>=2.0.4) pkg_check_modules(AVCODEC libavcodec) pkg_check_modules(AVUTIL libavutil) -pkg_check_modules(XLIB x11-xcb) +pkg_check_modules(XLIB x11) pkg_check_modules(LIBVA vdpau) pkg_check_modules(PULSE libpulse-simple) pkg_check_modules(CEC libcec>=3.0.0) +pkg_check_modules(EGL egl) +pkg_check_modules(GLES glesv2) -if(AVCODEC_FOUND AND AVUTIL_FOUND AND SDL_FOUND) - set(SOFTWARE_FOUND TRUE) - if(XLIB_FOUND AND LIBVA_FOUND) - set(VDPAU_FOUND TRUE) - else() - set(VDPAU_FOUND FALSE) +set(VDPAU_FOUND FALSE) +set(SOFTWARE_FOUND FALSE) + +if(AVCODEC_FOUND AND AVUTIL_FOUND) + if(EGL_FOUND AND GLES_FOUND AND XLIB_FOUND) + set(X11_FOUND TRUE) + if(XLIB_FOUND AND LIBVA_FOUND) + set(VDPAU_FOUND TRUE) + endif() + endif() + if(SDL_FOUND OR X11_FOUND) + set(SOFTWARE_FOUND TRUE) endif() -else() - set(SOFTWARE_FOUND FALSE) endif() SET(MOONLIGHT_COMMON_INCLUDE_DIR ./third_party/moonlight-common-c/src) SET(GAMESTREAM_INCLUDE_DIR ./libgamestream) -if(CMAKE_BUILD_TYPE MATCHES Debug) - list(APPEND MOONLIGHT_DEFINITIONS LC_DEBUG) - list(APPEND MOONLIGHT_OPTIONS DEBUG) -elseif(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT SOFTWARE_FOUND) +if(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT SOFTWARE_FOUND) message(FATAL_ERROR "No video output available") endif() if (SOFTWARE_FOUND) - list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c ./src/audio/sdl.c) - list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) - list(APPEND MOONLIGHT_OPTIONS SDL) + list(APPEND SRC_LIST ./src/video/ffmpeg.c) + if (SDL_FOUND) + list(APPEND SRC_LIST ./src/video/sdl.c ./src/audio/sdl.c ./src/input/sdl.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) + list(APPEND MOONLIGHT_OPTIONS SDL) + endif() + if (X11_FOUND) + list(APPEND SRC_LIST ./src/video/x11.c ./src/video/egl.c ./src/input/x11.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_X11) + list(APPEND MOONLIGHT_OPTIONS X11) + endif() if(VDPAU_FOUND) list(APPEND SRC_LIST ./src/video/ffmpeg_vdpau.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_VDPAU) @@ -62,20 +68,28 @@ if (SOFTWARE_FOUND) endif() endif() -if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR CMAKE_BUILD_TYPE MATCHES Debug) +if (AMLOGIC_FOUND OR BROADCOM_FOUND OR FREESCALE_FOUND OR X11_FOUND) list(APPEND MOONLIGHT_DEFINITIONS HAVE_EMBEDDED) list(APPEND MOONLIGHT_OPTIONS EMBEDDED) endif() +if (ALSA_FOUND) + list(APPEND SRC_LIST ./src/audio/alsa.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_ALSA) + list(APPEND MOONLIGHT_OPTIONS ALSA) +endif() + if (PULSE_FOUND) list(APPEND SRC_LIST ./src/audio/pulse.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_PULSE) list(APPEND MOONLIGHT_OPTIONS PULSE) endif() -include_directories("${PROJECT_BINARY_DIR}") +if (CEC_FOUND) + list(APPEND SRC_LIST ./src/input/cec.c) +endif() -list(APPEND SRC_LIST ./src/audio/alsa.c ./src/audio/fake.c ./src/video/fake.c) +include_directories("${PROJECT_BINARY_DIR}") add_subdirectory(libgamestream) @@ -120,15 +134,30 @@ if(FREESCALE_FOUND) install(TARGETS moonlight-imx DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() +if(SDL_FOUND) + target_include_directories(moonlight PRIVATE ${SDL_INCLUDE_DIRS}) + target_link_libraries(moonlight ${SDL_LIBRARIES}) +endif() + +if(X11_FOUND) + target_include_directories(moonlight PRIVATE ${XLIB_INCLUDE_DIRS} ${EGL_INCLUDE_DIRS} ${GLES_INCLUDE_DIRS}) + target_link_libraries(moonlight ${XLIB_LIBRARIES} ${EGL_LIBRARIES} ${GLES_LIBRARIES}) +endif() + if (SOFTWARE_FOUND) - target_include_directories(moonlight PRIVATE ${SDL_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) - target_link_libraries(moonlight ${SDL_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES}) + target_include_directories(moonlight PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) + target_link_libraries(moonlight ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES}) if(VDPAU_FOUND) target_include_directories(moonlight PRIVATE ${XLIB_INCLUDE_DIRS} ${LIBVA_INCLUDE_DIRS}) target_link_libraries(moonlight ${XLIB_LIBRARIES} ${LIBVA_LIBRARIES}) endif() endif() +if (ALSA_FOUND) + target_include_directories(moonlight PRIVATE ${ALSA_INCLUDE_DIR}) + target_link_libraries(moonlight ${ALSA_LIBRARY}) +endif() + if (PULSE_FOUND) target_include_directories(moonlight PRIVATE ${PULSE_INCLUDE_DIRS}) target_link_libraries(moonlight ${PULSE_LIBRARIES}) @@ -138,10 +167,10 @@ configure_file("./src/configuration.h.in" "${PROJECT_BINARY_DIR}/configuration.h set_property(TARGET moonlight PROPERTY COMPILE_DEFINITIONS ${MOONLIGHT_DEFINITIONS}) target_include_directories(moonlight PRIVATE ${GAMESTREAM_INCLUDE_DIR} ${MOONLIGHT_COMMON_INCLUDE_DIR} ${OPUS_INCLUDE_DIRS} ${EVDEV_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS}) -target_link_libraries(moonlight ${EVDEV_LIBRARIES} ${ALSA_LIBRARY} ${OPUS_LIBRARY} ${UDEV_LIBRARIES} ${CMAKE_DL_LIBS}) +target_link_libraries(moonlight ${EVDEV_LIBRARIES} ${OPUS_LIBRARY} ${UDEV_LIBRARIES} ${CMAKE_DL_LIBS}) add_subdirectory(docs) install(TARGETS moonlight DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(DIRECTORY mappings DESTINATION ${CMAKE_INSTALL_DATADIR}/moonlight) +install(FILES gamecontrollerdb.txt DESTINATION ${CMAKE_INSTALL_DATADIR}/moonlight) install(FILES moonlight.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}) diff --git a/README.md b/README.md index 7fb13ea..f762051 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Moonlight Embedded is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for Linux. -Moonlight Embedded allows you to stream your full collection of Steam games from +Moonlight Embedded allows you to stream your full collection of games from your powerful Windows desktop to your (embedded) Linux system, like Raspberry Pi, CuBox-i and ODROID. ## Documentation @@ -11,7 +11,7 @@ More information about installing and runnning Moonlight Embedded is available o ## Requirements -* [GFE compatible](http://shield.nvidia.com/play-pc-games/) computer with GTX 600/700/900 series GPU (for the PC you're streaming from) +* [GFE compatible](http://shield.nvidia.com/play-pc-games/) computer with GTX 600/700/900/1000 series GPU (for the PC you're streaming from) * High-end wireless router (802.11n dual-band recommended) or wired network * Geforce Experience 2.1.1 or higher diff --git a/cmake/FindAmlogic.cmake b/cmake/FindAmlogic.cmake old mode 100644 new mode 100755 index eb06031..82a8fe9 --- a/cmake/FindAmlogic.cmake +++ b/cmake/FindAmlogic.cmake @@ -1,7 +1,7 @@ find_path(AMLOGIC_INCLUDE_DIR NAMES codec.h DOC "Amlogic include directory" - PATHS /usr/local/include/amcodec /usr/include/amcodec) + PATHS /usr/local/include/amcodec /usr/include/amcodec /usr/include/) mark_as_advanced(AMLOGIC_INCLUDE_DIR) find_library(AMAVUTILS_LIBRARY diff --git a/docs/README.pod b/docs/README.pod index 5da4f0a..7a6ccc3 100644 --- a/docs/README.pod +++ b/docs/README.pod @@ -11,10 +11,6 @@ Usage: I EactionE [options] [host] =over 4 -=item B - -Create a mapping file for a gamepad. - =item B Pair this computer with the host. @@ -64,6 +60,10 @@ This is the default option. Use the resolution 1920x1080 for streaming. +=item B<-4k> + +Use the resolution 3840x2160 for streaming. + =item B<-width> [I] Change the horizontal resolution to I @@ -72,22 +72,15 @@ Change the horizontal resolution to I Change the vertical resolution to I -=item B<-30fps> - -Use 30 fps for streaming. - -=item B<-60fps> - -Use 60 fps for streaming. -This is the default configuration. - =item B<-fps> [I] -Change the number of frame per second to I +Change the number of frame per second to I. +Defaults to 60fps for 720p and 30fps for 1080p and higher. +Only 30 and 60 fps are currently supported by Gamestream. =item B<-bitrate> [I] -Change bitrate to I kbps. +Change bitrate to I Kbps. By default the bitrate depends on the selected resolution and fps. For resolution 1080p and 60 fps and higher 20 Mbps is used. For resolution 1080p or 60 fps and higher 10 Mbps is used @@ -95,18 +88,17 @@ For other configurations 5 Mbps is used by default. =item B<-packetsize> [I] -Change the network packetsize to I. +Change the network packetsize to I bytes. The packetsize should the smaller than the MTU of the network. +This value must be a multiply of 16. By default a safe value of 1024 is used. -=item B<-hevc> +=item B<-codec> [I] -Request a h265/HEVC from the server. -Will still use h264 if server doesn't support HEVC. - -=item B<-h264> - -Request a h264 from the server even if server and video decoder supports HEVC. +Select codec to use. +Can be 'auto', 'h264', 'h265' or 'hevc'. +Not all video decoders do support H.265/HEVC. +Will still use H.264 if server doesn't support HEVC. =item B<-remote> @@ -123,7 +115,11 @@ Stop GFE from changing the graphical settings of the requested game or applicati =item B<-localaudio> -Play the audio on the host instead of this computer. +Play the audio on the host computer instead of this device. + +=item B<-surround> + +Enable 5.1 surround sound instead of stereo. =item B<-keydir> [I] @@ -132,21 +128,43 @@ By default the encryption keys are stored in $XDG_CACHE_DIR/moonlight or ~/.cach =item B<-mapping> [I] -Use I as the mapping file for all inputs specified after this B<-mapping>. -If no B<-input> is specified after the B<-mapping> this mapping is used for autoloaded inputs. +Use I as the mapping file for all inputs. +This mapping file should have the same format as the gamecontrollerdb.txt for SDL2. +By default the gamecontrollerdb.txt provided by Moonlight Embedded is used. + +=item B<-platform> [I] + +Select platform for audio and video output and input. + can be pi, imx, aml, x11, x11_vdpau, sdl or fake. + +=item B<-unsupported> + +Try streaming if GFE version is unsupported + +=item B<-verbose> + +Enable verbose output + +=item B<-debug> + +Enable verbose and debug output =item B<-input> [I] Enable the I device. By default all available input devices are enabled. Only evdev devices /dev/input/event* are supported. -To use a different gamepad mapping then the default the B<-mapping> should be specified before the B<-input>. =item B<-audio> [I] Use as audio output device. The default value is 'sysdefault' for ALSA and 'hdmi' for OMX on the Raspberry Pi. +=item B<-windowed> + +Display the stream in a window instead of fullscreen. +Only available when X11 or SDL platform is used. + =back =head1 CONFIG FILE @@ -163,7 +181,7 @@ A documented example configuration file can be found at /etc/moonlight/moonlight =head1 COMMENTS -Use Ctrl+Alt+Shift+Q to quit the streaming session. +Use Ctrl+Alt+Shift+Q or Play+Back+LeftShoulder+RightShoulder to quit the streaming session. =head1 AUTHOR diff --git a/gamecontrollerdb.txt b/gamecontrollerdb.txt new file mode 100644 index 0000000..0ff8bb1 --- /dev/null +++ b/gamecontrollerdb.txt @@ -0,0 +1,158 @@ +03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3, +05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3, +05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3, +03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3, +03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0, +03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3, +03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3, +030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3, +03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3, +03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3, +050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3, +050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2, +030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3, +05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, +030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12, +050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12, +030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3, +030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3, +050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3, +0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1, +03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3, +050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4, +05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4, +03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000016c2000011010000,Logitech F310 Gamepad (DInput),x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux, +030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, +050000004c050000c405000000010000,Sony DualShock 4 BT,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011010000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, +050000004c050000cc09000000010000,Sony DualShock 4 V2 BT,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, +030000004c050000a00b000011010000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, +030000006f0e00003001000001010000,EA Sports PS3 Controller,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux, +03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, +030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5, +030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux, +030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux, +030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4, +030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7, +0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, +0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, +030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1, +030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000005e0400008502000000010000,Microsoft X-Box pad (Japan),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, +03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5, +030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, +03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux, +050000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux, +03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick ,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a3,rightx:a1,righty:a4, +03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13, +05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, +05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, +030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000005e040000dd02000003020000,Microsoft X-Box One pad v2,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux, +03000000790000001100000010010000,RetroLink Saturn Classic Controller,platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, +050000007e0500003003000001000000,Nintendo Wii U Pro Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,start:b9,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16, +030000005e0400008e02000004010000,Microsoft X-Box 360 pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, +030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1, +030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7 +03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3, +03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000006f0e00001304000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:a0,rightstick:a3,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, +03000000bd12000015d0000010010000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, +03000000790000001100000010010000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, +03000000c9110000f055000011010000,HJC Game GAMEPAD,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b11,rightshoulder:b5,rightx:a2,start:b9,righty:a3,dpleft:h0.8,lefttrigger:b6,x:b2,dpup:h0.1,back:b8,leftstick:b10,leftshoulder:b4,y:b3,a:b0,dpright:h0.2,righttrigger:b7,b:b1,platform:Linux, +03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux, +03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,y:b3,x:b0,start:b9,guide:,back:,leftstick:,rightstick:,leftshoulder:,dpleft:b15,dpdown:b14,dpright:b13,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,rightshoulder:b7,dpup:b12,platform:Linux, +030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b10,guide:b12,start:b11,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3, +030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,platform:Linux,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,lefttrigger:a2,righttrigger:a5,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux, +030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7, +05000000102800000900000000010000,8Bitdo SFC30 GamePad,platform:Linux,x:b4,a:b1,b:b0,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, +03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,leftx:a0,lefty:a1, +030000000d0f00000d00000000010000,hori,platform:Linux,a:b0,b:b6,y:b2,x:b1,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,start:b9,guide:b10,back:b8,leftshoulder:b3,rightshoulder:b7,leftx:b4,lefty:b5, +03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5, +03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,platform:Linux,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7, +03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),platform:Linux,a:b3,b:b4,y:b1,x:b0,start:b7,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5, +05000000010000000100000003000000,Nintendo Wiimote,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +030000005e0400008e02000062230000,Microsoft X-Box 360 pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,y:b1,x:b0,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000006f0e00000103000000020000,Logic3 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +05000000380700006652000025010000,Mad Catz C.T.R.L.R ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:a2,righttrigger:a5, +05000000a00500003232000001000000,8Bitdo Zero GamePad,platform:Linux,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, +030000001008000001e5000010010000,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Linux, +03000000100800000300000010010000,USB Gamepad,platform:Linux,a:b2,b:b1,x:b3,y:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5, +05000000ac0500003232000001000000,VR-BOX,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5, +03000000780000000600000010010000,Microntek USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftx:a0,lefty:a1, diff --git a/libgamestream/CMakeLists.txt b/libgamestream/CMakeLists.txt index f9f9299..c6ddde5 100644 --- a/libgamestream/CMakeLists.txt +++ b/libgamestream/CMakeLists.txt @@ -1,3 +1,5 @@ +set(SO_VERSION 1) + find_package(LibUUID REQUIRED) find_package(Threads REQUIRED) find_package(CURL REQUIRED) @@ -19,8 +21,8 @@ add_library(gamestream SHARED ${GAMESTREAM_SRC_LIST}) set_property(TARGET gamestream PROPERTY C_STANDARD 99) target_link_libraries(gamestream moonlight-common) -set_target_properties(gamestream PROPERTIES SOVERSION 0 VERSION ${MOONLIGHT_VERSION}) -set_target_properties(moonlight-common PROPERTIES SOVERSION 0 VERSION ${MOONLIGHT_VERSION}) +set_target_properties(gamestream PROPERTIES SOVERSION ${SO_VERSION} VERSION ${PROJECT_VERSION}) +set_target_properties(moonlight-common PROPERTIES SOVERSION ${SO_VERSION} VERSION ${PROJECT_VERSION}) target_include_directories(gamestream PRIVATE ../third_party/moonlight-common-c/src ../third_party/h264bitstream ${AVAHI_INCLUDE_DIRS} ${LIBUUID_INCLUDE_DIRS}) target_include_directories(moonlight-common PRIVATE ../third_party/moonlight-common-c/reedsolomon ${ENET_INCLUDE_DIRS}) diff --git a/libgamestream/client.c b/libgamestream/client.c index 2d547ce..eff1d64 100644 --- a/libgamestream/client.c +++ b/libgamestream/client.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015-2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -179,7 +179,6 @@ static int load_server_status(PSERVER_DATA server) { char *pairedText = NULL; char *currentGameText = NULL; char *stateText = NULL; - char *heightText = NULL; char *serverCodecModeSupportText = NULL; ret = GS_INVALID; @@ -217,25 +216,28 @@ static int load_server_status(PSERVER_DATA server) { if (xml_search(data->memory, data->size, "state", &stateText) != GS_OK) goto cleanup; - if (xml_search(data->memory, data->size, "Height", &heightText) != GS_OK) - goto cleanup; - if (xml_search(data->memory, data->size, "ServerCodecModeSupport", &serverCodecModeSupportText) != GS_OK) goto cleanup; if (xml_search(data->memory, data->size, "gputype", &server->gpuType) != GS_OK) goto cleanup; + if (xml_search(data->memory, data->size, "GsVersion", &server->gsVersion) != GS_OK) + goto cleanup; + if (xml_search(data->memory, data->size, "GfeVersion", (char**) &server->serverInfo.serverInfoGfeVersion) != GS_OK) goto cleanup; + if (xml_modelist(data->memory, data->size, &server->modes) != GS_OK) + goto cleanup; + // These fields are present on all version of GFE that this client supports if (!strlen(currentGameText) || !strlen(pairedText) || !strlen(server->serverInfo.serverInfoAppVersion) || !strlen(stateText)) goto cleanup; server->paired = pairedText != NULL && strcmp(pairedText, "1") == 0; server->currentGame = currentGameText == NULL ? 0 : atoi(currentGameText); - server->supports4K = heightText != NULL && serverCodecModeSupportText != NULL && atoi(heightText) >= 2160; + server->supports4K = serverCodecModeSupportText != NULL; server->serverMajorVersion = atoi(server->serverInfo.serverInfoAppVersion); if (strstr(stateText, "_SERVER_AVAILABLE")) { @@ -256,9 +258,6 @@ static int load_server_status(PSERVER_DATA server) { if (currentGameText != NULL) free(currentGameText); - if (heightText != NULL) - free(heightText); - if (serverCodecModeSupportText != NULL) free(serverCodecModeSupportText); @@ -494,12 +493,15 @@ int gs_pair(PSERVER_DATA server, char* pin) { char client_secret_data[16]; RAND_bytes(client_secret_data, 16); + const ASN1_BIT_STRING *asnSignature; + X509_get0_signature(&asnSignature, NULL, cert); + char challenge_response[16 + 256 + 16]; char challenge_response_hash[32]; char challenge_response_hash_enc[32]; char challenge_response_hex[65]; memcpy(challenge_response, challenge_response_data + hash_length, 16); - memcpy(challenge_response + 16, cert->signature->data, 256); + memcpy(challenge_response + 16, asnSignature->data, 256); memcpy(challenge_response + 16 + 256, client_secret_data, 16); if (server->serverMajorVersion >= 7) SHA256(challenge_response, 16 + 256 + 16, challenge_response_hash); @@ -635,6 +637,18 @@ int gs_start_app(PSERVER_DATA server, STREAM_CONFIGURATION *config, int appId, b char* result = NULL; char uuid_str[37]; + PDISPLAY_MODE mode = server->modes; + bool correct_mode = false; + while (mode != NULL) { + if (mode->width == config->width && mode->height == config->height && mode->refresh == config->fps) + correct_mode = true; + + mode = mode->next; + } + + if (!correct_mode) + return GS_NOT_SUPPORTED_MODE; + if (config->height >= 2160 && !server->supports4K) return GS_NOT_SUPPORTED_4K; @@ -713,7 +727,7 @@ int gs_quit_app(PSERVER_DATA server) { return ret; } -int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory) { +int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory, int log_level) { mkdirtree(keyDirectory); if (load_unique_id(keyDirectory) != GS_OK) return GS_FAILED; @@ -721,7 +735,7 @@ int gs_init(PSERVER_DATA server, char *address, const char *keyDirectory) { if (load_cert(keyDirectory)) return GS_FAILED; - http_init(keyDirectory); + http_init(keyDirectory, log_level); LiInitializeServerInformation(&server->serverInfo); server->serverInfo.address = address; diff --git a/libgamestream/client.h b/libgamestream/client.h index f8b821d..ae0971d 100644 --- a/libgamestream/client.h +++ b/libgamestream/client.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,10 +35,12 @@ typedef struct _SERVER_DATA { bool supports4K; int currentGame; int serverMajorVersion; + char* gsVersion; + PDISPLAY_MODE modes; SERVER_INFORMATION serverInfo; } SERVER_DATA, *PSERVER_DATA; -int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory); +int gs_init(PSERVER_DATA server, char* address, const char *keyDirectory, int logLevel); int gs_start_app(PSERVER_DATA server, PSTREAM_CONFIGURATION config, int appId, bool sops, bool localaudio); int gs_applist(PSERVER_DATA server, PAPP_LIST *app_list); int gs_unpair(PSERVER_DATA server); diff --git a/libgamestream/errors.h b/libgamestream/errors.h index 99e3187..f657ae7 100644 --- a/libgamestream/errors.h +++ b/libgamestream/errors.h @@ -27,5 +27,6 @@ #define GS_IO_ERROR -5 #define GS_NOT_SUPPORTED_4K -6 #define GS_UNSUPPORTED_VERSION -7 +#define GS_NOT_SUPPORTED_MODE -8 const char* gs_error; diff --git a/libgamestream/http.c b/libgamestream/http.c index 757deb5..c8f9bf1 100644 --- a/libgamestream/http.c +++ b/libgamestream/http.c @@ -20,6 +20,7 @@ #include "http.h" #include "errors.h" +#include #include #include @@ -28,6 +29,8 @@ static CURL *curl; static const char *pCertFile = "./client.pem"; static const char *pKeyFile = "./key.pem"; +static bool debug; + static size_t _write_curl(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; @@ -44,8 +47,9 @@ static size_t _write_curl(void *contents, size_t size, size_t nmemb, void *userp return realsize; } -int http_init(const char* keyDirectory) { +int http_init(const char* keyDirectory, int logLevel) { curl = curl_easy_init(); + debug = logLevel >= 2; if (!curl) return GS_FAILED; @@ -73,6 +77,9 @@ int http_request(char* url, PHTTP_DATA data) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, data); curl_easy_setopt(curl, CURLOPT_URL, url); + if (debug) + printf("Request %s\n", url); + if (data->size > 0) { free(data->memory); data->memory = malloc(1); @@ -89,6 +96,9 @@ int http_request(char* url, PHTTP_DATA data) { } else if (data->memory == NULL) { return GS_OUT_OF_MEMORY; } + + if (debug) + printf("Response:\n%s\n\n", data->memory); return GS_OK; } diff --git a/libgamestream/http.h b/libgamestream/http.h index be28344..f06ace1 100644 --- a/libgamestream/http.h +++ b/libgamestream/http.h @@ -29,7 +29,7 @@ typedef struct _HTTP_DATA { size_t size; } HTTP_DATA, *PHTTP_DATA; -int http_init(const char* keyDirectory); +int http_init(const char* keyDirectory, int logLevel); PHTTP_DATA http_create_data(); int http_request(char* url, PHTTP_DATA data); void http_free_data(PHTTP_DATA data); diff --git a/libgamestream/mkcert.c b/libgamestream/mkcert.c index b0d5e5a..6dfc1c2 100644 --- a/libgamestream/mkcert.c +++ b/libgamestream/mkcert.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -42,7 +43,7 @@ CERT_KEY_PAIR mkcert_generate() { CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); - SSLeay_add_all_algorithms(); + OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS); @@ -54,7 +55,6 @@ CERT_KEY_PAIR mkcert_generate() { #endif CRYPTO_cleanup_all_ex_data(); - CRYPTO_mem_leaks(bio_err); BIO_free(bio_err); return (CERT_KEY_PAIR) {x509, pkey, p12}; @@ -103,8 +103,22 @@ int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) { } else { x = *x509p; } + + if ((rsa = RSA_new()) == NULL) + goto err; - rsa = RSA_generate_key(bits, RSA_F4, NULL, NULL); + BIGNUM* bne = BN_new(); + if (bne == NULL) { + abort(); + goto err; + } + + BN_set_word(bne, RSA_F4); + if (RSA_generate_key_ex(rsa, bits, bne, NULL) == 0) { + abort(); + goto err; + } + if (!EVP_PKEY_assign_RSA(pk, rsa)) { abort(); goto err; diff --git a/libgamestream/sps.h b/libgamestream/sps.h index 9123b6f..ed55c3f 100644 --- a/libgamestream/sps.h +++ b/libgamestream/sps.h @@ -22,5 +22,5 @@ #define GS_SPS_BITSTREAM_FIXUP 0x01 #define GS_SPS_BASELINE_HACK 0x02 -void gs_sps_init(); +void gs_sps_init(int width, int height); PLENTRY gs_sps_fix(PLENTRY *entry, int flags); diff --git a/libgamestream/xml.c b/libgamestream/xml.c index 9efa963..4e5e86e 100644 --- a/libgamestream/xml.c +++ b/libgamestream/xml.c @@ -79,6 +79,37 @@ static void XMLCALL _xml_end_applist_element(void *userData, const char *name) { } } +static void XMLCALL _xml_start_mode_element(void *userData, const char *name, const char **atts) { + struct xml_query *search = (struct xml_query*) userData; + if (strcmp("DisplayMode", name) == 0) { + PDISPLAY_MODE mode = calloc(1, sizeof(DISPLAY_MODE)); + if (mode != NULL) { + mode->next = (PDISPLAY_MODE) search->data; + search->data = mode; + } + } else if (search->data != NULL && (strcmp("Height", name) == 0 || strcmp("Width", name) == 0 || strcmp("RefreshRate", name) == 0)) { + search->memory = malloc(1); + search->size = 0; + search->start = 1; + } +} + +static void XMLCALL _xml_end_mode_element(void *userData, const char *name) { + struct xml_query *search = (struct xml_query*) userData; + if (search->data != NULL && search->start) { + PDISPLAY_MODE mode = (PDISPLAY_MODE) search->data; + if (strcmp("Width", name) == 0) + mode->width = atoi(search->memory); + else if (strcmp("Height", name) == 0) + mode->height = atoi(search->memory); + else if (strcmp("RefreshRate", name) == 0) + mode->refresh = atoi(search->memory); + + free(search->memory); + search->start = 0; + } +} + static void XMLCALL _xml_write_data(void *userData, const XML_Char *s, int len) { struct xml_query *search = (struct xml_query*) userData; if (search->start > 0) { @@ -141,3 +172,24 @@ int xml_applist(char* data, size_t len, PAPP_LIST *app_list) { return GS_OK; } + +int xml_modelist(char* data, size_t len, PDISPLAY_MODE *mode_list) { + struct xml_query query = {0}; + query.memory = calloc(1, 1); + XML_Parser parser = XML_ParserCreate("UTF-8"); + XML_SetUserData(parser, &query); + XML_SetElementHandler(parser, _xml_start_mode_element, _xml_end_mode_element); + XML_SetCharacterDataHandler(parser, _xml_write_data); + if (! XML_Parse(parser, data, len, 1)) { + int code = XML_GetErrorCode(parser); + gs_error = XML_ErrorString(code); + XML_ParserFree(parser); + return GS_INVALID; + } + + XML_ParserFree(parser); + *mode_list = (PDISPLAY_MODE) query.data; + + return GS_OK; + +} diff --git a/libgamestream/xml.h b/libgamestream/xml.h index 99f488c..aee4214 100644 --- a/libgamestream/xml.h +++ b/libgamestream/xml.h @@ -26,5 +26,13 @@ typedef struct _APP_LIST { struct _APP_LIST *next; } APP_LIST, *PAPP_LIST; +typedef struct _DISPLAY_MODE { + unsigned int height; + unsigned int width; + unsigned int refresh; + struct _DISPLAY_MODE *next; +} DISPLAY_MODE, *PDISPLAY_MODE; + int xml_search(char* data, size_t len, char* node, char** result); int xml_applist(char* data, size_t len, PAPP_LIST *app_list); +int xml_modelist(char* data, size_t len, PDISPLAY_MODE *mode_list); diff --git a/mappings/cordlessrumblepad2.conf b/mappings/cordlessrumblepad2.conf deleted file mode 100644 index 3cd667b..0000000 --- a/mappings/cordlessrumblepad2.conf +++ /dev/null @@ -1,30 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = -1 -reverse_x = false -reverse_y = true -abs_rx = 2 -abs_ry = 5 -abs_rz = -1 -reverse_rx = false -reverse_ry = true -abs_dpad_x = 16 -abs_dpad_y = 17 -reverse_dpad_x = false -reverse_dpad_y = false -btn_east = 306 -btn_south = 305 -btn_north = 307 -btn_west = 304 -btn_select = 312 -btn_start = 313 -btn_thumbl = 314 -btn_thumbr = 315 -btn_tl = 308 -btn_tr = 309 -btn_tl2 = 310 -btn_tr2 = 311 -btn_dpad_up = -1 -btn_dpad_down = -1 -btn_dpad_left = -1 -btn_dpad_right = -1 diff --git a/mappings/default.conf b/mappings/default.conf deleted file mode 100644 index 33946e4..0000000 --- a/mappings/default.conf +++ /dev/null @@ -1,32 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = 2 -abs_rx = 3 -abs_ry = 4 -abs_rz = 5 -abs_deadzone = 0 -abs_dpad_y = 16 -abs_dpad_x = 17 -reverse_x = false -reverse_y = true -reverse_rx = false -reverse_ry = true -reverse_dpad_y = false -reverse_dpad_x = false -btn_south = 304 -btn_east = 305 -btn_north = 307 -btn_west = 308 -btn_select = 314 -btn_start = 315 -btn_mode = 316 -btn_thumbl = 317 -btn_thumbr = 318 -btn_tl = 310 -btn_tr = 311 -btn_tl2 = 312 -btn_tr2 = 313 -btn_dpad_up = 544 -btn_dpad_down = 545 -btn_dpad_left = 546 -btn_dpad_right = 547 diff --git a/mappings/dualshock3.conf b/mappings/dualshock3.conf deleted file mode 100644 index 01e9b19..0000000 --- a/mappings/dualshock3.conf +++ /dev/null @@ -1,32 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = 50 -abs_rx = 2 -abs_ry = 5 -abs_rz = 51 -abs_deadzone = 0 -abs_dpad_x = -1 -abs_dpad_y = -1 -reverse_x = false -reverse_y = true -reverse_rx = false -reverse_ry = true -reverse_dpad_x = false -reverse_dpad_y = false -btn_west = 303 -btn_south = 302 -btn_north = 300 -btn_east = 301 -btn_select = 288 -btn_start = 291 -btn_mode = 704 -btn_thumbl = 289 -btn_thumbr = 290 -btn_tl = 298 -btn_tr = 299 -btn_tl2 = 296 -btn_tr2 = 297 -btn_dpad_up = 292 -btn_dpad_down = 294 -btn_dpad_left = 295 -btn_dpad_right = 293 diff --git a/mappings/dualshock4.conf b/mappings/dualshock4.conf deleted file mode 100644 index 195e7f6..0000000 --- a/mappings/dualshock4.conf +++ /dev/null @@ -1,32 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = 3 -abs_rx = 2 -abs_ry = 5 -abs_rz = 4 -abs_deadzone = 0 -abs_dpad_x = 16 -abs_dpad_y = 17 -reverse_x = false -reverse_y = true -reverse_rx = false -reverse_ry = true -reverse_dpad_x = false -reverse_dpad_y = false -btn_west = 304 -btn_south = 305 -btn_north = 307 -btn_east = 306 -btn_select = 312 -btn_start = 313 -btn_mode = 316 -btn_thumbl = 314 -btn_thumbr = 315 -btn_tl = 308 -btn_tr = 309 -btn_tl2 = 310 -btn_tr2 = 311 -btn_dpad_up = -1 -btn_dpad_down = -1 -btn_dpad_left = -1 -btn_dpad_right = -1 diff --git a/mappings/rumblepad2.conf b/mappings/rumblepad2.conf deleted file mode 100644 index 3c93388..0000000 --- a/mappings/rumblepad2.conf +++ /dev/null @@ -1,31 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = -1 -reverse_x = false -reverse_y = true -abs_rx = 2 -abs_ry = 5 -abs_rz = -1 -reverse_rx = false -reverse_ry = true -abs_dpad_x = 16 -abs_dpad_y = 17 -reverse_dpad_x = false -reverse_dpad_y = false -btn_north = 291 -btn_east = 290 -btn_south = 289 -btn_west = 288 -btn_select = 297 -btn_start = 296 -btn_mode = 0 -btn_thumbl = 298 -btn_thumbr = 299 -btn_tl = 292 -btn_tr = 293 -btn_tl2 = 294 -btn_tr2 = 295 -btn_dpad_up = -1 -btn_dpad_down = -1 -btn_dpad_left = -1 -btn_dpad_right = -1 diff --git a/mappings/wiiupro.conf b/mappings/wiiupro.conf deleted file mode 100644 index 18a9bb9..0000000 --- a/mappings/wiiupro.conf +++ /dev/null @@ -1,32 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = -1 -reverse_x = false -reverse_y = true -abs_rx = 3 -abs_ry = 4 -abs_rz = -1 -reverse_rx = false -reverse_ry = true -abs_deadzone = 24088 -abs_dpad_x = -1 -abs_dpad_y = -1 -reverse_dpad_x = false -reverse_dpad_y = false -btn_north = 308 -btn_east = 304 -btn_south = 305 -btn_west = 307 -btn_select = 314 -btn_start = 315 -btn_mode = 316 -btn_thumbl = 317 -btn_thumbr = 318 -btn_tl = 310 -btn_tr = 311 -btn_tl2 = 312 -btn_tr2 = 313 -btn_dpad_up = 544 -btn_dpad_down = 545 -btn_dpad_left = 546 -btn_dpad_right = 547 \ No newline at end of file diff --git a/mappings/xbox360.conf b/mappings/xbox360.conf deleted file mode 100644 index d62423a..0000000 --- a/mappings/xbox360.conf +++ /dev/null @@ -1,32 +0,0 @@ -abs_x = 0 -abs_y = 1 -abs_z = 10 -abs_rx = 3 -abs_ry = 4 -abs_rz = 9 -abs_deadzone = 0 -abs_dpad_x = 17 -abs_dpad_y = 16 -reverse_x = false -reverse_y = true -reverse_rx = false -reverse_ry = true -reverse_dpad_x = false -reverse_dpad_y = false -btn_east = 305 -btn_south = 304 -btn_north = 308 -btn_west = 307 -btn_select = 314 -btn_start = 315 -btn_mode = 316 -btn_thumbl = 317 -btn_thumbr = 318 -btn_tl = 310 -btn_tr = 311 -btn_tl2 = 312 -btn_tr2 = 313 -btn_dpad_up = -1 -btn_dpad_down = -1 -btn_dpad_left = -1 -btn_dpad_right = -1 diff --git a/moonlight.conf b/moonlight.conf index a800d6c..f3311c5 100644 --- a/moonlight.conf +++ b/moonlight.conf @@ -17,8 +17,8 @@ ## Size of network packets should be lower than MTU #packetsize = 1024 -## Use of h265/HEVC video codec -#h265 = false +## Select video codec (auto/h264/h265) +#codec = auto ## Default started application on host #app = Steam @@ -26,7 +26,7 @@ ## Default used mapping for streaming ## Searched for in $XDG_DATA_DIRS/moonlight or /usr/share/moonlight and /usr/local/share/moonlight ## Mapping can also be user overrided in $XDG_CONFIG_DIR/moonlight or ~/.config/moonlight or current directory -#mapping = mappings/default.conf +#mapping = gamecontrollerdb.txt ## Enable selected input devices ## By default all available input devices should be used @@ -34,7 +34,7 @@ ## To use a different mapping then default another mapping should be declared above the input #input = /dev/input/event1 -## Let GFE change graphical game settings for optimal performance and quality +## Enable GFE for changing graphical game settings for optimal performance and quality #sops = true ## Play audio on host instead of streaming to client @@ -48,13 +48,20 @@ ## aml - hardware video decoder for ODROID-C1/C2 ## omx - hardware video decoder for Raspberry Pi ## imx - hardware video decoder for i.MX6 devices -## sdl - software decoder -## fake - save to file (only available in debug builds) +## x11 - software decoder +## sdl - software decoder with SDL input and audio +## fake - no audio and video #platform = default ## Directory to store encryption keys ## By default keys are stored in $XDG_CACHE_DIR/moonlight or ~/.cache/moonlight #keydir = /dir/to/keys +## Enable QOS settings to optimize for internet instead of local network +#remote = false + +## Enable 5.1 surround sound +#surround = false + ## Load additional configuration files #config = /path/to/config diff --git a/src/audio/alsa.c b/src/audio/alsa.c index 273ce82..8e08256 100644 --- a/src/audio/alsa.c +++ b/src/audio/alsa.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,24 +17,21 @@ * along with Moonlight; if not, see . */ -#include "../audio.h" +#include "audio.h" #include #include #include -#define CHECK_RETURN(f) if ((rc = f) < 0) { printf("Alsa error code %d\n", rc); exit(-1); } - -#define MAX_CHANNEL_COUNT 6 -#define FRAME_SIZE 240 +#define CHECK_RETURN(f) if ((rc = f) < 0) { printf("Alsa error code %d\n", rc); return -1; } static snd_pcm_t *handle; static OpusMSDecoder* decoder; static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT]; -static void alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { +static int alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) { int rc; - unsigned char alsaMapping[6]; + unsigned char alsaMapping[MAX_CHANNEL_COUNT]; /* The supplied mapping array has order: FL-FR-C-LFE-RL-RR * ALSA expects the order: FL-FR-RL-RR-C-LFE @@ -49,19 +46,15 @@ static void alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGU alsaMapping[5] = opusConfig->mapping[3]; } - decoder = opus_multistream_decoder_create(opusConfig->sampleRate, - opusConfig->channelCount, - opusConfig->streams, - opusConfig->coupledStreams, - alsaMapping, - &rc); + decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, alsaMapping, &rc); snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; - snd_pcm_uframes_t period_size = FRAME_SIZE * opusConfig->channelCount * 2; - snd_pcm_uframes_t buffer_size = 12 * period_size; + snd_pcm_uframes_t period_size = FRAME_SIZE * FRAME_BUFFER; + snd_pcm_uframes_t buffer_size = 2 * period_size; unsigned int sampleRate = opusConfig->sampleRate; + char* audio_device = (char*) context; if (audio_device == NULL) audio_device = "sysdefault"; @@ -75,20 +68,22 @@ static void alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGU CHECK_RETURN(snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16_LE)); CHECK_RETURN(snd_pcm_hw_params_set_rate_near(handle, hw_params, &sampleRate, NULL)); CHECK_RETURN(snd_pcm_hw_params_set_channels(handle, hw_params, opusConfig->channelCount)); - CHECK_RETURN(snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size)); CHECK_RETURN(snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, NULL)); + CHECK_RETURN(snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size)); CHECK_RETURN(snd_pcm_hw_params(handle, hw_params)); snd_pcm_hw_params_free(hw_params); /* Set software parameters */ CHECK_RETURN(snd_pcm_sw_params_malloc(&sw_params)); CHECK_RETURN(snd_pcm_sw_params_current(handle, sw_params)); - CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size)); CHECK_RETURN(snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size)); + CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, 1)); CHECK_RETURN(snd_pcm_sw_params(handle, sw_params)); snd_pcm_sw_params_free(sw_params); CHECK_RETURN(snd_pcm_prepare(handle)); + + return 0; } static void alsa_renderer_cleanup() { diff --git a/src/audio.h b/src/audio/audio.h similarity index 84% rename from src/audio.h rename to src/audio/audio.h index 3981212..91776ca 100644 --- a/src/audio.h +++ b/src/audio/audio.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,16 +21,17 @@ #include -extern const char* audio_device; +#define MAX_CHANNEL_COUNT 6 +#define FRAME_SIZE 240 +#define FRAME_BUFFER 12 +#ifdef HAVE_ALSA extern AUDIO_RENDERER_CALLBACKS audio_callbacks_alsa; +#endif #ifdef HAVE_SDL extern AUDIO_RENDERER_CALLBACKS audio_callbacks_sdl; #endif #ifdef HAVE_PULSE extern AUDIO_RENDERER_CALLBACKS audio_callbacks_pulse; -bool audio_pulse_init(); -#endif -#ifdef HAVE_PI -extern AUDIO_RENDERER_CALLBACKS audio_callbacks_omx; +bool audio_pulse_init(char* audio_device); #endif diff --git a/src/audio/omx.c b/src/audio/omx.c index bf8f7d3..9fa9d8c 100644 --- a/src/audio/omx.c +++ b/src/audio/omx.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * along with Moonlight; if not, see . */ -#include "../audio.h" +#include "audio.h" #include @@ -25,9 +25,6 @@ #include "bcm_host.h" #include "ilclient.h" -#define MAX_CHANNEL_COUNT 6 -#define FRAME_SIZE 240 - static OpusMSDecoder* decoder; ILCLIENT_T* handle; COMPONENT_T* component; @@ -35,10 +32,10 @@ static OMX_BUFFERHEADERTYPE *buf; static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT]; static int channelCount; -static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { +static int omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) { int rc, error; OMX_ERRORTYPE err; - unsigned char omxMapping[6]; + unsigned char omxMapping[MAX_CHANNEL_COUNT]; char* componentName = "audio_render"; channelCount = opusConfig->channelCount; @@ -52,27 +49,22 @@ static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR omxMapping[3] = opusConfig->mapping[2]; } - decoder = opus_multistream_decoder_create(opusConfig->sampleRate, - opusConfig->channelCount, - opusConfig->streams, - opusConfig->coupledStreams, - omxMapping, - &rc); + decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, omxMapping, &rc); handle = ilclient_init(); if (handle == NULL) { - fprintf(stderr, "IL client init failed\n"); - exit(1); + fprintf(stderr, "IL client init failed\n"); + return -1; } if (ilclient_create_component(handle, &component, componentName, ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0) { fprintf(stderr, "Component create failed\n"); - exit(1); + return -1; } if (ilclient_change_component_state(component, OMX_StateIdle)!= 0) { fprintf(stderr, "Couldn't change state to Idle\n"); - exit(1); + return -1; } // must be before we enable buffers @@ -104,33 +96,34 @@ static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR switch(channelCount) { case 1: - sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelCF; - break; + sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelCF; + break; case 8: - sPCMMode.eChannelMapping[7] = OMX_AUDIO_ChannelRS; + sPCMMode.eChannelMapping[7] = OMX_AUDIO_ChannelRS; case 7: - sPCMMode.eChannelMapping[6] = OMX_AUDIO_ChannelLS; + sPCMMode.eChannelMapping[6] = OMX_AUDIO_ChannelLS; case 6: - sPCMMode.eChannelMapping[5] = OMX_AUDIO_ChannelRR; + sPCMMode.eChannelMapping[5] = OMX_AUDIO_ChannelRR; case 5: - sPCMMode.eChannelMapping[4] = OMX_AUDIO_ChannelLR; + sPCMMode.eChannelMapping[4] = OMX_AUDIO_ChannelLR; case 4: - sPCMMode.eChannelMapping[3] = OMX_AUDIO_ChannelLFE; + sPCMMode.eChannelMapping[3] = OMX_AUDIO_ChannelLFE; case 3: - sPCMMode.eChannelMapping[2] = OMX_AUDIO_ChannelCF; + sPCMMode.eChannelMapping[2] = OMX_AUDIO_ChannelCF; case 2: - sPCMMode.eChannelMapping[1] = OMX_AUDIO_ChannelRF; - sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelLF; - break; + sPCMMode.eChannelMapping[1] = OMX_AUDIO_ChannelRF; + sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelLF; + break; } err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPcm, &sPCMMode); if(err != OMX_ErrorNone){ - fprintf(stderr, "PCM mode unsupported\n"); - return; + fprintf(stderr, "PCM mode unsupported\n"); + return -1; } OMX_CONFIG_BRCMAUDIODESTINATIONTYPE arDest; + char* audio_device = (char*) context; if (audio_device == NULL) audio_device = "hdmi"; @@ -144,7 +137,7 @@ static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexConfigBrcmAudioDestination, &arDest); if (err != OMX_ErrorNone) { fprintf(stderr, "Error on setting audio destination\nomx option must be set to hdmi or local\n"); - exit(1); + return -1; } } @@ -154,9 +147,11 @@ static void omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR err = ilclient_change_component_state(component, OMX_StateExecuting); if (err < 0) { - fprintf(stderr, "Couldn't change state to Executing\n"); - exit(1); + fprintf(stderr, "Couldn't change state to Executing\n"); + return -1; } + + return 0; } static void omx_renderer_cleanup() { diff --git a/src/audio/pulse.c b/src/audio/pulse.c index bcb92aa..2da5631 100644 --- a/src/audio/pulse.c +++ b/src/audio/pulse.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * along with Moonlight; if not, see . */ -#include "../audio.h" +#include "audio.h" #include #include @@ -26,33 +26,30 @@ #include #include -#define MAX_CHANNEL_COUNT 6 -#define FRAME_SIZE 240 - static OpusMSDecoder* decoder; static pa_simple *dev = NULL; static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT]; static int channelCount; -bool audio_pulse_init() { - pa_sample_spec spec = { - .format = PA_SAMPLE_S16LE, - .rate = 44000, - .channels = 2 - }; +bool audio_pulse_init(char* audio_device) { + pa_sample_spec spec = { + .format = PA_SAMPLE_S16LE, + .rate = 44000, + .channels = 2 + }; - int error; - dev = pa_simple_new(NULL, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error); - if (dev) { - pa_simple_free(dev); - return true; - } else - return false; + int error; + dev = pa_simple_new(audio_device, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error); + + if (dev) + pa_simple_free(dev); + + return (bool) dev; } -static void pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { +static int pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) { int rc, error; - unsigned char alsaMapping[6]; + unsigned char alsaMapping[MAX_CHANNEL_COUNT]; channelCount = opusConfig->channelCount; @@ -69,12 +66,7 @@ static void pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIG alsaMapping[5] = opusConfig->mapping[3]; } - decoder = opus_multistream_decoder_create(opusConfig->sampleRate, - opusConfig->channelCount, - opusConfig->streams, - opusConfig->coupledStreams, - alsaMapping, - &rc); + decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, alsaMapping, &rc); pa_sample_spec spec = { .format = PA_SAMPLE_S16LE, @@ -82,12 +74,15 @@ static void pulse_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIG .channels = opusConfig->channelCount }; - dev = pa_simple_new(NULL, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error); + char* audio_device = (char*) context; + dev = pa_simple_new(audio_device, "Moonlight Embedded", PA_STREAM_PLAYBACK, NULL, "Streaming", &spec, NULL, NULL, &error); if (!dev) { printf("Pulseaudio error: %s\n", pa_strerror(error)); - exit(-1); + return -1; } + + return 0; } static void pulse_renderer_decode_and_play_sample(char* data, int length) { diff --git a/src/audio/sdl.c b/src/audio/sdl.c index 8da514a..15a9ee0 100644 --- a/src/audio/sdl.c +++ b/src/audio/sdl.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * along with Moonlight; if not, see . */ -#include "../audio.h" +#include "audio.h" #include #include @@ -25,22 +25,14 @@ #include #include -#define MAX_CHANNEL_COUNT 6 -#define FRAME_SIZE 240 - static OpusMSDecoder* decoder; static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT]; static SDL_AudioDeviceID dev; static int channelCount; -static void sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { +static int sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) { int rc; - decoder = opus_multistream_decoder_create(opusConfig->sampleRate, - opusConfig->channelCount, - opusConfig->streams, - opusConfig->coupledStreams, - opusConfig->mapping, - &rc); + decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, opusConfig->mapping, &rc); channelCount = opusConfig->channelCount; @@ -56,11 +48,14 @@ static void sdl_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGUR dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_FORMAT_CHANGE); if (dev == 0) { printf("Failed to open audio: %s\n", SDL_GetError()); + return -1; } else { if (have.format != want.format) // we let this one thing change. printf("We didn't get requested audio format.\n"); SDL_PauseAudioDevice(dev, 0); // start audio playing. } + + return 0; } static void sdl_renderer_cleanup() { diff --git a/src/config.c b/src/config.c index 1381905..db7aaa9 100644 --- a/src/config.c +++ b/src/config.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,10 @@ * along with Moonlight; if not, see . */ -#include "input/evdev.h" #include "config.h" -#include "audio.h" + +#include "input/evdev.h" +#include "audio/audio.h" #include #include @@ -37,16 +38,13 @@ #define write_config_bool(fd, key, value) fprintf(fd, "%s = %s\n", key, value?"true":"false"); bool inputAdded = false; -static bool mapped = true; -const char* audio_device = NULL; static struct option long_options[] = { {"720", no_argument, NULL, 'a'}, {"1080", no_argument, NULL, 'b'}, + {"4k", no_argument, NULL, '0'}, {"width", required_argument, NULL, 'c'}, {"height", required_argument, NULL, 'd'}, - {"30fps", no_argument, NULL, 'e'}, - {"60fps", no_argument, NULL, 'f'}, {"bitrate", required_argument, NULL, 'g'}, {"packetsize", required_argument, NULL, 'h'}, {"app", required_argument, NULL, 'i'}, @@ -63,10 +61,10 @@ static struct option long_options[] = { {"windowed", no_argument, NULL, 't'}, {"surround", no_argument, NULL, 'u'}, {"fps", required_argument, NULL, 'v'}, - {"forcehw", no_argument, NULL, 'w'}, - {"hevc", no_argument, NULL, 'x'}, - {"h264", no_argument, NULL, 'z'}, + {"codec", required_argument, NULL, 'x'}, {"unsupported", no_argument, NULL, 'y'}, + {"verbose", no_argument, NULL, 'z'}, + {"debug", no_argument, NULL, 'Z'}, {0, 0, 0, 0}, }; @@ -126,18 +124,16 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) { config->stream.width = 1920; config->stream.height = 1080; break; + case '0': + config->stream.width = 3840; + config->stream.height = 2160; + break; case 'c': config->stream.width = atoi(value); break; case 'd': config->stream.height = atoi(value); break; - case 'e': - config->stream.fps = 30; - break; - case 'f': - config->stream.fps = 60; - break; case 'g': config->stream.bitrate = atoi(value); break; @@ -152,11 +148,9 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) { perror("Too many inputs specified"); exit(-1); } - config->inputs[config->inputsCount].path = value; - config->inputs[config->inputsCount].mapping = config->mapping; + config->inputs[config->inputsCount] = value; config->inputsCount++; inputAdded = true; - mapped = true; break; case 'k': config->mapping = get_path(value, getenv("XDG_DATA_DIRS")); @@ -164,13 +158,12 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) { fprintf(stderr, "Unable to open custom mapping file: %s\n", value); exit(-1); } - mapped = false; break; case 'l': config->sops = false; break; case 'm': - audio_device = value; + config->audio_device = value; break; case 'n': config->localaudio = true; @@ -201,18 +194,23 @@ static void parse_argument(int c, char* value, PCONFIGURATION config) { case 'v': config->stream.fps = atoi(value); break; - case 'w': - config->forcehw = true; - break; case 'x': - config->codec = CODEC_HEVC; - break; - case 'z': - config->codec = CODEC_H264; + if (strcasecmp(value, "auto") == 0) + config->codec = CODEC_UNSPECIFIED; + else if (strcasecmp(value, "h264") == 0) + config->codec = CODEC_H264; + if (strcasecmp(value, "h265") == 0 || strcasecmp(value, "hevc") == 0) + config->codec = CODEC_HEVC; break; case 'y': config->unsupported_version = true; break; + case 'z': + config->debug_level = 1; + break; + case 'Z': + config->debug_level = 2; + break; case 1: if (config->action == NULL) config->action = value; @@ -246,8 +244,11 @@ bool config_file_parse(char* filename, PCONFIGURATION config) { config->localaudio = strcmp("true", value) == 0; } else { for (int i=0;long_options[i].name != NULL;i++) { - if (long_options[i].has_arg == required_argument && strcmp(long_options[i].name, key) == 0) { - parse_argument(long_options[i].val, value, config); + if (strcmp(long_options[i].name, key) == 0) { + if (long_options[i].has_arg == required_argument) + parse_argument(long_options[i].val, value, config); + else if (strcmp("true", value) == 0) + parse_argument(long_options[i].val, NULL, config); } } } @@ -289,27 +290,28 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) { config->stream.width = 1280; config->stream.height = 720; - config->stream.fps = 60; + config->stream.fps = -1; config->stream.bitrate = -1; config->stream.packetSize = 1024; config->stream.streamingRemotely = 0; config->stream.audioConfiguration = AUDIO_CONFIGURATION_STEREO; config->stream.supportsHevc = false; - config->platform = "default"; + config->debug_level = 0; + config->platform = "auto"; config->app = "Steam"; config->action = NULL; config->address = NULL; config->config_file = NULL; + config->audio_device = NULL; config->sops = true; config->localaudio = false; config->fullscreen = true; config->unsupported_version = false; - config->forcehw = false; config->codec = CODEC_UNSPECIFIED; config->inputsCount = 0; - config->mapping = get_path("mappings/default.conf", getenv("XDG_DATA_DIRS")); + config->mapping = get_path("gamecontrollerdb.txt", getenv("XDG_DATA_DIRS")); config->key_dir[0] = 0; char* config_file = get_path("moonlight.conf", "/etc"); @@ -342,6 +344,9 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) { } } + if (config->stream.fps == -1) + config->stream.fps = config->stream.height >= 1080 ? 30 : 60; + if (config->stream.bitrate == -1) { if (config->stream.height >= 1080 && config->stream.fps >= 60) config->stream.bitrate = 20000; @@ -350,14 +355,4 @@ void config_parse(int argc, char* argv[], PCONFIGURATION config) { else config->stream.bitrate = 5000; } - - if (inputAdded) { - if (!mapped) { - fprintf(stderr, "Mapping option should be followed by the input to be mapped.\n"); - exit(-1); - } else if (config->mapping == NULL) { - fprintf(stderr, "Please specify mapping file as default mapping could not be found.\n"); - exit(-1); - } - } } diff --git a/src/config.h b/src/config.h index de7d3a8..f6271bf 100644 --- a/src/config.h +++ b/src/config.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,26 +25,22 @@ enum codecs { CODEC_UNSPECIFIED, CODEC_H264, CODEC_HEVC }; -struct input_config { - char* path; - char* mapping; -}; - typedef struct _CONFIGURATION { STREAM_CONFIGURATION stream; + int debug_level; char* app; char* action; char* address; char* mapping; char* platform; + char* audio_device; char* config_file; char key_dir[4096]; bool sops; bool localaudio; bool fullscreen; - bool forcehw; bool unsupported_version; - struct input_config inputs[MAX_INPUTS]; + char* inputs[MAX_INPUTS]; int inputsCount; enum codecs codec; } CONFIGURATION, *PCONFIGURATION; diff --git a/src/configuration.h.in b/src/configuration.h.in index 1da5c11..e329365 100644 --- a/src/configuration.h.in +++ b/src/configuration.h.in @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +17,8 @@ * along with Moonlight; if not, see . */ -#define VERSION_MAJOR @MOONLIGHT_MAJOR_VERSION@ -#define VERSION_MINOR @MOONLIGHT_MINOR_VERSION@ -#define VERSION_PATCH @MOONLIGHT_PATCH_VERSION@ +#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define VERSION_PATCH @PROJECT_VERSION_PATCH@ #define COMPILE_OPTIONS "@MOONLIGHT_OPTIONS@" diff --git a/src/connection.c b/src/connection.c index f25a508..abf42c4 100644 --- a/src/connection.c +++ b/src/connection.c @@ -18,12 +18,17 @@ */ #include "connection.h" -#include "global.h" #include +#include +#include -static void connection_connection_terminated() { - quit(); +pthread_t main_thread_id = 0; +bool connection_debug; + +static void connection_terminated() { + if (main_thread_id != 0) + pthread_kill(main_thread_id, SIGTERM); } static void connection_display_message(const char *msg) { @@ -34,12 +39,20 @@ static void connection_display_transient_message(const char *msg) { printf("%s\n", msg); } +static void connection_log_message(const char* format, ...) { + va_list arglist; + va_start(arglist, format); + vprintf(format, arglist); + va_end(arglist); +} + CONNECTION_LISTENER_CALLBACKS connection_callbacks = { .stageStarting = NULL, .stageComplete = NULL, .stageFailed = NULL, .connectionStarted = NULL, - .connectionTerminated = connection_connection_terminated, + .connectionTerminated = connection_terminated, .displayMessage = connection_display_message, .displayTransientMessage = connection_display_transient_message, + .logMessage = connection_log_message, }; diff --git a/src/connection.h b/src/connection.h index 8b985b6..a8a9ea6 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,4 +19,9 @@ #include +#include +#include + extern CONNECTION_LISTENER_CALLBACKS connection_callbacks; +extern pthread_t main_thread_id; +extern bool connection_debug; diff --git a/src/input/cec.c b/src/input/cec.c index 58ec12b..342ef27 100644 --- a/src/input/cec.c +++ b/src/input/cec.c @@ -17,8 +17,6 @@ * along with Moonlight; if not, see . */ -#ifdef HAVE_LIBCEC - #include #include @@ -111,4 +109,3 @@ void cec_init() { g_iface.set_active_source(g_iface.connection, g_config.deviceTypes.types[0]); } -#endif /* HAVE_LIBCEC */ diff --git a/src/input/evdev.c b/src/input/evdev.c index b3c1136..5f9706c 100644 --- a/src/input/evdev.c +++ b/src/input/evdev.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,11 +17,11 @@ * along with Moonlight; if not, see . */ -#include "../loop.h" -#include "../global.h" +#include "evdev.h" #include "keyboard.h" -#include "mapping.h" + +#include "../loop.h" #include "libevdev/libevdev.h" #include @@ -38,6 +38,13 @@ #include #include #include +#include + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define int16_to_le(val) val +#else +#define int16_to_le(val) ((((val) >> 8) & 0x00FF) | (((val) << 8) & 0xFF00)) +#endif struct input_abs_parms { int min, max; @@ -48,7 +55,10 @@ struct input_abs_parms { struct input_device { struct libevdev *dev; - struct mapping map; + struct mapping* map; + int key_map[KEY_MAX]; + int abs_map[ABS_MAX]; + int hats_state[3][2]; int fd; char modifiers; __s32 mouseDeltaX, mouseDeltaY, mouseScroll; @@ -59,9 +69,16 @@ struct input_device { short rightStickX, rightStickY; bool gamepadModified; struct input_abs_parms xParms, yParms, rxParms, ryParms, zParms, rzParms; - struct input_abs_parms dpadxParms, dpadyParms; }; +#define HAT_UP 1 +#define HAT_RIGHT 2 +#define HAT_DOWN 4 +#define HAT_LEFT 8 +static const int hat_constants[3][3] = {{HAT_UP | HAT_LEFT, HAT_UP, HAT_UP | HAT_RIGHT}, {HAT_LEFT, 0, HAT_RIGHT}, {HAT_LEFT | HAT_DOWN, HAT_DOWN, HAT_RIGHT}}; + +#define set_hat(flags, flag, hat, hat_flag) flags = (-((hat & hat_flag) > 0) ^ flags) & flag + static struct input_device* devices = NULL; static int numDevices = 0; static int assignedControllerIds = 0; @@ -74,13 +91,14 @@ static bool grabbingDevices; #define QUIT_MODIFIERS (MODIFIER_SHIFT|MODIFIER_ALT|MODIFIER_CTRL) #define QUIT_KEY KEY_Q +#define QUIT_BUTTONS (PLAY_FLAG|BACK_FLAG|LB_FLAG|RB_FLAG) static bool (*handler) (struct input_event*, struct input_device*); static void evdev_init_parms(struct input_device *dev, struct input_abs_parms *parms, int code) { - parms->flat = libevdev_get_abs_flat(dev->dev, code); - parms->min = libevdev_get_abs_minimum(dev->dev, code); - parms->max = libevdev_get_abs_maximum(dev->dev, code); + parms->flat = libevdev_get_abs_flat(dev->dev, dev->abs_map[code]); + parms->min = libevdev_get_abs_minimum(dev->dev, dev->abs_map[code]); + parms->max = libevdev_get_abs_maximum(dev->dev, dev->abs_map[code]); parms->avg = (parms->min+parms->max)/2; parms->range = parms->max - parms->avg; parms->diff = parms->max - parms->min; @@ -120,15 +138,6 @@ static char evdev_convert_value_byte(struct input_event *ev, struct input_device return (ev->value - parms->flat - parms->min) * UCHAR_MAX / (parms->diff - parms->flat); } -static int evdev_convert_value_direction(struct input_event *ev, struct input_device *dev, struct input_abs_parms *parms, bool reverse) { - if (ev->value > (parms->avg+parms->range/4)) - return reverse?-1:1; - else if (ev->value < (parms->avg-parms->range/4)) - return reverse?1:-1; - else - return 0; -} - static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) { bool gamepadModified = false; @@ -195,6 +204,8 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) } else { int mouseCode = 0; short gamepadCode = 0; + int index = dev->key_map[ev->code - BTN_MISC]; + switch (ev->code) { case BTN_LEFT: mouseCode = BUTTON_LEFT; @@ -206,35 +217,35 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) mouseCode = BUTTON_RIGHT; break; default: - if (ev->code == dev->map.btn_south) + if (index == dev->map->btn_a) gamepadCode = A_FLAG; - else if (ev->code == dev->map.btn_west) + else if (index == dev->map->btn_x) gamepadCode = X_FLAG; - else if (ev->code == dev->map.btn_north) + else if (index == dev->map->btn_y) gamepadCode = Y_FLAG; - else if (ev->code == dev->map.btn_east) + else if (index == dev->map->btn_b) gamepadCode = B_FLAG; - else if (ev->code == dev->map.btn_dpad_up) + else if (index == dev->map->btn_dpup) gamepadCode = UP_FLAG; - else if (ev->code == dev->map.btn_dpad_down) + else if (index == dev->map->btn_dpdown) gamepadCode = DOWN_FLAG; - else if (ev->code == dev->map.btn_dpad_right) + else if (index == dev->map->btn_dpright) gamepadCode = RIGHT_FLAG; - else if (ev->code == dev->map.btn_dpad_left) + else if (index == dev->map->btn_dpleft) gamepadCode = LEFT_FLAG; - else if (ev->code == dev->map.btn_thumbl) + else if (index == dev->map->btn_leftstick) gamepadCode = LS_CLK_FLAG; - else if (ev->code == dev->map.btn_thumbr) + else if (index == dev->map->btn_rightstick) gamepadCode = RS_CLK_FLAG; - else if (ev->code == dev->map.btn_tl) + else if (index == dev->map->btn_leftshoulder) gamepadCode = LB_FLAG; - else if (ev->code == dev->map.btn_tr) + else if (index == dev->map->btn_rightshoulder) gamepadCode = RB_FLAG; - else if (ev->code == dev->map.btn_start) + else if (index == dev->map->btn_start) gamepadCode = PLAY_FLAG; - else if (ev->code == dev->map.btn_select) + else if (index == dev->map->btn_back) gamepadCode = BACK_FLAG; - else if (ev->code == dev->map.btn_mode) + else if (index == dev->map->btn_guide) gamepadCode = SPECIAL_FLAG; } @@ -248,9 +259,9 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) dev->buttonFlags |= gamepadCode; else dev->buttonFlags &= ~gamepadCode; - } else if (ev->code == dev->map.btn_tl2) + } else if (index == dev->map->btn_lefttrigger) dev->leftTrigger = ev->value?UCHAR_MAX:0; - else if (ev->code == dev->map.btn_tr2) + else if (index == dev->map->btn_righttrigger) dev->rightTrigger = ev->value?UCHAR_MAX:0; else { fprintf(stderr, "Unmapped button: %d\n", ev->code); @@ -274,48 +285,51 @@ static bool evdev_handle_event(struct input_event *ev, struct input_device *dev) break; case EV_ABS: gamepadModified = true; - if (ev->code == dev->map.abs_x) - dev->leftStickX = evdev_convert_value(ev, dev, &dev->xParms, dev->map.reverse_x); - else if (ev->code == dev->map.abs_y) - dev->leftStickY = evdev_convert_value(ev, dev, &dev->yParms, dev->map.reverse_y); - else if (ev->code == dev->map.abs_rx) - dev->rightStickX = evdev_convert_value(ev, dev, &dev->rxParms, dev->map.reverse_rx); - else if (ev->code == dev->map.abs_ry) - dev->rightStickY = evdev_convert_value(ev, dev, &dev->ryParms, dev->map.reverse_ry); - else if (ev->code == dev->map.abs_z) - dev->leftTrigger = evdev_convert_value_byte(ev, dev, &dev->zParms); - else if (ev->code == dev->map.abs_rz) - dev->rightTrigger = evdev_convert_value_byte(ev, dev, &dev->rzParms); - else if (ev->code == dev->map.abs_dpad_x) { - int dir = evdev_convert_value_direction(ev, dev, &dev->dpadxParms, dev->map.reverse_dpad_x); - if (dir == 1) { - dev->buttonFlags |= RIGHT_FLAG; - dev->buttonFlags &= ~LEFT_FLAG; - } else if (dir == 0) { - dev->buttonFlags &= ~RIGHT_FLAG; - dev->buttonFlags &= ~LEFT_FLAG; - } else { - dev->buttonFlags &= ~RIGHT_FLAG; - dev->buttonFlags |= LEFT_FLAG; - } - } else if (ev->code == dev->map.abs_dpad_y) { - int dir = evdev_convert_value_direction(ev, dev, &dev->dpadyParms, dev->map.reverse_dpad_y); - if (dir == 1) { - dev->buttonFlags |= DOWN_FLAG; - dev->buttonFlags &= ~UP_FLAG; - } else if (dir == 0) { - dev->buttonFlags &= ~DOWN_FLAG; - dev->buttonFlags &= ~UP_FLAG; - } else { - dev->buttonFlags &= ~DOWN_FLAG; - dev->buttonFlags |= UP_FLAG; - } - } else - gamepadModified = false; + int index = dev->abs_map[ev->code]; + int hat_index = (ev->code - ABS_HAT0X) / 2; + int har_dir_index = (ev->code - ABS_HAT0X) % 2; - break; + switch (ev->code) { + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + case ABS_HAT2X: + case ABS_HAT2Y: + case ABS_HAT3X: + case ABS_HAT3Y: + dev->hats_state[hat_index][har_dir_index] = ev->value < 0 ? 0 : (ev->value == 0 ? 1 : 2); + int hat_state = hat_constants[dev->hats_state[hat_index][0]][dev->hats_state[hat_index][1]]; + if (hat_index == dev->map->hat_dpup) + set_hat(dev->buttonFlags, UP_FLAG, hat_state, dev->map->hat_dir_dpup); + if (hat_index == dev->map->hat_dpdown) + set_hat(dev->buttonFlags, DOWN_FLAG, hat_state, dev->map->hat_dir_dpdown); + if (hat_index == dev->map->hat_dpright) + set_hat(dev->buttonFlags, HAT_RIGHT, hat_state, dev->map->hat_dir_dpright); + if (hat_index == dev->map->hat_dpleft) + set_hat(dev->buttonFlags, HAT_LEFT, hat_state, dev->map->hat_dir_dpleft); + break; + default: + if (index == dev->map->abs_leftx) + dev->leftStickX = evdev_convert_value(ev, dev, &dev->xParms, dev->map->reverse_leftx); + else if (index == dev->map->abs_lefty) + dev->leftStickY = evdev_convert_value(ev, dev, &dev->yParms, !dev->map->reverse_lefty); + else if (index == dev->map->abs_rightx) + dev->rightStickX = evdev_convert_value(ev, dev, &dev->rxParms, dev->map->reverse_rightx); + else if (index == dev->map->abs_righty) + dev->rightStickY = evdev_convert_value(ev, dev, &dev->ryParms, !dev->map->reverse_righty); + else if (index == dev->map->abs_lefttrigger) + dev->leftTrigger = evdev_convert_value_byte(ev, dev, &dev->zParms); + else if (index == dev->map->abs_righttrigger) + dev->rightTrigger = evdev_convert_value_byte(ev, dev, &dev->rzParms); + else + gamepadModified = false; + } } + if (gamepadModified && (dev->buttonFlags & QUIT_BUTTONS) == QUIT_BUTTONS) + return false; + dev->gamepadModified |= gamepadModified; return true; } @@ -380,7 +394,7 @@ static int evdev_handle(int fd) { return LOOP_OK; } -void evdev_create(const char* device, char* mapFile) { +void evdev_create(const char* device, struct mapping* mappings) { int fd = open(device, O_RDONLY|O_NONBLOCK); if (fd <= 0) { fprintf(stderr, "Failed to open device %s\n", device); @@ -388,6 +402,31 @@ void evdev_create(const char* device, char* mapFile) { return; } + struct libevdev *evdev = libevdev_new(); + libevdev_set_fd(evdev, fd); + + int16_t guid[8] = {0}; + guid[0] = int16_to_le(libevdev_get_id_bustype(evdev)); + guid[2] = int16_to_le(libevdev_get_id_vendor(evdev)); + guid[4] = int16_to_le(libevdev_get_id_product(evdev)); + guid[6] = int16_to_le(libevdev_get_id_version(evdev)); + + char str_guid[33]; + char* buf = str_guid; + for (int i = 0; i < 16; i++) + buf += sprintf(buf, "%02x", ((unsigned char*) guid)[i]); + + while (mappings != NULL && strncmp(str_guid, mappings->guid, 32) != 0) + mappings = mappings->next; + + if (mappings == NULL) { + fprintf(stderr, "No mapping available for %s\n", device); + fflush(stderr); + close(fd); + libevdev_free(evdev); + return; + } + int dev = numDevices; numDevices++; @@ -404,21 +443,35 @@ void evdev_create(const char* device, char* mapFile) { memset(&devices[dev], 0, sizeof(devices[0])); devices[dev].fd = fd; - devices[dev].dev = libevdev_new(); - libevdev_set_fd(devices[dev].dev, devices[dev].fd); + devices[dev].dev = evdev; + devices[dev].map = mappings; - if (mapFile != NULL) - mapping_load(mapFile, &(devices[dev].map)); + int nbuttons = 0; + for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) { + if (libevdev_has_event_code(devices[dev].dev, EV_KEY, i)) + devices[dev].key_map[i - BTN_MISC] = nbuttons++; + } + for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) { + if (libevdev_has_event_code(devices[dev].dev, EV_KEY, i)) + devices[dev].key_map[i - BTN_MISC] = nbuttons++; + } + + int naxes = 0; + for (int i = 0; i < ABS_MAX; ++i) { + /* Skip hats */ + if (i == ABS_HAT0X) + i = ABS_HAT3Y; + else if (libevdev_has_event_code(devices[dev].dev, EV_ABS, i)) + devices[dev].abs_map[i] = naxes++; + } devices[dev].controllerId = -1; - evdev_init_parms(&devices[dev], &(devices[dev].xParms), devices[dev].map.abs_x); - evdev_init_parms(&devices[dev], &(devices[dev].yParms), devices[dev].map.abs_y); - evdev_init_parms(&devices[dev], &(devices[dev].zParms), devices[dev].map.abs_z); - evdev_init_parms(&devices[dev], &(devices[dev].rxParms), devices[dev].map.abs_rx); - evdev_init_parms(&devices[dev], &(devices[dev].ryParms), devices[dev].map.abs_ry); - evdev_init_parms(&devices[dev], &(devices[dev].rzParms), devices[dev].map.abs_rz); - evdev_init_parms(&devices[dev], &(devices[dev].dpadxParms), devices[dev].map.abs_dpad_x); - evdev_init_parms(&devices[dev], &(devices[dev].dpadyParms), devices[dev].map.abs_dpad_y); + evdev_init_parms(&devices[dev], &(devices[dev].xParms), devices[dev].map->abs_leftx); + evdev_init_parms(&devices[dev], &(devices[dev].yParms), devices[dev].map->abs_lefty); + evdev_init_parms(&devices[dev], &(devices[dev].zParms), devices[dev].map->abs_lefttrigger); + evdev_init_parms(&devices[dev], &(devices[dev].rxParms), devices[dev].map->abs_rightx); + evdev_init_parms(&devices[dev], &(devices[dev].ryParms), devices[dev].map->abs_righty); + evdev_init_parms(&devices[dev], &(devices[dev].rzParms), devices[dev].map->abs_righttrigger); if (grabbingDevices) { if (ioctl(fd, EVIOCGRAB, 1) < 0) { @@ -429,85 +482,6 @@ void evdev_create(const char* device, char* mapFile) { loop_add_fd(devices[dev].fd, &evdev_handle, POLLIN); } -static void evdev_map_key(char* keyName, short* key) { - printf("Press %s\n", keyName); - currentKey = key; - currentAbs = NULL; - *key = -1; - loop_main(); - - usleep(250000); - evdev_drain(); -} - -static void evdev_map_abs(char* keyName, short* abs, bool* reverse) { - printf("Move %s\n", keyName); - currentKey = NULL; - currentAbs = abs; - currentReverse = reverse; - *abs = -1; - loop_main(); - - usleep(250000); - evdev_drain(); -} - -static void evdev_map_abskey(char* keyName, short* key, short* abs, bool* reverse) { - printf("Press %s\n", keyName); - currentKey = key; - currentAbs = abs; - currentReverse = reverse; - *key = -1; - *abs = -1; - *currentReverse = false; - loop_main(); - - usleep(250000); - evdev_drain(); -} - -void evdev_map(char* fileName) { - struct mapping map; - - handler = evdev_handle_mapping_event; - - evdev_map_abs("Left Stick Right", &(map.abs_x), &(map.reverse_x)); - evdev_map_abs("Left Stick Up", &(map.abs_y), &(map.reverse_y)); - evdev_map_key("Left Stick Button", &(map.btn_thumbl)); - - evdev_map_abs("Right Stick Right", &(map.abs_rx), &(map.reverse_rx)); - evdev_map_abs("Right Stick Up", &(map.abs_ry), &(map.reverse_ry)); - evdev_map_key("Right Stick Button", &(map.btn_thumbr)); - - evdev_map_abskey("D-Pad Right", &(map.btn_dpad_right), &(map.abs_dpad_x), &(map.reverse_dpad_x)); - if (map.btn_dpad_right >= 0) - evdev_map_key("D-Pad Left", &(map.btn_dpad_left)); - else - map.btn_dpad_left = -1; - - evdev_map_abskey("D-Pad Down", &(map.btn_dpad_down), &(map.abs_dpad_y), &(map.reverse_dpad_y)); - if (map.btn_dpad_down >= 0) - evdev_map_key("D-Pad Up", &(map.btn_dpad_up)); - else - map.btn_dpad_up = -1; - - evdev_map_key("Button X (1)", &(map.btn_west)); - evdev_map_key("Button A (2)", &(map.btn_south)); - evdev_map_key("Button B (3)", &(map.btn_east)); - evdev_map_key("Button Y (4)", &(map.btn_north)); - evdev_map_key("Back Button", &(map.btn_select)); - evdev_map_key("Start Button", &(map.btn_start)); - evdev_map_key("Special Button", &(map.btn_mode)); - - bool ignored; - evdev_map_abskey("Left Trigger", &(map.btn_tl2), &(map.abs_z), &ignored); - evdev_map_abskey("Right Trigger", &(map.btn_tr2), &(map.abs_rz), &ignored); - - evdev_map_key("Left Bumper", &(map.btn_tl)); - evdev_map_key("Right Bumper", &(map.btn_tr)); - mapping_save(fileName, &map); -} - void evdev_start() { // After grabbing, the only way to quit via the keyboard // is via the special key combo that the input handling diff --git a/src/input/evdev.h b/src/input/evdev.h index 37af027..c3330e4 100644 --- a/src/input/evdev.h +++ b/src/input/evdev.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,10 @@ * along with Moonlight; if not, see . */ -void evdev_create(const char* device, char* mapFile); +#include "mapping.h" + +void evdev_create(const char* device, struct mapping* mappings); void evdev_loop(); -void evdev_map(char* fileName); void evdev_init(); void evdev_start(); diff --git a/src/input/mapping.c b/src/input/mapping.c index 4cccf06..029f1a7 100644 --- a/src/input/mapping.c +++ b/src/input/mapping.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,10 +23,9 @@ #include #include -#define write_config(fd, key, value) fprintf(fd, "%s = %hd\n", key, value) -#define write_config_bool(fd, key, value) fprintf(fd, "%s = %s\n", key, value?"true":"false"); - -void mapping_load(char* fileName, struct mapping* map) { +struct mapping* mapping_load(char* fileName) { + struct mapping* mappings = NULL; + struct mapping* map = NULL; FILE* fd = fopen(fileName, "r"); if (fd == NULL) { fprintf(stderr, "Can't open mapping file: %s\n", fileName); @@ -36,135 +35,117 @@ void mapping_load(char* fileName, struct mapping* map) { char *line = NULL; size_t len = 0; while (getline(&line, &len, fd) != -1) { - char *key = NULL, *value = NULL; - if (sscanf(line, "%ms = %ms", &key, &value) == 2) { - long int_value = strtol(value, NULL, 10); - if (strcmp("abs_x", key) == 0) - map->abs_x = int_value; - else if (strcmp("abs_y", key) == 0) - map->abs_y = int_value; - else if (strcmp("abs_z", key) == 0) - map->abs_z = int_value; - else if (strcmp("abs_rx", key) == 0) - map->abs_rx = int_value; - else if (strcmp("abs_ry", key) == 0) - map->abs_ry = int_value; - else if (strcmp("abs_rz", key) == 0) - map->abs_rz = int_value; - else if (strcmp("abs_deadzone", key) == 0) - map->abs_deadzone = int_value; - else if (strcmp("abs_dpad_x", key) == 0) - map->abs_dpad_x = int_value; - else if (strcmp("abs_dpad_y", key) == 0) - map->abs_dpad_y = int_value; - else if (strcmp("btn_south", key) == 0) - map->btn_south = int_value; - else if (strcmp("btn_north", key) == 0) - map->btn_north = int_value; - else if (strcmp("btn_east", key) == 0) - map->btn_east = int_value; - else if (strcmp("btn_west", key) == 0) - map->btn_west = int_value; - else if (strcmp("btn_select", key) == 0) - map->btn_select = int_value; - else if (strcmp("btn_start", key) == 0) - map->btn_start = int_value; - else if (strcmp("btn_mode", key) == 0) - map->btn_mode = int_value; - else if (strcmp("btn_thumbl", key) == 0) - map->btn_thumbl = int_value; - else if (strcmp("btn_thumbr", key) == 0) - map->btn_thumbr = int_value; - else if (strcmp("btn_tl", key) == 0) - map->btn_tl = int_value; - else if (strcmp("btn_tr", key) == 0) - map->btn_tr = int_value; - else if (strcmp("btn_tl2", key) == 0) - map->btn_tl2 = int_value; - else if (strcmp("btn_tr2", key) == 0) - map->btn_tr2 = int_value; - else if (strcmp("btn_dpad_up", key) == 0) - map->btn_dpad_up = int_value; - else if (strcmp("btn_dpad_down", key) == 0) - map->btn_dpad_down = int_value; - else if (strcmp("btn_dpad_left", key) == 0) - map->btn_dpad_left = int_value; - else if (strcmp("btn_dpad_right", key) == 0) - map->btn_dpad_right = int_value; - else if (strcmp("reverse_x", key) == 0) - map->reverse_x = strcmp("true", value) == 0; - else if (strcmp("reverse_y", key) == 0) - map->reverse_y = strcmp("true", value) == 0; - else if (strcmp("reverse_rx", key) == 0) - map->reverse_rx = strcmp("true", value) == 0; - else if (strcmp("reverse_ry", key) == 0) - map->reverse_ry = strcmp("true", value) == 0; - else if (strcmp("reverse_dpad_x", key) == 0) - map->reverse_dpad_x = strcmp("true", value) == 0; - else if (strcmp("reverse_dpad_y", key) == 0) - map->reverse_dpad_y = strcmp("true", value) == 0; - else - fprintf(stderr, "Can't map (%s)\n", key); - } - if (key != NULL) - free(key); + char* strpoint; + char* guid = strtok_r(line, ",", &strpoint); + char* name = strtok_r(NULL, ",", &strpoint); + if (guid == NULL || name == NULL) + continue; - if (value != NULL) - free(value); + struct mapping* newmap = malloc(sizeof(struct mapping)); + if (newmap == NULL) { + fprintf(stderr, "Not enough memory"); + exit(EXIT_FAILURE); + } else if (mappings == NULL) + mappings = newmap; + else + map->next = newmap; + + map = newmap; + + strncpy(map->guid, guid, sizeof(map->guid)); + strncpy(map->name, name, sizeof(map->name)); + + char* option; + while ((option = strtok_r(NULL, ",", &strpoint)) != NULL) { + char *key = NULL, *value = NULL; + int ret; + if ((ret = sscanf(option, "%m[^:]:%ms", &key, &value)) == 2) { + int int_value, direction_value; + char flag = 0; + if (strcmp("platform", key) == 0) + strncpy(map->platform, value, sizeof(map->platform)); + else if (sscanf(value, "b%d", &int_value) == 1) { + if (strcmp("a", key) == 0) + map->btn_a = int_value; + else if (strcmp("y", key) == 0) + map->btn_y = int_value; + else if (strcmp("x", key) == 0) + map->btn_x = int_value; + else if (strcmp("b", key) == 0) + map->btn_b = int_value; + else if (strcmp("back", key) == 0) + map->btn_back = int_value; + else if (strcmp("start", key) == 0) + map->btn_start = int_value; + else if (strcmp("guide", key) == 0) + map->btn_guide = int_value; + else if (strcmp("dpup", key) == 0) + map->btn_dpup = int_value; + else if (strcmp("dpdown", key) == 0) + map->btn_dpdown = int_value; + else if (strcmp("dpleft", key) == 0) + map->btn_dpleft = int_value; + else if (strcmp("dpright", key) == 0) + map->btn_dpright = int_value; + else if (strcmp("leftstick", key) == 0) + map->btn_leftstick = int_value; + else if (strcmp("rightstick", key) == 0) + map->btn_rightstick = int_value; + else if (strcmp("leftshoulder", key) == 0) + map->btn_leftshoulder = int_value; + else if (strcmp("rightshoulder", key) == 0) + map->btn_rightshoulder = int_value; + else if (strcmp("lefttrigger", key) == 0) + map->btn_lefttrigger = int_value; + else if (strcmp("righttrigger", key) == 0) + map->btn_righttrigger = int_value; + } else if (sscanf(value, "a%d%c", &int_value, &flag) >= 1) { + if (strcmp("leftx", key) == 0) { + map->abs_leftx = int_value; + map->reverse_leftx = flag == '~'; + } else if (strcmp("lefty", key) == 0) { + map->abs_lefty = int_value; + map->reverse_lefty = flag == '~'; + } else if (strcmp("rightx", key) == 0) { + map->abs_rightx = int_value; + map->reverse_rightx = flag == '~'; + } else if (strcmp("righty", key) == 0) { + map->abs_righty = int_value; + map->reverse_righty = flag == '~'; + } else if (strcmp("lefttrigger", key) == 0) + map->abs_lefttrigger = int_value; + else if (strcmp("righttrigger", key) == 0) + map->abs_righttrigger = int_value; + } else if (sscanf(value, "h%d.%d", &int_value, &direction_value) == 2) { + if (strcmp("dpright", key) == 0) { + map->hat_dpright = int_value; + map->hat_dir_dpright = direction_value; + } else if (strcmp("dpleft", key) == 0) { + map->hat_dpleft = int_value; + map->hat_dir_dpleft = direction_value; + } else if (strcmp("dpup", key) == 0) { + map->hat_dpup = int_value; + map->hat_dir_dpup = direction_value; + } else if (strcmp("dpdown", key) == 0) { + map->hat_dpdown = int_value; + map->hat_dir_dpdown = direction_value; + } + } else + fprintf(stderr, "Can't map (%s)\n", option); + } else if (ret == 0 && option[0] != '\n') + fprintf(stderr, "Can't map (%s)\n", option); + + if (key != NULL) + free(key); + + if (value != NULL) + free(value); + } + map->guid[32] = '\0'; + map->name[256] = '\0'; + map->platform[32] = '\0'; } free(line); -} - -void mapping_save(char* fileName, struct mapping* map) { - FILE* fd = fopen(fileName, "w"); - if (fd == NULL) { - fprintf(stderr, "Can't open mapping file: %s\n", fileName); - exit(EXIT_FAILURE); - } - - write_config(fd, "abs_x", map->abs_x); - write_config(fd, "abs_y", map->abs_y); - write_config(fd, "abs_z", map->abs_z); - - write_config_bool(fd, "reverse_x", map->reverse_x); - write_config_bool(fd, "reverse_y", map->reverse_y); - - write_config(fd, "abs_rx", map->abs_rx); - write_config(fd, "abs_ry", map->abs_ry); - write_config(fd, "abs_rz", map->abs_rz); - - write_config_bool(fd, "reverse_rx", map->reverse_rx); - write_config_bool(fd, "reverse_ry", map->reverse_ry); - - write_config(fd, "abs_deadzone", map->abs_deadzone); - - write_config(fd, "abs_dpad_x", map->abs_dpad_x); - write_config(fd, "abs_dpad_y", map->abs_dpad_y); - - write_config_bool(fd, "reverse_dpad_x", map->reverse_dpad_x); - write_config_bool(fd, "reverse_dpad_y", map->reverse_dpad_y); - - write_config(fd, "btn_north", map->btn_north); - write_config(fd, "btn_east", map->btn_east); - write_config(fd, "btn_south", map->btn_south); - write_config(fd, "btn_west", map->btn_west); - - write_config(fd, "btn_select", map->btn_select); - write_config(fd, "btn_start", map->btn_start); - write_config(fd, "btn_mode", map->btn_mode); - - write_config(fd, "btn_thumbl", map->btn_thumbl); - write_config(fd, "btn_thumbr", map->btn_thumbr); - - write_config(fd, "btn_tl", map->btn_tl); - write_config(fd, "btn_tr", map->btn_tr); - write_config(fd, "btn_tl2", map->btn_tl2); - write_config(fd, "btn_tr2", map->btn_tr2); - - write_config(fd, "btn_dpad_up", map->btn_dpad_up); - write_config(fd, "btn_dpad_down", map->btn_dpad_down); - write_config(fd, "btn_dpad_left", map->btn_dpad_left); - write_config(fd, "btn_dpad_right", map->btn_dpad_right); - - fclose(fd); + + return mappings; } diff --git a/src/input/mapping.h b/src/input/mapping.h index f62d1b1..c80b0a5 100644 --- a/src/input/mapping.h +++ b/src/input/mapping.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,27 +17,34 @@ * along with Moonlight; if not, see . */ +#pragma once + #include struct mapping { - short abs_x, abs_y, abs_z; - short abs_rx, abs_ry, abs_rz; + char guid[33]; + char platform[33]; + char name[257]; - bool reverse_x, reverse_y; - bool reverse_rx, reverse_ry; + bool reverse_leftx, reverse_lefty; + bool reverse_rightx, reverse_righty; + + short abs_leftx, abs_lefty; + short abs_rightx, abs_righty; - short abs_deadzone; + short hat_dpright, hat_dpleft, hat_dpup, hat_dpdown; + short hat_dir_dpright, hat_dir_dpleft, hat_dir_dpup, hat_dir_dpdown; + short btn_dpup, btn_dpdown, btn_dpleft, btn_dpright; + + short btn_a, btn_x, btn_y, btn_b; + short btn_back, btn_start, btn_guide; + short btn_leftstick, btn_rightstick; + short btn_leftshoulder, btn_rightshoulder; + + short abs_lefttrigger, abs_righttrigger; + short btn_lefttrigger, btn_righttrigger; - short abs_dpad_x, abs_dpad_y; - bool reverse_dpad_x, reverse_dpad_y; - - short btn_south, btn_east, btn_north, btn_west; - short btn_select, btn_start, btn_mode; - short btn_thumbl, btn_thumbr; - short btn_tl, btn_tr, btn_tl2, btn_tr2; - - short btn_dpad_up, btn_dpad_down, btn_dpad_left, btn_dpad_right; + struct mapping* next; }; -void mapping_load(char* fileName, struct mapping* map); -void mapping_save(char* fileName, struct mapping* map); +struct mapping* mapping_load(char* fileName); diff --git a/src/input/sdlinput.c b/src/input/sdl.c similarity index 88% rename from src/input/sdlinput.c rename to src/input/sdl.c index 67e0670..b0ae29c 100644 --- a/src/input/sdlinput.c +++ b/src/input/sdl.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,15 +17,14 @@ * along with Moonlight; if not, see . */ -#ifdef HAVE_SDL - -#include "sdlinput.h" +#include "sdl.h" #include "../sdl.h" #include #define ACTION_MODIFIERS (MODIFIER_SHIFT|MODIFIER_ALT|MODIFIER_CTRL) #define QUIT_KEY SDLK_q +#define QUIT_BUTTONS (PLAY_FLAG|BACK_FLAG|LB_FLAG|RB_FLAG) #define FULLSCREEN_KEY SDLK_f typedef struct _GAMEPAD_STATE { @@ -43,11 +42,11 @@ static GAMEPAD_STATE gamepads[4]; static int keyboard_modifiers; static int activeGamepadMask = 0; -void sdlinput_init() { +void sdlinput_init(char* mappings) { memset(gamepads, 0, sizeof(gamepads)); SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); - SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); + SDL_GameControllerAddMappingsFromFile(mappings); for (int i = 0; i < SDL_NumJoysticks(); ++i) { if (SDL_IsGameController(i)) { @@ -103,10 +102,20 @@ int sdlinput_handle_event(SDL_Event* event) { case SDL_KEYDOWN: case SDL_KEYUP: button = event->key.keysym.sym; - if (button >= (0x40000000 + 0x39) && button < (0x40000000 + 0x39 + sizeof(keyCodes))) - button = keyCodes[button - 0x40000039]; - else if (button >= 0x61) + if (button >= 0x21 && button <= 0x2f) + button = keyCodes1[button - 0x21]; + else if (button >= 0x3a && button <= 0x40) + button = keyCodes2[button - 0x3a]; + else if (button >= 0x5b && button <= 0x60) + button = keyCodes3[button - 0x5b]; + else if (button >= 0x40000039 && button < 0x40000039 + sizeof(keyCodes4)) + button = keyCodes4[button - 0x40000039]; + else if (button >= 0x400000E0 && button <= 0x400000E7) + button = keyCodes5[button - 0x400000E0]; + else if (button >= 0x61 && button <= 0x7a) button -= 0x20; + else if (button == 0x7f) + button = 0x2e; int modifier = 0; switch (event->key.keysym.sym) { @@ -224,10 +233,11 @@ int sdlinput_handle_event(SDL_Event* event) { else gamepad->buttons &= ~button; + if ((gamepad->buttons & QUIT_BUTTONS) == QUIT_BUTTONS) + return SDL_QUIT_APPLICATION; + LiSendMultiControllerEvent(gamepad->id, activeGamepadMask, gamepad->buttons, gamepad->leftTrigger, gamepad->rightTrigger, gamepad->leftStickX, gamepad->leftStickY, gamepad->rightStickX, gamepad->rightStickY); break; } return SDL_NOTHING; } - -#endif /* HAVE_SDL */ diff --git a/src/input/sdlinput.h b/src/input/sdl.h similarity index 55% rename from src/input/sdlinput.h rename to src/input/sdl.h index e7cd2f3..9157680 100644 --- a/src/input/sdlinput.h +++ b/src/input/sdl.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,12 +17,47 @@ * along with Moonlight; if not, see . */ -#ifdef HAVE_SDL - #include #include -static const short keyCodes[] = { +static const short keyCodes1[] = { + 0, //SDLK_EXCLAIM + 0, //SDLK_QUOTEDBL + 0, //SDLK_HASH + 0, //SDLK_DOLLAR + 0, //SDLK_PERCENT + 0, //SDLK_AMPERSAND + 0xDE, //SDLK_QUOTE + 0, //SDLK_LEFTPAREN + 0, //SDLK_RIGHTPAREN + 0, //SDLK_ASTERISK + 0, //SDLK_PLUS + 0xBC, //SDLK_COMMA + 0xBD, //SDLK_MINUS + 0xBE, //SDLK_PERIOD + 0xBF, //SDLK_SLASH +}; + +static const short keyCodes2[] = { + 0, //SDLK_COLON + 0xBA, //SDLK_SEMICOLON + 0, //SDLK_LESS + 0xBB, //SDLK_EQUALS + 0, //SDLK_GREATER + 0, //SDLK_QUESTION + 0, //SDLK_AT +}; + +static const short keyCodes3[] = { + 0xDB, //SDLK_LEFTBRACKET + 0xDC, //SDLK_BACKSLASH + 0xDD, //SDLK_RIGHTBRACKET + 0, //SDLK_CARET + 0, //SDLK_UNDERSCORE + 0xC0, //SDLK_BACKQUOTE +}; + +static const short keyCodes4[] = { 0x14, //SDLK_CAPSLOCK 0x70, //SDLK_F1 0x71, //SDLK_F2 @@ -51,7 +86,16 @@ static const short keyCodes[] = { 0x26, //SDLK_UP }; -void sdlinput_init(); -int sdlinput_handle_event(SDL_Event* event); +static const short keyCodes5[] = { + 0x11, //SDLK_LCTRL + 0x10, //SDLK_LSHIFT + 0x12, //SDLK_LALT + 0x5B, //SDLK_LGUI + 0x11, //SDLK_LRCTRL + 0x10, //SDLK_RSHIFT + 0x12, //SDLK_RALT + 0x5C, //SDLK_RGUI +}; -#endif /* HAVE_SDL */ +void sdlinput_init(char* mappings); +int sdlinput_handle_event(SDL_Event* event); diff --git a/src/input/udev.c b/src/input/udev.c index b587cd9..6766b91 100644 --- a/src/input/udev.c +++ b/src/input/udev.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include "../loop.h" +#include "udev.h" #include "evdev.h" #include @@ -31,7 +32,7 @@ #include static bool autoadd; -static char* defaultMapfile; +static struct mapping* defaultMappings; static struct udev *udev; static struct udev_monitor *udev_mon; @@ -45,7 +46,7 @@ static int udev_handle(int fd) { const char *devnode = udev_device_get_devnode(dev); int id; if (devnode != NULL && sscanf(devnode, "/dev/input/event%d", &id) == 1) { - evdev_create(devnode, defaultMapfile); + evdev_create(devnode, defaultMappings); } } udev_device_unref(dev); @@ -53,7 +54,7 @@ static int udev_handle(int fd) { return LOOP_OK; } -void udev_init(bool autoload, char* mapfile) { +void udev_init(bool autoload, struct mapping* mappings) { udev = udev_new(); if (!udev) { fprintf(stderr, "Can't create udev\n"); @@ -74,7 +75,7 @@ void udev_init(bool autoload, char* mapfile) { const char *devnode = udev_device_get_devnode(dev); int id; if (devnode != NULL && sscanf(devnode, "/dev/input/event%d", &id) == 1) { - evdev_create(devnode, mapfile); + evdev_create(devnode, mappings); } udev_device_unref(dev); } @@ -86,7 +87,7 @@ void udev_init(bool autoload, char* mapfile) { udev_monitor_filter_add_match_subsystem_devtype(udev_mon, "input", NULL); udev_monitor_enable_receiving(udev_mon); - defaultMapfile = mapfile; + defaultMappings = mappings; int udev_fd = udev_monitor_get_fd(udev_mon); loop_add_fd(udev_fd, &udev_handle, POLLIN); diff --git a/src/input/udev.h b/src/input/udev.h index b834adc..9414b6a 100644 --- a/src/input/udev.h +++ b/src/input/udev.h @@ -17,5 +17,7 @@ * along with Moonlight; if not, see . */ -void udev_init(bool autoload, char* mapfile); +#include "mapping.h" + +void udev_init(bool autoload, struct mapping* mappings); void evdev_destroy(); diff --git a/src/input/x11.c b/src/input/x11.c new file mode 100644 index 0000000..99f4b77 --- /dev/null +++ b/src/input/x11.c @@ -0,0 +1,152 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include "x11.h" +#include "keyboard.h" + +#include "../loop.h" + +#include + +#include +#include + +#include +#include +#include + +#define MODIFIERS (MODIFIER_SHIFT|MODIFIER_ALT|MODIFIER_CTRL) + +static Display *display; +static Window window; + +static Atom wm_deletemessage; + +static int last_x = -1, last_y = -1; +static int keyboard_modifiers; + +static const char data[1] = {0}; +static Cursor cursor; +static bool grabbed = True; + +static int x11_handler(int fd) { + XEvent event; + int button = 0; + int motion_x, motion_y; + + while (XPending(display)) { + XNextEvent(display, &event); + switch (event.type) { + case KeyPress: + case KeyRelease: + if (event.xkey.keycode >= 8 && event.xkey.keycode < (sizeof(keyCodes)/sizeof(keyCodes[0]) + 8)) { + if ((keyboard_modifiers & MODIFIERS) == MODIFIERS && event.type == KeyRelease) { + if (event.xkey.keycode == 0x18) + return LOOP_RETURN; + else { + grabbed = !grabbed; + XDefineCursor(display, window, grabbed ? cursor : 0); + } + } + + int modifier = 0; + switch (event.xkey.keycode) { + case 0x32: + case 0x3e: + modifier = MODIFIER_SHIFT; + break; + case 0x40: + case 0x6c: + modifier = MODIFIER_ALT; + break; + case 0x25: + case 0x69: + modifier = MODIFIER_CTRL; + break; + } + + if (modifier != 0) { + if (event.type == KeyPress) + keyboard_modifiers |= modifier; + else + keyboard_modifiers &= ~modifier; + } + + short code = 0x80 << 8 | keyCodes[event.xkey.keycode - 8]; + LiSendKeyboardEvent(code, event.type == KeyPress ? KEY_ACTION_DOWN : KEY_ACTION_UP, keyboard_modifiers); + } + break; + case ButtonPress: + case ButtonRelease: + switch (event.xbutton.button) { + case Button1: + button = BUTTON_LEFT; + break; + case Button2: + button = BUTTON_MIDDLE; + break; + case Button3: + button = BUTTON_RIGHT; + break; + } + + if (button != 0) + LiSendMouseButtonEvent(event.type==ButtonPress ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, button); + break; + case MotionNotify: + motion_x = event.xmotion.x - last_x; + motion_y = event.xmotion.y - last_y; + if (abs(motion_x) > 0 || abs(motion_y) > 0) { + if (last_x >= 0 && last_y >= 0) + LiSendMouseMoveEvent(motion_x, motion_y); + + if (grabbed) + XWarpPointer(display, None, window, 0, 0, 0, 0, 640, 360); + } + + last_x = grabbed ? 640 : event.xmotion.x; + last_y = grabbed ? 360 : event.xmotion.y; + break; + case ClientMessage: + if (event.xclient.data.l[0] == wm_deletemessage) + return LOOP_RETURN; + + break; + } + } + + return LOOP_OK; +} + +void x11_input_init(Display* x11_display, Window x11_window) { + display = x11_display; + window = x11_window; + + wm_deletemessage = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &wm_deletemessage, 1); + + /* make a blank cursor */ + XColor dummy; + Pixmap blank = XCreateBitmapFromData(display, window, data, 1, 1); + cursor = XCreatePixmapCursor(display, blank, blank, &dummy, &dummy, 0, 0); + XFreePixmap(display, blank); + XDefineCursor(display, window, cursor); + + loop_add_fd(ConnectionNumber(display), x11_handler, POLLIN | POLLERR | POLLHUP); +} diff --git a/src/global.h b/src/input/x11.h similarity index 86% rename from src/global.h rename to src/input/x11.h index f558716..86f6f21 100644 --- a/src/global.h +++ b/src/input/x11.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +17,6 @@ * along with Moonlight; if not, see . */ -#include +#include -extern pthread_t main_thread_id; - -void quit(); +void x11_input_init(Display* display, Window window); diff --git a/src/loop.c b/src/loop.c index 10f80ba..c7b68f4 100644 --- a/src/loop.c +++ b/src/loop.c @@ -19,7 +19,7 @@ #include "loop.h" -#include "global.h" +#include "connection.h" #include #include diff --git a/src/main.c b/src/main.c index 619cd3e..ebfdfe9 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,23 +18,30 @@ */ #include "loop.h" -#include "client.h" #include "connection.h" #include "configuration.h" -#include "audio.h" -#include "video.h" -#include "discover.h" #include "config.h" #include "platform.h" #include "sdl.h" +#include "audio/audio.h" +#include "video/video.h" + +#include "input/mapping.h" #include "input/evdev.h" #include "input/udev.h" +#ifdef HAVE_LIBCEC #include "input/cec.h" -#include "input/sdlinput.h" +#endif +#ifdef HAVE_SDL +#include "input/sdl.h" +#endif #include +#include +#include + #include #include #include @@ -87,6 +94,8 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys if (ret < 0) { if (ret == GS_NOT_SUPPORTED_4K) fprintf(stderr, "Server doesn't support 4K\n"); + else if (ret == GS_NOT_SUPPORTED_MODE) + fprintf(stderr, "Server doesn't support %dx%d (%d fps)\n", config->stream.width, config->stream.height, config->stream.fps); else fprintf(stderr, "Errorcode starting app: %d\n", ret); exit(-1); @@ -96,11 +105,13 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys if (config->fullscreen) drFlags |= DISPLAY_FULLSCREEN; - if (config->forcehw) - drFlags |= FORCE_HARDWARE_ACCELERATION; + if (config->debug_level > 0) { + printf("Stream %d x %d, %d fps, %d kbps\n", config->stream.width, config->stream.height, config->stream.fps, config->stream.bitrate); + connection_debug = true; + } - printf("Stream %d x %d, %d fps, %d kbps\n", config->stream.width, config->stream.height, config->stream.fps, config->stream.bitrate); - LiStartConnection(&server->serverInfo, &config->stream, &connection_callbacks, platform_get_video(system), platform_get_audio(system), NULL, drFlags); + platform_start(system); + LiStartConnection(&server->serverInfo, &config->stream, &connection_callbacks, platform_get_video(system), platform_get_audio(system, config->audio_device), NULL, drFlags, config->audio_device, 0); if (IS_EMBEDDED(system)) { evdev_start(); @@ -113,13 +124,14 @@ static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform sys #endif LiStopConnection(); + platform_stop(system); } static void help() { + printf("Moonlight Embedded %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); printf("Usage: moonlight [action] (options) [host]\n"); printf(" moonlight [configfile]\n"); printf("\n Actions\n\n"); - printf("\tmap\t\t\tCreate mapping file for gamepad\n"); printf("\tpair\t\t\tPair device with computer\n"); printf("\tunpair\t\t\tUnpair device with computer\n"); printf("\tstream\t\t\tStream computer to device\n"); @@ -129,35 +141,37 @@ static void help() { printf("\n Global Options\n\n"); printf("\t-config \tLoad configuration file\n"); printf("\t-save \t\tSave configuration file\n"); + printf("\t-verbose\t\tEnable verbose output\n"); + printf("\t-debug\t\t\tEnable verbose and debug output\n"); printf("\n Streaming options\n\n"); printf("\t-720\t\t\tUse 1280x720 resolution [default]\n"); printf("\t-1080\t\t\tUse 1920x1080 resolution\n"); + printf("\t-4k\t\t\tUse 3840x2160 resolution\n"); printf("\t-width \t\tHorizontal resolution (default 1280)\n"); printf("\t-height \tVertical resolution (default 720)\n"); - printf("\t-30fps\t\t\tUse 30fps\n"); - printf("\t-60fps\t\t\tUse 60fps [default]\n"); + printf("\t-fps \t\tSpecify the fps to use (default -1)\n"); printf("\t-bitrate \tSpecify the bitrate in Kbps\n"); printf("\t-packetsize \tSpecify the maximum packetsize in bytes\n"); - printf("\t-hevc\t\t\tUse the high efficiency video coding (HEVC)\n"); - printf("\t-h264\t\t\tUse the advanced video coding (H264)\n"); + printf("\t-codec \t\tSelect used codec: auto/h264/h265 (default auto)\n"); printf("\t-remote\t\t\tEnable remote optimizations\n"); printf("\t-app \t\tName of app to stream\n"); printf("\t-nosops\t\t\tDon't allow GFE to modify game settings\n"); printf("\t-localaudio\t\tPlay audio locally\n"); printf("\t-surround\t\tStream 5.1 surround sound (requires GFE 2.7)\n"); printf("\t-keydir \tLoad encryption keys from directory\n"); - #ifdef HAVE_SDL - printf("\n Video options (SDL Only)\n\n"); + printf("\t-mapping \t\tUse as gamepad mappings configuration file\n"); + printf("\t-platform \tSpecify system used for audio, video and input: pi/imx/aml/x11/x11_vdpau/sdl/fake (default auto)\n"); + printf("\t-unsupported\t\tTry streaming if GFE version is unsupported\n"); + #if defined(HAVE_SDL) || defined(HAVE_X11) + printf("\n WM options (SDL and X11 only)\n\n"); printf("\t-windowed\t\tDisplay screen in a window\n"); #endif #ifdef HAVE_EMBEDDED - printf("\n I/O options\n\n"); - printf("\t-mapping \t\tUse as gamepad mapping configuration file (use before -input)\n"); + printf("\n I/O options (Not for SDL)\n\n"); printf("\t-input \t\tUse as input. Can be used multiple times\n"); printf("\t-audio \t\tUse as audio output device\n"); - printf("\t-forcehw \t\tTry to use video hardware acceleration\n"); #endif - printf("\nUse Ctrl+Alt+Shift+Q to exit streaming session\n\n"); + printf("\nUse Ctrl+Alt+Shift+Q or Play+Back+LeftShoulder+RightShoulder to exit streaming session\n\n"); exit(0); } @@ -169,33 +183,14 @@ static void pair_check(PSERVER_DATA server) { } int main(int argc, char* argv[]) { - printf("Moonlight Embedded %d.%d.%d (%s)\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, COMPILE_OPTIONS); - CONFIGURATION config; config_parse(argc, argv, &config); if (config.action == NULL || strcmp("help", config.action) == 0) help(); - enum platform system = platform_check(config.platform); - if (system == 0) { - fprintf(stderr, "Platform '%s' not found\n", config.platform); - exit(-1); - } - config.stream.supportsHevc = config.codec != CODEC_H264 && (config.codec == CODEC_HEVC || platform_supports_hevc(system)); - - if (strcmp("map", config.action) == 0) { - if (config.address == NULL) { - perror("No filename for mapping"); - exit(-1); - } - udev_init(!inputAdded, config.mapping); - for (int i=0;i 0) + printf("Moonlight Embedded %d.%d.%d (%s)\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, COMPILE_OPTIONS); if (config.address == NULL) { config.address = malloc(MAX_ADDRESS_SIZE); @@ -221,7 +216,7 @@ int main(int argc, char* argv[]) { printf("Connect to %s...\n", config.address); int ret; - if ((ret = gs_init(&server, config.address, config.key_dir)) == GS_OUT_OF_MEMORY) { + if ((ret = gs_init(&server, config.address, config.key_dir, config.debug_level)) == GS_OUT_OF_MEMORY) { fprintf(stderr, "Not enough memory\n"); exit(-1); } else if (ret == GS_INVALID) { @@ -237,28 +232,57 @@ int main(int argc, char* argv[]) { exit(-1); } - printf("NVIDIA %s, GFE %s (protocol version %d)\n", server.gpuType, server.serverInfo.serverInfoGfeVersion, server.serverMajorVersion); + if (config.debug_level > 0) + printf("NVIDIA %s, GFE %s (%s, %s)\n", server.gpuType, server.serverInfo.serverInfoGfeVersion, server.gsVersion, server.serverInfo.serverInfoAppVersion); if (strcmp("list", config.action) == 0) { pair_check(&server); applist(&server); } else if (strcmp("stream", config.action) == 0) { pair_check(&server); + enum platform system = platform_check(config.platform); + if (config.debug_level > 0) + printf("Platform %s\n", platform_name(system)); + + if (system == 0) { + fprintf(stderr, "Platform '%s' not found\n", config.platform); + exit(-1); + } else if (system == SDL && config.audio_device != NULL) { + fprintf(stderr, "You can't select a audio device for SDL\n"); + exit(-1); + } + config.stream.supportsHevc = config.codec != CODEC_H264 && (config.codec == CODEC_HEVC || platform_supports_hevc(system)); + if (IS_EMBEDDED(system)) { + if (config.mapping == NULL) { + fprintf(stderr, "Please specify mapping file as default mapping could not be found.\n"); + exit(-1); + } + struct mapping* mappings = mapping_load(config.mapping); + for (int i=0;i 0) + printf("Add input %s...\n", config.inputs[i]); + + evdev_create(config.inputs[i], mappings); } - udev_init(!inputAdded, config.mapping); + udev_init(!inputAdded, mappings); evdev_init(); #ifdef HAVE_LIBCEC cec_init(); #endif /* HAVE_LIBCEC */ } #ifdef HAVE_SDL - else if (system == SDL) + else if (system == SDL) { + if (config.inputsCount > 0) { + fprintf(stderr, "You can't select input devices as SDL will automatically use all available controllers\n"); + exit(-1); + } + sdl_init(config.stream.width, config.stream.height, config.fullscreen); + sdlinput_init(config.mapping); + } #endif stream(&server, &config, system); diff --git a/src/platform.c b/src/platform.c index 951d853..680c1ea 100644 --- a/src/platform.c +++ b/src/platform.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,11 @@ #define _GNU_SOURCE #include "platform.h" -#include "audio.h" + +#include "util.h" + +#include "audio/audio.h" +#include "video/video.h" #include #include @@ -30,7 +34,7 @@ typedef bool(*ImxInit)(); enum platform platform_check(char* name) { - bool std = strcmp(name, "default") == 0; + bool std = strcmp(name, "auto") == 0; #ifdef HAVE_IMX if (std || strcmp(name, "imx") == 0) { void *handle = dlopen("libmoonlight-imx.so", RTLD_NOW | RTLD_GLOBAL); @@ -55,6 +59,16 @@ enum platform platform_check(char* name) { return AML; } #endif + #ifdef HAVE_X11 + if (std || strcmp(name, "x11") == 0 || strcmp(name, "x11_vdpau") == 0) { + int x11 = x11_init(strcmp(name, "x11") != 0); + #ifdef HAVE_VDPAU + if (strcmp(name, "x11") != 0 && x11 == 0) + return X11_VDPAU; + #endif + return X11; + } + #endif #ifdef HAVE_SDL if (std || strcmp(name, "sdl") == 0) return SDL; @@ -65,8 +79,48 @@ enum platform platform_check(char* name) { return 0; } +void platform_start(enum platform system) { + switch (system) { + #ifdef HAVE_AML + case AML: + blank_fb("/sys/class/graphics/fb0/blank", true); + blank_fb("/sys/class/graphics/fb1/blank", true); + break; + #endif + #ifdef HAVE_PI + case PI: + blank_fb("/sys/class/graphics/fb0/blank", true); + break; + #endif + } +} + +void platform_stop(enum platform system) { + switch (system) { + #ifdef HAVE_AML + case AML: + blank_fb("/sys/class/graphics/fb0/blank", false); + blank_fb("/sys/class/graphics/fb1/blank", false); + break; + #endif + #ifdef HAVE_PI + case PI: + blank_fb("/sys/class/graphics/fb0/blank", false); + break; + #endif + } +} + DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { switch (system) { + #ifdef HAVE_X11 + case X11: + return &decoder_callbacks_x11; + #ifdef HAVE_VDPAU + case X11_VDPAU: + return &decoder_callbacks_x11_vdpau; + #endif + #endif #ifdef HAVE_SDL case SDL: return &decoder_callbacks_sdl; @@ -83,13 +137,11 @@ DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { case AML: return (PDECODER_RENDERER_CALLBACKS) dlsym(RTLD_DEFAULT, "decoder_callbacks_aml"); #endif - case FAKE: - return &decoder_callbacks_fake; } return NULL; } -AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) { +AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system, char* audio_device) { switch (system) { #ifdef HAVE_SDL case SDL: @@ -102,12 +154,12 @@ AUDIO_RENDERER_CALLBACKS* platform_get_audio(enum platform system) { #endif default: #ifdef HAVE_PULSE - if (audio_pulse_init()) + if (audio_pulse_init(audio_device)) return &audio_callbacks_pulse; #endif + #ifdef HAVE_ALSA return &audio_callbacks_alsa; - case FAKE: - return &audio_callbacks_fake; + #endif } return NULL; } @@ -119,3 +171,24 @@ bool platform_supports_hevc(enum platform system) { } return false; } + +char* platform_name(enum platform system) { + switch(system) { + case PI: + return "Raspberry Pi (Broadcom)"; + case IMX: + return "i.MX6 (MXC Vivante)"; + case AML: + return "AMLogic VPU"; + case X11: + return "X Window System (software decoding)"; + case X11_VDPAU: + return "X Window System (VDPAU)"; + case SDL: + return "SDL2 (software decoding)"; + case FAKE: + return "Fake (no a/v output)"; + default: + return "Unknown"; + } +} diff --git a/src/platform.h b/src/platform.h index 8e9ee8c..54db256 100644 --- a/src/platform.h +++ b/src/platform.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,16 +26,13 @@ #define IS_EMBEDDED(SYSTEM) SYSTEM != SDL -enum platform { NONE, SDL, PI, IMX, AML, FAKE }; +enum platform { NONE, SDL, X11, X11_VDPAU, PI, IMX, AML, FAKE }; enum platform platform_check(char*); PDECODER_RENDERER_CALLBACKS platform_get_video(enum platform system); -PAUDIO_RENDERER_CALLBACKS platform_get_audio(enum platform system); +PAUDIO_RENDERER_CALLBACKS platform_get_audio(enum platform system, char* audio_device); bool platform_supports_hevc(enum platform system); +char* platform_name(enum platform system); -extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake; -extern AUDIO_RENDERER_CALLBACKS audio_callbacks_fake; -#ifdef HAVE_SDL -extern DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl; -void sdl_loop(); -#endif +void platform_start(enum platform system); +void platform_stop(enum platform system); diff --git a/src/sdl.c b/src/sdl.c index 63d0a72..a49d02e 100644 --- a/src/sdl.c +++ b/src/sdl.c @@ -20,7 +20,7 @@ #ifdef HAVE_SDL #include "sdl.h" -#include "input/sdlinput.h" +#include "input/sdl.h" #include @@ -68,8 +68,6 @@ void sdl_init(int width, int height, bool fullscreen) { fprintf(stderr, "Couldn't create mutex\n"); exit(1); } - - sdlinput_init(); } void sdl_loop() { diff --git a/src/audio/fake.c b/src/util.c similarity index 53% rename from src/audio/fake.c rename to src/util.c index d0c674e..2376221 100644 --- a/src/audio/fake.c +++ b/src/util.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2016 Iwan Timmer + * Copyright (C) 2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,28 +17,24 @@ * along with Moonlight; if not, see . */ -#include "../audio.h" +#include "util.h" +#include +#include +#include #include +#include -static FILE* fd; -static const char* fileName = "fake.opus"; +int blank_fb(char *path, bool clear) { + int fd = open(path, O_RDWR); -static void alsa_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) { - fd = fopen(fileName, "w"); + if(fd >= 0) { + int ret = write(fd, clear ? "1" : "0", 1); + if (ret < 0) + fprintf(stderr, "Failed to clear framebuffer %s: %d\n", path, ret); + + close(fd); + return 0; + } else + return -1; } - -static void alsa_renderer_cleanup() { - fclose(fd); -} - -static void alsa_renderer_decode_and_play_sample(char* data, int length) { - fwrite(data, length, 1, fd); -} - -AUDIO_RENDERER_CALLBACKS audio_callbacks_fake = { - .init = alsa_renderer_init, - .cleanup = alsa_renderer_cleanup, - .decodeAndPlaySample = alsa_renderer_decode_and_play_sample, - .capabilities = CAPABILITY_DIRECT_SUBMIT, -}; diff --git a/src/video.h b/src/util.h similarity index 87% rename from src/video.h rename to src/util.h index f258896..7e6541c 100644 --- a/src/video.h +++ b/src/util.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,5 +17,6 @@ * along with Moonlight; if not, see . */ -#define DISPLAY_FULLSCREEN 1 -#define FORCE_HARDWARE_ACCELERATION 2 +#include + +int blank_fb(char *path, bool clear); diff --git a/src/video/aml.c b/src/video/aml.c old mode 100644 new mode 100755 index 7925c9c..3858967 --- a/src/video/aml.c +++ b/src/video/aml.c @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015, 2016 Iwan Timmer + * Copyright (C) 2015-2017 Iwan Timmer * Copyright (C) 2016 OtherCrashOverride, Daniel Mehrwald * * Moonlight is free software; you can redistribute it and/or modify @@ -27,36 +27,15 @@ #include #include #include -#include +#include +#include #define SYNC_OUTSIDE 0x02 #define UCODE_IP_ONLY_PARAM 0x08 static codec_para_t codecParam = { 0 }; -static int osd_blank(char *path,int cmd) { - int fd; - char bcmd[16]; - - fd = open(path, O_CREAT|O_RDWR | O_TRUNC, 0644); - - if(fd>=0) { - sprintf(bcmd,"%d",cmd); - int ret = write(fd,bcmd,strlen(bcmd)); - if (ret < 0) { - printf("osd_blank error during write: %x\n", ret); - } - close(fd); - return 0; - } - - return -1; -} - -void aml_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { - osd_blank("/sys/class/graphics/fb0/blank",1); - osd_blank("/sys/class/graphics/fb1/blank",0); - +int aml_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { codecParam.stream_type = STREAM_TYPE_ES_VIDEO; codecParam.has_video = 1; codecParam.noblock = 0; @@ -86,7 +65,7 @@ void aml_setup(int videoFormat, int width, int height, int redrawRate, void* con break; default: printf("Video format not supported\n"); - exit(1); + return -1; } codecParam.am_sysinfo.width = width; @@ -94,17 +73,22 @@ void aml_setup(int videoFormat, int width, int height, int redrawRate, void* con codecParam.am_sysinfo.rate = 96000 / redrawRate; codecParam.am_sysinfo.param = (void*) ((size_t) codecParam.am_sysinfo.param | SYNC_OUTSIDE); - int api = codec_init(&codecParam); - if (api != 0) { - fprintf(stderr, "codec_init error: %x\n", api); - exit(1); + int ret; + if ((ret = codec_init(&codecParam)) != 0) { + fprintf(stderr, "codec_init error: %x\n", ret); + return -2; } + + if ((ret = codec_set_freerun_mode(&codecParam, 1)) != 0) { + fprintf(stderr, "Can't set Freerun mode: %x\n", ret); + return -2; + } + + return 0; } void aml_cleanup() { - int api = codec_close(&codecParam); - osd_blank("/sys/class/graphics/fb0/blank",0); - osd_blank("/sys/class/graphics/fb1/blank",0); + codec_close(&codecParam); } int aml_submit_decode_unit(PDECODE_UNIT decodeUnit) { diff --git a/src/video/egl.c b/src/video/egl.c new file mode 100644 index 0000000..bed03e7 --- /dev/null +++ b/src/video/egl.c @@ -0,0 +1,199 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include "egl.h" + +#include + +#include + +#include +#include +#include +#include +#include + +static const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; +static const char* texture_mappings[] = { "ymap", "umap", "vmap" }; +static const char* vertex_source = "\ +attribute vec2 position;\ +varying mediump vec2 tex_position;\ +\ +void main() {\ + gl_Position = vec4(position, 0, 1);\ + tex_position = vec2((position.x + 1.) / 2., (1. - position.y) / 2.);\ +}\ +"; + +static const char* fragment_source = "\ +uniform lowp sampler2D ymap;\ +uniform lowp sampler2D umap;\ +uniform lowp sampler2D vmap;\ +varying mediump vec2 tex_position;\ +\ +void main() {\ + mediump float y = texture2D(ymap, tex_position).r;\ + mediump float u = texture2D(umap, tex_position).r - .5;\n\ + mediump float v = texture2D(vmap, tex_position).r - .5;\n\ + lowp float r = y + 1.28033 * v;\ + lowp float g = y - .21482 * u - .38059 * v;\ + lowp float b = y + 2.12798 * u;\ + gl_FragColor = vec4(r, g, b, 1.0);\ +}\ +"; + +static const float vertices[] = { + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f +}; + +static const GLuint elements[] = { + 0, 1, 2, + 2, 3, 0 +}; + +static EGLDisplay display; +static EGLSurface surface; +static EGLContext context; + +static int width, height; +static bool current; + +static GLuint texture_id[3], texture_uniform[3]; +static GLuint shader_program; + +void egl_init(EGLNativeDisplayType native_display, NativeWindowType native_window, int display_width, int display_height) { + width = display_width; + height = display_height; + + // get an EGL display connection + display = eglGetDisplay(native_display); + if (display == EGL_NO_DISPLAY) { + fprintf( stderr, "EGL: error get display\n" ); + exit(EXIT_FAILURE); + } + + // initialize the EGL display connection + int major, minor; + EGLBoolean result = eglInitialize(display, &major, &minor); + if (result == EGL_FALSE) { + fprintf( stderr, "EGL: error initialising display\n"); + exit(EXIT_FAILURE); + } + + // get our config from the config class + EGLConfig config = NULL; + static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; + + EGLint totalConfigsFound = 0; + result = eglChooseConfig(display, attribute_list, &config, 1, &totalConfigsFound); + if (result != EGL_TRUE || totalConfigsFound == 0) { + fprintf(stderr, "EGL: Unable to query for available configs, found %d.\n", totalConfigsFound); + exit(EXIT_FAILURE); + } + + // bind the OpenGL API to the EGL + result = eglBindAPI(EGL_OPENGL_ES_API); + if (result == EGL_FALSE) { + fprintf(stderr, "EGL: error binding API\n"); + exit(EXIT_FAILURE); + } + + // create an EGL rendering context + context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes); + if (context == EGL_NO_CONTEXT) { + fprintf(stderr, "EGL: couldn't get a valid context\n"); + exit(EXIT_FAILURE); + } + + // finally we can create a new surface using this config and window + surface = eglCreateWindowSurface(display, config, (NativeWindowType) native_window, NULL); + eglMakeCurrent(display, surface, surface, context); + + glEnable(GL_TEXTURE_2D); + + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + GLuint ebo; + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW); + + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vertex_source, NULL); + glCompileShader(vertex_shader); + GLint maxLength = 0; + + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_source, NULL); + glCompileShader(fragment_shader); + + shader_program = glCreateProgram(); + glAttachShader(shader_program, vertex_shader); + glAttachShader(shader_program, fragment_shader); + + glLinkProgram(shader_program); + glBindAttribLocation(shader_program, 0, "position"); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); + + glGenTextures(3, texture_id); + for (int i = 0; i < 3; i++) { + glBindTexture(GL_TEXTURE_2D, texture_id[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, i > 0 ? width / 2 : width, i > 0 ? height / 2 : height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0); + + texture_uniform[i] = glGetUniformLocation(shader_program, texture_mappings[i]); + } + + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +void egl_draw(uint8_t* image[3]) { + if (!current) { + eglMakeCurrent(display, surface, surface, context); + current = True; + } + + glUseProgram(shader_program); + glEnableVertexAttribArray(0); + + for (int i = 0; i < 3; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, texture_id[i]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, i > 0 ? width / 2 : width, i > 0 ? height / 2 : height, GL_LUMINANCE, GL_UNSIGNED_BYTE, image[i]); + glUniform1i(texture_uniform[i], i); + } + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + eglSwapBuffers(display, surface); +} + +void egl_destroy() { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); +} diff --git a/src/global.c b/src/video/egl.h similarity index 74% rename from src/global.c rename to src/video/egl.h index c5992ba..b38260c 100644 --- a/src/global.c +++ b/src/video/egl.h @@ -1,7 +1,7 @@ /* * This file is part of Moonlight Embedded. * - * Copyright (C) 2015 Iwan Timmer + * Copyright (C) 2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,11 +17,8 @@ * along with Moonlight; if not, see . */ -#include -#include +#include -pthread_t main_thread_id; - -void quit() { - pthread_kill(main_thread_id, SIGTERM); -} +void egl_init(EGLNativeDisplayType native_display, NativeWindowType native_window, int display_width, int display_height); +void egl_draw(uint8_t* image[3]); +void egl_destroy(); diff --git a/src/video/fake.c b/src/video/fake.c deleted file mode 100644 index a9e3e3d..0000000 --- a/src/video/fake.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of Moonlight Embedded. - * - * Copyright (C) 2015 Iwan Timmer - * - * Moonlight is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * Moonlight is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Moonlight; if not, see . - */ - -#include - -#include - -static FILE* fd; -static const char* fileName = "fake.h264"; - -void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { - fd = fopen(fileName, "w"); -} - -void decoder_renderer_cleanup() { - fclose(fd); -} - -int decoder_renderer_submit_decode_unit(PDECODE_UNIT decodeUnit) { - PLENTRY entry = decodeUnit->bufferList; - while (entry != NULL) { - fwrite(entry->data, entry->length, 1, fd); - entry = entry->next; - } - return DR_OK; -} - -DECODER_RENDERER_CALLBACKS decoder_callbacks_fake = { - .setup = decoder_renderer_setup, - .cleanup = decoder_renderer_cleanup, - .submitDecodeUnit = decoder_renderer_submit_decode_unit, - .capabilities = CAPABILITY_DIRECT_SUBMIT, -}; diff --git a/src/video/ffmpeg.c b/src/video/ffmpeg.c index 7e0d9b9..00e6dda 100644 --- a/src/video/ffmpeg.c +++ b/src/video/ffmpeg.c @@ -40,8 +40,7 @@ static AVFrame** dec_frames; static int dec_frames_cnt; static int current_frame, next_frame; -enum decoders {SOFTWARE, VDPAU}; -enum decoders decoder_system; +enum decoders ffmpeg_decoder; #define BYTES_PER_PIXEL 4 @@ -66,12 +65,12 @@ int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer } if (decoder != NULL) - decoder_system = VDPAU; + ffmpeg_decoder = VDPAU; } #endif if (decoder == NULL) { - decoder_system = SOFTWARE; + ffmpeg_decoder = SOFTWARE; switch (videoFormat) { case VIDEO_FORMAT_H264: decoder = avcodec_find_decoder_by_name("h264"); @@ -133,7 +132,7 @@ int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer } #ifdef HAVE_VDPAU - if (decoder_system == VDPAU) + if (ffmpeg_decoder == VDPAU) vdpau_init(decoder_ctx, width, height); #endif @@ -156,16 +155,16 @@ void ffmpeg_destroy(void) { } } -AVFrame* ffmpeg_get_frame() { +AVFrame* ffmpeg_get_frame(bool native_frame) { int err = avcodec_receive_frame(decoder_ctx, dec_frames[next_frame]); if (err == 0) { current_frame = next_frame; next_frame = (current_frame+1) % dec_frames_cnt; - if (decoder_system == SOFTWARE) + if (ffmpeg_decoder == SOFTWARE || native_frame) return dec_frames[current_frame]; #ifdef HAVE_VDPAU - else if (decoder_system == VDPAU) + else if (ffmpeg_decoder == VDPAU) return vdpau_get_frame(dec_frames[current_frame]); #endif } else if (err != AVERROR(EAGAIN)) { diff --git a/src/video/ffmpeg.h b/src/video/ffmpeg.h index e65f585..9060e5e 100644 --- a/src/video/ffmpeg.h +++ b/src/video/ffmpeg.h @@ -17,6 +17,8 @@ * along with Moonlight; if not, see . */ +#include + #include // Disables the deblocking filter at the cost of image quality @@ -34,9 +36,12 @@ // Uses hardware acceleration #define HARDWARE_ACCELERATION 0x40 +enum decoders {SOFTWARE, VDPAU}; +extern enum decoders ffmpeg_decoder; + int ffmpeg_init(int videoFormat, int width, int height, int perf_lvl, int buffer_count, int thread_count); void ffmpeg_destroy(void); int ffmpeg_draw_frame(AVFrame *pict); -AVFrame* ffmpeg_get_frame(); +AVFrame* ffmpeg_get_frame(bool native_frame); int ffmpeg_decode(unsigned char* indata, int inlen); diff --git a/src/video/ffmpeg_vdpau.c b/src/video/ffmpeg_vdpau.c index ae2b119..3660e6b 100644 --- a/src/video/ffmpeg_vdpau.c +++ b/src/video/ffmpeg_vdpau.c @@ -34,17 +34,26 @@ static AVFrame* cpu_frame; static VdpDevice vdp_device; static VdpDecoder vdp_decoder; static VdpVideoMixer vdp_mixer; +static VdpPresentationQueue vdp_queue; +static VdpPresentationQueueTarget vdp_queue_target; +static VdpOutputSurface vdp_output; static struct vdpau_render_state* vdp_render_state[MAX_RENDER_STATES]; static int vdp_render_states = 0; static VdpGetProcAddress* vdp_get_proc_address; +static VdpDeviceDestroy* vdp_device_destroy; static VdpDecoderCreate* vdp_decoder_create; static VdpDecoderRender* vdp_decoder_render; static VdpVideoSurfaceGetBitsYCbCr* vdp_video_surface_get_bits_y_cb_cr; static VdpVideoSurfaceCreate* vdp_video_surface_create; static VdpVideoMixerCreate* vdp_video_mixer_create; +static VdpVideoMixerRender* vdp_video_mixer_render; +static VdpOutputSurfaceCreate* vdp_output_surface_create; +static VdpPresentationQueueCreate* vdp_presentation_queue_create; +static VdpPresentationQueueDisplay* vdp_presentation_queue_display; +static VdpPresentationQueueTargetCreateX11* vdp_presentation_queue_target_create_x11; -struct vdpau_render_state* vdp_get_free_render_state() { +struct vdpau_render_state* vdp_get_free_render_state(int width, int height) { for (unsigned i = 0; i < vdp_render_states; i++) { struct vdpau_render_state* render_state = vdp_render_state[i]; if (!render_state->state) @@ -60,7 +69,7 @@ struct vdpau_render_state* vdp_get_free_render_state() { vdp_render_states++; memset(render_state, 0, sizeof(struct vdpau_render_state)); render_state->surface = VDP_INVALID_HANDLE; - VdpStatus status = vdp_video_surface_create(vdp_device, VDP_CHROMA_TYPE_420, 1280, 720, &render_state->surface); + VdpStatus status = vdp_video_surface_create(vdp_device, VDP_CHROMA_TYPE_420, width, height, &render_state->surface); return render_state; } @@ -70,7 +79,7 @@ static void vdp_release_buffer(void* opaque, uint8_t *data) { } static int vdp_get_buffer(AVCodecContext* context, AVFrame* frame, int flags) { - struct vdpau_render_state* pRenderState = vdp_get_free_render_state(); + struct vdpau_render_state* pRenderState = vdp_get_free_render_state(frame->width, frame->height); frame->data[0] = (uint8_t*) pRenderState; frame->buf[0] = av_buffer_create(frame->data[0], 0, vdp_release_buffer, NULL, 0); @@ -85,32 +94,33 @@ static enum AVPixelFormat vdp_get_format(AVCodecContext* context, const enum AVP static void vdp_draw_horiz_band(struct AVCodecContext* context, const AVFrame* frame, int offset[4], int y, int type, int height) { struct vdpau_render_state* render_state = (struct vdpau_render_state*)frame->data[0]; - VdpStatus status = vdp_decoder_render(vdp_decoder, render_state->surface, (VdpPictureInfo const*)&(render_state->info), render_state->bitstream_buffers_used, render_state->bitstream_buffers); - status = vdp_decoder_render(vdp_decoder, render_state->surface, (VdpPictureInfo const*)&(render_state->info), render_state->bitstream_buffers_used, render_state->bitstream_buffers); + vdp_decoder_render(vdp_decoder, render_state->surface, (VdpPictureInfo const*)&(render_state->info), render_state->bitstream_buffers_used, render_state->bitstream_buffers); } -int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) { - if (vdp_device) - return vdp_device; - - Display* xdisplay = XOpenDisplay(0); - if (!xdisplay) - return -1; - - VdpStatus status = vdp_device_create_x11(xdisplay, DefaultScreen(xdisplay), &vdp_device, &vdp_get_proc_address); +int vdpau_init_lib(Display* display) { + VdpStatus status = vdp_device_create_x11(display, DefaultScreen(display), &vdp_device, &vdp_get_proc_address); if (status != VDP_STATUS_OK) return -1; + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DEVICE_DESTROY, (void**)&vdp_device_destroy); + return 0; +} + +int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) { vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, (void**)&vdp_video_surface_get_bits_y_cb_cr); vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_SURFACE_CREATE, (void**)&vdp_video_surface_create); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, (void**)&vdp_output_surface_create); vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DECODER_RENDER, (void**)&vdp_decoder_render); vdp_get_proc_address(vdp_device, VDP_FUNC_ID_DECODER_CREATE, (void**)&vdp_decoder_create); vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_MIXER_CREATE, (void**)&vdp_video_mixer_create); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_VIDEO_MIXER_RENDER, (void**)&vdp_video_mixer_render); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, (void**)&vdp_presentation_queue_create); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11, (void**)&vdp_presentation_queue_target_create_x11); + vdp_get_proc_address(vdp_device, VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, (void**)&vdp_presentation_queue_display); decoder_ctx->get_buffer2 = vdp_get_buffer; decoder_ctx->draw_horiz_band = vdp_draw_horiz_band; decoder_ctx->get_format = vdp_get_format; - decoder_ctx->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; cpu_frame = av_frame_alloc(); if (cpu_frame == NULL) { @@ -127,35 +137,19 @@ int vdpau_init(AVCodecContext* decoder_ctx, int width, int height) { return -1; } - status = vdp_decoder_create(vdp_device, VDP_DECODER_PROFILE_H264_HIGH, width, height, 16, &vdp_decoder); + VdpStatus status = vdp_decoder_create(vdp_device, VDP_DECODER_PROFILE_H264_HIGH, width, height, 16, &vdp_decoder); if (status != VDP_STATUS_OK) { printf("Can't create VDPAU decoder\n"); return -1; } - VdpVideoMixerFeature features[] = { - VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, - VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL, - }; - VdpVideoMixerParameter params[] = { - VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, - VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT, - VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE, - VDP_VIDEO_MIXER_PARAMETER_LAYERS - }; - VdpChromaType chroma = VDP_CHROMA_TYPE_420; - int numLayers = 0; - void const* paramValues[] = { &width, &height, &chroma, &numLayers }; - - status = vdp_video_mixer_create(vdp_device, 0, features, 4, params, paramValues, &vdp_mixer); - if (status != VDP_STATUS_OK) { - printf("Can't create VDPAU mixer\n"); - return -1; - } - return vdp_device; } +void vdpau_destroy() { + vdp_device_destroy(vdp_device); +} + AVFrame* vdpau_get_frame(AVFrame* dec_frame) { struct vdpau_render_state *render_state = (struct vdpau_render_state *)dec_frame->data[0]; void *dest[3] = { @@ -169,6 +163,35 @@ AVFrame* vdpau_get_frame(AVFrame* dec_frame) { cpu_frame->linesize[1] }; - VdpStatus status = vdp_video_surface_get_bits_y_cb_cr(render_state->surface, VDP_YCBCR_FORMAT_YV12, dest, pitches); + vdp_video_surface_get_bits_y_cb_cr(render_state->surface, VDP_YCBCR_FORMAT_YV12, dest, pitches); return cpu_frame; } + +int vdpau_init_presentation(Drawable win, int width, int height, int display_width, int display_height) { + VdpVideoMixerParameter params[] = { + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT + }; + void const* paramValues[] = { &width, &height }; + + if (vdp_video_mixer_create(vdp_device, 0, NULL, 2, params, paramValues, &vdp_mixer) != VDP_STATUS_OK) + return -1; + + if (vdp_output_surface_create(vdp_device, VDP_RGBA_FORMAT_B8G8R8A8, display_width, display_height, &vdp_output) != VDP_STATUS_OK) + return -1; + + if(vdp_presentation_queue_target_create_x11(vdp_device, win, &vdp_queue_target) != VDP_STATUS_OK) + return -1; + + if(vdp_presentation_queue_create(vdp_device, vdp_queue_target, &vdp_queue) != VDP_STATUS_OK) + return -1; + + return 0; +} + +void vdpau_queue(AVFrame* dec_frame) { + struct vdpau_render_state *render_state = (struct vdpau_render_state *)dec_frame->data[0]; + vdp_video_mixer_render(vdp_mixer, VDP_INVALID_HANDLE, 0, VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME, 0, (VdpVideoSurface*)VDP_INVALID_HANDLE, render_state->surface, 0,(VdpVideoSurface*)VDP_INVALID_HANDLE, NULL, vdp_output, NULL, NULL, 0, NULL); + + vdp_presentation_queue_display(vdp_queue, vdp_output, 0, 0, 0); +} diff --git a/src/video/ffmpeg_vdpau.h b/src/video/ffmpeg_vdpau.h index 13f2b69..f28a61b 100644 --- a/src/video/ffmpeg_vdpau.h +++ b/src/video/ffmpeg_vdpau.h @@ -17,7 +17,12 @@ * along with Moonlight; if not, see . */ +#include #include +int vdpau_init_lib(Display* display); int vdpau_init(AVCodecContext* decoder_ctx, int width, int height); +void vdpau_destroy(); AVFrame* vdpau_get_frame(AVFrame* dec_frame); +int vdpau_init_presentation(Drawable win, int width, int height, int display_width, int display_height); +void vdpau_queue(AVFrame* dec_frame); diff --git a/src/video/imx.c b/src/video/imx.c index 83979b0..64e490a 100644 --- a/src/video/imx.c +++ b/src/video/imx.c @@ -17,9 +17,10 @@ * along with Moonlight; if not, see . */ -#include "../loop.h" #include "imx_vpu.h" +#include "../loop.h" + #include #include #include @@ -114,10 +115,10 @@ static int frame_handle(int pipefd) { return LOOP_OK; } -static void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { +int decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { if (videoFormat != VIDEO_FORMAT_H264) { fprintf(stderr, "Video format not supported\n"); - exit(1); + return -1; } struct mxcfb_gbl_alpha alpha; @@ -126,14 +127,14 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if (fd_fb < 0){ fprintf(stderr, "Can't access framebuffer\n"); - exit(EXIT_FAILURE); + return -2; } alpha.alpha = 0; alpha.enable = 1; if (ioctl(fd_fb, MXCFB_SET_GBL_ALPHA, &alpha) < 0){ fprintf(stderr, "Can't set framebuffer output\n"); - exit(EXIT_FAILURE); + return -2; } close(fd_fb); @@ -147,7 +148,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r fd = open(v4l_device, O_RDWR, 0); if (fd < 0){ fprintf(stderr, "Can't access video output\n"); - exit(EXIT_FAILURE); + return -2; } struct v4l2_rect icrop = {0}; @@ -166,7 +167,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { fprintf(stderr, "Can't set source video format\n"); - exit(EXIT_FAILURE); + return -2; } if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) { @@ -183,12 +184,12 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) { fprintf(stderr, "Can't get video buffers\n"); - exit(EXIT_FAILURE); + return -2; } if (reqbuf.count < regfbcount) { fprintf(stderr, "Not enough video buffers\n"); - exit(EXIT_FAILURE); + return -2; } for (int i = 0; i < regfbcount; i++) { @@ -198,7 +199,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r buf = calloc(1, sizeof(struct vpu_buf)); if (buf == NULL) { fprintf(stderr, "Not enough memory\n"); - exit(EXIT_FAILURE); + return -2; } buffers[i] = buf; @@ -209,7 +210,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) { fprintf(stderr, "Can't get video buffer\n"); - exit(EXIT_FAILURE); + return -2; } buf->start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer.m.offset); @@ -220,7 +221,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r */ if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) { fprintf(stderr, "Can't set source video format\n"); - exit(EXIT_FAILURE); + return -2; } buf->offset = buffer.m.offset; @@ -228,7 +229,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if (buf->start == MAP_FAILED) { fprintf(stderr, "Failed to map video buffer\n"); - exit(EXIT_FAILURE); + return -2; } } @@ -236,13 +237,15 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if (pipe(pipefd) == -1 || pipe(clearpipefd) == -1) { fprintf(stderr, "Can't create communication channel between threads\n"); - exit(EXIT_FAILURE); + return -2; } loop_add_fd(pipefd[0], &frame_handle, POLLIN); fcntl(clearpipefd[0], F_SETFL, O_NONBLOCK); fcntl(pipefd[0], F_SETFL, O_NONBLOCK); + + return 0; } static int decoder_renderer_submit_decode_unit(PDECODE_UNIT decodeUnit) { diff --git a/src/video/pi.c b/src/video/pi.c index c433e08..bb91503 100644 --- a/src/video/pi.c +++ b/src/video/pi.c @@ -28,10 +28,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Video decode on Raspberry Pi using OpenMAX IL though the ilcient helper library // Based upon video decode example from the Raspberry Pi firmware -#include "sps.h" - #include +#include + #include #include #include @@ -53,10 +53,10 @@ static unsigned char *dest; static int port_settings_changed; static int first_packet; -static void decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { +int decoder_renderer_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { if (videoFormat != VIDEO_FORMAT_H264) { fprintf(stderr, "Video format not supported\n"); - exit(1); + return -1; } bcm_host_init(); @@ -71,18 +71,18 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if((client = ilclient_init()) == NULL) { fprintf(stderr, "Can't initialize video\n"); - exit(EXIT_FAILURE); + return -2; } if(OMX_Init() != OMX_ErrorNone) { fprintf(stderr, "Can't initialize OMX\n"); - exit(EXIT_FAILURE); + return -2; } // create video_decode if(ilclient_create_component(client, &video_decode, "video_decode", ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0){ fprintf(stderr, "Can't create video decode\n"); - exit(EXIT_FAILURE); + return -2; } list[0] = video_decode; @@ -90,7 +90,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r // create video_render if(ilclient_create_component(client, &video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0){ fprintf(stderr, "Can't create video renderer\n"); - exit(EXIT_FAILURE); + return -2; } list[1] = video_render; @@ -117,6 +117,31 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r if(OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamVideoPortFormat, &format) != OMX_ErrorNone || OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmDataUnit, &unit) != OMX_ErrorNone) { fprintf(stderr, "Failed to set video parameters\n"); + return -2; + } + + OMX_CONFIG_LATENCYTARGETTYPE latencyTarget; + memset(&latencyTarget, 0, sizeof(OMX_CONFIG_LATENCYTARGETTYPE)); + latencyTarget.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); + latencyTarget.nVersion.nVersion = OMX_VERSION; + latencyTarget.nPortIndex = 90; + latencyTarget.bEnabled = OMX_TRUE; + latencyTarget.nFilter = 2; + latencyTarget.nTarget = 4000; + latencyTarget.nShift = 3; + latencyTarget.nSpeedFactor = -135; + latencyTarget.nInterFactor = 500; + latencyTarget.nAdjCap = 20; + + OMX_CONFIG_DISPLAYREGIONTYPE displayRegion; + displayRegion.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); + displayRegion.nVersion.nVersion = OMX_VERSION; + displayRegion.nPortIndex = 90; + displayRegion.fullscreen = OMX_TRUE; + displayRegion.mode = OMX_DISPLAY_SET_FULLSCREEN; + + if(OMX_SetParameter(ILC_GET_HANDLE(video_render), OMX_IndexConfigLatencyTarget, &latencyTarget) != OMX_ErrorNone) { + fprintf(stderr, "Failed to set video render parameters\n"); exit(EXIT_FAILURE); } @@ -128,7 +153,7 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r port.nPortIndex = 130; if(OMX_GetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamPortDefinition, &port) != OMX_ErrorNone) { fprintf(stderr, "Failed to get decoder port definition\n"); - exit(EXIT_FAILURE); + return -2; } // Increase the buffer size to fit the largest possible frame @@ -143,8 +168,10 @@ static void decoder_renderer_setup(int videoFormat, int width, int height, int r ilclient_change_component_state(video_decode, OMX_StateExecuting); } else { fprintf(stderr, "Can't setup video\n"); - exit(EXIT_FAILURE); + return -2; } + + return 0; } static void decoder_renderer_cleanup() { diff --git a/src/video/sdl.c b/src/video/sdl.c index 14016a1..e20a3a1 100644 --- a/src/video/sdl.c +++ b/src/video/sdl.c @@ -17,36 +17,37 @@ * along with Moonlight; if not, see . */ -#include "../video.h" -#include "../sdl.h" +#include "video.h" #include "ffmpeg.h" -#include +#include "../sdl.h" #include #include +#include #include #define DECODER_BUFFER_SIZE 92*1024 static char* ffmpeg_buffer; -static void sdl_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { +static int sdl_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { int avc_flags = SLICE_THREADING; - if (drFlags & FORCE_HARDWARE_ACCELERATION) - avc_flags |= HARDWARE_ACCELERATION; - if (ffmpeg_init(videoFormat, width, height, avc_flags, SDL_BUFFER_FRAMES, 2) < 0) { + if (ffmpeg_init(videoFormat, width, height, avc_flags, SDL_BUFFER_FRAMES, sysconf(_SC_NPROCESSORS_ONLN)) < 0) { fprintf(stderr, "Couldn't initialize video decoding\n"); - exit(1); + return -1; } ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); if (ffmpeg_buffer == NULL) { fprintf(stderr, "Not enough memory\n"); - exit(1); + ffmpeg_destroy(); + return -1; } + + return 0; } static void sdl_cleanup() { @@ -65,7 +66,7 @@ static int sdl_submit_decode_unit(PDECODE_UNIT decodeUnit) { ffmpeg_decode(ffmpeg_buffer, length); if (SDL_LockMutex(mutex) == 0) { - AVFrame* frame = ffmpeg_get_frame(); + AVFrame* frame = ffmpeg_get_frame(false); if (frame != NULL) { sdlNextFrame++; @@ -92,5 +93,5 @@ DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl = { .setup = sdl_setup, .cleanup = sdl_cleanup, .submitDecodeUnit = sdl_submit_decode_unit, - .capabilities = CAPABILITY_SLICES_PER_FRAME(2) | CAPABILITY_REFERENCE_FRAME_INVALIDATION | CAPABILITY_DIRECT_SUBMIT, + .capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT, }; diff --git a/src/video/video.h b/src/video/video.h new file mode 100644 index 0000000..e56aeec --- /dev/null +++ b/src/video/video.h @@ -0,0 +1,36 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2015-2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include + +#include + +#define DISPLAY_FULLSCREEN 1 +#define ENABLE_HARDWARE_ACCELERATION 2 + +#ifdef HAVE_X11 +int x11_init(bool vdpau); +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_x11; +#ifdef HAVE_VDPAU +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_x11_vdpau; +#endif +#endif +#ifdef HAVE_SDL +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl; +#endif diff --git a/src/video/x11.c b/src/video/x11.c new file mode 100644 index 0000000..95de9ee --- /dev/null +++ b/src/video/x11.c @@ -0,0 +1,188 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moonlight; if not, see . + */ + +#include "video.h" +#include "egl.h" +#include "ffmpeg.h" +#ifdef HAVE_VDPAU +#include "ffmpeg_vdpau.h" +#endif + +#include "../input/x11.h" +#include "../loop.h" + +#include +#include + +#include +#include +#include +#include + +#define DECODER_BUFFER_SIZE 92*1024 + +static char* ffmpeg_buffer = NULL; + +static Display *display = NULL; + +static int pipefd[2]; + +static int frame_handle(int pipefd) { + AVFrame* frame = NULL; + while (read(pipefd, &frame, sizeof(void*)) > 0); + if (frame) + egl_draw(frame->data); + + return LOOP_OK; +} + +int x11_init(bool vdpau) { + XInitThreads(); + display = XOpenDisplay(NULL); + if (!display) + return -1; + + #ifdef HAVE_VDPAU + if (vdpau && vdpau_init_lib(display) != 0) + return -2; + #endif + + return 0; +} + +int x11_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { + ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + if (ffmpeg_buffer == NULL) { + fprintf(stderr, "Not enough memory\n"); + return -1; + } + + if (!display) { + fprintf(stderr, "Error: failed to open X display.\n"); + return -1; + } + + int display_width; + int display_height; + if (drFlags & DISPLAY_FULLSCREEN) { + Screen* screen = DefaultScreenOfDisplay(display); + display_width = WidthOfScreen(screen); + display_height = HeightOfScreen(screen); + } else { + display_width = width; + display_height = height; + } + + Window root = DefaultRootWindow(display); + XSetWindowAttributes winattr = { .event_mask = PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask }; + Window window = XCreateWindow(display, root, 0, 0, display_width, display_height, 0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask, &winattr); + XMapWindow(display, window); + XStoreName(display, window, "Moonlight"); + + if (drFlags & DISPLAY_FULLSCREEN) { + Atom wm_state = XInternAtom(display, "_NET_WM_STATE", False); + Atom fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False); + + XEvent xev = {0}; + xev.type = ClientMessage; + xev.xclient.window = window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = fullscreen; + xev.xclient.data.l[2] = 0; + + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } + XFlush(display); + + int avc_flags = SLICE_THREADING; + #ifdef HAVE_VDPAU + if (drFlags & ENABLE_HARDWARE_ACCELERATION) + avc_flags |= HARDWARE_ACCELERATION; + #endif + + if (ffmpeg_init(videoFormat, width, height, avc_flags, 2, 2) < 0) { + fprintf(stderr, "Couldn't initialize video decoding\n"); + return -1; + } + + if (ffmpeg_decoder == SOFTWARE) { + egl_init(display, window, width, height); + if (pipe(pipefd) == -1) { + fprintf(stderr, "Can't create communication channel between threads\n"); + return -2; + } + loop_add_fd(pipefd[0], &frame_handle, POLLIN); + fcntl(pipefd[0], F_SETFL, O_NONBLOCK); + } + #ifdef HAVE_VDPAU + else if (ffmpeg_decoder == VDPAU) + vdpau_init_presentation(window, width, height, display_width, display_height); + #endif + + x11_input_init(display, window); + + return 0; +} + +int x11_setup_vdpau(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { + return x11_setup(videoFormat, width, height, redrawRate, context, drFlags | ENABLE_HARDWARE_ACCELERATION); +} + +void x11_cleanup() { + ffmpeg_destroy(); + egl_destroy(); +} + +int x11_submit_decode_unit(PDECODE_UNIT decodeUnit) { + if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) { + PLENTRY entry = decodeUnit->bufferList; + int length = 0; + while (entry != NULL) { + memcpy(ffmpeg_buffer+length, entry->data, entry->length); + length += entry->length; + entry = entry->next; + } + ffmpeg_decode(ffmpeg_buffer, length); + AVFrame* frame = ffmpeg_get_frame(true); + if (frame != NULL) { + if (ffmpeg_decoder == SOFTWARE) + write(pipefd[1], &frame, sizeof(void*)); + else if (ffmpeg_decoder == VDPAU) + vdpau_queue(frame); + } + } + + return DR_OK; +} + +DECODER_RENDERER_CALLBACKS decoder_callbacks_x11 = { + .setup = x11_setup, + .cleanup = x11_cleanup, + .submitDecodeUnit = x11_submit_decode_unit, + .capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_DIRECT_SUBMIT, +}; + +DECODER_RENDERER_CALLBACKS decoder_callbacks_x11_vdpau = { + .setup = x11_setup_vdpau, + .cleanup = x11_cleanup, + .submitDecodeUnit = x11_submit_decode_unit, + .capabilities = CAPABILITY_DIRECT_SUBMIT, +}; diff --git a/third_party/moonlight-common-c b/third_party/moonlight-common-c index dcdfcd5..a1bdb36 160000 --- a/third_party/moonlight-common-c +++ b/third_party/moonlight-common-c @@ -1 +1 @@ -Subproject commit dcdfcd55e575e7f73e813e338fe9ab11ed1211a7 +Subproject commit a1bdb36766f8db5dc9cc0694c9a376f0dca3ab59