├── docs
├── CNAME
├── .gitignore
├── player.md
├── streamer.md
├── stream_overlay.md
├── _config.yml
├── assets
│ ├── css
│ │ ├── style.scss
│ │ ├── help.css
│ │ ├── volume-slider.scss
│ │ ├── volume-slider.css
│ │ ├── gamepad.css
│ │ └── general.css
│ ├── images
│ │ ├── joyconSpritesheet.png
│ │ ├── joyconSpritesheet2.png
│ │ ├── sprites
│ │ │ ├── joycons
│ │ │ │ ├── btn_A.png
│ │ │ │ ├── btn_B.png
│ │ │ │ ├── btn_L.png
│ │ │ │ ├── btn_R.png
│ │ │ │ ├── btn_X.png
│ │ │ │ ├── btn_Y.png
│ │ │ │ ├── btn_ZL.png
│ │ │ │ ├── btn_ZR.png
│ │ │ │ ├── btn_up.png
│ │ │ │ ├── btn_down.png
│ │ │ │ ├── btn_home.png
│ │ │ │ ├── btn_left.png
│ │ │ │ ├── btn_minus.png
│ │ │ │ ├── btn_plus.png
│ │ │ │ ├── btn_right.png
│ │ │ │ ├── btn_share.png
│ │ │ │ ├── left_joycon.png
│ │ │ │ ├── left_stick.png
│ │ │ │ ├── right_stick.png
│ │ │ │ ├── btn_A_pressed.png
│ │ │ │ ├── btn_B_pressed.png
│ │ │ │ ├── btn_L_pressed.png
│ │ │ │ ├── btn_R_pressed.png
│ │ │ │ ├── btn_X_pressed.png
│ │ │ │ ├── btn_Y_pressed.png
│ │ │ │ ├── btn_ZL_pressed.png
│ │ │ │ ├── btn_ZR_pressed.png
│ │ │ │ ├── btn_up_pressed.png
│ │ │ │ ├── right_joycon.png
│ │ │ │ ├── btn_down_pressed.png
│ │ │ │ ├── btn_home_pressed.png
│ │ │ │ ├── btn_left_pressed.png
│ │ │ │ ├── btn_plus_pressed.png
│ │ │ │ ├── xcf
│ │ │ │ │ ├── left_joycon.xcf
│ │ │ │ │ ├── right_joycon.xcf
│ │ │ │ │ └── switch_body.xcf
│ │ │ │ ├── btn_minus_pressed.png
│ │ │ │ ├── btn_right_pressed.png
│ │ │ │ ├── btn_share_pressed.png
│ │ │ │ ├── left_stick_pressed.png
│ │ │ │ ├── right_stick_pressed.png
│ │ │ │ └── originals
│ │ │ │ │ ├── left_joycon_original.png
│ │ │ │ │ ├── right_joycon_original.png
│ │ │ │ │ └── switch_body_original.png
│ │ │ └── pro_controller
│ │ │ │ ├── btn_A.png
│ │ │ │ ├── btn_B.png
│ │ │ │ ├── btn_L.png
│ │ │ │ ├── btn_R.png
│ │ │ │ ├── btn_X.png
│ │ │ │ ├── btn_Y.png
│ │ │ │ ├── btn_ZL.png
│ │ │ │ ├── btn_ZR.png
│ │ │ │ ├── btn_up.png
│ │ │ │ ├── stick.png
│ │ │ │ ├── btn_down.png
│ │ │ │ ├── btn_home.png
│ │ │ │ ├── btn_left.png
│ │ │ │ ├── btn_minus.png
│ │ │ │ ├── btn_plus.png
│ │ │ │ ├── btn_right.png
│ │ │ │ ├── btn_share.png
│ │ │ │ ├── btn_A_pressed.png
│ │ │ │ ├── btn_B_pressed.png
│ │ │ │ ├── btn_L_pressed.png
│ │ │ │ ├── btn_R_pressed.png
│ │ │ │ ├── btn_X_pressed.png
│ │ │ │ ├── btn_Y_pressed.png
│ │ │ │ ├── btn_ZL_pressed.png
│ │ │ │ ├── btn_ZR_pressed.png
│ │ │ │ ├── btn_up_overlay.png
│ │ │ │ ├── pro_controller.png
│ │ │ │ ├── stick_pressed.png
│ │ │ │ ├── btn_down_overlay.png
│ │ │ │ ├── btn_home_pressed.png
│ │ │ │ ├── btn_left_overlay.png
│ │ │ │ ├── btn_minus_pressed.png
│ │ │ │ ├── btn_plus_pressed.png
│ │ │ │ ├── btn_right_overlay.png
│ │ │ │ └── btn_share_pressed.png
│ │ ├── xboxGamepadSpritesheet.png
│ │ └── proControllerSpritesheet.png
│ └── js
│ │ ├── twitch-auth-config.template.js
│ │ ├── twitch-auth-config.js
│ │ ├── Utils.js
│ │ ├── SwitchProController.js
│ │ ├── main.js
│ │ ├── DualshockController.js
│ │ ├── H264WebSocketPlayer.js
│ │ ├── KeyboardInputSource.js
│ │ ├── BaseController.js
│ │ ├── ControlWebSocket.js
│ │ ├── lib
│ │ └── ws-audio-player.js
│ │ ├── stats.js
│ │ ├── ControlModeSelect.js
│ │ ├── OpusWebSocketPlayer.js
│ │ ├── PowerAWiredController.js
│ │ └── ServerStatus.js
├── index.md
├── gamepad.md
├── Gemfile
├── _layouts
│ ├── help.html
│ ├── post.html
│ ├── streamer.html
│ ├── player.html
│ ├── default.html
│ └── play.html
├── help.md
└── chat_help.md
├── Arduino
├── .gitignore
├── LICENSE
├── include
│ ├── avr.h
│ ├── avr_mock.h
│ ├── LUFAConfig.h
│ └── Joystick.h
├── Makefile
└── utils
│ ├── compare.py
│ ├── decode_usb_descriptors.py
│ └── emulator.py
├── MultiInput
├── qt
│ ├── stick.png
│ ├── MultiInput
│ ├── btn_ZL.png
│ ├── btn_ZR.png
│ ├── switch.png
│ ├── switch2.png
│ ├── switch_nostick.png
│ ├── resources.qrc
│ ├── xboxcontrollerinput.h
│ ├── controllercommand.h
│ ├── controllerwindow.h
│ ├── abstractcontrollercommand.h
│ ├── compositecontrollercommand.h
│ ├── main.cpp
│ ├── textcommandparser.h
│ ├── twitchircbotwindow.ui
│ ├── serialportwriter.h
│ ├── controllerwindow.cpp
│ ├── MultiInput.pro
│ ├── controllerinput.h
│ ├── controllercommand.cpp
│ ├── compositecontrollercommand.cpp
│ ├── compositecontrollercommand.cpp.autosave
│ ├── controlleropenglwidget.h
│ ├── tcpinputserver.h
│ ├── multiinput.h
│ ├── controllerconstants.h
│ ├── controllerstate.h
│ ├── controllerinput.cpp
│ ├── xboxcontrollerinput.cpp
│ └── serialportwriter.cpp
├── .gitignore
├── help.md
└── mapping_generator.py
├── .gitmodules
├── InputServer
├── InputServer
│ ├── DPad.cs
│ ├── IInputSink.cs
│ ├── Button.cs
│ ├── InputCallbacks.cs
│ ├── InputServer.csproj
│ ├── Program.cs
│ ├── Utils.cs
│ ├── ClientQueue.cs
│ ├── InputFrame.cs
│ ├── InputWsClient.cs
│ └── TwitchUser.cs
└── InputServer.sln
├── README.md
└── .gitignore
/docs/CNAME:
--------------------------------------------------------------------------------
1 | twitchplays.gg
--------------------------------------------------------------------------------
/Arduino/.gitignore:
--------------------------------------------------------------------------------
1 | emulator
2 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | .idea
--------------------------------------------------------------------------------
/docs/player.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: player
3 | ---
--------------------------------------------------------------------------------
/docs/streamer.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: streamer
3 | ---
--------------------------------------------------------------------------------
/docs/stream_overlay.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: stream_overlay
3 | ---
--------------------------------------------------------------------------------
/MultiInput/qt/stick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/stick.png
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-hacker
2 | repository: wchill/SwitchInputEmulator
3 | markdown: kramdown
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Arduino/lufa"]
2 | path = Arduino/lufa
3 | url = https://github.com/abcminiuser/lufa.git
--------------------------------------------------------------------------------
/MultiInput/qt/MultiInput:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/MultiInput
--------------------------------------------------------------------------------
/MultiInput/qt/btn_ZL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/btn_ZL.png
--------------------------------------------------------------------------------
/MultiInput/qt/btn_ZR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/btn_ZR.png
--------------------------------------------------------------------------------
/MultiInput/qt/switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/switch.png
--------------------------------------------------------------------------------
/MultiInput/qt/switch2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/switch2.png
--------------------------------------------------------------------------------
/MultiInput/qt/switch_nostick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/MultiInput/qt/switch_nostick.png
--------------------------------------------------------------------------------
/docs/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | @import "{{ site.theme }}";
5 |
6 | .container {
7 | max-width: 1920px;
8 | }
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: play
3 | ---
4 |
5 | [Controller not responding or not showing up, or just need help?](gamepad_help.md)
--------------------------------------------------------------------------------
/docs/gamepad.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: play
3 | ---
4 |
5 | [Controller not responding or not showing up, or just need help?](gamepad_help.md)
--------------------------------------------------------------------------------
/docs/assets/images/joyconSpritesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/joyconSpritesheet.png
--------------------------------------------------------------------------------
/docs/assets/images/joyconSpritesheet2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/joyconSpritesheet2.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_A.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_B.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_B.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_L.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_L.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_R.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_R.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_X.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_Y.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_Y.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_ZL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_ZL.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_ZR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_ZR.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_up.png
--------------------------------------------------------------------------------
/docs/assets/images/xboxGamepadSpritesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/xboxGamepadSpritesheet.png
--------------------------------------------------------------------------------
/docs/assets/images/proControllerSpritesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/proControllerSpritesheet.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_down.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_home.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_left.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_minus.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_plus.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_right.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_share.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/left_joycon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/left_joycon.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/left_stick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/left_stick.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/right_stick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/right_stick.png
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'github-pages', group: :jekyll_plugins
3 | gem 'wdm', '>= 0.1.0' if Gem.win_platform?
4 | gem 'jekyll-relative-links'
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_A_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_A_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_B_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_B_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_L_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_L_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_R_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_R_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_X_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_X_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_Y_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_Y_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_ZL_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_ZL_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_ZR_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_ZR_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_up_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_up_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/right_joycon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/right_joycon.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_A.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_B.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_B.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_L.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_L.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_R.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_R.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_X.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_Y.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_Y.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_ZL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_ZL.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_ZR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_ZR.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_up.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/stick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/stick.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_down_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_down_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_home_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_home_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_left_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_left_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_plus_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_plus_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/xcf/left_joycon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/xcf/left_joycon.xcf
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/xcf/right_joycon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/xcf/right_joycon.xcf
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/xcf/switch_body.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/xcf/switch_body.xcf
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_down.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_home.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_left.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_minus.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_plus.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_right.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_share.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_minus_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_minus_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_right_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_right_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/btn_share_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/btn_share_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/left_stick_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/left_stick_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/right_stick_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/right_stick_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_A_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_A_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_B_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_B_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_L_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_L_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_R_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_R_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_X_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_X_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_Y_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_Y_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_ZL_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_ZL_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_ZR_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_ZR_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_up_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_up_overlay.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/pro_controller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/pro_controller.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/stick_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/stick_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_down_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_down_overlay.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_home_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_home_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_left_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_left_overlay.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_minus_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_minus_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_plus_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_plus_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_right_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_right_overlay.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/pro_controller/btn_share_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/pro_controller/btn_share_pressed.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/originals/left_joycon_original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/originals/left_joycon_original.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/originals/right_joycon_original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/originals/right_joycon_original.png
--------------------------------------------------------------------------------
/docs/assets/images/sprites/joycons/originals/switch_body_original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wchill/SwitchInputEmulator/HEAD/docs/assets/images/sprites/joycons/originals/switch_body_original.png
--------------------------------------------------------------------------------
/MultiInput/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | thumbs.db
3 | .idea
4 | build-*
5 | moc_*.cpp
6 | moc_*.h
7 | .qmake.stash
8 | *.pro.user.*
9 | *.pro.user
10 | qt/Makefile
11 | qt/qrc_resources.cpp
12 | qt/ui_*.h
13 |
--------------------------------------------------------------------------------
/docs/_layouts/help.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
6 |
7 |
8 |
{{content}}
--------------------------------------------------------------------------------
/docs/assets/css/help.css:
--------------------------------------------------------------------------------
1 | .help-container {
2 | font-family: Open Sans;
3 | }
4 | .help-container > h1, h2 {
5 | font-family: Open Sans, monospace; color: #b5e853;
6 | }
7 | .help-container > h3, h4, h5, h6 {
8 | font-family: Open Sans, monospace; color: #eaeaea;
9 | }
--------------------------------------------------------------------------------
/MultiInput/qt/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | switch.png
4 | btn_ZR.png
5 | btn_ZL.png
6 | switch_nostick.png
7 | stick.png
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/assets/js/twitch-auth-config.template.js:
--------------------------------------------------------------------------------
1 | // CONFIG: twitch oauth settings
2 | window.twitch_auth_config = {
3 | clientId: PLEASE_CONFIG_ME, // from [Twitch Client ID](https://glass.twitch.tv/console/apps)
4 | redirect: window.location.origin + '/gamepad.html', // from [Twitch OAuth Redirect URL](https://glass.twitch.tv/console/apps)
5 | };
6 |
--------------------------------------------------------------------------------
/docs/_layouts/post.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 | {{ page.date | date: "%-d %B %Y" }}
6 | {{ page.title }}
7 |
8 | by {{ page.author | default: site.author }}
9 |
10 | {{content}}
11 |
12 | {% if page.tags %}
13 | tags: {{ page.tags | join: " - " }}
14 | {% endif %}
--------------------------------------------------------------------------------
/InputServer/InputServer/DPad.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace InputServer
6 | {
7 | public enum DPad
8 | {
9 | Up = 0,
10 | UpRight = 1,
11 | Right = 2,
12 | DownRight = 3,
13 | Down = 4,
14 | DownLeft = 5,
15 | Left = 6,
16 | UpLeft = 7,
17 | None = 8
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/InputServer/InputServer/IInputSink.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace InputServer
6 | {
7 | interface IInputSink
8 | {
9 | void Reset();
10 | void Update(InputFrame newFrame);
11 | void WaitFrames(int numFrames);
12 | string GetStateStr();
13 | void AddStateListener(OnUpdateCallback cb);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/InputServer/InputServer/Button.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace InputServer
6 | {
7 | [Flags]
8 | public enum Button
9 | {
10 | None = 0,
11 | Y = 1,
12 | B = 2,
13 | A = 4,
14 | X = 8,
15 | L = 16,
16 | R = 32,
17 | ZL = 64,
18 | ZR = 128,
19 | Minus = 256,
20 | Plus = 512,
21 | L3 = 1024,
22 | R3 = 2048,
23 | Home = 4096,
24 | Share = 8192,
25 | All = 16383
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docs/assets/js/twitch-auth-config.js:
--------------------------------------------------------------------------------
1 | // CONFIG: twitch oauth settings
2 | if (window.location.origin.indexOf('localhost') > -1) {
3 | window.twitch_auth_config = {
4 | clientId: 'sa5pewo51b3fi5d70le38sj5916iz5',
5 | redirect: window.location.origin
6 | };
7 | } else {
8 | window.twitch_auth_config = {
9 | clientId: '6ilamg1dh1d2fwi30x5ryiarfq6y86', // from [Twitch Client ID](https://glass.twitch.tv/console/apps)
10 | redirect: window.location.origin, // from [Twitch OAuth Redirect URL](https://glass.twitch.tv/console/apps)
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/InputServer/InputServer/InputCallbacks.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using WebSocketSharp.Server;
5 |
6 | namespace InputServer
7 | {
8 | public delegate void OnOpenCallback(string id);
9 |
10 | public delegate void OnInputCallback(TwitchUser user, string frame);
11 |
12 | public delegate void OnTurnRequestCallback(TwitchUser user);
13 |
14 | public delegate void OnTurnCancelCallback(TwitchUser user);
15 |
16 | public delegate void OnAddListenerCallback(WebSocketBehavior client);
17 |
18 | public delegate void OnUpdateCallback(string stateStr);
19 | }
20 |
--------------------------------------------------------------------------------
/InputServer/InputServer/InputServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net471
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/MultiInput/qt/xboxcontrollerinput.h:
--------------------------------------------------------------------------------
1 | #ifndef XBOXCONTROLLERINPUT_H
2 | #define XBOXCONTROLLERINPUT_H
3 |
4 | #include
5 | #include "controllerinput.h"
6 |
7 | class XboxControllerInput : public ControllerInput
8 | {
9 | Q_OBJECT
10 | public:
11 | XboxControllerInput(int deviceId, std::shared_ptr writer, QObject *parent = nullptr);
12 | virtual void begin();
13 | virtual void getState(quint8 *outLx, quint8 *outLy, quint8 *outRx, quint8 *outRy, Dpad_t *outDpad, Button_t *outButtons, uint8_t *outVendorspec);
14 | private:
15 | std::unique_ptr gamepad;
16 | QByteArray lastState;
17 | };
18 |
19 | #endif // XBOXCONTROLLERINPUT_H
20 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllercommand.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLERCOMMAND_H
2 | #define CONTROLLERCOMMAND_H
3 |
4 | #include "abstractcontrollercommand.h"
5 |
6 | class ControllerCommand : public AbstractControllerCommand
7 | {
8 | public:
9 | ControllerCommand(QString name, ControllerState state);
10 | ControllerCommand(QString name, QList states);
11 | virtual ControllerState getNextState();
12 | virtual int getRemainingPackets();
13 | virtual bool hasPackets();
14 | virtual std::unique_ptr clone();
15 | private:
16 | QList states;
17 | int remaining;
18 | int index;
19 | };
20 |
21 | #endif // CONTROLLERCOMMAND_H
22 |
--------------------------------------------------------------------------------
/InputServer/InputServer/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.IdentityModel.Tokens.Jwt;
5 | using System.IO.Ports;
6 | using System.Linq;
7 | using System.Security.Cryptography;
8 | using System.Threading;
9 | using Microsoft.IdentityModel.Tokens;
10 | using WebSocketSharp;
11 | using WebSocketSharp.Server;
12 |
13 | namespace InputServer
14 | {
15 |
16 | class Program
17 | {
18 | public static void Main(string[] args)
19 | {
20 | var sink = new SwitchInputSink("COM4");
21 | var server = new InputWsServer(31338, sink);
22 | server.Start();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllerwindow.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLERWINDOW_H
2 | #define CONTROLLERWINDOW_H
3 |
4 | #include
5 | #include
6 | #include "controllerinput.h"
7 | #include "controllerconstants.h"
8 |
9 | class ControllerWindow : public QDialog
10 | {
11 | Q_OBJECT
12 |
13 | public:
14 | explicit ControllerWindow(std::shared_ptr controller, QWidget *parent = nullptr);
15 |
16 | signals:
17 | void controllerWindowClosing();
18 | void error(const QString &error);
19 | void warning(const QString &warning);
20 | void message(const QString &message);
21 |
22 | protected:
23 | void closeEvent(QCloseEvent *event);
24 | };
25 |
26 | #endif // CONTROLLERWINDOW_H
27 |
--------------------------------------------------------------------------------
/MultiInput/qt/abstractcontrollercommand.h:
--------------------------------------------------------------------------------
1 | #ifndef ABSTRACTCONTROLLERCOMMAND_H
2 | #define ABSTRACTCONTROLLERCOMMAND_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "controllerstate.h"
8 |
9 | class AbstractControllerCommand
10 | {
11 | public:
12 | AbstractControllerCommand() {}
13 | AbstractControllerCommand(QString name) : name(name) {}
14 | virtual QString getName() {return name;}
15 | virtual ControllerState getNextState() = 0;
16 | virtual int getRemainingPackets() = 0;
17 | virtual bool hasPackets() = 0;
18 | virtual std::unique_ptr clone() = 0;
19 | private:
20 | QString name;
21 | };
22 |
23 | #endif // ABSTRACTCONTROLLERCOMMAND_H
24 |
--------------------------------------------------------------------------------
/docs/_layouts/streamer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MultiInput/qt/compositecontrollercommand.h:
--------------------------------------------------------------------------------
1 | #ifndef COMPOSITECONTROLLERCOMMAND_H
2 | #define COMPOSITECONTROLLERCOMMAND_H
3 |
4 | #include
5 | #include
6 | #include "abstractcontrollercommand.h"
7 |
8 | class CompositeControllerCommand : public AbstractControllerCommand
9 | {
10 | public:
11 | CompositeControllerCommand(QString name);
12 | virtual ControllerState getNextState();
13 | virtual int getRemainingPackets();
14 | virtual bool hasPackets();
15 | virtual std::unique_ptr clone();
16 |
17 | void addCommand(std::unique_ptr command);
18 | private:
19 | std::list> subcommands;
20 | };
21 |
22 | #endif // COMPOSITECONTROLLERCOMMAND_H
23 |
--------------------------------------------------------------------------------
/docs/_layouts/player.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/InputServer/InputServer/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace InputServer
6 | {
7 | public static class Utils
8 | {
9 | public static byte CalculateCrc8(byte[] data, int off, int len, byte start = 0)
10 | {
11 | var output = start;
12 | for (var i = off; i < len; i++)
13 | {
14 | output ^= data[i];
15 | for (var j = 0; j < 8; j++)
16 | {
17 | if ((output & 0x80) != 0)
18 | {
19 | output <<= 1;
20 | output ^= 0x07;
21 | }
22 | else
23 | {
24 | output <<= 1;
25 | }
26 | }
27 | }
28 |
29 | return output;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/MultiInput/qt/main.cpp:
--------------------------------------------------------------------------------
1 | #include "multiinput.h"
2 | #include "controllerconstants.h"
3 | #include
4 | #include
5 | #include
6 |
7 | int main(int argc, char *argv[])
8 | {
9 | QApplication::addLibraryPath(".");
10 | QApplication::addLibraryPath("~/Qt/5.10.1/gcc_64/lib/");
11 | QApplication a(argc, argv);
12 |
13 | qRegisterMetaType();
14 | qRegisterMetaType("Button_t");
15 |
16 | QSurfaceFormat format;
17 | format.setDepthBufferSize(24);
18 | format.setStencilBufferSize(8);
19 | format.setVersion(3, 2);
20 | format.setProfile(QSurfaceFormat::CoreProfile);
21 | QSurfaceFormat::setDefaultFormat(format);
22 |
23 | QGamepadManager::instance();
24 | QWindow* window = new QWindow();
25 | window->show();
26 | delete window;
27 | QGuiApplication::processEvents();
28 |
29 | MultiInput w;
30 | w.show();
31 |
32 | return a.exec();
33 | }
34 |
--------------------------------------------------------------------------------
/MultiInput/qt/textcommandparser.h:
--------------------------------------------------------------------------------
1 | #ifndef TEXTCOMMANDPARSER_H
2 | #define TEXTCOMMANDPARSER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "optional.h"
9 | #include "controllerstate.h"
10 | #include "controllerconstants.h"
11 | #include "abstractcontrollercommand.h"
12 | #include "compositecontrollercommand.h"
13 | #include "controllercommand.h"
14 |
15 | class TextCommandParser
16 | {
17 | public:
18 | TextCommandParser() {}
19 | TextCommandParser(QString mapping);
20 | void readCommandMapping(QString mapping);
21 | std::unique_ptr parseLine(QString line);
22 | ControllerState parseUpdateFrame(QString line);
23 | private:
24 | std::unique_ptr parseSimultaneousCommands(QString line);
25 | QList parseIndividualCommand(QString line);
26 |
27 | QMap> commandMapping;
28 | };
29 |
30 | #endif // TEXTCOMMANDPARSER_H
31 |
--------------------------------------------------------------------------------
/MultiInput/qt/twitchircbotwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | TwitchIrcBotWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 580
11 |
12 |
13 |
14 | Twitch Chat
15 |
16 |
17 |
18 |
19 | 10
20 | 10
21 | 381
22 | 561
23 |
24 |
25 |
26 |
27 | 0
28 | 0
29 |
30 |
31 |
32 | true
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/MultiInput/qt/serialportwriter.h:
--------------------------------------------------------------------------------
1 | #ifndef SERIALPORTWRITER_H
2 | #define SERIALPORTWRITER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | class SerialPortWriter : public QThread
11 | {
12 | Q_OBJECT
13 | public:
14 | explicit SerialPortWriter(const QString &portName, const QByteArray &data, QObject *parent = nullptr);
15 | ~SerialPortWriter();
16 |
17 | void changeData(const QByteArray &newData);
18 |
19 | signals:
20 | void error(const QString &s);
21 | void timeout(const QString &s);
22 | void message(const QString &s);
23 | void writeComplete();
24 | void synced();
25 |
26 | private:
27 | void run() override;
28 | bool writeAndExpectResponse(QSerialPort *serial, uint8_t send, uint8_t expect);
29 |
30 | QString m_portName;
31 | bool m_quit = false;
32 | QByteArray data;
33 | QMutex m_mutex;
34 |
35 | const quint8 sync_bytes[3] = {0xFF, 0x33, 0xCC};
36 | const quint8 sync_resp[3] = {0xFF, 0xCC, 0x33};
37 | };
38 |
39 | #endif // SERIALPORTWRITER_H
40 |
--------------------------------------------------------------------------------
/Arduino/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 @progmem, @wchill
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllerwindow.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "controllerwindow.h"
3 | #include "controlleropenglwidget.h"
4 |
5 | ControllerWindow::ControllerWindow(std::shared_ptr controller, QWidget *parent) :
6 | QDialog(parent)
7 | {
8 | setMinimumSize(400, 400);
9 |
10 | setWindowTitle(tr("Controller"));
11 | ControllerOpenGLWidget *openGL = new ControllerOpenGLWidget(controller, this);
12 |
13 | QGridLayout *layout = new QGridLayout();
14 | layout->addWidget(openGL, 0, 0);
15 | layout->setMargin(0);
16 | layout->setSpacing(0);
17 | setLayout(layout);
18 | }
19 |
20 | /*
21 | QSize ControllerWindow::minimumSizeHint() const {
22 | return QSize(886, 616);
23 | //return QSize(888, 620);
24 | }
25 |
26 | QSize ControllerWindow::sizeHint() const {
27 | return QSize(886, 616);
28 | //return QSize(888, 620);
29 | }
30 |
31 | QSize ControllerWindow::maximumSizeHint() const {
32 | return QSize(886, 616);
33 | //return QSize(1774, 1238);
34 | }
35 | */
36 |
37 | void ControllerWindow::closeEvent(QCloseEvent *event) {
38 | emit controllerWindowClosing();
39 | event->accept();
40 | }
41 |
--------------------------------------------------------------------------------
/docs/assets/css/volume-slider.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #2ecc71;
2 |
3 | .volume-slider {
4 | width: 350px;
5 | height: 40px;
6 | position: relative;
7 | margin: 0 auto;
8 |
9 | i {
10 | position: absolute;
11 | top: 50%;
12 | transform: translate(0, -50%);
13 | color: #666;
14 | }
15 |
16 | i.fa-volume-down {
17 | margin-left: -8px;
18 | }
19 |
20 | i.fa-volume-up {
21 | margin-right: -8px;
22 | right: 0;
23 | }
24 | }
25 |
26 | .slider {
27 | position: absolute;
28 | left: 24px;
29 | margin: auto;
30 | height: 5px;
31 | width: 300px;
32 | background: #555;
33 | border-radius: 15px;
34 | top: 50%;
35 | transform: translate(0, -50%);
36 |
37 | .ui-slider-range-min {
38 | height: 5px;
39 | width: 300px;
40 | position: absolute;
41 | background: $primary-color;
42 | border: none;
43 | border-radius: 10px;
44 | outline: none;
45 | }
46 |
47 | .ui-slider-handle {
48 | width: 20px;
49 | height: 20px;
50 | border-radius: 20px;
51 | background: #FFF;
52 | position: absolute;
53 | margin-left: -8px;
54 | margin-top: -8px;
55 | cursor: pointer;
56 | outline: none;
57 | }
58 | }
--------------------------------------------------------------------------------
/docs/assets/css/volume-slider.css:
--------------------------------------------------------------------------------
1 | .volume-slider {
2 | width: 350px;
3 | height: 40px;
4 | position: relative;
5 | margin: 0 auto; }
6 | .volume-slider i {
7 | position: absolute;
8 | top: 50%;
9 | transform: translate(0, -50%);
10 | color: #666; }
11 | .volume-slider i.fa-volume-down {
12 | margin-left: -8px; }
13 | .volume-slider i.fa-volume-up {
14 | margin-right: -8px;
15 | right: 0; }
16 |
17 | .slider {
18 | position: absolute;
19 | left: 24px;
20 | margin: auto;
21 | height: 5px;
22 | width: 300px;
23 | background: #555;
24 | border-radius: 15px;
25 | top: 50%;
26 | transform: translate(0, -50%); }
27 | .slider .ui-slider-range-min {
28 | height: 5px;
29 | width: 300px;
30 | position: absolute;
31 | background: #2ecc71;
32 | border: none;
33 | border-radius: 10px;
34 | outline: none; }
35 | .slider .ui-slider-handle {
36 | width: 20px;
37 | height: 20px;
38 | border-radius: 20px;
39 | background: #FFF;
40 | position: absolute;
41 | margin-left: -8px;
42 | margin-top: -8px;
43 | cursor: pointer;
44 | outline: none; }
45 |
46 | /*# sourceMappingURL=volume-slider.css.map */
47 |
--------------------------------------------------------------------------------
/InputServer/InputServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2043
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InputServer", "InputServer\InputServer.csproj", "{9466FA25-69F4-40E1-ABCA-64EE4D63DC5B}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {9466FA25-69F4-40E1-ABCA-64EE4D63DC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {9466FA25-69F4-40E1-ABCA-64EE4D63DC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {9466FA25-69F4-40E1-ABCA-64EE4D63DC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {9466FA25-69F4-40E1-ABCA-64EE4D63DC5B}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {7EDBE753-13EE-4C7A-91AD-2FFB16574FAE}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/MultiInput/qt/MultiInput.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------
2 | #
3 | # Project created by QtCreator 2015-03-05T00:45:33
4 | #
5 | #-------------------------------------------------
6 |
7 | QT += core gui network serialport gamepad
8 |
9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10 |
11 | TARGET = MultiInput
12 | TEMPLATE = app
13 |
14 | DEFINES += QT_DEPRECATED_WARNINGS
15 |
16 | SOURCES += main.cpp\
17 | multiinput.cpp \
18 | serialportwriter.cpp \
19 | controllerwindow.cpp \
20 | controllerinput.cpp \
21 | xboxcontrollerinput.cpp \
22 | tcpinputserver.cpp \
23 | textcommandparser.cpp \
24 | compositecontrollercommand.cpp \
25 | controllercommand.cpp \
26 | controlleropenglwidget.cpp
27 |
28 | HEADERS += multiinput.h \
29 | serialportwriter.h \
30 | controllerwindow.h \
31 | controllerconstants.h \
32 | controllerinput.h \
33 | xboxcontrollerinput.h \
34 | tcpinputserver.h \
35 | textcommandparser.h \
36 | controllerstate.h \
37 | optional.h \
38 | compositecontrollercommand.h \
39 | abstractcontrollercommand.h \
40 | controllercommand.h \
41 | controlleropenglwidget.h
42 |
43 | FORMS +=
44 |
45 | RESOURCES += \
46 | resources.qrc
47 |
48 | CONFIG += c++11 debug
49 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllerinput.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLERINPUT_H
2 | #define CONTROLLERINPUT_H
3 |
4 | #include
5 | #include
6 | #include "serialportwriter.h"
7 | #include "controllerconstants.h"
8 |
9 | class ControllerInput : public QObject
10 | {
11 | Q_OBJECT
12 | public:
13 | ControllerInput(std::shared_ptr writer, QObject *parent = nullptr);
14 | virtual void begin() = 0;
15 | const QByteArray getData();
16 | virtual void getState(quint8 *outLx, quint8 *outLy, quint8 *outRx, quint8 *outRy, Dpad_t *outDpad, Button_t *outButtons, uint8_t *outVendorspec) = 0;
17 | static const QByteArray getInitialData();
18 | signals:
19 | void controllerStateChanged(QByteArray data);
20 | void controllerConnectionStateChanged(bool connected);
21 | void error(const QString &s);
22 | void warning(const QString &s);
23 | void message(const QString &s);
24 | void controllerReady();
25 | public slots:
26 | virtual void onPacketSent();
27 | void onControllerChange();
28 | protected slots:
29 | virtual void onSerialReady();
30 | protected:
31 | static quint8 quantizeDouble(double const val);
32 | std::shared_ptr writer;
33 | private:
34 | static quint8 calculateCrc8Ccitt(quint8 inCrc, quint8 inData);
35 | QByteArray lastState;
36 | };
37 |
38 | #endif // CONTROLLERINPUT_H
39 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllercommand.cpp:
--------------------------------------------------------------------------------
1 | #include "controllercommand.h"
2 |
3 | ControllerCommand::ControllerCommand(QString name, ControllerState state) : AbstractControllerCommand(name)
4 | {
5 | states.append(state);
6 | remaining = -1;
7 | }
8 |
9 | ControllerCommand::ControllerCommand(QString name, QList states) : AbstractControllerCommand(name), states(states)
10 | {
11 | remaining = -1;
12 | }
13 |
14 | ControllerState ControllerCommand::getNextState() {
15 | while (states.first().waitPackets == 0) states.removeFirst();
16 | ControllerState state = states.first();
17 | states.removeFirst();
18 | remaining -= state.waitPackets;
19 | return state;
20 | }
21 |
22 | int ControllerCommand::getRemainingPackets() {
23 | if (remaining >= 0) return remaining;
24 | remaining = 0;
25 | for (auto it = states.begin(); it != states.end(); ++it) {
26 | remaining += it->waitPackets;
27 | }
28 | return remaining;
29 | }
30 |
31 | bool ControllerCommand::hasPackets() {
32 | if (remaining >= 0) return remaining;
33 | for (auto it = states.begin(); it != states.end(); ++it) {
34 | if (it->waitPackets > 0) return true;
35 | }
36 | return false;
37 | }
38 |
39 | std::unique_ptr ControllerCommand::clone() {
40 | return std::unique_ptr(new ControllerCommand(getName(), states));
41 | }
42 |
--------------------------------------------------------------------------------
/InputServer/InputServer/ClientQueue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace InputServer
7 | {
8 | public class ClientQueue
9 | {
10 | private readonly ConcurrentQueue _queue;
11 | private readonly ConcurrentDictionary _dict;
12 |
13 | public ClientQueue()
14 | {
15 | _queue = new ConcurrentQueue();
16 | _dict = new ConcurrentDictionary();
17 | }
18 |
19 | public void Enqueue(T client)
20 | {
21 | _dict[client] = _queue.Count;
22 | _queue.Enqueue(client);
23 | }
24 |
25 | public void RemoveFromQueue(T client)
26 | {
27 | _dict.TryRemove(client, out var value);
28 | }
29 |
30 | public bool Dequeue(out T result)
31 | {
32 | while (true)
33 | {
34 | if (_queue.TryDequeue(out result))
35 | {
36 | if (_dict.ContainsKey(result))
37 | {
38 | _dict.TryRemove(result, out var value);
39 | return true;
40 | }
41 | }
42 | else
43 | {
44 | return false;
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/assets/css/gamepad.css:
--------------------------------------------------------------------------------
1 | .controlCanvas {
2 | display: block;
3 | margin-left: auto;
4 | margin-right: auto;
5 | }
6 | #gamepadStatusContainer, .center-text {
7 | text-align: center;
8 | display: block;
9 | margin-left: auto;
10 | margin-right: auto;
11 | }
12 | .min-padding {
13 | margin-top: 5px;
14 | margin-bottom: 5px;
15 | }
16 | .inactive {
17 | color: lightgrey;
18 | }
19 | .error {
20 | color: red;
21 | }
22 | .active {
23 | color: green;
24 | }
25 | .connecting {
26 | color: orange;
27 | }
28 | .no-margin {
29 | margin-bottom: 0;
30 | margin-top: 0;
31 | }
32 | #help {
33 | text-align: center;
34 | }
35 | #version {
36 | text-align: center;
37 | font-size: 0.8em;
38 | }
39 | [v-cloak] {
40 | display: none;
41 | }
42 | .hidden {
43 | display: none;
44 | }
45 | .stats {
46 | float: right;
47 | }
48 | .warning {
49 | color: red;
50 | }
51 | .debug-watch {
52 | text-align: center;
53 | width: 40%;
54 | margin: 0 25%;
55 | }
56 | .debug-watch > div {
57 | text-align: left;
58 | }
59 | .debug-watch label {
60 | display: inline-block;
61 | width: 150px;
62 | background-color: gray;
63 | padding: 1pt 4pt;
64 | border: 1pt solid transparent;
65 | margin-right: 2pt;
66 | }
67 | .debug-watch span {
68 | display: inline-block;
69 | width: 150px;
70 | padding: 1pt 4pt;
71 | border: 1pt solid gray;
72 | margin-left: 2pt;
73 | }
--------------------------------------------------------------------------------
/MultiInput/qt/compositecontrollercommand.cpp:
--------------------------------------------------------------------------------
1 | #include "compositecontrollercommand.h"
2 |
3 | CompositeControllerCommand::CompositeControllerCommand(QString name) : AbstractControllerCommand(name) {
4 | }
5 |
6 | ControllerState CompositeControllerCommand::getNextState() {
7 | while (!subcommands.front().get()->hasPackets()) subcommands.pop_front();
8 | return subcommands.front().get()->getNextState();
9 | }
10 |
11 | int CompositeControllerCommand::getRemainingPackets() {
12 | int total = 0;
13 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
14 | total += it->get()->getRemainingPackets();
15 | }
16 | return total;
17 | }
18 |
19 | bool CompositeControllerCommand::hasPackets() {
20 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
21 | if (it->get()->getRemainingPackets() > 0) return true;
22 | }
23 | return false;
24 | }
25 |
26 | void CompositeControllerCommand::addCommand(std::unique_ptr command) {
27 | subcommands.push_back(std::move(command));
28 | }
29 |
30 | std::unique_ptr CompositeControllerCommand::clone() {
31 | CompositeControllerCommand *result = new CompositeControllerCommand(getName());
32 |
33 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
34 | result->addCommand(it->get()->clone());
35 | }
36 |
37 | return std::unique_ptr(result);
38 | }
39 |
--------------------------------------------------------------------------------
/MultiInput/qt/compositecontrollercommand.cpp.autosave:
--------------------------------------------------------------------------------
1 | #include "compositecontrollercommand.h"
2 |
3 | CompositeControllerCommand::CompositeControllerCommand(QString name) : AbstractControllerCommand(name) {
4 | }
5 |
6 | ControllerState CompositeControllerCommand::getNextState() {
7 | while (!subcommands.front().get()->hasPackets()) subcommands.pop_front();
8 | return subcommands.front().get()->getNextState();
9 | }
10 |
11 | int CompositeControllerCommand::getRemainingPackets() {
12 | int total = 0;
13 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
14 | total += it->get()->getRemainingPackets();
15 | }
16 | return total;
17 | }
18 |
19 | bool CompositeControllerCommand::hasPackets() {
20 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
21 | if (it->get()->getRemainingPackets() > 0) return true;
22 | }
23 | return false;
24 | }
25 |
26 | void CompositeControllerCommand::addCommand(std::unique_ptr command) {
27 | subcommands.push_back(std::move(command));
28 | }
29 |
30 | std::unique_ptr CompositeControllerCommand::clone() {
31 | CompositeControllerCommand *result = new CompositeControllerCommand(getName());
32 |
33 | for (auto it = subcommands.begin(); it != subcommands.end(); ++it) {
34 | result->addCommand(it->get()->clone());
35 | }
36 |
37 | return std::unique_ptr(result);
38 | }
39 |
--------------------------------------------------------------------------------
/docs/assets/js/Utils.js:
--------------------------------------------------------------------------------
1 | export let detectBrowser = function() {
2 | if(navigator.userAgent.indexOf('Edge') !== -1 ) {
3 | return 'Edge';
4 | } else if(navigator.userAgent.indexOf('Chrome') !== -1 ) {
5 | return 'Chrome';
6 | } else if(navigator.userAgent.indexOf('Firefox') !== -1 ) {
7 | return 'Firefox';
8 | } else if(navigator.userAgent.indexOf('Safari') !== -1 ) {
9 | return 'Safari';
10 | } else {
11 | return 'unknown';
12 | }
13 | };
14 | export let detectOS = function() {
15 | let userAgent = window.navigator.userAgent,
16 | platform = window.navigator.platform,
17 | macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
18 | windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
19 | iosPlatforms = ['iPhone', 'iPad', 'iPod'];
20 |
21 | if (macosPlatforms.indexOf(platform) !== -1) {
22 | return 'Mac OS';
23 | } else if (iosPlatforms.indexOf(platform) !== -1) {
24 | return 'iOS';
25 | } else if (windowsPlatforms.indexOf(platform) !== -1) {
26 | return 'Windows';
27 | } else if (/Android/.test(userAgent)) {
28 | return 'Android';
29 | } else if (/CrOS/.test(userAgent)) {
30 | return 'Chrome OS';
31 | } else if (/Linux/.test(platform)) {
32 | return 'Linux';
33 | }
34 |
35 | return 'unknown';
36 | };
37 | export let checkVidPid = function(id, vid, pid) {
38 | return id.indexOf(vid) > -1 && id.indexOf(pid) > -1;
39 | };
--------------------------------------------------------------------------------
/MultiInput/qt/controlleropenglwidget.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLEROPENGLWIDGET_H
2 | #define CONTROLLEROPENGLWIDGET_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "controllerconstants.h"
9 | #include "controllerinput.h"
10 |
11 | class ControllerOpenGLWidget : public QOpenGLWidget
12 | {
13 | Q_OBJECT
14 | public:
15 | ControllerOpenGLWidget(std::shared_ptr controller, QWidget *parent);
16 |
17 | protected:
18 | void paintEvent(QPaintEvent *e) override;
19 |
20 | private slots:
21 | void invalidateUi();
22 |
23 | private:
24 | void drawFilledRect(QPainter &painter, const QRectF &rect);
25 | void drawFilledEllipse(QPainter &painter, const QPointF ¢er, const qreal rx, const qreal ry);
26 | void drawFilledPath(QPainter &painter, const std::vector &points);
27 |
28 | void renderDpad(QPainter &painter, const Dpad_t dpad);
29 | void renderButtons(QPainter &painter, const Button_t buttons);
30 | void renderLeftStick(QPainter &painter, const quint8 lx, const quint8 ly, const Button_t buttons);
31 | void renderRightStick(QPainter &painter, const quint8 rx, const quint8 ry, const Button_t buttons);
32 |
33 | std::unique_ptr image;
34 | std::unique_ptr zl;
35 | std::unique_ptr zr;
36 | std::unique_ptr stick;
37 |
38 | QPixmap image_scaled;
39 | QPixmap zl_scaled;
40 | QPixmap zr_scaled;
41 | QPixmap stick_pixmap;
42 | QBitmap zl_mask;
43 | QBitmap zr_mask;
44 | double scaleFactor;
45 |
46 | std::shared_ptr controller;
47 | QByteArray lastState;
48 | };
49 |
50 | #endif // CONTROLLEROPENGLWIDGET_H
51 |
--------------------------------------------------------------------------------
/MultiInput/qt/tcpinputserver.h:
--------------------------------------------------------------------------------
1 | #ifndef TCPINPUTSERVER_H
2 | #define TCPINPUTSERVER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "textcommandparser.h"
10 | #include "controllerconstants.h"
11 | #include "controllerinput.h"
12 | #include "controllerstate.h"
13 |
14 | class TcpInputServer : public ControllerInput
15 | {
16 | Q_OBJECT
17 | public:
18 | TcpInputServer::TcpInputServer(std::shared_ptr writer, QObject *parent = nullptr);
19 | TcpInputServer::~TcpInputServer();
20 | virtual void begin();
21 | virtual void getState(quint8 *outLx, quint8 *outLy, quint8 *outRx, quint8 *outRy, Dpad_t *outDpad, Button_t *outButtons, uint8_t *outVendorspec);
22 | public slots:
23 | virtual void onPacketSent();
24 | protected slots:
25 | virtual void onSerialReady();
26 | private slots:
27 | void sessionOpened();
28 | void createConnection();
29 | void onMappingFileChange(QString path);
30 | private:
31 | void cleanupConnection(QTcpSocket *client);
32 | void onIncomingData(QTcpSocket *client);
33 | void broadcastToListeners(QString data);
34 |
35 | ControllerState currentState;
36 | ControllerState currentCommandState;
37 | std::unique_ptr currentCommand;
38 | //ControllerState currentCommand;
39 | std::queue> commandQueue;
40 | bool isSerialReady = false;
41 |
42 | QTcpServer *tcpServer = nullptr;
43 | QNetworkSession *networkSession = nullptr;
44 | QSet clients;
45 |
46 | QSet notifyClients;
47 |
48 | QFileSystemWatcher commandMapWatcher;
49 | TextCommandParser commandParser;
50 | };
51 |
52 | #endif // TCPINPUTSERVER_H
53 |
--------------------------------------------------------------------------------
/Arduino/include/avr.h:
--------------------------------------------------------------------------------
1 | #ifndef _JOYSTICK_AVR_H
2 | #define _JOYSTICK_AVR_H
3 |
4 | /* Includes: */
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #define SERIAL_UBBRVAL(Baud) ((((F_CPU / 16) + (Baud / 2)) / (Baud)) - 1)
21 | #define PRINT_DEBUG(...)
22 |
23 | // Initializes the USART, note that the RX/TX interrupts need to be enabled manually.
24 | void USART_Init(int baud) {
25 | UCSR1A = 0; // disable double speed mode
26 | UCSR1C = _BV(UCSZ11) | _BV(UCSZ10); // no parity, 8 data bits, 1 stop bit
27 | UCSR1D = 0; // no cts, no rts
28 | UBRR1 = SERIAL_UBBRVAL(baud); // set baud rate
29 | UCSR1B = _BV(RXEN1) | _BV(TXEN1); // enable RX and TX
30 | DDRD |= _BV(3); // set TX pin as output
31 | PORTD |= _BV(2); // set RX pin as input
32 | }
33 |
34 | void disable_watchdog(void) {
35 | MCUSR &= ~(1 << WDRF);
36 | wdt_disable();
37 | }
38 |
39 | inline void disable_rx_isr(void) {
40 | UCSR1B &= ~_BV(RXCIE1);
41 | }
42 |
43 | inline void enable_rx_isr(void) {
44 | UCSR1B |= _BV(RXCIE1);
45 | }
46 |
47 | inline void send_byte(uint8_t c) {
48 | while (!(UCSR1A & _BV(UDRE1)));
49 | UDR1 = c;
50 | }
51 |
52 | inline void send_string(const char *str) {
53 | while (*str) {
54 | send_byte(*str++);
55 | }
56 | }
57 |
58 | inline uint8_t recv_byte(void) {
59 | while (!(UCSR1A & _BV(RXC1)));
60 | return UDR1;
61 | }
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/MultiInput/qt/multiinput.h:
--------------------------------------------------------------------------------
1 | #ifndef MULTIINPUT_H
2 | #define MULTIINPUT_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include "controllerinput.h"
16 | #include "serialportwriter.h"
17 | #include "controllerwindow.h"
18 |
19 | class MultiInput : public QWidget
20 | {
21 | Q_OBJECT
22 | public:
23 | explicit MultiInput(QWidget *parent = nullptr);
24 | ~MultiInput();
25 |
26 | public slots:
27 | void logMessage(const QString &message);
28 | void logWarning(const QString &message);
29 | void logError(const QString &message);
30 |
31 | private slots:
32 | void serialPortIndexChanged(int index);
33 | void onStartButtonClicked();
34 | void onControllerWindowClosed();
35 | void onGamepadDisconnected(int deviceId);
36 | void onGamepadConnected(int deviceId);
37 |
38 | void onControllerError(const QString &message);
39 | void onControllerReady();
40 |
41 | private:
42 | void enumerateSerialPorts();
43 | void enumerateInputDevices();
44 |
45 | void setupUi();
46 | void createInputGroupBox();
47 | void createSerialPortGroupBox();
48 |
49 | QComboBox *inputSelect;
50 | QPushButton *refreshInputsButton;
51 | QGroupBox *inputGroupBox;
52 |
53 | QLabel *serialPortDescription;
54 | QComboBox *serialPortSelect;
55 | QPushButton *refreshSerialPortsButton;
56 | QGroupBox *serialPortGroupBox;
57 |
58 | QTextEdit *eventLog;
59 | QPushButton *startButton;
60 |
61 | QList availableSerialPorts;
62 | QString currentPort;
63 |
64 | ControllerWindow *controllerWindow = nullptr;
65 | QThread controllerThread;
66 | };
67 |
68 | #endif // MULTIINPUT_H
69 |
--------------------------------------------------------------------------------
/docs/assets/js/SwitchProController.js:
--------------------------------------------------------------------------------
1 | import {BaseController, StandardMappings} from "./BaseController.js";
2 |
3 | const SwitchProControllerBase = {
4 | mixins: [BaseController],
5 | data: function() {
6 | return {
7 | canonicalName: 'Switch Pro Controller'
8 | };
9 | }
10 | };
11 |
12 | export const SwitchProControllerStandard = {
13 | mixins: [SwitchProControllerBase, StandardMappings]
14 | };
15 |
16 | export const SwitchProControllerEdge = {
17 | mixins: [SwitchProControllerBase],
18 | data: function() {
19 | return {
20 | buttonMapping: {
21 | faceDown: 1,
22 | faceRight: 0,
23 | faceLeft: 3,
24 | faceUp: 2,
25 | leftTop: 4,
26 | rightTop: 5,
27 | leftTrigger: 6,
28 | rightTrigger: 7,
29 | // Share/Home, no way to read Minus/Plus directly
30 | select: 8,
31 | start: 9,
32 | leftStick: 10,
33 | rightStick: 11,
34 | dpadUp: 12,
35 | dpadDown: 13,
36 | dpadLeft: 14,
37 | dpadRight: 15
38 | },
39 | stickMapping: {
40 | leftStick: {axisX: 0, axisY: 1},
41 | rightStick: {axisX: 2, axisY: 3}
42 | }
43 | };
44 | }
45 | };
46 |
47 | export const SwitchProControllerMacFirefox = {
48 | mixins: [SwitchProControllerBase, StandardMappings]
49 | };
50 |
51 | export const SwitchProControllerWinFirefox = {
52 | mixins: [SwitchProControllerBase, StandardMappings],
53 | data: function() {
54 | return {
55 | notifyMessage: 'The D-Pad does not work properly in Firefox on Windows. Share has been mapped to D-Pad Down and Home has been mapped to D-Pad Up. If this doesn\'t work for you, read the help documentation.'
56 | }
57 | }
58 | };
--------------------------------------------------------------------------------
/docs/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% seo %}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
{{ site.description | default: site.github.project_tagline }}
20 |
21 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 | {% if site.google_analytics %}
38 |
46 | {% endif %}
47 |
48 |
--------------------------------------------------------------------------------
/docs/assets/js/main.js:
--------------------------------------------------------------------------------
1 | import {StatusBus, store, BusEvents} from "./Common";
2 | import {ControlModeSelect} from "./ControlModeSelect";
3 | import {ControlWs} from "./ControlWebSocket";
4 | import {ControllerRenderer} from "./ControllerRenderer";
5 | import {JoyconStreamRenderer} from "./JoyconStreamRenderer";
6 | import {JoyconStreamRendererWebRTC} from "./JoyconStreamRendererWebRTC";
7 | import {TwitchAuth} from "./twitch-auth";
8 | import {ServerStatus} from "./ServerStatus";
9 | import * as Utils from "./Utils";
10 |
11 | new Vue({
12 | el: '#app',
13 | store,
14 | components: {
15 | 'control-ws': ControlWs,
16 | 'controller-renderer': ControllerRenderer,
17 | 'joycon-stream-renderer': JoyconStreamRenderer,
18 | 'joycon-stream-renderer-webrtc': JoyconStreamRendererWebRTC,
19 | 'control-mode-select': ControlModeSelect,
20 | 'twitch-auth': TwitchAuth,
21 | 'server-status': ServerStatus
22 | },
23 | data: function() {
24 | return {
25 | controlEndpoint: 'wss://api.twitchplays.gg/switch/ws',
26 | videoEndpoint: 'wss://api.twitchplays.gg/switch/stream/video',
27 | audioEndpoint: 'wss://api.twitchplays.gg/switch/stream/audio'
28 | //webRtcEndpoint: 'wss://webrtc.twitchplays.gg/stream',
29 | };
30 | },
31 | mounted: function() {
32 | let browser = Utils.detectBrowser();
33 | let os = Utils.detectOS();
34 |
35 | console.log(`Running on ${os}/${browser}`);
36 |
37 | this.$nextTick(function() {
38 | requestAnimationFrame(this.update);
39 | });
40 | },
41 | methods: {
42 | update: function() {
43 |
44 | StatusBus.$emit(BusEvents.RENDER_TIME_START);
45 | // Give input sources a chance to perform operations before actually updating
46 | StatusBus.$emit(BusEvents.BEFORE_UPDATE_INPUT);
47 | StatusBus.$emit(BusEvents.UPDATE_INPUT);
48 |
49 | requestAnimationFrame(this.update);
50 | StatusBus.$emit(BusEvents.RENDER_TIME_END);
51 | }
52 | }
53 | });
54 |
--------------------------------------------------------------------------------
/InputServer/InputServer/InputFrame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace InputServer
6 | {
7 | public class InputFrame
8 | {
9 | public Button? PressedButtons { get; set; }
10 | public Button? ReleasedButtons { get; set; }
11 | public DPad? DPad { get; set; }
12 | public byte? LeftX { get; set; }
13 | public byte? LeftY { get; set; }
14 | public byte? RightX { get; set; }
15 | public byte? RightY { get; set; }
16 | public int Wait { get; set; }
17 |
18 | public static InputFrame ParseInputString(string str)
19 | {
20 | var frame = new InputFrame();
21 | var args = str.Split(' ');
22 |
23 | foreach (var arg in args)
24 | {
25 | var kv = arg.Split('=');
26 | if (kv.Length != 2)
27 | {
28 | throw new Exception($"Invalid input frame: {str}");
29 | }
30 |
31 | switch (kv[0])
32 | {
33 | case "P":
34 | frame.PressedButtons = (Button) int.Parse(kv[1]);
35 | break;
36 | case "R":
37 | frame.ReleasedButtons = (Button) int.Parse(kv[1]);
38 | break;
39 | case "D":
40 | frame.DPad = (DPad) int.Parse(kv[1]);
41 | break;
42 | case "LX":
43 | frame.LeftX = byte.Parse(kv[1]);
44 | break;
45 | case "LY":
46 | frame.LeftY = byte.Parse(kv[1]);
47 | break;
48 | case "RX":
49 | frame.RightX = byte.Parse(kv[1]);
50 | break;
51 | case "RY":
52 | frame.RightY = byte.Parse(kv[1]);
53 | break;
54 | default:
55 | throw new Exception($"Invalid frame modifier specified: {str}");
56 | }
57 | }
58 |
59 | return frame;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllerconstants.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLERCONSTANTS_H
2 | #define CONTROLLERCONSTANTS_H
3 |
4 | #include
5 | #include
6 |
7 | #define BUTTON_NONE 0x00
8 | #define BUTTON_Y 0x01
9 | #define BUTTON_B 0x02
10 | #define BUTTON_A 0x04
11 | #define BUTTON_X 0x08
12 | #define BUTTON_L 0x10
13 | #define BUTTON_R 0x20
14 | #define BUTTON_ZL 0x40
15 | #define BUTTON_ZR 0x80
16 | #define BUTTON_MINUS 0x100
17 | #define BUTTON_PLUS 0x200
18 | #define BUTTON_LCLICK 0x400
19 | #define BUTTON_RCLICK 0x800
20 | #define BUTTON_HOME 0x1000
21 | #define BUTTON_CAPTURE 0x2000
22 | #define BUTTON_ALL 0x3FFF
23 |
24 | #define STICK_MIN 0
25 | #define STICK_CENTER 128
26 | #define STICK_MAX 255
27 |
28 | #define STICK_DEADZONE 2
29 |
30 | #define BUTTON_NONE_NAME "None"
31 | #define BUTTON_Y_NAME "Y"
32 | #define BUTTON_B_NAME "B"
33 | #define BUTTON_A_NAME "A"
34 | #define BUTTON_X_NAME "X"
35 | #define BUTTON_L_NAME "L"
36 | #define BUTTON_R_NAME "R"
37 | #define BUTTON_ZL_NAME "ZL"
38 | #define BUTTON_ZR_NAME "ZR"
39 | #define BUTTON_MINUS_NAME "Minus"
40 | #define BUTTON_PLUS_NAME "Plus"
41 | #define BUTTON_L3_NAME "LStick"
42 | #define BUTTON_R3_NAME "RStick"
43 | #define BUTTON_HOME_NAME "Home"
44 | #define BUTTON_CAPTURE_NAME "Capture"
45 |
46 | #define DPAD_UP_NAME "Up"
47 | #define DPAD_UP_RIGHT_NAME "UpRight"
48 | #define DPAD_RIGHT_NAME "Right"
49 | #define DPAD_DOWN_RIGHT_NAME "DownRight"
50 | #define DPAD_DOWN_NAME "Down"
51 | #define DPAD_DOWN_LEFT_NAME "DownLeft"
52 | #define DPAD_LEFT_NAME "Left"
53 | #define DPAD_UP_LEFT_NAME "UpLeft"
54 | #define DPAD_NONE_NAME "None"
55 |
56 | typedef enum {
57 | DPAD_UP = 0,
58 | DPAD_UP_RIGHT,
59 | DPAD_RIGHT,
60 | DPAD_DOWN_RIGHT,
61 | DPAD_DOWN,
62 | DPAD_DOWN_LEFT,
63 | DPAD_LEFT,
64 | DPAD_UP_LEFT,
65 | DPAD_NONE
66 | } Dpad_t;
67 |
68 | typedef uint16_t Button_t;
69 |
70 | Q_DECLARE_METATYPE(Dpad_t)
71 | Q_DECLARE_METATYPE(Button_t)
72 |
73 | // 50 ms update time (20hz)
74 | #define WAIT_TIME 50
75 |
76 | #endif // CONTROLLERCONSTANTS_H
77 |
--------------------------------------------------------------------------------
/MultiInput/qt/controllerstate.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLERSTATE_H
2 | #define CONTROLLERSTATE_H
3 |
4 | #include
5 | #include "controllerconstants.h"
6 | #include "optional.h"
7 |
8 | class ControllerState
9 | {
10 | public:
11 | void mergeWith(ControllerState other) {
12 | if (other.lx) lx = other.lx;
13 | if (other.ly) ly = other.ly;
14 | if (other.rx) rx = other.rx;
15 | if (other.ry) ry = other.ry;
16 |
17 | if (!buttonsPressed) buttonsPressed = other.buttonsPressed;
18 | else if (buttonsPressed && other.buttonsPressed) buttonsPressed = buttonsPressed.value() | other.buttonsPressed.value();
19 |
20 | if (!buttonsReleased) buttonsReleased = other.buttonsReleased;
21 | else if (buttonsReleased && other.buttonsReleased) buttonsReleased = buttonsReleased.value() | other.buttonsReleased.value();
22 |
23 | if (other.dpad) dpad = other.dpad;
24 |
25 | if (other.originalCommand != "") originalCommand = QObject::tr("%1 & %2").arg(originalCommand).arg(other.originalCommand);
26 | }
27 |
28 | QList split(int numPackets) {
29 | QList result;
30 | if (numPackets >= waitPackets) {
31 | result.push_back(*this);
32 | return result;
33 | }
34 |
35 | ControllerState p1;
36 | ControllerState p2;
37 | p1.lx = p2.lx = lx;
38 | p1.ly = p2.ly = ly;
39 | p1.rx = p2.rx = rx;
40 | p1.ry = p2.ry = ry;
41 | p1.buttonsPressed = p2.buttonsPressed = buttonsPressed;
42 | p1.buttonsReleased = p2.buttonsReleased = buttonsReleased;
43 | p1.dpad = p2.dpad = dpad;
44 | p1.waitPackets = numPackets;
45 | p2.waitPackets = waitPackets - numPackets;
46 |
47 | result.push_back(p1);
48 | result.push_back(p2);
49 |
50 | return result;
51 | }
52 |
53 | ControllerState() {}
54 | stx::optional lx;
55 | stx::optional ly;
56 | stx::optional rx;
57 | stx::optional ry;
58 | stx::optional buttonsPressed;
59 | stx::optional buttonsReleased;
60 | stx::optional dpad;
61 | int waitPackets;
62 | QString originalCommand;
63 | };
64 |
65 | #endif // CONTROLLERSTATE_H
66 |
--------------------------------------------------------------------------------
/docs/_layouts/play.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
{{content}}
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Arduino/Makefile:
--------------------------------------------------------------------------------
1 | #
2 | # LUFA Library
3 | # Copyright (C) Dean Camera, 2014.
4 | #
5 | # dean [at] fourwalledcubicle [dot] com
6 | # www.lufa-lib.org
7 | #
8 | # --------------------------------------
9 | # LUFA Project Makefile.
10 | # --------------------------------------
11 |
12 | # Run "make help" for target help.
13 |
14 | # Set the MCU accordingly to your device (e.g. at90usb1286 for a Teensy 2.0++, or atmega16u2 for an Arduino UNO R3)
15 | MCU = atmega16u2
16 | ARCH = AVR8
17 | F_CPU = 16000000
18 | F_USB = $(F_CPU)
19 | OPTIMIZATION = s
20 | INCLUDE_DIR = include
21 | TARGET = Joystick
22 | SRC_DIR = src
23 | SRC = $(SRC_DIR)/Joystick.c $(LUFA_SRC_USB)
24 | LUFA_PATH = lufa/LUFA
25 | CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -Iinclude/
26 | LD_FLAGS =
27 |
28 | EMULATOR_TARGET = emulator
29 | EMULATOR_CC = gcc
30 | EMULATOR_CC_FLAGS = -Iinclude/ -DMOCK_AVR
31 | EMULATOR_OBJ_DIR = emulator_obj
32 | EMULATOR_SRC_FILES := $(wildcard $(SRC_DIR)/*.c)
33 | EMULATOR_OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(EMULATOR_OBJ_DIR)/%.o,$(EMULATOR_SRC_FILES))
34 |
35 | # Default target
36 | all: $(EMULATOR_TARGET)
37 | # Include LUFA build script makefiles
38 | include $(LUFA_PATH)/Build/lufa_core.mk
39 | include $(LUFA_PATH)/Build/lufa_sources.mk
40 | include $(LUFA_PATH)/Build/lufa_build.mk
41 | include $(LUFA_PATH)/Build/lufa_cppcheck.mk
42 | include $(LUFA_PATH)/Build/lufa_doxygen.mk
43 | include $(LUFA_PATH)/Build/lufa_dfu.mk
44 | include $(LUFA_PATH)/Build/lufa_hid.mk
45 | include $(LUFA_PATH)/Build/lufa_avrdude.mk
46 | include $(LUFA_PATH)/Build/lufa_atprogram.mk
47 |
48 | # Target for LED/buzzer to alert when print is done
49 | with-alert: all
50 | with-alert: CC_FLAGS += -DALERT_WHEN_DONE
51 |
52 | flash: all
53 | sudo dfu-programmer $(MCU) erase
54 | sudo dfu-programmer $(MCU) flash $(TARGET).hex
55 | sudo dfu-programmer $(MCU) reset
56 |
57 | $(EMULATOR_TARGET): $(EMULATOR_OBJ_FILES)
58 | $(EMULATOR_CC) -o $@ $^
59 |
60 | $(EMULATOR_OBJ_DIR)/%.o: $(SRC_DIR)/%.c
61 | mkdir -p $(EMULATOR_OBJ_DIR)
62 | $(EMULATOR_CC) $(EMULATOR_CC_FLAGS) -c -o $@ $<
63 |
64 | clean-emu:
65 | rm -rf $(EMULATOR_OBJ_DIR) $(EMULATOR_TARGET)
66 |
67 | run-emu:
68 | socat -d -d PTY,link=/tmp/faketty0,raw,echo=0 EXEC:./$(EMULATOR_TARGET)
69 |
--------------------------------------------------------------------------------
/Arduino/utils/compare.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import sys
3 | import queue
4 | import threading
5 |
6 | SERIAL_1 = '/dev/ttyACM0'
7 | SERIAL_2 = '/dev/vtty0'
8 | BAUD_RATE = 19200
9 |
10 | def pipe_stdin(ser1, ser2):
11 | while True:
12 | data = sys.stdin.buffer.read(1)
13 | if len(data) == 0:
14 | return
15 | ser1.write(data)
16 | ser2.write(data)
17 |
18 | def read_ser(ser, q):
19 | while True:
20 | if ser.in_waiting > 0:
21 | data = ser.read(1)
22 | if len(data) == 0:
23 | return
24 | q.put(data)
25 |
26 | def compare_queue(q1, q2, q3):
27 | buf1 = b''
28 | buf2 = b''
29 |
30 | while True:
31 | while True:
32 | try:
33 | buf1 += q1.get(False)
34 | except queue.Empty:
35 | break
36 |
37 | while True:
38 | try:
39 | buf2 += q2.get(False)
40 | except queue.Empty:
41 | break
42 |
43 | while len(buf1) > 0 and len(buf2) > 0:
44 | if buf1[0] == buf2[0]:
45 | q3.put(buf1[:1])
46 | else:
47 | sys.stderr.write('Mismatch: Hardware returned {} but emulator returned {}\n'.format(buf1[:1], buf2[:1]))
48 | buf1 = buf1[1:]
49 | buf2 = buf2[1:]
50 |
51 |
52 |
53 | def main():
54 | try:
55 | ser1 = serial.Serial(SERIAL_1, BAUD_RATE)
56 | ser2 = serial.Serial(SERIAL_2, BAUD_RATE)
57 | q1 = queue.Queue()
58 | q2 = queue.Queue()
59 | q3 = queue.Queue()
60 | t = threading.Thread(target=pipe_stdin, args=(ser1, ser2))
61 | t1 = threading.Thread(target=read_ser, args=(ser1, q1))
62 | t2 = threading.Thread(target=read_ser, args=(ser2, q2))
63 | t3 = threading.Thread(target=compare_queue, args=(q1, q2, q3))
64 | t.daemon = True
65 | t1.daemon = True
66 | t2.daemon = True
67 | t3.daemon = True
68 | t.start()
69 | t1.start()
70 | t2.start()
71 | t3.start()
72 | while True:
73 | d1 = q3.get()
74 | #sys.stderr.write(str(d1)[2:-1])
75 | #sys.stderr.flush()
76 | #sys.stderr.write('Wrote byte to output\n')
77 | sys.stdout.buffer.write(d1)
78 | sys.stdout.flush()
79 | except KeyboardInterrupt:
80 | pass
81 |
82 | if __name__ == '__main__':
83 | main()
84 |
--------------------------------------------------------------------------------
/docs/assets/js/DualshockController.js:
--------------------------------------------------------------------------------
1 | import {BaseController, StandardMappings} from "./BaseController";
2 |
3 | const dualShockControllerBase = {
4 | mixins: [BaseController],
5 | data: function() {
6 | return {
7 | canonicalName: 'DualShock Controller'
8 | };
9 | }
10 | };
11 |
12 | export const dualShockControllerStandard = {
13 | mixins: [dualShockControllerBase, StandardMappings]
14 | };
15 |
16 | export const dualShockControllerWinFirefox = {
17 | mixins: [dualShockControllerBase],
18 | data: function() {
19 | return {
20 | buttonMapping: {
21 | faceLeft: 0,
22 | faceDown: 1,
23 | faceRight: 2,
24 | faceUp: 3,
25 | leftTop: 4,
26 | rightTop: 5,
27 | leftTrigger: 6,
28 | rightTrigger: 7,
29 | select: 8,
30 | start: 9,
31 | leftStick: 10,
32 | rightStick: 11,
33 | // Remap the guide button to index 12
34 | // The other buttons don't seem to work, so leave them blank.
35 | dpadUp: 13,
36 | dpadDown: 12,
37 | dpadLeft: null,
38 | dpadRight: null
39 | },
40 | stickMapping: {
41 | leftStick: {axisX: 0, axisY: 1},
42 | rightStick: {axisX: 2, axisY: 5}
43 | },
44 | notifyMessage: 'The D-Pad does not work properly in Firefox on Windows. The touchpad has been mapped to D-Pad Up. If this doesn\'t work for you, try using Chrome.'
45 | };
46 | }
47 | };
48 |
49 | export const dualShockControllerMacFirefox = {
50 | mixins: [dualShockControllerBase],
51 | data: function() {
52 | return {
53 | buttonMapping: {
54 | faceLeft: 0,
55 | faceDown: 1,
56 | faceRight: 2,
57 | faceUp: 3,
58 | leftTop: 4,
59 | rightTop: 5,
60 | leftTrigger: 6,
61 | rightTrigger: 7,
62 | select: 8,
63 | start: 9,
64 | leftStick: 10,
65 | rightStick: 11,
66 | dpadUp: 14,
67 | dpadDown: 15,
68 | dpadLeft: 16,
69 | dpadRight: 17
70 | },
71 | stickMapping: {
72 | leftStick: {axisX: 0, axisY: 1},
73 | rightStick: {axisX: 2, axisY: 5}
74 | }
75 | };
76 | }
77 | };
--------------------------------------------------------------------------------
/InputServer/InputServer/InputWsClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Microsoft.IdentityModel.Tokens;
6 | using WebSocketSharp;
7 | using WebSocketSharp.Server;
8 |
9 | namespace InputServer
10 | {
11 | class InputWsClient : WebSocketBehavior
12 | {
13 | public bool IsListener { get; private set; }
14 | public TwitchUser MyUser { get; private set; }
15 | public OnInputCallback InputCallback { get; set; }
16 | public OnTurnRequestCallback TurnRequestCallback { get; set; }
17 | public OnTurnCancelCallback TurnCancelCallback { get; set; }
18 | public OnOpenCallback OnNewConnectionCallback { get; set; }
19 | public OnAddListenerCallback OnListenerAddedCallback { get; set; }
20 |
21 | protected override void OnOpen()
22 | {
23 | OnNewConnectionCallback(ID);
24 | }
25 |
26 | protected override void OnClose(CloseEventArgs e)
27 | {
28 | TurnCancelCallback(MyUser);
29 | }
30 |
31 | protected override void OnMessage(MessageEventArgs e)
32 | {
33 | var wsArgs = e.Data.Split(' ');
34 | var cmd = wsArgs[0];
35 | var restOfArgs = string.Join(" ", wsArgs.Skip(1));
36 |
37 | if (cmd == "PING")
38 | {
39 | Send($"PONG {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()} {restOfArgs}");
40 | }
41 | else if (cmd == "TWITCH_LOGIN")
42 | {
43 | try
44 | {
45 | MyUser = new TwitchUser(wsArgs[1], wsArgs[2]);
46 | //Console.WriteLine($"{MyUser.UserName} logged in");
47 | Send("TWITCH_VERIFIED");
48 | }
49 | catch (SecurityTokenValidationException)
50 | {
51 | MyUser = null;
52 | //Console.WriteLine("Invalid login attempt");
53 | Send("TWITCH_INVALID");
54 | }
55 | }
56 | else if (cmd == "TWITCH_LOGOUT")
57 | {
58 | Console.WriteLine("Logged out");
59 | }
60 | else if (cmd == "UPDATE")
61 | {
62 | InputCallback(MyUser, restOfArgs);
63 | }
64 | else if (cmd == "REQUEST_TURN")
65 | {
66 | TurnRequestCallback(MyUser);
67 | }
68 | else if (cmd == "CANCEL_TURN")
69 | {
70 | TurnCancelCallback(MyUser);
71 | }
72 | else if (cmd == "LISTENER")
73 | {
74 | IsListener = true;
75 | OnListenerAddedCallback(this);
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Arduino/utils/decode_usb_descriptors.py:
--------------------------------------------------------------------------------
1 | import io
2 | import struct
3 | import binascii
4 |
5 | global_items = {
6 | 0x00: 'HID_RI_USAGE_PAGE',
7 | 0x10: 'HID_RI_LOGICAL_MINIMUM',
8 | 0x20: 'HID_RI_LOGICAL_MAXIMUM',
9 | 0x30: 'HID_RI_PHYSICAL_MINIMUM',
10 | 0x40: 'HID_RI_PHYSICAL_MAXIMUM',
11 | 0x50: 'HID_RI_UNIT_EXPONENT',
12 | 0x60: 'HID_RI_UNIT',
13 | 0x70: 'HID_RI_REPORT_SIZE',
14 | 0x80: 'HID_RI_REPORT_ID',
15 | 0x90: 'HID_RI_REPORT_COUNT',
16 | 0xA0: 'HID_RI_PUSH',
17 | 0xB0: 'HID_RI_POP'
18 | }
19 |
20 | local_items = {
21 | 0x00: 'HID_RI_USAGE',
22 | 0x10: 'HID_RI_USAGE_MINIMUM',
23 | 0x20: 'HID_RI_USAGE_MAXIMUM'
24 | }
25 |
26 | main_items = {
27 | 0x80: 'HID_RI_INPUT',
28 | 0x90: 'HID_RI_OUTPUT',
29 | 0xA0: 'HID_RI_COLLECTION',
30 | 0xB0: 'HID_RI FEATURE',
31 | 0xC0: 'HID_RI_END_COLLECTION'
32 | }
33 |
34 | pokken_pad_modified = b'\x05\x01\t\x05\xa1\x01\x15\x00%\x015\x00E\x01u\x01\x95\x10\x05\t\x19\x01)\x10\x81\x02\x05\x01%\x07F;\x01u\x04\x95\x01e\x14\t9\x81Be\x00\x95\x01\x81\x01&\xff\x00F\xff\x00\t0\t1\t2\t5u\x08\x95\x04\x81\x02\x06\x00\xff\t \x95\x01\x81\x02\n!&\x95\x08\x91\x02\xc0'
35 | hori_pad = b'\x05\x01\t\x05\xa1\x01\x15\x00%\x015\x00E\x01u\x01\x95\x0e\x05\t\x19\x01)\x0e\x81\x02\x95\x02\x81\x01\x05\x01%\x07F;\x01u\x04\x95\x01e\x14\t9\x81Be\x00\x95\x01\x81\x01&\xff\x00F\xff\x00\t0\t1\t2\t5u\x08\x95\x04\x81\x02u\x08\x95\x01\x81\x01\xc0'
36 |
37 | def decode_hid_descriptor(descriptor):
38 | res = []
39 | buf = io.BytesIO(descriptor)
40 | r = buf.read(1)
41 | while len(r) > 0:
42 | res_line = ''
43 |
44 | num = struct.unpack('B', r)[0]
45 | hid_item = num & 0xF0
46 | is_global = bool(num & 0x04)
47 | is_local = bool(num & 0x08)
48 | size_type = num & 0x03
49 | num_bits = 0
50 | if size_type == 0x01:
51 | num_bits = 8
52 | elif size_type == 0x02:
53 | num_bits = 16
54 | elif size_type == 0x03:
55 | num_bits = 32
56 |
57 | if is_global:
58 | res_line += global_items[hid_item]
59 | elif is_local:
60 | res_line += local_items[hid_item]
61 | else:
62 | res_line += main_items[hid_item]
63 |
64 | res_line += '(%d' % num_bits
65 | if num_bits > 0:
66 | data = buf.read(num_bits // 8)
67 | if num_bits == 8:
68 | res_line += ', 0x%.2X' % (struct.unpack('"
77 | };
--------------------------------------------------------------------------------
/docs/assets/js/KeyboardInputSource.js:
--------------------------------------------------------------------------------
1 | import {InputSource} from "./InputSource";
2 | import {InputState, StoreMutations} from "./Common";
3 |
4 | export const KeyboardInputSource = {
5 | mixins: [InputSource],
6 | data: function() {
7 | return {
8 | keyMapping: {
9 | faceDown: 'down',
10 | faceRight: 'right',
11 | faceLeft: 'left',
12 | faceUp: 'up',
13 | leftTop: 'q',
14 | rightTop: 'o',
15 | leftTrigger: 'e',
16 | rightTrigger: 'u',
17 | select: '-',
18 | start: '=',
19 | leftStick: 'r',
20 | rightStick: 'y',
21 | dpadUp: 't',
22 | dpadDown: 'g',
23 | dpadLeft: 'f',
24 | dpadRight: 'h'
25 | },
26 | stickMapping: {
27 | leftStick: {
28 | up: 'w',
29 | down: 's',
30 | left: 'a',
31 | right: 'd',
32 | slow: function() {return key.shift;}
33 | },
34 | rightStick: {
35 | up: 'i',
36 | down: 'k',
37 | left: 'j',
38 | right: 'l',
39 | slow: '/'
40 | }
41 | }
42 | };
43 | },
44 | mounted: function() {
45 | this.$store.commit(StoreMutations.INPUT_STATE, InputState.READY);
46 | },
47 | methods: {
48 | isButtonPressed: function(name) {
49 | if (!this.keyMapping[name]) return false;
50 | if (key.ctrl || key.alt) return false;
51 | if (typeof this.keyMapping[name] === 'function') {
52 | return this.keyMapping[name]();
53 | }
54 | return key.isPressed(this.keyMapping[name]);
55 | },
56 | getStickX: function(stick) {
57 | if (!this.stickMapping[stick]) return false;
58 | if (key.ctrl || key.alt) return false;
59 | let val = 0;
60 | if (key.isPressed(this.stickMapping[stick].left)) val -= 1;
61 | if (key.isPressed(this.stickMapping[stick].right)) val += 1;
62 | if (key.isPressed(this.stickMapping[stick].slow)) val *= 0.5;
63 | return val;
64 | },
65 | getStickY: function(stick) {
66 | if (!this.stickMapping[stick]) return false;
67 | if (key.ctrl || key.alt) return false;
68 | let val = 0;
69 | if (key.isPressed(this.stickMapping[stick].up)) val -= 1;
70 | if (key.isPressed(this.stickMapping[stick].down)) val += 1;
71 | if (key.isPressed(this.stickMapping[stick].slow)) val *= 0.5;
72 | return val;
73 | }
74 | },
75 | template: 'Using keyboard
'
76 | };
--------------------------------------------------------------------------------
/MultiInput/qt/controllerinput.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "controllerinput.h"
3 |
4 | ControllerInput::ControllerInput(std::shared_ptr writer, QObject *parent) : QObject(parent), writer(writer)
5 | {
6 | connect(writer.get(), SIGNAL(error(QString)), this, SIGNAL(error(QString)));
7 | connect(writer.get(), SIGNAL(timeout(QString)), this, SIGNAL(warning(QString)));
8 | connect(writer.get(), SIGNAL(message(QString)), this, SIGNAL(message(QString)));
9 | connect(writer.get(), SIGNAL(synced()), this, SLOT(onSerialReady()));
10 | connect(writer.get(), SIGNAL(writeComplete()), this, SLOT(onPacketSent()));
11 | }
12 |
13 | void ControllerInput::onSerialReady() {
14 | emit controllerReady();
15 | }
16 |
17 | const QByteArray ControllerInput::getData() {
18 | quint8 lx;
19 | quint8 ly;
20 | quint8 rx;
21 | quint8 ry;
22 | Dpad_t press;
23 | Button_t button;
24 | quint8 vendorSpec;
25 |
26 | getState(&lx, &ly, &rx, &ry, &press, &button, &vendorSpec);
27 |
28 | button &= BUTTON_ALL;
29 |
30 | quint8 buf[9];
31 | qToBigEndian(button, &buf[0]);
32 | buf[2] = press;
33 | buf[3] = lx;
34 | buf[4] = ly;
35 | buf[5] = rx;
36 | buf[6] = ry;
37 | buf[7] = 0;
38 |
39 | quint8 crc = 0;
40 | for(int i = 0; i < 8; i++) {
41 | crc = calculateCrc8Ccitt(crc, buf[i]);
42 | }
43 | buf[8] = crc;
44 |
45 | return QByteArray((char*) buf, 9);
46 | }
47 |
48 | void ControllerInput::onPacketSent() {
49 | QByteArray newState = getData();
50 | if (newState != lastState) {
51 | lastState = newState;
52 | writer.get()->changeData(lastState);
53 | emit controllerStateChanged(lastState);
54 | }
55 | }
56 |
57 | void ControllerInput::onControllerChange() {
58 | lastState = getData();
59 | writer.get()->changeData(lastState);
60 | emit controllerStateChanged(lastState);
61 | }
62 |
63 | const QByteArray ControllerInput::getInitialData() {
64 | const uint8_t state[] = {0x00, 0x00, 0x08, 0x80, 0x80, 0x80, 0x80, 0x00};
65 | QByteArray data = QByteArray((const char*) state, sizeof(state));
66 | quint8 crc = 0;
67 | for (auto it = data.begin(); it != data.end(); ++it) {
68 | crc = calculateCrc8Ccitt(crc, *it);
69 | }
70 | data.push_back(crc);
71 | return data;
72 | }
73 |
74 | quint8 ControllerInput::quantizeDouble(const double val) {
75 | double scaled = (val + 1.0) * 128.0;
76 | if (scaled < 0) scaled = 0;
77 | else if (scaled > 255) scaled = 255;
78 | return (quint8) scaled;
79 | }
80 |
81 | quint8 ControllerInput::calculateCrc8Ccitt(quint8 inCrc, quint8 inData) {
82 | quint8 data = inCrc ^ inData;
83 |
84 | for (int i = 0; i < 8; i++ )
85 | {
86 | if (( data & 0x80 ) != 0 )
87 | {
88 | data <<= 1;
89 | data ^= 0x07;
90 | }
91 | else
92 | {
93 | data <<= 1;
94 | }
95 | }
96 | return data;
97 | }
98 |
--------------------------------------------------------------------------------
/docs/assets/js/BaseController.js:
--------------------------------------------------------------------------------
1 | import {InputSource} from "./InputSource";
2 |
3 | export const StandardMappings = {
4 | data: function() {
5 | return {
6 | buttonMapping: {
7 | faceDown: 0,
8 | faceRight: 1,
9 | faceLeft: 2,
10 | faceUp: 3,
11 | leftTop: 4,
12 | rightTop: 5,
13 | leftTrigger: 6,
14 | rightTrigger: 7,
15 | select: 8,
16 | start: 9,
17 | leftStick: 10,
18 | rightStick: 11,
19 | dpadUp: 12,
20 | dpadDown: 13,
21 | dpadLeft: 14,
22 | dpadRight: 15
23 | },
24 | stickMapping: {
25 | leftStick: {axisX: 0, axisY: 1},
26 | rightStick: {axisX: 2, axisY: 3}
27 | }
28 | };
29 | }
30 | };
31 |
32 | export const BaseController = {
33 | mixins: [InputSource],
34 | props: ['gamepadindex', 'gamepadname', 'axes', 'buttons'],
35 | data: function() {
36 | return {
37 | experimental: false
38 | };
39 | },
40 | methods: {
41 | isButtonPressed: function(name) {
42 | // May need to override for certain controllers due to dpad
43 | let index = this.buttonMapping[name];
44 | if (index === null || index === undefined || index < 0) return false;
45 | return !!this.buttons[index];
46 | },
47 | getStickX: function(name) {
48 | return this.axes[this.stickMapping[name].axisX] || 0.0;
49 | },
50 | getStickY: function(name) {
51 | return this.axes[this.stickMapping[name].axisY] || 0.0;
52 | }
53 | },
54 | mounted: function() {
55 | if (this.experimental) {
56 | this.$notify({
57 | title: 'Experimental controller support',
58 | text: `Please note that support for this controller (${this.canonicalName}) is experimental and may have issues or limitations. Please check the help documentation for details.`,
59 | duration: 10000
60 | });
61 | }
62 | if (this.notifyMessage) {
63 | this.$notify({
64 | type: 'warn',
65 | title: 'Warning',
66 | text: this.notifyMessage,
67 | duration: 10000
68 | });
69 | }
70 | document.addEventListener('keydown', function(e) {
71 | if (e.key.startsWith('Gamepad')) {
72 | e.preventDefault();
73 | }
74 | });
75 | },
76 | template: 'Controller (( gamepadindex )): (( gamepadname ))
Detected as: (( canonicalName ))
'
77 | };
78 |
79 | export const XboxController = {
80 | mixins: [BaseController, StandardMappings],
81 | data: function() {
82 | return {
83 | canonicalName: 'Xbox/XInput controller'
84 | };
85 | }
86 | };
--------------------------------------------------------------------------------
/Arduino/include/avr_mock.h:
--------------------------------------------------------------------------------
1 | #ifndef _JOYSTICK_AVR_H
2 | #define _JOYSTICK_AVR_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #define USART1_RX_vect USART1_RX_vect
12 | #define clock_div_1 0
13 | #define JOYSTICK_OUT_EPADDR 0
14 | #define JOYSTICK_IN_EPADDR 0
15 | #define EP_TYPE_INTERRUPT 0
16 | #define JOYSTICK_EPSIZE 0
17 | #define USB_DeviceState 0
18 | #define DEVICE_STATE_Configured 0
19 | #define ENDPOINT_RWSTREAM_NoError 0
20 |
21 | #define MAKE_FN_NAME(x) void _ISR_ ## x (void)
22 | #define ISR(vector_num) MAKE_FN_NAME(vector_num)
23 |
24 | #define PRINT_DEBUG(fmt_str, args...) fprintf(stderr, fmt_str, args)
25 |
26 | // https://stackoverflow.com/a/1594514
27 | int is_ready(int fd) {
28 | fd_set fdset;
29 | struct timeval timeout;
30 | int ret;
31 | FD_ZERO(&fdset);
32 | FD_SET(fd, &fdset);
33 | timeout.tv_sec = 0;
34 | timeout.tv_usec = 1;
35 | //int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
36 | return select(fd+1, &fdset, NULL, NULL, &timeout) == 1 ? 1 : 0;
37 | }
38 |
39 | void USART_Init(int baud) {}
40 |
41 | void disable_watchdog(void) {}
42 |
43 | void disable_rx_isr(void) {}
44 |
45 | void enable_rx_isr(void) {}
46 |
47 | void send_byte(uint8_t c) {
48 | write(fileno(stdout), &c, sizeof(c));
49 | }
50 |
51 | void send_string(const char *str) {
52 | write(fileno(stdout), str, strlen(str));
53 | fsync(fileno(stdout));
54 | }
55 |
56 | uint8_t recv_byte(void) {
57 | uint8_t val;
58 | read(fileno(stdin), &val, sizeof(val));
59 | #ifdef DEBUG_VERBOSE
60 | fprintf(stderr, "%02x ", val);
61 | #endif
62 | return val;
63 | }
64 |
65 | // https://www.microchip.com/webdoc/AVRLibcReferenceManual/group__util__crc_1gab27eaaef6d7fd096bd7d57bf3f9ba083.html
66 | uint8_t _crc8_ccitt_update (uint8_t inCrc, uint8_t inData)
67 | {
68 | uint8_t i;
69 | uint8_t data;
70 |
71 | data = inCrc ^ inData;
72 |
73 | for ( i = 0; i < 8; i++ )
74 | {
75 | if (( data & 0x80 ) != 0 )
76 | {
77 | data <<= 1;
78 | data ^= 0x07;
79 | }
80 | else
81 | {
82 | data <<= 1;
83 | }
84 | }
85 | return data;
86 | }
87 |
88 | void GlobalInterruptEnable(void) {}
89 | void clock_prescale_set(int val) {}
90 |
91 | void USB_Init(void) {}
92 |
93 | extern void _ISR_USART1_RX_vect(void);
94 | void USB_USBTask(void) {
95 | while (is_ready(0)) _ISR_USART1_RX_vect();
96 | }
97 | bool Endpoint_ConfigureEndpoint(int val1, int val2, int val3, int val4) {return true;}
98 |
99 | void Endpoint_SelectEndpoint(int val) {}
100 | bool Endpoint_IsOUTReceived(void) {return false;}
101 | bool Endpoint_IsReadWriteAllowed(void) {return false;}
102 | int Endpoint_Read_Stream_LE(void *const buf, size_t len, uint16_t *const bytesProcessed) {return ENDPOINT_RWSTREAM_NoError;}
103 | void Endpoint_ClearOUT(void) {}
104 |
105 | bool Endpoint_IsINReady(void) {return true;}
106 | int Endpoint_Write_Stream_LE(void *const buf, size_t len, uint16_t *const bytesProcessed) {return ENDPOINT_RWSTREAM_NoError;}
107 | void Endpoint_ClearIN(void) {}
108 |
109 | #endif
110 |
--------------------------------------------------------------------------------
/InputServer/InputServer/TwitchUser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IdentityModel.Tokens.Jwt;
4 | using System.Linq;
5 | using System.Security.Cryptography;
6 | using System.Text;
7 | using Microsoft.IdentityModel.Tokens;
8 |
9 | namespace InputServer
10 | {
11 | public class TwitchUser
12 | {
13 | private readonly JwtSecurityToken validatedToken;
14 | public int UserId { get; }
15 | public string UserName { get; }
16 | public string Picture { get; }
17 |
18 | public TwitchUser(string token, string picture)
19 | {
20 | Picture = picture;
21 |
22 | var modulus =
23 | "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw";
24 | var exponent = "AQAB";
25 | var audiences = new []
26 | {
27 | "6ilamg1dh1d2fwi30x5ryiarfq6y86",
28 | "sa5pewo51b3fi5d70le38sj5916iz5"
29 | };
30 | var issuer = "https://id.twitch.tv/oauth2";
31 |
32 | var decodedMod = FromBase64Url(modulus);
33 | var decodedExp = FromBase64Url(exponent);
34 | var rsa = new RSACryptoServiceProvider();
35 | rsa.ImportParameters(new RSAParameters
36 | {
37 | Modulus = decodedMod,
38 | Exponent = decodedExp
39 | });
40 |
41 | var validationParameters = new TokenValidationParameters
42 | {
43 | RequireExpirationTime = true,
44 | RequireSignedTokens = true,
45 | ValidateAudience = true,
46 | ValidAudiences = audiences,
47 | ValidateIssuer = true,
48 | ValidIssuer = issuer,
49 | ValidateLifetime = false,
50 | IssuerSigningKey = new RsaSecurityKey(rsa)
51 | };
52 | var handler = new JwtSecurityTokenHandler();
53 | handler.ValidateToken(token, validationParameters, out var securityToken);
54 | validatedToken = securityToken as JwtSecurityToken;
55 | UserId = GetUserId(validatedToken);
56 | UserName = GetUserName(validatedToken);
57 | }
58 | private static int GetUserId(JwtSecurityToken token)
59 | {
60 | return int.Parse(token.Subject);
61 | }
62 |
63 | private static string GetUserName(JwtSecurityToken token)
64 | {
65 | return token.Claims.First(c => c.Type == "preferred_username").Value;
66 | }
67 | private static byte[] FromBase64Url(string base64Url)
68 | {
69 | var padded = base64Url.Length % 4 == 0 ? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
70 | var base64 = padded.Replace("_", "/").Replace("-", "+");
71 | return Convert.FromBase64String(base64);
72 | }
73 |
74 | public override bool Equals(object other)
75 | {
76 | if (!(other is TwitchUser player)) return false;
77 | return UserId == player.UserId;
78 | }
79 |
80 | public override int GetHashCode()
81 | {
82 | return UserId.GetHashCode();
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Arduino/include/LUFAConfig.h:
--------------------------------------------------------------------------------
1 | // LUFA Library Configuration Header File. Used to configure LUFA's compile time options, as an alternative to the compile-time defines.
2 | #ifndef _LUFA_CONFIG_H_
3 | #define _LUFA_CONFIG_H_
4 | #if (ARCH == ARCH_AVR8)
5 | // Non-USB Related Configuration Tokens
6 | // #define DISABLE_TERMINAL_CODES
7 |
8 | // USB Class Driver Related Tokens
9 | // #define HID_HOST_BOOT_PROTOCOL_ONLY
10 | // #define HID_STATETABLE_STACK_DEPTH {Insert Value Here}
11 | // #define HID_USAGE_STACK_DEPTH {Insert Value Here}
12 | // #define HID_MAX_COLLECTIONS {Insert Value Here}
13 | // #define HID_MAX_REPORTITEMS {Insert Value Here}
14 | // #define HID_MAX_REPORT_IDS {Insert Value Here}
15 | // #define NO_CLASS_DRIVER_AUTOFLUSH
16 |
17 | // General USB Driver Related Tokens
18 | // #define ORDERED_EP_CONFIG
19 | #define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL)
20 | #define USB_DEVICE_ONLY
21 | // #define USB_HOST_ONLY
22 | // #define USB_STREAM_TIMEOUT_MS {Insert Value Here}
23 | // #define NO_LIMITED_CONTROLLER_CONNECT
24 | // #define NO_SOF_EVENTS
25 |
26 | // USB Device Mode Driver Related Tokens
27 | // #define USE_RAM_DESCRIPTORS
28 | #define USE_FLASH_DESCRIPTORS
29 | // #define USE_EEPROM_DESCRIPTORS
30 | // #define NO_INTERNAL_SERIAL
31 | #define FIXED_CONTROL_ENDPOINT_SIZE 64
32 | // #define DEVICE_STATE_AS_GPIOR {Insert Value Here}
33 | #define FIXED_NUM_CONFIGURATIONS 1
34 | // #define CONTROL_ONLY_DEVICE
35 | // #define INTERRUPT_CONTROL_ENDPOINT
36 | // #define NO_DEVICE_REMOTE_WAKEUP
37 | // #define NO_DEVICE_SELF_POWER
38 |
39 | // USB Host Mode Driver Related Tokens
40 | // #define HOST_STATE_AS_GPIOR {Insert Value Here}
41 | // #define USB_HOST_TIMEOUT_MS {Insert Value Here}
42 | // #define HOST_DEVICE_SETTLE_DELAY_MS {Insert Value Here}
43 | // #define NO_AUTO_VBUS_MANAGEMENT
44 | // #define INVERTED_VBUS_ENABLE_LINE
45 |
46 | #elif (ARCH == ARCH_XMEGA)
47 | // Non-USB Related Configuration Tokens
48 | // #define DISABLE_TERMINAL_CODES
49 |
50 | // USB Class Driver Related Tokens
51 | // #define HID_HOST_BOOT_PROTOCOL_ONLY
52 | // #define HID_STATETABLE_STACK_DEPTH {Insert Value Here}
53 | // #define HID_USAGE_STACK_DEPTH {Insert Value Here}
54 | // #define HID_MAX_COLLECTIONS {Insert Value Here}
55 | // #define HID_MAX_REPORTITEMS {Insert Value Here}
56 | // #define HID_MAX_REPORT_IDS {Insert Value Here}
57 | // #define NO_CLASS_DRIVER_AUTOFLUSH
58 |
59 | // General USB Driver Related Tokens
60 | #define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_RC32MCLKSRC | USB_OPT_BUSEVENT_PRIHIGH)
61 | // #define USB_STREAM_TIMEOUT_MS {Insert Value Here}
62 | // #define NO_LIMITED_CONTROLLER_CONNECT
63 | // #define NO_SOF_EVENTS
64 |
65 | // USB Device Mode Driver Related Tokens
66 | // #define USE_RAM_DESCRIPTORS
67 | #define USE_FLASH_DESCRIPTORS
68 | // #define USE_EEPROM_DESCRIPTORS
69 | // #define NO_INTERNAL_SERIAL
70 | #define FIXED_CONTROL_ENDPOINT_SIZE 64
71 | // #define DEVICE_STATE_AS_GPIOR {Insert Value Here}
72 | #define FIXED_NUM_CONFIGURATIONS 1
73 | // #define CONTROL_ONLY_DEVICE
74 | #define MAX_ENDPOINT_INDEX 1
75 | // #define NO_DEVICE_REMOTE_WAKEUP
76 | // #define NO_DEVICE_SELF_POWER
77 |
78 | #else
79 | #error Unsupported architecture for this LUFA configuration file.
80 | #endif
81 | #endif
82 |
--------------------------------------------------------------------------------
/Arduino/utils/emulator.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import struct
3 | import sys
4 | import time
5 |
6 | CONTROLLER_SERIAL = '/dev/ttyACM0'
7 |
8 | SYNC_START = 1
9 | SYNC_1 = 2
10 | NEW_PACKET = 3
11 | REPLAY_PACKET = 4
12 | OUT_OF_SYNC = 5
13 |
14 | COMMAND_NOP = 0x00
15 | COMMAND_SYNC_1 = 0x33
16 | COMMAND_SYNC_2 = 0xCC
17 | COMMAND_SYNC_START = 0xFF
18 |
19 | RESP_USB_ACK = b'\x90'
20 | RESP_UPDATE_ACK = b'\x91'
21 | RESP_UPDATE_NACK = b'\x92'
22 | RESP_SYNC_START = b'\xFF'
23 | RESP_SYNC_1 = b'\xCC'
24 | RESP_SYNC_OK = b'\x33'
25 |
26 | def crc8_ccitt(old_crc, new_data):
27 | # https://www.microchip.com/webdoc/AVRLibcReferenceManual/group__util__crc_1gab27eaaef6d7fd096bd7d57bf3f9ba083.html
28 | data = old_crc ^ new_data
29 |
30 | for i in range(8):
31 | if (data & 0x80) != 0:
32 | data = data << 1
33 | data = data ^ 0x07
34 | else:
35 | data = data << 1
36 | data = data & 0xff
37 | return data
38 |
39 | def read():
40 | byte_in = sys.stdin.buffer.read(1)
41 | byte_int = byte_in[0]
42 | sys.stderr.write("\\x%x" % byte_int)
43 | sys.stderr.flush()
44 | return byte_int, byte_in
45 |
46 | def main(use_serial=False):
47 | data_buf = []
48 | state = OUT_OF_SYNC
49 | ser = None
50 |
51 | def start_sync():
52 | nonlocal state
53 | state = SYNC_START
54 | write(RESP_SYNC_START)
55 |
56 | def write(data):
57 | sys.stdout.buffer.write(data)
58 | sys.stdout.flush()
59 |
60 | if use_serial:
61 | ser_in = ser.read(1)
62 | if ser_in != data:
63 | sys.stderr.write('Hardware mismatch: received {} but expected {}\n'.format(ser_in, data))
64 |
65 | if use_serial:
66 | ser = serial.Serial(CONTROLLER_SERIAL, 19200)
67 | sys.stderr.write('Using serial passthrough\n')
68 |
69 | while True:
70 | byte_in, byte_str = read()
71 |
72 | if use_serial:
73 | ser.write(byte_str)
74 |
75 | if state == NEW_PACKET or state == REPLAY_PACKET:
76 | data_buf.append(byte_in)
77 |
78 | if len(data_buf) == 9:
79 | # Complete packet received
80 | crc = 0
81 | for d in data_buf[:-1]:
82 | crc = crc8_ccitt(crc, d)
83 |
84 | if crc != data_buf[-1]:
85 | # Bad CRC, check for special case
86 | if data_buf[-2] == data_buf[-1] == COMMAND_SYNC_START:
87 | start_sync()
88 | else:
89 | sys.stderr.write('CRC mismatch: sent checksum was {} but calculated {}\n'.format(data_buf[-1], crc))
90 | write(RESP_UPDATE_NACK)
91 | else:
92 | state = NEW_PACKET
93 | write(RESP_UPDATE_ACK)
94 | data_buf = []
95 |
96 | elif byte_in == COMMAND_SYNC_START:
97 | start_sync()
98 |
99 | elif state == SYNC_START and byte_in == COMMAND_SYNC_1:
100 | state = SYNC_1
101 | write(RESP_SYNC_1)
102 |
103 | elif state == SYNC_1 and byte_in == COMMAND_SYNC_2:
104 | state = NEW_PACKET
105 | write(RESP_SYNC_OK)
106 |
107 | else:
108 | state = OUT_OF_SYNC
109 |
110 | if state == NEW_PACKET:
111 | write(RESP_USB_ACK)
112 | state = REPLAY_PACKET
113 |
114 | if __name__ == '__main__':
115 | use_serial = len(sys.argv) > 1 and sys.argv[1] == '-serial'
116 | main(use_serial)
117 |
--------------------------------------------------------------------------------
/docs/help.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: help
3 | ---
4 |
5 | ## Q: What is this?
6 | ### A: A side project that I've been working on since March 2018. You can think of it as a Twitch Plays Switch with some nifty features.
7 |
8 | ## Q: Why did you make this?
9 | ### A: I was bored. Also, I wanted an easy way to use OpenCV to control the Switch, like [this guy](https://www.youtube.com/watch?v=w8kSVKgwpfM).
10 |
11 | ## Q: How does it work?
12 | ### A: At its core, it takes input either from a locally connected controller or via network and relays it to a Nintendo Switch over USB. I used [this project](https://github.com/progmem/Switch-Fightstick) and [this project](https://github.com/shinyquagsire23/Switch-Fightstick) as a base and grafted on everything else. Frankly, very little of the original projects remain.
13 |
14 | ### Currently, I've used the network support to add input support for Twitch/Mixer chat and for a webpage where players can control the Switch via a gamepad.
15 |
16 | ### I'll be writing up more documentation on the workings of this project at a later date.
17 |
18 | ## Q: What makes this better than \?
19 | ### A: There are a lot of modifications I've made to mine that aren't present in other versions.
20 |
21 | * User friendly, minimal config desktop software.
22 | * I've modified the controller to appear as an officially licensed [Nintendo Switch HORIPAD](https://www.amazon.com/Nintendo-Switch-HORIPAD-Controller-Officially-Licensed/dp/B01NAUATSM) instead of a hacked Pokken Tournament Pad like other implementations. There's no change in features, but the hardware emulation is much more accurate.
23 | * In case the desktop software crashes, the Switch turns off, or the hardware is disconnected, the hardware can recover by simply using the software to reconnect. A sync protocol is in place that brings the hardware into a known state.
24 | * The hardware's firmware buffers an input frame and checks it for data integrity via a fast algorithm before sending it to the Switch, so a crash will never cause random buttons to be pressed. Other implementations naively trust the input sent by the software and are susceptible to this problem.
25 | * Input packets are synchronized to the Switch's poll rate *in hardware*. All commands are mapped to one or more input packets with a certain number of wait frames before the next input packet is processed, so this is as close to TAS as you can get. Most other implementations do not do this and rely on hacks like sleeping for 100ms on the computer side, which can cause variable latency (OS timers are not guaranteed to be accurate) and desyncs for TAS.
26 | * Easier debugging. I've written some code that stubs out the firmware's AVR-specific calls and replaces them with implementations that allow the firmware to be run on desktop. When used in conjunction with something like `socat`, developers can quickly test out changes to the firmware without having to reflash the hardware.
27 | * Other implementations usually only support one input source. By using a TCP server, input commands can be sent from many different clients or feeder sources. This also makes testing very easy since all that is needed to send test inputs is something like `netcat`.
28 | * As far as I know, no other "Twitch Plays" project has ever allowed users to use controllers.
29 | * Chat commands and modifications to gamepad support can be added/modified **while the project is running** and can be modified in most cases without ever touching code!
30 |
31 | ## Q: How does chat work?
32 | ### A: Type in your commands into chat. A bot will relay them to my server and my desktop software will parse the commands and queue up the inputs. Your message must start with a command for it to be recognized.
33 |
34 | ## Q: What are the chat commands?
35 | ### A: [See the chat help page](chat_help.md)
36 |
37 | ## Q: How do the controllers work?
38 | ### A: Plug in a controller into your computer, go to the [gamepad control page](gamepad.md), press a button to activate it, and you're set! You can request a turn by clicking on the controller image. Each turn lasts 20 seconds unless you are the only person waiting for a turn, in which case you will keep your turn after 20 seconds until someone else requests one. Note that chat can and will mess with your inputs!
--------------------------------------------------------------------------------
/docs/assets/js/ControlWebSocket.js:
--------------------------------------------------------------------------------
1 | import {ConnectionState, StoreMutations} from "./Common"
2 | import {WebSocketClient} from "./lib/WebSocketClient";
3 |
4 | export const SocketBus = new Vue();
5 | export const SocketEvents = Object.freeze({
6 | SEND_MESSAGE: 'send',
7 | QUEUE_MESSAGE: 'queue',
8 | PONG: 'pong'
9 | });
10 |
11 | // This really shouldn't be a Vue component, but I don't know how I want to structure this. This works for now though
12 | export const ControlWs = {
13 | props: ['endpoint'],
14 | data: function() {
15 | return {
16 | ws: null,
17 | pendingPings: {},
18 | serverClockRequestTime: 0,
19 | queuedMessages: []
20 | };
21 | },
22 | created: function() {
23 | let self = this;
24 |
25 | this.ws = new WebSocketClient(this.endpoint, null, {
26 | backoff: 'fibonacci'
27 | });
28 |
29 | this.$store.commit(StoreMutations.CONNECTION_STATE, ConnectionState.CONNECTING);
30 |
31 | this.ws.addEventListener('open', function(e) {
32 | self.$store.commit(StoreMutations.CONNECTION_STATE, ConnectionState.CONNECTED);
33 | while (self.queuedMessages.length > 0) {
34 | let message = self.queuedMessages.shift();
35 | self.sendMessage(message);
36 | }
37 | });
38 |
39 | this.ws.addEventListener('close', function(e) {
40 | self.$store.commit(StoreMutations.CONNECTION_STATE, ConnectionState.NOT_CONNECTED);
41 | });
42 |
43 | this.ws.addEventListener('error', function(e) {
44 | self.$store.commit(StoreMutations.CONNECTION_STATE, ConnectionState.ERROR);
45 | });
46 |
47 | this.ws.addEventListener('reconnect', function(e) {
48 | if (self.ws.readyState === self.ws.CONNECTING) {
49 | self.$store.commit(StoreMutations.CONNECTION_STATE, ConnectionState.CONNECTING);
50 | }
51 | });
52 |
53 | this.ws.addEventListener('message', function(e) {
54 | const wsParseRegex = /(\w+)(?: (.*))?/;
55 | let match = wsParseRegex.exec(e.data);
56 | if (!match) {
57 | console.warn(`Got invalid message: ${e.data}`);
58 | return;
59 | }
60 |
61 | let command = match[1];
62 | let args = (match[2] || '').split(' ');
63 |
64 | if (command === 'PONG') {
65 | let time = performance.now();
66 | let serverTime = args[0];
67 | let id = args[1];
68 | if (self.pendingPings.hasOwnProperty(id)) {
69 | let duration = (time - self.pendingPings[id]) / 2;
70 | let actualServerTime = parseInt(serverTime);
71 | let calculatedServerTime = performance.timing.navigationStart + self.pendingPings[id] + duration;
72 | delete self.pendingPings[id];
73 | self.$store.commit(StoreMutations.SERVER_CLOCK_SKEW, actualServerTime - calculatedServerTime);
74 | SocketBus.$emit('pong', duration);
75 | }
76 | } else {
77 | SocketBus.$emit(command, args);
78 | }
79 | });
80 |
81 | SocketBus.$on(SocketEvents.SEND_MESSAGE, this.sendMessage);
82 | SocketBus.$on(SocketEvents.QUEUE_MESSAGE, this.queueMessage);
83 | setInterval(this.ping, 3000);
84 | },
85 | methods: {
86 | queueMessage: function(message) {
87 | if (this.ws && this.ws.readyState === this.ws.OPEN) {
88 | this.ws.send(message);
89 | return true;
90 | } else {
91 | console.log(`WebSocket not connected, so message was queued: ${message}`);
92 | this.queuedMessages.push(message);
93 | }
94 | },
95 | sendMessage: function(message) {
96 | if (this.ws && this.ws.readyState === this.ws.OPEN) {
97 | this.ws.send(message);
98 | return true;
99 | }
100 | console.warn('Failed to send message: ' + message);
101 | return false;
102 | },
103 | ping: function() {
104 | if (this.ws && this.ws.readyState === this.ws.OPEN) {
105 | let id = Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER));
106 | this.pendingPings[id] = performance.now();
107 | this.sendMessage(`PING ${id}`);
108 | }
109 | }
110 | },
111 | template: ''
112 | };
--------------------------------------------------------------------------------
/Arduino/include/Joystick.h:
--------------------------------------------------------------------------------
1 | /*
2 | LUFA Library
3 | Copyright (C) Dean Camera, 2014.
4 |
5 | dean [at] fourwalledcubicle [dot] com
6 | www.lufa-lib.org
7 | */
8 |
9 | /*
10 | Copyright 2014 Dean Camera (dean [at] fourwalledcubicle [dot] com)
11 |
12 | Permission to use, copy, modify, distribute, and sell this
13 | software and its documentation for any purpose is hereby granted
14 | without fee, provided that the above copyright notice appear in
15 | all copies and that both that the copyright notice and this
16 | permission notice and warranty disclaimer appear in supporting
17 | documentation, and that the name of the author not be used in
18 | advertising or publicity pertaining to distribution of the
19 | software without specific, written prior permission.
20 |
21 | The author disclaims all warranties with regard to this
22 | software, including all implied warranties of merchantability
23 | and fitness. In no event shall the author be liable for any
24 | special, indirect or consequential damages or any damages
25 | whatsoever resulting from loss of use, data or profits, whether
26 | in an action of contract, negligence or other tortious action,
27 | arising out of or in connection with the use or performance of
28 | this software.
29 | */
30 |
31 | /** \file
32 | *
33 | * Header file for Joystick.c.
34 | */
35 |
36 | #ifndef _JOYSTICK_H_
37 | #define _JOYSTICK_H_
38 |
39 | #ifdef MOCK_AVR
40 | #include "avr_mock.h"
41 | #else
42 | #include "avr.h"
43 | #include "Descriptors.h"
44 | #endif
45 |
46 | // Type Defines
47 | // Enumeration for joystick buttons.
48 | typedef enum {
49 | SWITCH_Y = 0x01,
50 | SWITCH_B = 0x02,
51 | SWITCH_A = 0x04,
52 | SWITCH_X = 0x08,
53 | SWITCH_L = 0x10,
54 | SWITCH_R = 0x20,
55 | SWITCH_ZL = 0x40,
56 | SWITCH_ZR = 0x80,
57 | SWITCH_MINUS = 0x100,
58 | SWITCH_PLUS = 0x200,
59 | SWITCH_LCLICK = 0x400,
60 | SWITCH_RCLICK = 0x800,
61 | SWITCH_HOME = 0x1000,
62 | SWITCH_CAPTURE = 0x2000,
63 | } JoystickButtons_t;
64 |
65 | #define HAT_TOP 0x00
66 | #define HAT_TOP_RIGHT 0x01
67 | #define HAT_RIGHT 0x02
68 | #define HAT_BOTTOM_RIGHT 0x03
69 | #define HAT_BOTTOM 0x04
70 | #define HAT_BOTTOM_LEFT 0x05
71 | #define HAT_LEFT 0x06
72 | #define HAT_TOP_LEFT 0x07
73 | #define HAT_CENTER 0x08
74 |
75 | #define STICK_MIN 0
76 | #define STICK_CENTER 128
77 | #define STICK_MAX 255
78 |
79 | typedef enum {
80 | COMMAND_NOP = 0,
81 | COMMAND_SYNC_1 = 0x33,
82 | COMMAND_SYNC_2 = 0xCC,
83 | COMMAND_SYNC_START = 0xFF
84 | } Command_t;
85 |
86 | typedef enum {
87 | RESP_USB_ACK = 0x90,
88 | RESP_UPDATE_ACK = 0x91,
89 | RESP_UPDATE_NACK = 0x92,
90 | RESP_SYNC_START = 0xFF,
91 | RESP_SYNC_1 = 0xCC,
92 | RESP_SYNC_OK = 0x33,
93 | } Response_t;
94 |
95 | // Joystick HID report structure. We have an input and an output.
96 | typedef struct {
97 | uint16_t Button; // 16 buttons; see JoystickButtons_t for bit mapping
98 | uint8_t HAT; // HAT switch; one nibble w/ unused nibble
99 | uint8_t LX; // Left Stick X
100 | uint8_t LY; // Left Stick Y
101 | uint8_t RX; // Right Stick X
102 | uint8_t RY; // Right Stick Y
103 | uint8_t VendorSpec;
104 | } USB_JoystickReport_Input_t;
105 |
106 | // The output is structured as a mirror of the input.
107 | // This is based on initial observations of the Pokken Controller.
108 | typedef struct {
109 | uint16_t Button; // 16 buttons; see JoystickButtons_t for bit mapping
110 | uint8_t HAT; // HAT switch; one nibble w/ unused nibble
111 | uint8_t LX; // Left Stick X
112 | uint8_t LY; // Left Stick Y
113 | uint8_t RX; // Right Stick X
114 | uint8_t RY; // Right Stick Y
115 | } USB_JoystickReport_Output_t;
116 |
117 | // Function Prototypes
118 | void USART_Init(int baud);
119 | void disable_watchdog(void);
120 | void disable_rx_isr(void);
121 | void enable_rx_isr(void);
122 | void send_byte(uint8_t c);
123 | uint8_t recv_byte(void);
124 | void send_string(const char *str);
125 |
126 | // Setup all necessary hardware, including USB initialization.
127 | void SetupHardware(void);
128 |
129 | // Process and deliver data from IN and OUT endpoints.
130 | void HID_Task(void);
131 |
132 | // USB device event handlers.
133 | void EVENT_USB_Device_Connect(void);
134 | void EVENT_USB_Device_Disconnect(void);
135 | void EVENT_USB_Device_ConfigurationChanged(void);
136 | void EVENT_USB_Device_ControlRequest(void);
137 |
138 | #endif
139 |
--------------------------------------------------------------------------------
/MultiInput/qt/xboxcontrollerinput.cpp:
--------------------------------------------------------------------------------
1 | #include "xboxcontrollerinput.h"
2 |
3 | XboxControllerInput::XboxControllerInput(int deviceId, std::shared_ptr writer, QObject *parent) : ControllerInput(writer, parent)
4 | {
5 | gamepad = std::unique_ptr(new QGamepad(deviceId));
6 |
7 | connect(gamepad.get(), SIGNAL(axisLeftXChanged(double)), this, SLOT(onControllerChange()));
8 | connect(gamepad.get(), SIGNAL(axisLeftYChanged(double)), this, SLOT(onControllerChange()));
9 | connect(gamepad.get(), SIGNAL(axisRightXChanged(double)), this, SLOT(onControllerChange()));
10 | connect(gamepad.get(), SIGNAL(axisRightYChanged(double)), this, SLOT(onControllerChange()));
11 | connect(gamepad.get(), SIGNAL(buttonL2Changed(double)), this, SLOT(onControllerChange()));
12 | connect(gamepad.get(), SIGNAL(buttonR2Changed(double)), this, SLOT(onControllerChange()));
13 | connect(gamepad.get(), SIGNAL(buttonL1Changed(bool)), this, SLOT(onControllerChange()));
14 | connect(gamepad.get(), SIGNAL(buttonR1Changed(bool)), this, SLOT(onControllerChange()));
15 | connect(gamepad.get(), SIGNAL(buttonL3Changed(bool)), this, SLOT(onControllerChange()));
16 | connect(gamepad.get(), SIGNAL(buttonR3Changed(bool)), this, SLOT(onControllerChange()));
17 | connect(gamepad.get(), SIGNAL(buttonAChanged(bool)), this, SLOT(onControllerChange()));
18 | connect(gamepad.get(), SIGNAL(buttonBChanged(bool)), this, SLOT(onControllerChange()));
19 | connect(gamepad.get(), SIGNAL(buttonXChanged(bool)), this, SLOT(onControllerChange()));
20 | connect(gamepad.get(), SIGNAL(buttonYChanged(bool)), this, SLOT(onControllerChange()));
21 | connect(gamepad.get(), SIGNAL(buttonUpChanged(bool)), this, SLOT(onControllerChange()));
22 | connect(gamepad.get(), SIGNAL(buttonDownChanged(bool)), this, SLOT(onControllerChange()));
23 | connect(gamepad.get(), SIGNAL(buttonLeftChanged(bool)), this, SLOT(onControllerChange()));
24 | connect(gamepad.get(), SIGNAL(buttonRightChanged(bool)), this, SLOT(onControllerChange()));
25 | connect(gamepad.get(), SIGNAL(buttonSelectChanged(bool)), this, SLOT(onControllerChange()));
26 | connect(gamepad.get(), SIGNAL(buttonStartChanged(bool)), this, SLOT(onControllerChange()));
27 | connect(gamepad.get(), SIGNAL(buttonGuideChanged(bool)), this, SLOT(onControllerChange()));
28 | connect(gamepad.get(), SIGNAL(buttonCenterChanged(bool)), this, SLOT(onControllerChange()));
29 |
30 | connect(gamepad.get(), &QGamepad::connectedChanged, [=](bool connected) { emit controllerConnectionStateChanged(connected); });
31 | }
32 |
33 | void XboxControllerInput::begin() {}
34 |
35 | void XboxControllerInput::getState(quint8 *outLx, quint8 *outLy, quint8 *outRx, quint8 *outRy, Dpad_t *outDpad, Button_t *outButtons, uint8_t *outVendorspec) {
36 | quint8 lx = STICK_CENTER;
37 | quint8 ly = STICK_CENTER;
38 | quint8 rx = STICK_CENTER;
39 | quint8 ry = STICK_CENTER;
40 | Dpad_t press = DPAD_NONE;
41 | Button_t button = BUTTON_NONE;
42 |
43 | lx = quantizeDouble(gamepad->axisLeftX());
44 | ly = quantizeDouble(gamepad->axisLeftY());
45 | rx = quantizeDouble(gamepad->axisRightX());
46 | ry = quantizeDouble(gamepad->axisRightY());
47 |
48 | bool up = gamepad->buttonUp();
49 | bool right = gamepad->buttonRight();
50 | bool down = gamepad->buttonDown();
51 | bool left = gamepad->buttonLeft();
52 |
53 | if (up) {
54 | if (right) press = DPAD_UP_RIGHT;
55 | else if (left) press = DPAD_UP_LEFT;
56 | else press = DPAD_UP;
57 | } else if (down) {
58 | if (right) press = DPAD_DOWN_RIGHT;
59 | else if (left) press = DPAD_DOWN_LEFT;
60 | else press = DPAD_DOWN;
61 | } else if (right) {
62 | press = DPAD_RIGHT;
63 | } else if (left) {
64 | press = DPAD_LEFT;
65 | } else {
66 | press = DPAD_NONE;
67 | }
68 |
69 | if (gamepad->buttonA()) button |= BUTTON_B;
70 | if (gamepad->buttonB()) button |= BUTTON_A;
71 | if (gamepad->buttonX()) button |= BUTTON_Y;
72 | if (gamepad->buttonY()) button |= BUTTON_X;
73 | if (gamepad->buttonL1()) button |= BUTTON_L;
74 | if (gamepad->buttonL2() > 0.6) button |= BUTTON_ZL;
75 | if (gamepad->buttonL3()) button |= BUTTON_LCLICK;
76 | if (gamepad->buttonR1()) button |= BUTTON_R;
77 | if (gamepad->buttonR2() > 0.6) button |= BUTTON_ZR;
78 | if (gamepad->buttonR3()) button |= BUTTON_RCLICK;
79 | if (gamepad->buttonSelect()) button |= BUTTON_MINUS;
80 | if (gamepad->buttonStart()) button |= BUTTON_PLUS;
81 | if (gamepad->buttonGuide() || gamepad->buttonCenter()) button |= BUTTON_HOME;
82 |
83 | if (outLx) *outLx = lx;
84 | if (outLy) *outLy = ly;
85 | if (outRx) *outRx = rx;
86 | if (outRy) *outRy = ry;
87 | if (outDpad) *outDpad = press;
88 | if (outButtons) *outButtons = button;
89 | if (outVendorspec) *outVendorspec = 0;
90 | }
91 |
--------------------------------------------------------------------------------
/docs/assets/css/general.css:
--------------------------------------------------------------------------------
1 | a.no-link {
2 | color: inherit;
3 | text-decoration: inherit;
4 | }
5 | .meter {
6 | height: 32px;
7 | position: relative;
8 | margin: 10px 0 10px 0;
9 | background: #555;
10 | -moz-border-radius: 25px;
11 | -webkit-border-radius: 25px;
12 | border-radius: 25px;
13 | padding: 6px;
14 | -webkit-box-shadow: inset 0 -1px 1px rgba(255,255,255,0.3);
15 | -moz-box-shadow : inset 0 -1px 1px rgba(255,255,255,0.3);
16 | box-shadow : inset 0 -1px 1px rgba(255,255,255,0.3);
17 | }
18 | .meter > .text-overlay {
19 | position: absolute;
20 | width: 100%;
21 | color: white;
22 | text-shadow: 0 0 8px #000000;
23 | z-index: 10000;
24 | display: flex;
25 | justify-content: center;
26 | }
27 | .meter > .text-overlay > img {
28 | height: 24px;
29 | width: 24px;
30 | margin-top: 4px;
31 | margin-right: 10px;
32 | float: left;
33 | vertical-align: center;
34 | }
35 | .meter > .text-overlay > span {
36 | float: right;
37 | line-height: 32px;
38 | }
39 | .meter > span {
40 | display: block;
41 | height: 100%;
42 | -webkit-border-radius: 20px 20px 20px 20px;
43 | -moz-border-radius: 20px 20px 20px 20px;
44 | border-radius: 20px 20px 20px 20px;
45 | background-color: rgb(43,194,83);
46 | background-image: -webkit-gradient(
47 | linear,
48 | left bottom,
49 | left top,
50 | color-stop(0, rgb(43,194,83)),
51 | color-stop(1, rgb(84,240,84))
52 | );
53 | background-image: -moz-linear-gradient(
54 | center bottom,
55 | rgb(43,194,83) 37%,
56 | rgb(84,240,84) 69%
57 | );
58 | -webkit-box-shadow:
59 | inset 0 2px 9px rgba(255,255,255,0.3),
60 | inset 0 -2px 6px rgba(0,0,0,0.4);
61 | -moz-box-shadow:
62 | inset 0 2px 9px rgba(255,255,255,0.3),
63 | inset 0 -2px 6px rgba(0,0,0,0.4);
64 | box-shadow:
65 | inset 0 2px 9px rgba(255,255,255,0.3),
66 | inset 0 -2px 6px rgba(0,0,0,0.4);
67 | position: relative;
68 | overflow: hidden;
69 | }
70 | .meter > span:after, .animate > span > span {
71 | content: "";
72 | position: absolute;
73 | top: 0; left: 0; bottom: 0; right: 0;
74 | background-image:
75 | -webkit-gradient(linear, 0 0, 100% 100%,
76 | color-stop(.25, rgba(255, 255, 255, .2)),
77 | color-stop(.25, transparent), color-stop(.5, transparent),
78 | color-stop(.5, rgba(255, 255, 255, .2)),
79 | color-stop(.75, rgba(255, 255, 255, .2)),
80 | color-stop(.75, transparent), to(transparent)
81 | );
82 | background-image:
83 | -moz-linear-gradient(
84 | -45deg,
85 | rgba(255, 255, 255, .2) 25%,
86 | transparent 25%,
87 | transparent 50%,
88 | rgba(255, 255, 255, .2) 50%,
89 | rgba(255, 255, 255, .2) 75%,
90 | transparent 75%,
91 | transparent
92 | );
93 | z-index: 1;
94 | -webkit-background-size: 50px 50px;
95 | -moz-background-size: 50px 50px;
96 | animation: move 2s linear infinite;
97 | -webkit-border-radius: 20px 20px 20px 20px;
98 | -moz-border-radius: 20px 20px 20px 20px;
99 | border-radius: 20px 20px 20px 20px;
100 | overflow: hidden;
101 | }
102 |
103 | .animate > span:after {
104 | display: none;
105 | }
106 |
107 | @keyframes move {
108 | 0% {
109 | background-position: 0 0;
110 | }
111 | 100% {
112 | background-position: 50px 50px;
113 | }
114 | }
115 |
116 | .orange > span {
117 | background-color: #f1a165;
118 | background-image: -moz-linear-gradient(top, #f1a165, #f36d0a);
119 | background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #f1a165),color-stop(1, #f36d0a));
120 | background-image: -webkit-linear-gradient(#f1a165, #f36d0a);
121 | }
122 |
123 | .red > span {
124 | background-color: #f0a3a3;
125 | background-image: -moz-linear-gradient(top, #f0a3a3, #f42323);
126 | background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #f0a3a3),color-stop(1, #f42323));
127 | background-image: -webkit-linear-gradient(#f0a3a3, #f42323);
128 | }
129 |
130 | .purple > span {
131 | background-color: #815fc0;
132 | background-image: -moz-linear-gradient(top, #815fc0, #6441a5);
133 | background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #815fc0),color-stop(1, #6441a5));
134 | background-image: -webkit-linear-gradient(#815fc0, #6441a5);
135 | }
136 |
137 | .nostripes > span > span, .nostripes > span:after {
138 | -webkit-animation: none;
139 | background-image: none;
140 | }
--------------------------------------------------------------------------------
/MultiInput/qt/serialportwriter.cpp:
--------------------------------------------------------------------------------
1 | #include "serialportwriter.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "controllerwindow.h"
10 |
11 | using std::cout;
12 | using std::cerr;
13 | using std::endl;
14 |
15 | SerialPortWriter::SerialPortWriter(const QString &portName, const QByteArray &data, QObject *parent) : QThread(parent) {
16 | m_portName = portName;
17 | this->data = data;
18 | }
19 |
20 | SerialPortWriter::~SerialPortWriter() {
21 | m_quit = true;
22 | wait();
23 | }
24 |
25 | void SerialPortWriter::changeData(const QByteArray &newData) {
26 | const QMutexLocker locker(&m_mutex);
27 | data = newData;
28 | }
29 |
30 | bool SerialPortWriter::writeAndExpectResponse(QSerialPort *serial, uint8_t send, uint8_t expect) {
31 | serial->clear();
32 | uint8_t readBuf;
33 | std::cout << "Writing and expecting: " << std::hex << static_cast(send) << " " << std::hex << static_cast(expect) << std::endl;
34 | serial->write((const char*) &send, 1);
35 | if (!serial->waitForBytesWritten(100) || !serial->waitForReadyRead(100)) {
36 | std::cout << "Timed out" << std::endl;
37 | return false;
38 | }
39 | int numRead = serial->read((char*) &readBuf, 1);
40 | std::cout << "Got back " << std::hex << static_cast(readBuf) << std::endl;
41 | return (numRead > 0 && readBuf == expect);
42 | }
43 |
44 | void SerialPortWriter::run() {
45 | QSerialPort serial;
46 |
47 | if (m_portName.isEmpty()) {
48 | emit error(tr("No port name specified"));
49 | return;
50 | }
51 | serial.setPortName(m_portName);
52 | serial.setBaudRate(19200);
53 | if (!serial.open(QIODevice::ReadWrite)) {
54 | emit error(tr("Can't open %1, error code %2")
55 | .arg(m_portName).arg(serial.error()));
56 | return;
57 | }
58 | emit message(tr("Serial port opened"));
59 | emit message(tr("Synchronizing hardware"));
60 |
61 | //this->setPriority(QThread::TimeCriticalPriority);
62 | bool isSynced = false;
63 |
64 | while(!m_quit && !isSynced) {
65 | if(writeAndExpectResponse(&serial, sync_bytes[0], sync_resp[0]))
66 | emit message(tr("Handshake stage 1 complete"));
67 | else continue;
68 |
69 | if(writeAndExpectResponse(&serial, sync_bytes[1], sync_resp[1]))
70 | emit message(tr("Handshake stage 2 complete"));
71 | else {
72 | emit timeout(tr("Handshake failed at stage 2, retrying..."));
73 | continue;
74 | }
75 |
76 | if(writeAndExpectResponse(&serial, sync_bytes[2], sync_resp[2]))
77 | emit message(tr("Handshake stage 3 complete"));
78 | else {
79 | emit timeout(tr("Handshake failed at stage 3, retrying..."));
80 | continue;
81 | }
82 |
83 | isSynced = true;
84 | }
85 |
86 | if(m_quit) {
87 | serial.close();
88 | return;
89 | }
90 | emit synced();
91 |
92 | m_mutex.lock();
93 | QByteArray pending = data;
94 | m_mutex.unlock();
95 |
96 | bool alreadyReturnedNoData = false;
97 | bool errorState = false;
98 |
99 | while (!m_quit) {
100 | if (!errorState) {
101 | serial.write(pending);
102 | serial.waitForReadyRead(16);
103 | QByteArray result = serial.read(1);
104 | if (result.length() > 0) {
105 | quint8 val = (quint8) result.at(0);
106 | if (val == 0x92) {
107 | std::cout << "Writing: " << QString(pending.toHex()).toStdString() << " Response: NACK 0x" << std::hex << (unsigned int) val << std::endl;
108 | emit error(tr("Got a NACK %1").arg(QTime::currentTime().toString()));
109 | errorState = true;
110 | } else if (val != 0x90) {
111 | std::cout << "Writing: " << QString(pending.toHex()).toStdString() << " Response: 0x" << std::hex << (unsigned int) val << std::endl;
112 | emit timeout(tr("Got unexpected value %1 %2").arg(val).arg(QTime::currentTime().toString()));
113 | } else {
114 | emit writeComplete();
115 | }
116 | alreadyReturnedNoData = false;
117 | } else if (!alreadyReturnedNoData) {
118 | alreadyReturnedNoData = true;
119 | emit timeout(tr("Read returned no data %1").arg(QTime::currentTime().toString()));
120 | }
121 | m_mutex.lock();
122 | pending = data;
123 | m_mutex.unlock();
124 | } else {
125 | msleep(10);
126 | }
127 | }
128 |
129 | std::cout << "Closing" << std::endl;
130 |
131 | serial.close();
132 | }
133 |
--------------------------------------------------------------------------------
/docs/assets/js/lib/ws-audio-player.js:
--------------------------------------------------------------------------------
1 | // WebSockets Audio API
2 | //
3 | // Opus Quality Settings
4 | // =====================
5 | // App: 2048=voip, 2049=audio, 2051=low-delay
6 | // Sample Rate: 8000, 12000, 16000, 24000, or 48000
7 | // Frame Duration: 2.5, 5, 10, 20, 40, 60
8 | // Buffer Size = sample rate/6000 * 1024
9 |
10 | (function(global) {
11 | const defaultConfig = {
12 | codec: {
13 | sampleRate: 24000,
14 | channels: 1,
15 | app: 2051,
16 | frameDuration: 20,
17 | bufferSize: 4096
18 | },
19 | server: {
20 | host: window.location.hostname,
21 | port: 5000
22 | }
23 | };
24 |
25 | const WSAudioAPI = global.WSAudioAPI = {
26 | Player: function(config, socket) {
27 | config = config || {};
28 | this.config = {};
29 | this.config.codec = config.codec || defaultConfig.codec;
30 | this.config.server = config.server || defaultConfig.server;
31 | this.sampler = new Resampler(this.config.codec.sampleRate, 44100, 1, this.config.codec.bufferSize);
32 | this.parentSocket = socket;
33 | this.decoder = new OpusDecoder(this.config.codec.sampleRate, this.config.codec.channels);
34 | this.silence = new Float32Array(this.config.codec.bufferSize);
35 | }
36 | };
37 |
38 | WSAudioAPI.Player.prototype.start = function() {
39 | const self = this;
40 |
41 | this.audioQueue = {
42 | buffer: new Float32Array(0),
43 |
44 | write: function(newAudio) {
45 | var currentQLength = this.buffer.length;
46 | newAudio = self.sampler.resampler(newAudio);
47 | var newBuffer = new Float32Array(currentQLength + newAudio.length);
48 | newBuffer.set(this.buffer, 0);
49 | newBuffer.set(newAudio, currentQLength);
50 | this.buffer = newBuffer;
51 | },
52 |
53 | read: function(nSamples) {
54 | var samplesToPlay = this.buffer.subarray(0, nSamples);
55 | this.buffer = this.buffer.subarray(nSamples, this.buffer.length);
56 | return samplesToPlay;
57 | },
58 |
59 | length: function() {
60 | return this.buffer.length;
61 | }
62 | };
63 | var audioContext = new(window.AudioContext || window.webkitAudioContext)();
64 |
65 | this.scriptNode = audioContext.createScriptProcessor(this.config.codec.bufferSize, 1, 1);
66 | this.scriptNode.onaudioprocess = function(e) {
67 | if (self.audioQueue.length()) {
68 | e.outputBuffer.getChannelData(0).set(self.audioQueue.read(self.config.codec.bufferSize));
69 | } else {
70 | e.outputBuffer.getChannelData(0).set(self.silence);
71 | }
72 | };
73 | this.gainNode = audioContext.createGain();
74 | this.scriptNode.connect(this.gainNode);
75 | this.gainNode.connect(audioContext.destination);
76 |
77 | if (!this.parentSocket) {
78 | this.socket = new WebSocket('wss://' + this.config.server.host + ':' + this.config.server.port);
79 | } else {
80 | this.socket = this.parentSocket;
81 | }
82 | //this.socket.onopen = function () {
83 | // console.log('Connected to server ' + self.config.server.host + ' as listener');
84 | //};
85 | var _onmessage = this.parentOnmessage = this.socket.onmessage;
86 | this.socket.onmessage = function(message) {
87 | if (_onmessage) {
88 | _onmessage(message);
89 | }
90 | if (message.data instanceof Blob) {
91 | var reader = new FileReader();
92 | reader.onload = function() {
93 | self.audioQueue.write(self.decoder.decode_float(reader.result));
94 | };
95 | reader.readAsArrayBuffer(message.data);
96 | }
97 | };
98 | };
99 |
100 | WSAudioAPI.Player.prototype.getVolume = function() {
101 | return this.gainNode ? this.gainNode.gain.value : 'Stream not started yet';
102 | };
103 |
104 | WSAudioAPI.Player.prototype.setVolume = function(value) {
105 | if (this.gainNode) this.gainNode.gain.value = value;
106 | };
107 |
108 | WSAudioAPI.Player.prototype.stop = function() {
109 | this.audioQueue = null;
110 | this.scriptNode.disconnect();
111 | this.scriptNode = null;
112 | this.gainNode.disconnect();
113 | this.gainNode = null;
114 |
115 | if (!this.parentSocket) {
116 | this.socket.close();
117 | } else {
118 | this.socket.onmessage = this.parentOnmessage;
119 | }
120 | };
121 | })(window);
--------------------------------------------------------------------------------
/docs/assets/js/stats.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (global.Stats = factory());
5 | }(this, (function () { 'use strict';
6 |
7 | /**
8 | * @author mrdoob / http://mrdoob.com/
9 | */
10 |
11 | let Stats = function () {
12 | let container = document.createElement( 'div' );
13 | container.style.cssText = 'position:absolute;top:0;right:0;opacity:0.9;z-index:10000';
14 |
15 | function addPanel( panel ) {
16 |
17 | container.appendChild( panel.dom );
18 | return panel;
19 |
20 | }
21 |
22 | function showPanel( id ) {
23 | for ( let i = 0; i < container.children.length; i ++ ) {
24 | if (i === id) {
25 | container.children[i].style.display = 'block';
26 | }
27 | }
28 | }
29 |
30 | function hidePanel( id ) {
31 | for ( let i = 0; i < container.children.length; i ++ ) {
32 | if (i === id) {
33 | container.children[i].style.display = 'none';
34 | }
35 | }
36 | }
37 |
38 | //
39 |
40 | let beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
41 |
42 | let fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
43 | let msPanel = addPanel( new Stats.Panel( 'ms render', '#0f0', '#020' ) );
44 |
45 | return {
46 |
47 | REVISION: 16,
48 |
49 | dom: container,
50 |
51 | addPanel: addPanel,
52 | showPanel: showPanel,
53 |
54 | begin: function () {
55 |
56 | beginTime = ( performance || Date ).now();
57 |
58 | },
59 |
60 | end: function () {
61 |
62 | frames ++;
63 |
64 | let time = ( performance || Date ).now();
65 |
66 | msPanel.update( time - beginTime, 200 );
67 |
68 | if ( time > prevTime + 1000 ) {
69 |
70 | fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
71 |
72 | prevTime = time;
73 | frames = 0;
74 |
75 | }
76 |
77 | return time;
78 |
79 | },
80 |
81 | update: function () {
82 |
83 | beginTime = this.end();
84 |
85 | },
86 |
87 | // Backwards Compatibility
88 |
89 | domElement: container,
90 |
91 | };
92 |
93 | };
94 |
95 | Stats.Panel = function ( name, fg, bg ) {
96 |
97 | let min = Infinity, max = 0, round = Math.round;
98 | let PR = round( window.devicePixelRatio || 1 );
99 |
100 | let WIDTH = 120 * PR, HEIGHT = 48 * PR,
101 | TEXT_X = 4.5 * PR, TEXT_Y = 2 * PR,
102 | GRAPH_X = 4.5 * PR, GRAPH_Y = 15 * PR,
103 | GRAPH_WIDTH = 111 * PR, GRAPH_HEIGHT = 30 * PR;
104 |
105 | let canvas = document.createElement( 'canvas' );
106 | canvas.width = WIDTH;
107 | canvas.height = HEIGHT;
108 | canvas.style.cssText = 'width:120px;height:48px';
109 |
110 | let context = canvas.getContext( '2d' );
111 | context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
112 | context.textBaseline = 'top';
113 |
114 | context.fillStyle = bg;
115 | context.fillRect( 0, 0, WIDTH, HEIGHT );
116 |
117 | context.fillStyle = fg;
118 | context.fillText( name, TEXT_X, TEXT_Y );
119 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
120 |
121 | context.fillStyle = bg;
122 | context.globalAlpha = 0.9;
123 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
124 |
125 | return {
126 |
127 | dom: canvas,
128 |
129 | update: function ( value, maxValue ) {
130 |
131 | min = Math.min( min, value );
132 | max = Math.max( max, value );
133 |
134 | context.fillStyle = bg;
135 | context.globalAlpha = 1;
136 | context.fillRect( 0, 0, WIDTH, GRAPH_Y );
137 | context.fillStyle = fg;
138 | context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
139 |
140 | context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
141 |
142 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
143 |
144 | context.fillStyle = bg;
145 | context.globalAlpha = 0.9;
146 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
147 |
148 | }
149 |
150 | };
151 |
152 | };
153 |
154 | return Stats;
155 |
156 | })));
--------------------------------------------------------------------------------
/docs/assets/js/ControlModeSelect.js:
--------------------------------------------------------------------------------
1 | import {KeyboardInputSource} from "./KeyboardInputSource";
2 | import {ControllerInputSource} from "./ControllerInputSource";
3 | import {JoyConInputSource} from "./JoyConInputSource";
4 | import * as Utils from "./Utils";
5 |
6 | export const ControlMode = Object.freeze({
7 | SINGLE_CONTROLLER: 'controller-input',
8 | MULTIPLE_CONTROLLERS: 'multiple-controller-input',
9 | KEYBOARD: 'keyboard-input',
10 | TOUCH: 'touch-input'
11 | });
12 |
13 | export const ControlModeSelect = {
14 | components: {
15 | 'keyboard-input': KeyboardInputSource,
16 | 'controller-input': ControllerInputSource,
17 | 'multiple-controller-input': JoyConInputSource
18 | },
19 | data: function() {
20 | return {
21 | selectedMode: ControlMode.KEYBOARD,
22 | modeEnabled: {
23 | [ControlMode.KEYBOARD]: [true, ''],
24 | [ControlMode.SINGLE_CONTROLLER]: [false, ''],
25 | [ControlMode.MULTIPLE_CONTROLLERS]: [false, ''],
26 | [ControlMode.TOUCH]: [false, '']
27 | }
28 | }
29 | },
30 | computed: {
31 | currentControlModeComponent: function() {
32 | if (this.selectedMode === ControlMode.SINGLE_CONTROLLER) {
33 | return 'controller-input';
34 | } else if (this.selectedMode === ControlMode.MULTIPLE_CONTROLLERS) {
35 | return 'multiple-controller-input';
36 | } else if (this.selectedMode === ControlMode.TOUCH) {
37 | return 'touch-input';
38 | }
39 |
40 | return 'keyboard-input';
41 | }
42 | },
43 | watch: {
44 | selectedMode: function() {
45 | this.$refs.select.blur();
46 | if (!this.modeEnabled[this.selectedMode][0]) {
47 | this.selectedMode = ControlMode.KEYBOARD;
48 | }
49 | }
50 | },
51 | methods: {
52 | updateModeState: function() {
53 | let newState = {};
54 |
55 | let modes = Object.keys(this.modeEnabled);
56 | for (let i = 0; i < modes.length; i++) {
57 | let reason = this.getDisabledReason(modes[i]);
58 | if (reason && reason.length > 0) {
59 | newState[modes[i]] = [false, reason];
60 | } else {
61 | newState[modes[i]] = [true, ''];
62 | }
63 | }
64 |
65 | this.modeEnabled = newState;
66 | },
67 | getModeText: function(mode){
68 | let text;
69 | if (mode === ControlMode.SINGLE_CONTROLLER) {
70 | text = 'Controller';
71 | } else if (mode === ControlMode.MULTIPLE_CONTROLLERS) {
72 | text = 'Joycons';
73 | } else if (mode === ControlMode.TOUCH) {
74 | text = 'Touch controls';
75 | } else {
76 | text = 'Keyboard';
77 | }
78 | if (!this.modeEnabled[mode][0]) {
79 | text += ` (${this.modeEnabled[mode][1]})`;
80 | }
81 | return text;
82 | },
83 | getDisabledReason: function(mode) {
84 | if (mode === ControlMode.SINGLE_CONTROLLER) {
85 | let gp = navigator.getGamepads();
86 | for(let i = 0; i < gp.length; i++) {
87 | if (gp[i]) return '';
88 | }
89 | return 'No controllers detected';
90 | } else if (mode === ControlMode.MULTIPLE_CONTROLLERS) {
91 | let browser = Utils.detectBrowser();
92 | if (browser === 'Firefox') {
93 | return 'Not supported in Firefox';
94 | } else if (browser === 'Edge') {
95 | return 'Not supported in Edge';
96 | }
97 | let left = false, right = false;
98 | let gp = navigator.getGamepads();
99 | for(let i = 0; i < gp.length; i++) {
100 | if (gp[i] && Utils.checkVidPid(gp[i].id, '57e', '2006')) left = true;
101 | else if (gp[i] && Utils.checkVidPid(gp[i].id, '57e', '2007')) right = true;
102 | }
103 | if (left && right) return '';
104 | else if (left) return 'Right JoyCon not connected';
105 | else if (right) return 'Left JoyCon not connected';
106 | else return 'No JoyCons connected';
107 | } else if (mode === ControlMode.TOUCH) {
108 | return 'Not implemented yet';
109 | } else {
110 | return '';
111 | }
112 | }
113 | },
114 | mounted: function() {
115 | let self = this;
116 | window.addEventListener('gamepadconnected', function(e) {
117 | console.log('Detected gamepad: ' + e.gamepad.id);
118 | self.updateModeState();
119 | });
120 |
121 | window.addEventListener('gamepaddisconnected', function(e) {
122 | console.log('Gamepad disconnected: ' + e.gamepad.id);
123 | self.updateModeState();
124 | });
125 | },
126 | template: '' +
127 | '
'
128 | };
--------------------------------------------------------------------------------
/MultiInput/help.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: help
3 | ---
4 |
5 | ### Button commands
6 | * press
7 | * Presses the specified button for 500ms. Just the button name can also be used
8 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
9 |
10 | * hold
11 | * Holds the specified button
12 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
13 |
14 | * release
15 | * Aliases: *rel*
16 | * Releases the specified button
17 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
18 |
19 | * release all
20 | * Aliases: *release, rel, release all, rel all*
21 | * Releases all inputs (buttons, dpad, analog sticks)
22 |
23 | ### DPad commands
24 | * press
25 | * Presses the DPad in the specified direction for 500ms. Just the button name can also be used
26 | * Allowed parameters: `up, down, left, right, upleft, up left, upright, up right, downleft, down left, downright, down right`
27 |
28 | * hold
29 | * Holds the DPad in the specified direction
30 | * Allowed parameters: `up, down, left, right, upleft, up left, upright, up right, downleft, down left, downright, down right`
31 |
32 | * release dpad
33 | * Releases the DPad completely
34 |
35 | ### Move commands
36 | * move
37 | * Moves the left analog stick in the specified direction for 500ms
38 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
39 |
40 | * move hold
41 | * Holds the left analog stick in the specified direction
42 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
43 |
44 | * move release
45 | * Aliases: *move rel, adjust release, adjust rel, adj rel*
46 | * Releases the left analog stick. If direction is specified, releases that direction only, otherwise releases completely
47 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
48 |
49 | * move adjust
50 | * Aliases: *move adj*
51 | * Moves the left analog stick for 500ms
52 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
53 |
54 | * move adjust hold
55 | * Holds the left analog stick halfway in the specified direction
56 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
57 |
58 | ### Look commands
59 | * look
60 | * Moves the right analog stick in the specified direction for 500ms
61 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
62 |
63 | * look hold
64 | * Holds the right analog stick in the specified direction
65 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
66 |
67 | * look release
68 | * Aliases: *look rel, look adjust release, look adj rel*
69 | * Releases the right analog stick. If direction is specified, releases that direction only, otherwise releases completely
70 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
71 |
72 | * look adjust
73 | * Aliases: *look adj*
74 | * Moves the right analog stick for 500ms
75 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
76 |
77 | * look adjust hold
78 | * Aliases: *look adj hold*
79 | * Holds the right analog stick halfway in the specified direction
80 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
81 |
82 | ### Other commands
83 | * wait
84 | * Does nothing for 1000ms
85 |
86 | * longwait
87 | * Does nothing for 2000ms
88 |
89 | ### Game-specific commands
90 | * sphere bomb
91 | * Switches to the sphere bomb rune
92 |
93 | * cube bomb
94 | * Switches to the cube bomb rune
95 |
96 | * magnesis
97 | * Switches to the magnesis rune
98 |
99 | * stasis
100 | * Switches to the stasis rune
101 |
102 | * cryonis
103 | * Switches to the cryonis rune
104 |
105 | * camera
106 | * Switches to the camera rune
107 |
108 | * snowball
109 | * Cheeses the snowball game
110 |
111 | * turn 180
112 | * Turns around 180 degrees
113 |
114 | * turn 180
115 | * Turns around 180 degrees
116 |
117 | * turn left 90
118 | * Turns left 90 degrees
119 |
120 | * next weapon
121 | * Aliases: *next wep*
122 | * Switches to the next weapon
123 |
124 | * previous weapon
125 | * Aliases: *previous wep, prev weapon, prev wep*
126 | * Switches to the previous weapon
127 |
128 | * next shield
129 | * Aliases: *next arrow*
130 | * Switches to the next shield. If a bow is equipped, switches to the next arrow instead.
131 |
132 | * previous shield
133 | * Aliases: *prev shield, previous arrow, prev arrow*
134 | * Switches to the previous shield. If a bow is equipped, switches to the previous arrow instead.
135 |
136 | * save
137 | * Saves the game.
138 |
139 | * load
140 | * Loads the game.
141 |
142 |
--------------------------------------------------------------------------------
/docs/assets/js/OpusWebSocketPlayer.js:
--------------------------------------------------------------------------------
1 | import {WebSocketClient} from "./lib/WebSocketClient";
2 |
3 | export const OpusWebSocketPlayer = {
4 | props: ['endpoint'],
5 | data: function() {
6 | return {
7 | settings: {
8 | sampleRate: 24000,
9 | channels: 1,
10 | app: 2051,
11 | frameDuration: 20,
12 | bufferSize: 4096,
13 | },
14 | audioContext: null,
15 | scriptNode: null,
16 | gainNode: null,
17 | audioQueue: null,
18 | volume: 0.5,
19 | muted: true,
20 | decoder: null,
21 | sampler: null,
22 | silence: null
23 | };
24 | },
25 | mounted: function() {
26 | let self = this;
27 | $('.slider').slider({
28 | min: 0,
29 | max: 100,
30 | value: 0,
31 | range: 'min',
32 | slide: function(event, ui) {
33 | self.setVolume(ui.value / 100);
34 | self.unmute();
35 | if (!self.scriptNode) {
36 | self.start();
37 | }
38 | }
39 | });
40 | /*
41 | this.$refs.volumeControl.addEventListener('change', function(e) {
42 | let val = self.$refs.volumeControl.value;
43 | self.setVolume(val);
44 | self.unmute();
45 | if (!self.scriptNode) {
46 | self.start();
47 | }
48 | });
49 | */
50 | },
51 | methods: {
52 | // This has to be called by some user interaction or it will not work!
53 | start: function() {
54 | let self = this;
55 |
56 | this.decoder = new OpusDecoder(this.settings.sampleRate, this.settings.channels);
57 | this.sampler = new Resampler(this.settings.sampleRate, 44100, 1, this.settings.bufferSize);
58 | this.silence = new Float32Array(this.settings.bufferSize);
59 |
60 | this.audioContext = new(window.AudioContext || window.webkitAudioContext)();
61 |
62 | this.audioQueue = {
63 | buffer: new Float32Array(0),
64 |
65 | write: function(newAudio) {
66 | const currentQLength = this.buffer.length;
67 | newAudio = self.sampler.resampler(newAudio);
68 | let newBuffer = new Float32Array(currentQLength + newAudio.length);
69 | newBuffer.set(this.buffer, 0);
70 | newBuffer.set(newAudio, currentQLength);
71 | this.buffer = newBuffer;
72 | },
73 |
74 | read: function(nSamples) {
75 | const samplesToPlay = this.buffer.subarray(0, nSamples);
76 | this.buffer = this.buffer.subarray(nSamples, this.buffer.length);
77 | return samplesToPlay;
78 | },
79 |
80 | length: function() {
81 | return this.buffer.length;
82 | }
83 | };
84 |
85 | /* TODO: ScriptProcessorNode is deprecated (https://developer.mozilla.org/en-US/docs/Web/API/ScriptProcessorNode).
86 | Replace this with an AudioWorkletNode.
87 | */
88 | this.scriptNode = this.audioContext.createScriptProcessor(this.settings.bufferSize, 1, 1);
89 | this.scriptNode.onaudioprocess = function(e) {
90 | if (self.audioQueue.length()) {
91 | e.outputBuffer.getChannelData(0).set(self.audioQueue.read(self.settings.bufferSize));
92 | } else {
93 | e.outputBuffer.getChannelData(0).set(self.silence);
94 | }
95 | };
96 | this.gainNode = this.audioContext.createGain();
97 | this.setVolume(this.muted ? 0 : this.volume);
98 | this.scriptNode.connect(this.gainNode);
99 | this.gainNode.connect(this.audioContext.destination);
100 |
101 | this.ws = new WebSocketClient(this.endpoint, null, {
102 | backoff: 'fibonacci'
103 | });
104 | this.ws.addEventListener('message', function(e) {
105 | const fileReader = new FileReader();
106 | fileReader.addEventListener('load', function() {
107 | self.audioQueue.write(self.decoder.decode_float(fileReader.result));
108 | });
109 | fileReader.readAsArrayBuffer(e.data);
110 | });
111 | },
112 | stop: function() {
113 | this.audioQueue = null;
114 | if (this.scriptNode) {
115 | this.scriptNode.disconnect();
116 | this.scriptNode = null;
117 | }
118 | if (this.gainNode) {
119 | this.gainNode.disconnect();
120 | this.gainNode = null;
121 | }
122 |
123 | this.ws.close();
124 | },
125 | getVolume: function() {
126 | return this.volume;
127 | },
128 | setVolume: function(volume) {
129 | this.volume = volume;
130 | if (this.gainNode) {
131 | this.gainNode.gain.value = this.muted ? 0 : this.volume;
132 | }
133 | },
134 | mute: function() {
135 | this.muted = true;
136 | if (this.gainNode) {
137 | this.gainNode.gain.value = 0;
138 | }
139 | },
140 | unmute: function() {
141 | this.muted = false;
142 | if (this.gainNode) {
143 | this.gainNode.gain.value = this.volume;
144 | }
145 | }
146 | },
147 | //template: ""
148 | template: ""
149 | };
--------------------------------------------------------------------------------
/docs/chat_help.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: help
3 | ---
4 |
5 | # Notes
6 | * `command1 & command2` executes the commands at the same time (so you can press both A and B simultaneously using `a & b`).
7 | * `command1, command2` executes the commands in series (so `a & b` presses A first, then B).
8 | * These can be combined, for example: `move left, zl & a` moves the left analog stick left, then presses ZL and A simultaneously.
9 | * If two inputs conflict, the latter command has priority. For example, `up & down` will result in only `down` being pressed. `up, down` would work just fine, however.
10 | * Spaces are ignored when combining commands, so `a&b` is fine, for example.
11 |
12 | ### Button commands
13 | * press
14 | * Presses the specified button for 500ms. Just the button name can also be used
15 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
16 |
17 | * hold
18 | * Holds the specified button
19 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
20 |
21 | * release
22 | * Aliases: *rel*
23 | * Releases the specified button
24 | * Allowed parameters: `a, b, x, y, l, lb, l1, r, rb, r1, zl, lt, l2, zr, rt, r2, minus, -, plus, +, l3, r3`
25 |
26 | * release all
27 | * Aliases: *release, rel, release all, rel all*
28 | * Releases all inputs (buttons, dpad, analog sticks)
29 |
30 | ### DPad commands
31 | * press
32 | * Presses the DPad in the specified direction for 500ms. Just the button name can also be used
33 | * Allowed parameters: `up, down, left, right, upleft, up left, upright, up right, downleft, down left, downright, down right`
34 |
35 | * hold
36 | * Holds the DPad in the specified direction
37 | * Allowed parameters: `up, down, left, right, upleft, up left, upright, up right, downleft, down left, downright, down right`
38 |
39 | * release dpad
40 | * Releases the DPad completely
41 |
42 | ### Move commands
43 | * move
44 | * Moves the left analog stick in the specified direction for 500ms
45 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
46 |
47 | * move hold
48 | * Holds the left analog stick in the specified direction
49 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
50 |
51 | * move release
52 | * Aliases: *move rel, adjust release, adjust rel, adj rel*
53 | * Releases the left analog stick. If direction is specified, releases that direction only, otherwise releases completely
54 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
55 |
56 | * move adjust
57 | * Aliases: *move adj*
58 | * Moves the left analog stick for 500ms
59 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
60 |
61 | * move adjust hold
62 | * Holds the left analog stick halfway in the specified direction
63 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
64 |
65 | ### Look commands
66 | * look
67 | * Moves the right analog stick in the specified direction for 500ms
68 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
69 |
70 | * look hold
71 | * Holds the right analog stick in the specified direction
72 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
73 |
74 | * look release
75 | * Aliases: *look rel, look adjust release, look adj rel*
76 | * Releases the right analog stick. If direction is specified, releases that direction only, otherwise releases completely
77 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
78 |
79 | * look adjust
80 | * Aliases: *look adj*
81 | * Moves the right analog stick for 500ms
82 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
83 |
84 | * look adjust hold
85 | * Aliases: *look adj hold*
86 | * Holds the right analog stick halfway in the specified direction
87 | * Allowed parameters: `up, forward, down, backward, back, left, right, upright, up right, upleft, up left, downright, down right, downleft, down left`
88 |
89 | ### Other commands
90 | * wait
91 | * Does nothing for 1000ms
92 |
93 | * longwait
94 | * Does nothing for 2000ms
95 |
96 | ### Game-specific commands
97 | * sphere bomb
98 | * Switches to the sphere bomb rune
99 |
100 | * cube bomb
101 | * Switches to the cube bomb rune
102 |
103 | * magnesis
104 | * Switches to the magnesis rune
105 |
106 | * stasis
107 | * Switches to the stasis rune
108 |
109 | * cryonis
110 | * Switches to the cryonis rune
111 |
112 | * camera
113 | * Switches to the camera rune
114 |
115 | * turn 180
116 | * Turns around 180 degrees
117 |
118 | * next weapon
119 | * Aliases: *next wep*
120 | * Switches to the next weapon
121 |
122 | * previous weapon
123 | * Aliases: *previous wep, prev weapon, prev wep*
124 | * Switches to the previous weapon
125 |
126 | * next shield
127 | * Aliases: *next arrow*
128 | * Switches to the next shield. If a bow is equipped, switches to the next arrow instead.
129 |
130 | * previous shield
131 | * Aliases: *prev shield, previous arrow, prev arrow*
132 | * Switches to the previous shield. If a bow is equipped, switches to the previous arrow instead.
133 |
134 | * save
135 | * Saves the game.
136 |
137 |
--------------------------------------------------------------------------------
/docs/assets/js/PowerAWiredController.js:
--------------------------------------------------------------------------------
1 | import {BaseController, StandardMappings} from "./BaseController";
2 |
3 | const powerAWiredControllerBase = {
4 | mixins: [BaseController],
5 | data: function() {
6 | return {
7 | canonicalName: 'PowerA Wired Controller'
8 | };
9 | }
10 | };
11 |
12 | export const PowerAWiredControllerStandard = {
13 | mixins: [powerAWiredControllerBase, StandardMappings]
14 | };
15 |
16 | export const PowerAWiredControllerMacFirefox = {
17 | mixins: [powerAWiredControllerBase],
18 | data: function() {
19 | return {
20 | buttonMapping: {
21 | faceLeft: 0,
22 | faceDown: 1,
23 | faceRight: 2,
24 | faceUp: 3,
25 | leftTop: 4,
26 | rightTop: 5,
27 | leftTrigger: 6,
28 | rightTrigger: 7,
29 | select: 8,
30 | start: 9,
31 | leftStick: 10,
32 | rightStick: 11,
33 | dpadUp: 14,
34 | dpadDown: 15,
35 | dpadLeft: 16,
36 | dpadRight: 17
37 | },
38 | stickMapping: {
39 | leftStick: {axisX: 0, axisY: 1},
40 | rightStick: {axisX: 2, axisY: 3}
41 | }
42 | };
43 | }
44 | };
45 |
46 | export const PowerAWiredControllerChromeOS = {
47 | mixins: [powerAWiredControllerBase],
48 | data: function() {
49 | return {
50 | buttonMapping: {
51 | faceLeft: 0,
52 | faceDown: 1,
53 | faceRight: 2,
54 | faceUp: 3,
55 | leftTop: 4,
56 | rightTop: 5,
57 | leftTrigger: 6,
58 | rightTrigger: 7,
59 | select: 8,
60 | start: 9,
61 | leftStick: 10,
62 | rightStick: 11
63 | },
64 | dpadMapping: {
65 | dpadUp: {axis: 5, sign: -1},
66 | dpadDown: {axis: 5, sign: 1},
67 | dpadLeft: {axis: 4, sign: -1},
68 | dpadRight: {axis: 4, sign: 1}
69 | },
70 | stickMapping: {
71 | leftStick: {axisX: 0, axisY: 1},
72 | rightStick: {axisX: 2, axisY: 3}
73 | }
74 | };
75 | },
76 | methods: {
77 | isButtonPressed: function(name) {
78 | if (this.dpadMapping.hasOwnProperty(name)) {
79 | let mapping = this.dpadMapping[name];
80 | return mapping && mapping.hasOwnProperty('axis') && mapping.hasOwnProperty('sign') && this.axes[mapping.axis] * mapping.sign > this.deadzone;
81 | } else {
82 | let index = this.buttonMapping[name];
83 | if (index === null || index === undefined || index < 0) return false;
84 | return this.buttons[index];
85 | }
86 | }
87 | }
88 | };
89 |
90 | export const PowerAWiredControllerChrome = {
91 | mixins: [powerAWiredControllerBase],
92 | data: function() {
93 | return {
94 | buttonMapping: {
95 | faceLeft: 0,
96 | faceDown: 1,
97 | faceRight: 2,
98 | faceUp: 3,
99 | leftTop: 4,
100 | rightTop: 5,
101 | leftTrigger: 6,
102 | rightTrigger: 7,
103 | select: 8,
104 | start: 9,
105 | leftStick: 10,
106 | rightStick: 11
107 | },
108 | dpadMapping: {
109 | dpadUp: {axis: 9, axisVals: [-7, -5, 7]},
110 | dpadDown: {axis: 9, axisVals: [-1, 1, 3]},
111 | dpadLeft: {axis: 9, axisVals: [3, 5, 7]},
112 | dpadRight: {axis: 9, axisVals: [-5, -3, -1]},
113 | },
114 | stickMapping: {
115 | leftStick: {axisX: 0, axisY: 1},
116 | rightStick: {axisX: 2, axisY: 5}
117 | }
118 | }
119 | },
120 | methods: {
121 | isButtonPressed: function(name) {
122 | if (this.dpadMapping.hasOwnProperty(name)) {
123 | let mapping = this.dpadMapping[name];
124 | if (!(mapping && mapping.hasOwnProperty('axis') && mapping.hasOwnProperty('axisVals'))) return false;
125 | let axisValNormalized = this.axes[mapping.axis] * 7;
126 | for (let i = 0; i < mapping.axisVals.length; i++) {
127 | if (Math.abs(mapping.axisVals[i] - axisValNormalized) < 0.1) return true;
128 | }
129 | return false;
130 | } else {
131 | let index = this.buttonMapping[name];
132 | if (index === null || index === undefined || index < 0) return false;
133 | return this.buttons[index];
134 | }
135 | }
136 | }
137 | };
138 |
139 | export const PowerAWiredControllerWinFirefox = {
140 | mixins: [powerAWiredControllerBase],
141 | data: function() {
142 | return {
143 | buttonMapping: {
144 | faceLeft: 0,
145 | faceDown: 1,
146 | faceRight: 2,
147 | faceUp: 3,
148 | leftTop: 4,
149 | rightTop: 5,
150 | leftTrigger: 6,
151 | rightTrigger: 7,
152 | select: 8,
153 | start: 9,
154 | leftStick: 10,
155 | rightStick: 11,
156 | // DPad Up is mapped to share, DPad down is mapped to home
157 | dpadUp: 13,
158 | dpadDown: 12,
159 | dpadLeft: null,
160 | dpadRight: null
161 | },
162 | stickMapping: {
163 | leftStick: {axisX: 0, axisY: 1},
164 | rightStick: {axisX: 2, axisY: 3}
165 | },
166 | notifyMessage: 'The D-Pad does not work properly in Firefox on Windows. The Share button has been mapped to D-Pad Up. If this doesn\'t work for you, try using Chrome.'
167 | }
168 | }
169 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## SwitchInputEmulator
2 | USB Controller Emulator for the Nintendo Switch
3 |
4 | Uses the LUFA library and reverse-engineering of the HORIPAD for Nintendo Switch for accurate controller emulation. Can be controlled either directly via serial port or via the cross-platform Qt application.
5 |
6 | For more details, read the [project site](https://switch.chilly.codes)
7 |
8 | ### Wait, what?
9 | On June 20, 2017, Nintendo released System Update v3.0.0 for the Nintendo Switch. Along with a number of additional features that were advertised or noted in the changelog, additional hidden features were added. One of those features allows for the use of compatible USB controllers on the Nintendo Switch, such as the Pokken Tournament Pro Pad.
10 |
11 | Unlike the Wii U, which handles these controllers on a 'per-game' basis, the Switch treats the Pokken controller as if it was a Switch Pro Controller. Along with having the icon for the Pro Controller, it functions just like it in terms of using it in other games, apart from the lack of physical controls such as analog sticks, the buttons for the stick clicks, or other system buttons such as Home or Capture.
12 |
13 | The original version of the code that this repo is based off of emulated the Pokken Tournament Pro Pad, but changes have been made to support the HORIPAD wired controller for Nintendo Switch instead. In addition, many additional features/improvements have been added.
14 |
15 | ### Setup
16 |
17 | #### Prerequisites
18 | * A LUFA-compatible microcontroller such as the Teensy 2.0++, Arduino UNO R3, or the Arduino Micro
19 | * A USB-to-UART adapter. In a pinch, an Arduino UNO R3 with the ATMega328p disabled (connect RESET to GND) will work.
20 | * A machine running Linux or MacOS. Currently there are issues running under Windows.
21 |
22 | #### Compiling and Flashing onto the Teensy 2.0++
23 | Go to the Teensy website and download/install the [Teensy Loader application](https://www.pjrc.com/teensy/loader.html). For Linux, follow their instructions for installing the [GCC Compiler and Tools](https://www.pjrc.com/teensy/gcc.html). For Windows, you will need the [latest AVR toolchain](http://www.atmel.com/tools/atmelavrtoolchainforwindows.aspx) from the Atmel site. See [this issue](https://github.com/LightningStalker/Splatmeme-Printer/issues/10) and [this thread](http://gbatemp.net/threads/how-to-use-shinyquagsires-splatoon-2-post-printer.479497/) on GBAtemp for more information. (Note for Mac users - the AVR MacPack is now called AVR CrossPack. If that does not work, you can try installing `avr-gcc` with `brew`.)
24 |
25 | Next, you need to grab the LUFA library. You can download it in a zipped folder at the bottom of [this page](http://www.fourwalledcubicle.com/LUFA.php). Unzip the folder, rename it `LUFA`, and place it where you like. Then, download or clone the contents of this repository onto your computer. Next, you'll need to make sure the `LUFA_PATH` inside of the `makefile` points to the `LUFA` subdirectory inside your `LUFA` directory. My `Switch-Fightstick` directory is in the same directory as my `LUFA` directory, so I set `LUFA_PATH = ../LUFA/LUFA`.
26 |
27 | Now you should be ready to rock. Open a terminal window in the `Switch-Fightstick` directory, type `make`, and hit enter to compile. If all goes well, the printout in the terminal will let you know it finished the build! Follow the directions on flashing `Joystick.hex` onto your Teensy, which can be found page where you downloaded the Teensy Loader application.
28 |
29 | #### Compiling and Flashing onto the Arduino UNO R3
30 | You will need to set your [Arduino in DFU mode](https://www.arduino.cc/en/Hacking/DFUProgramming8U2), and flash its USB controller. (Note for Mac users - try [brew](https://brew.sh/index_it.html) to install the dfu-programmer with `brew install dfu-programmer`.) Setting an Arduino UNO R3 in DFU mode is quite easy, all you need is a jumper (the boards come with the needed pins in place). Please note that once the board is flashed, you will need to flash it back with the original firmware to make it work again as a standard Arduino. To compile this project you will need the AVR GCC Compiler and Tools. (Again for Mac users - try brew, adding the [osx-cross/avr](osx-cross/avr) repository, all you need to do is to type `brew tap osx-cross/avr` and `brew install avr-gcc`.) Next, you need to grab the LUFA library: download and install it following the steps described for the Teensy 2.0++.
31 |
32 | Finally, open a terminal window in the `Switch-InputEmulator` directory, edit the `makefile` setting `MCU = atmega16u2`, and compile by typing `make`. Follow the [DFU mode directions](https://www.arduino.cc/en/Hacking/DFUProgramming8U2) to flash `Joystick.hex` onto your Arduino UNO R3 and you are done.
33 |
34 | #### Compiling and Flashing onto the Arduino Micro
35 | The Arduino Micro is more like the Teensy in that it has a single microcontroller that communicates directly over USB. Most of the steps are the same as those for the Teensy, except do not download Teensy Loader program. You will also need to edit `makefile` before issuing `make`. Change `MCU = at90usb1286` on line 15 to `MCU = atmega32u4`.
36 |
37 | Once finished building, start up Arduino IDE. Under `File -> Preferences`, check `Show verbose output during: upload` and pick OK. With the Arduino plugged in and properly selected under `Tools`, upload any sketch. Find the line with `avrdude` and copy the entire `avrdude` command and all options into a terminal, replacing the `.hex` file and path to the location of the `Joystick.hex` created in the previous step. Also make sure the `-P/dev/??` port is the same as what Arduino IDE is currently reporting. Now double tap the reset button on the Arduino and quickly press Enter in the terminal. This may take several tries. You may need to press Enter first and then the reset button or try various timings. Eventually, `avrdude` should report success. Store the `avrdude` command in a text file or somewhere safe since you will need it every time you want to print a new image.
38 |
39 | Sometimes, the Arduino will show up under a different port, so you may need to run Arduino IDE again to see the current port of your Micro.
40 |
41 | If you ever need to use your Arduino Micro with Arduino IDE again, the process is somewhat similar. Upload your sketch in the usual way and double tap reset button on the Arduino. It may take several tries and various timings, but should eventually be successful.
42 |
43 | The Arduino Leonardo is theoretically compatible, but has not been tested. It also has the ATmega32u4, and is layed out somewhat similar to the Micro.
44 |
--------------------------------------------------------------------------------
/MultiInput/mapping_generator.py:
--------------------------------------------------------------------------------
1 | EMPTY = -1
2 |
3 | BUTTON_NONE = 0
4 | BUTTON_Y = 1
5 | BUTTON_B = 2
6 | BUTTON_A = 4
7 | BUTTON_X = 8
8 | BUTTON_L = 16
9 | BUTTON_R = 32
10 | BUTTON_ZL = 64
11 | BUTTON_ZR = 128
12 | BUTTON_MINUS = 256
13 | BUTTON_PLUS = 512
14 | BUTTON_LCLICK = 1024
15 | BUTTON_RCLICK = 2048
16 | BUTTON_HOME = 4096
17 | BUTTON_CAPTURE = 8192
18 | BUTTON_ALL = 16383
19 |
20 | STICK_MIN = 0
21 | STICK_CENTER = 128
22 | STICK_MAX = 255
23 | STICK_LEFT = 0
24 | STICK_RIGHT = 1
25 |
26 | DPAD_UP = 0
27 | DPAD_UP_RIGHT = 1
28 | DPAD_RIGHT = 2
29 | DPAD_DOWN_RIGHT = 3
30 | DPAD_DOWN = 4
31 | DPAD_DOWN_LEFT = 5
32 | DPAD_LEFT = 6
33 | DPAD_UP_LEFT = 7
34 | DPAD_NONE = 8
35 |
36 | PRESS_DELAY = 24
37 | PRESS_RELEASE_DELAY = 8
38 | HOLD_DELAY = 1
39 | HOLD_RELEASE_DELAY = 1
40 | MOVE_DELAY = 128
41 | LOOK_DELAY = 64
42 | ADJUST_DELAY = 16
43 |
44 |
45 | def delay_to_ms(delay):
46 | return delay * 7.8125
47 |
48 | class ControllerTransition:
49 | def __init__(self):
50 | self.dpad = None
51 | self.buttons_pressed = None
52 | self.buttons_released = None
53 | self.lx = None
54 | self.ly = None
55 | self.rx = None
56 | self.ry = None
57 | self.delay = 0
58 |
59 | def __str__(self):
60 | output = []
61 | output.append(str(self.lx) if self.lx is not None else '')
62 | output.append(str(self.ly) if self.ly is not None else '')
63 | output.append(str(self.rx) if self.rx is not None else '')
64 | output.append(str(self.ry) if self.ry is not None else '')
65 | output.append(str(self.buttons_pressed) if self.buttons_pressed is not None else '')
66 | output.append(str(self.buttons_released) if self.buttons_released is not None else '')
67 | output.append(str(self.dpad) if self.dpad is not None else '')
68 | output.append(str(self.delay))
69 | return ' : '.join(output)
70 |
71 | class ControllerCommand:
72 | def __init__(self, names):
73 | self.names = names
74 | self.transitions = []
75 | self.undo_stack = []
76 |
77 | def __enter__(self):
78 | pass
79 |
80 | def __exit__(self, *args):
81 | if len(self.undo_stack) > 0:
82 | self.transitions.append(self.undo_stack.pop())
83 | else:
84 | raise Exception('Invalid transition on __exit__')
85 |
86 | def add_state(self, state):
87 | self.transitions.append(state)
88 | return self
89 |
90 | def press_buttons(self, *args, delay=PRESS_DELAY, release_delay=PRESS_RELEASE_DELAY):
91 | with self.hold_buttons(*args, delay=delay, release_delay=release_delay):
92 | pass
93 | return self
94 |
95 | def hold_buttons(self, *args, delay=HOLD_DELAY, release_delay=HOLD_RELEASE_DELAY):
96 | button = BUTTON_NONE
97 | for b in args:
98 | button |= b
99 |
100 | state = ControllerTransition()
101 | state.buttons_pressed = button
102 | state.delay = delay
103 | self.transitions.append(state)
104 |
105 | rel_state = ControllerTransition()
106 | rel_state.buttons_released = button
107 | rel_state.delay = release_delay
108 | self.undo_stack.append(rel_state)
109 |
110 | return self
111 |
112 | def release_buttons(self, *args, release_delay=HOLD_RELEASE_DELAY):
113 | button = BUTTON_NONE
114 | for b in args:
115 | button |= b
116 | rel_state = ControllerTransition()
117 | rel_state.buttons_released = button
118 | rel_state.delay = release_delay
119 | self.transitions.append(rel_state)
120 |
121 | return self
122 |
123 | def press_dpad(self, dpad, delay=PRESS_DELAY, release_delay=PRESS_RELEASE_DELAY):
124 | with self.hold_dpad(dpad, delay=delay, release_delay=release_delay):
125 | pass
126 | return self
127 |
128 | def hold_dpad(self, dpad, delay=HOLD_DELAY, release_delay=HOLD_RELEASE_DELAY):
129 | state = ControllerTransition()
130 | state.dpad = dpad
131 | state.delay = delay
132 | self.transitions.append(state)
133 |
134 | rel_state = ControllerTransition()
135 | rel_state.dpad = DPAD_NONE
136 | rel_state.delay = release_delay
137 | self.undo_stack.append(rel_state)
138 |
139 | return self
140 |
141 | def release_dpad(self, release_delay=HOLD_RELEASE_DELAY):
142 | rel_state = ControllerTransition()
143 | rel_state.dpad = DPAD_NONE
144 | rel_state.delay = release_delay
145 | self.transitions.append(rel_state)
146 |
147 | return self
148 |
149 | def move_stick(self, stick, x, y, delay=PRESS_DELAY, release_delay=PRESS_RELEASE_DELAY):
150 | with self.hold_stick(stick, x, y, delay=delay, release_delay=release_delay):
151 | pass
152 | return self
153 |
154 | def hold_stick(self, stick, x, y, delay=HOLD_DELAY, release_delay=HOLD_RELEASE_DELAY):
155 | state = ControllerTransition()
156 | if stick == STICK_LEFT:
157 | state.lx = x
158 | state.ly = y
159 | else:
160 | state.rx = x
161 | state.ry = y
162 | state.delay = delay
163 | self.transitions.append(state)
164 |
165 | rel_state = ControllerTransition()
166 | if stick == STICK_LEFT:
167 | rel_state.lx = STICK_CENTER if x is not None else None
168 | rel_state.ly = STICK_CENTER if y is not None else None
169 | else:
170 | rel_state.rx = STICK_CENTER if x is not None else None
171 | rel_state.ry = STICK_CENTER if y is not None else None
172 | rel_state.delay = release_delay
173 | self.undo_stack.append(rel_state)
174 |
175 | return self
176 |
177 | def release_stick(self, stick, x, y, release_delay=HOLD_RELEASE_DELAY):
178 | rel_state = ControllerTransition()
179 | if stick == STICK_LEFT:
180 | rel_state.lx = STICK_CENTER if x is not None else None
181 | rel_state.ly = STICK_CENTER if y is not None else None
182 | else:
183 | rel_state.rx = STICK_CENTER if x is not None else None
184 | rel_state.ry = STICK_CENTER if y is not None else None
185 | rel_state.delay = release_delay
186 | self.transitions.append(rel_state)
187 |
188 | return self
189 |
190 | def wait(self, delay):
191 | state = ControllerTransition()
192 | state.delay = delay
193 | self.transitions.append(state)
194 |
195 | return self
196 |
197 | def __str__(self):
198 | output = []
199 | output.append(' : '.join(self.names) + ' : %d' % len(self.transitions))
200 | for t in self.transitions:
201 | output.append(str(t))
202 |
203 | return '\n'.join(output)
204 |
205 | class CommandHelp:
206 | def __init__(self, name, text, aliases=None, allowed=None):
207 | self.name = name
208 | self.aliases = aliases
209 | self.allowed = allowed
210 | self.text = text
211 |
212 | def __str__(self):
213 | if self.allowed:
214 | output = '* %s \n' % self.name
215 | else:
216 | output = '* %s\n' % self.name
217 | if self.aliases:
218 | if isinstance(self.aliases, dict):
219 | aliases = self.aliases.get(self.name, None)
220 | else:
221 | aliases = self.aliases
222 | if aliases:
223 | output += ' * Aliases: *%s*\n' % ', '.join(aliases)
224 | output += ' * '
225 | if isinstance(self.text, str):
226 | output += self.text
227 | else:
228 | output += '\n * '.join(self.text)
229 | if self.allowed:
230 | output += '\n * Allowed parameters: `%s`' % ', '.join(self.allowed)
231 | return output + '\n'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.d
3 | *.o
4 | *.bin
5 | *.elf
6 | *.hex
7 | *.lss
8 | *.map
9 | *.sym
10 | *.eep
11 | __pycache__/
12 | *.pyc
13 |
14 | ## Ignore Visual Studio temporary files, build results, and
15 | ## files generated by popular Visual Studio add-ons.
16 | ##
17 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
18 |
19 | # User-specific files
20 | *.suo
21 | *.user
22 | *.userosscache
23 | *.sln.docstates
24 |
25 | # User-specific files (MonoDevelop/Xamarin Studio)
26 | *.userprefs
27 |
28 | # Build results
29 | [Dd]ebug/
30 | [Dd]ebugPublic/
31 | [Rr]elease/
32 | [Rr]eleases/
33 | x64/
34 | x86/
35 | bld/
36 | [Bb]in/
37 | [Oo]bj/
38 | [Ll]og/
39 |
40 | # Visual Studio 2015/2017 cache/options directory
41 | .vs/
42 | # Uncomment if you have tasks that create the project's static files in wwwroot
43 | #wwwroot/
44 |
45 | # Visual Studio 2017 auto generated files
46 | Generated\ Files/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUNIT
53 | *.VisualState.xml
54 | TestResult.xml
55 |
56 | # Build Results of an ATL Project
57 | [Dd]ebugPS/
58 | [Rr]eleasePS/
59 | dlldata.c
60 |
61 | # Benchmark Results
62 | BenchmarkDotNet.Artifacts/
63 |
64 | # .NET Core
65 | project.lock.json
66 | project.fragment.lock.json
67 | artifacts/
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_i.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # JustCode is a .NET coding add-in
135 | .JustCode
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Visual Studio code coverage results
148 | *.coverage
149 | *.coveragexml
150 |
151 | # NCrunch
152 | _NCrunch_*
153 | .*crunch*.local.xml
154 | nCrunchTemp_*
155 |
156 | # MightyMoose
157 | *.mm.*
158 | AutoTest.Net/
159 |
160 | # Web workbench (sass)
161 | .sass-cache/
162 |
163 | # Installshield output folder
164 | [Ee]xpress/
165 |
166 | # DocProject is a documentation generator add-in
167 | DocProject/buildhelp/
168 | DocProject/Help/*.HxT
169 | DocProject/Help/*.HxC
170 | DocProject/Help/*.hhc
171 | DocProject/Help/*.hhk
172 | DocProject/Help/*.hhp
173 | DocProject/Help/Html2
174 | DocProject/Help/html
175 |
176 | # Click-Once directory
177 | publish/
178 |
179 | # Publish Web Output
180 | *.[Pp]ublish.xml
181 | *.azurePubxml
182 | # Note: Comment the next line if you want to checkin your web deploy settings,
183 | # but database connection strings (with potential passwords) will be unencrypted
184 | *.pubxml
185 | *.publishproj
186 |
187 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
188 | # checkin your Azure Web App publish settings, but sensitive information contained
189 | # in these scripts will be unencrypted
190 | PublishScripts/
191 |
192 | # NuGet Packages
193 | *.nupkg
194 | # The packages folder can be ignored because of Package Restore
195 | **/[Pp]ackages/*
196 | # except build/, which is used as an MSBuild target.
197 | !**/[Pp]ackages/build/
198 | # Uncomment if necessary however generally it will be regenerated when needed
199 | #!**/[Pp]ackages/repositories.config
200 | # NuGet v3's project.json files produces more ignorable files
201 | *.nuget.props
202 | *.nuget.targets
203 |
204 | # Microsoft Azure Build Output
205 | csx/
206 | *.build.csdef
207 |
208 | # Microsoft Azure Emulator
209 | ecf/
210 | rcf/
211 |
212 | # Windows Store app package directories and files
213 | AppPackages/
214 | BundleArtifacts/
215 | Package.StoreAssociation.xml
216 | _pkginfo.txt
217 | *.appx
218 |
219 | # Visual Studio cache files
220 | # files ending in .cache can be ignored
221 | *.[Cc]ache
222 | # but keep track of directories ending in .cache
223 | !*.[Cc]ache/
224 |
225 | # Others
226 | ClientBin/
227 | ~$*
228 | *~
229 | *.dbmdl
230 | *.dbproj.schemaview
231 | *.jfm
232 | *.pfx
233 | *.publishsettings
234 | orleans.codegen.cs
235 |
236 | # Including strong name files can present a security risk
237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
238 | #*.snk
239 |
240 | # Since there are multiple workflows, uncomment next line to ignore bower_components
241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242 | #bower_components/
243 |
244 | # RIA/Silverlight projects
245 | Generated_Code/
246 |
247 | # Backup & report files from converting an old project file
248 | # to a newer Visual Studio version. Backup files are not needed,
249 | # because we have git ;-)
250 | _UpgradeReport_Files/
251 | Backup*/
252 | UpgradeLog*.XML
253 | UpgradeLog*.htm
254 | ServiceFabricBackup/
255 | *.rptproj.bak
256 |
257 | # SQL Server files
258 | *.mdf
259 | *.ldf
260 | *.ndf
261 |
262 | # Business Intelligence projects
263 | *.rdl.data
264 | *.bim.layout
265 | *.bim_*.settings
266 | *.rptproj.rsuser
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # JetBrains Rider
303 | .idea/
304 | *.sln.iml
305 |
306 | # CodeRush
307 | .cr/
308 |
309 | # Python Tools for Visual Studio (PTVS)
310 | __pycache__/
311 | *.pyc
312 |
313 | # Cake - Uncomment if you are using it
314 | # tools/**
315 | # !tools/packages.config
316 |
317 | # Tabs Studio
318 | *.tss
319 |
320 | # Telerik's JustMock configuration file
321 | *.jmconfig
322 |
323 | # BizTalk build output
324 | *.btp.cs
325 | *.btm.cs
326 | *.odx.cs
327 | *.xsd.cs
328 |
329 | # OpenCover UI analysis results
330 | OpenCover/
331 |
332 | # Azure Stream Analytics local run output
333 | ASALocalRun/
334 |
335 | # MSBuild Binary and Structured Log
336 | *.binlog
337 |
338 | # NVidia Nsight GPU debugger configuration file
339 | *.nvuser
340 |
341 | # MFractors (Xamarin productivity tool) working folder
342 | .mfractor/
--------------------------------------------------------------------------------
/docs/assets/js/ServerStatus.js:
--------------------------------------------------------------------------------
1 | import {ConnectionState, AuthState, StatusBus, BusEvents, StoreMutations, InputState} from "./Common";
2 | import {SocketBus, SocketEvents} from "./ControlWebSocket";
3 |
4 | export const ServerStatus = {
5 | data: function() {
6 | // TODO: Have this handle all server status things.
7 | let stats = new Stats();
8 | let pingPanel = stats.addPanel(new Stats.Panel('ms ping', '#f08', '#201'));
9 | pingPanel.update(0, 1000);
10 | stats.showPanel(0);
11 | stats.showPanel(1);
12 | stats.showPanel(2);
13 |
14 | return {
15 | stats: stats,
16 | pingPanel: pingPanel,
17 | pendingPings: {},
18 | waiting: false,
19 | progressBarWidth: 0,
20 | timeRemaining: -1,
21 | animateFrameCount: -1,
22 | totalAnimateFrames: 30
23 | };
24 | },
25 | created: function() {
26 | let self = this;
27 |
28 | SocketBus.$on('CLIENT_ACTIVE', function(args) {
29 | let id = args[0];
30 | let name = args[1];
31 | let picture = args[2];
32 | let expire = parseInt(args[3]);
33 | let turnLength = parseInt(args[4]);
34 | if (name !== self.currentPlayerInfo.name) {
35 | self.animateFrameCount = self.totalAnimateFrames;
36 | }
37 | self.$store.commit(StoreMutations.CURRENT_PLAYER_INFO, {
38 | id: id,
39 | name: name,
40 | picture: picture,
41 | expire: expire,
42 | turnLength: turnLength
43 | });
44 | });
45 |
46 | SocketBus.$on('NO_CLIENTS', function() {
47 | self.$store.commit(StoreMutations.CURRENT_PLAYER_INFO, {
48 | id: null,
49 | name: null,
50 | picture: null,
51 | expire: -1,
52 | turnLength: -1
53 | });
54 | });
55 | },
56 | watch: {
57 | canControl: function() {
58 | this.waiting = false;
59 | }
60 | },
61 | mounted: function() {
62 | let self = this;
63 | this.$nextTick(function() {
64 | this.$refs.statsContainer.appendChild(this.stats.dom);
65 | StatusBus.$on(BusEvents.RENDER_TIME_START, this.stats.begin);
66 | StatusBus.$on(BusEvents.RENDER_TIME_END, this.stats.end);
67 | });
68 | SocketBus.$on(SocketEvents.PONG, function(time) {
69 | self.pingPanel.update(time, 1000);
70 | });
71 | StatusBus.$on(BusEvents.BEFORE_UPDATE_INPUT, function() {
72 | if (self.currentPlayerInfo.id === null) {
73 | self.progressBarWidth = 0;
74 | self.timeRemaining = -1;
75 | } else if (self.currentPlayerInfo.turnLength < 0) {
76 | if (self.animateFrameCount >= 0) {
77 | self.progressBarWidth = (100/self.totalAnimateFrames) * (self.totalAnimateFrames - self.animateFrameCount--);
78 | if (self.animateFrameCount <= 0) {
79 | self.progressBarWidth = 100;
80 | }
81 | }
82 | self.timeRemaining = -1;
83 | } else {
84 | self.timeRemaining = self.currentPlayerInfo.expire + self.serverClockSkew - (performance.timing.navigationStart + performance.now());
85 | let scaleFactor = (self.totalAnimateFrames - self.animateFrameCount--) / self.totalAnimateFrames;
86 | if (self.animateFrameCount <= 0) {
87 | scaleFactor = 1;
88 | }
89 | self.progressBarWidth = (self.timeRemaining / self.currentPlayerInfo.turnLength * 100) * (scaleFactor);
90 | }
91 | });
92 | },
93 | methods: {
94 | requestTurn: function() {
95 | if (this.canRequestTurn) {
96 | SocketBus.$emit(SocketEvents.SEND_MESSAGE, 'REQUEST_TURN');
97 | this.waiting = true;
98 | }
99 | }
100 | },
101 | computed: {
102 | progressBarStyle: function() {
103 | let width = `${this.progressBarWidth}%`;
104 | return {
105 | width: width
106 | }
107 | },
108 | progressBarText: function() {
109 | if (this.currentPlayerInfo.id) {
110 | if (this.timeRemaining >= 0) {
111 | return `${this.currentPlayerInfo.name} is controlling (${Math.round(this.timeRemaining / 1000)} seconds remaining)`;
112 | } else {
113 | return `${this.currentPlayerInfo.name} is controlling`;
114 | }
115 | } else {
116 | return 'No one is controlling right now';
117 | }
118 | },
119 | turnState: function() {
120 | if (this.connectionState === ConnectionState.CONNECTED) {
121 | if (this.inputState === InputState.NOT_CONNECTED) {
122 | return 'No controller connected';
123 | } else if (this.inputState === InputState.UNSUPPORTED) {
124 | return 'Unsupported controller';
125 | } else {
126 | if (this.authState === AuthState.NOT_SIGNED_IN) {
127 | return 'Not signed in with Twitch';
128 | } else if (this.authState === AuthState.SIGNED_IN) {
129 | return 'Authenticating';
130 | } else if (this.canControl) {
131 | return 'It\'s your turn';
132 | } else if (this.waiting) {
133 | return 'Waiting for turn';
134 | } else {
135 | return 'Request a turn';
136 | }
137 | }
138 | } else if (this.connectionState === ConnectionState.NOT_CONNECTED) {
139 | return 'Not connected';
140 | } else if (this.connectionState === ConnectionState.CONNECTING) {
141 | return 'Connecting to server';
142 | } else if (this.connectionState === ConnectionState.ERROR) {
143 | return 'Connection error';
144 | }
145 | return 'Not connected';
146 | },
147 | canRequestTurn: function() {
148 | return this.connectionState === ConnectionState.CONNECTED && this.inputState === InputState.READY && this.authState === AuthState.SERVER_SIGNED_IN && !this.canControl && !this.waiting;
149 | },
150 | ...Vuex.mapGetters([
151 | 'canControl',
152 | 'connectionState',
153 | 'inputState',
154 | 'authState',
155 | 'currentPlayerInfo',
156 | 'serverClockSkew'
157 | ])
158 | },
159 | template: ''
160 | };
--------------------------------------------------------------------------------