Add Stadia support (#8)
* Log some info * Fix pairing url and include TLS certificate in the connection request * Try to fix crashing related to shortcuts * Add print_exc() * Fix crash when pressing buttons on Joy-Con (L) * Linting * Add "Stadia" pairing method
This commit is contained in:
		
							parent
							
								
									2ebd7be378
								
							
						
					
					
						commit
						e89be3166f
					
				
					 4 changed files with 69 additions and 33 deletions
				
			
		
							
								
								
									
										14
									
								
								dance.py
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								dance.py
									
										
									
									
									
								
							|  | @ -29,6 +29,7 @@ class WsCommand(Enum): | |||
| class PairingMethod(Enum): | ||||
|     DEFAULT = 'default' | ||||
|     FAST = 'fast' | ||||
|     STADIA = 'stadia' | ||||
|     OLD = 'old' | ||||
| 
 | ||||
| 
 | ||||
|  | @ -139,16 +140,14 @@ async def connect_joycon(app, ws, data): | |||
|     config_parser['joydance'] = config | ||||
|     save_config(config_parser) | ||||
| 
 | ||||
|     if pairing_method == PairingMethod.DEFAULT.value: | ||||
|     if pairing_method == PairingMethod.DEFAULT.value or pairing_method == PairingMethod.STADIA.value: | ||||
|         app['joycons_info'][serial]['pairing_code'] = pairing_code | ||||
|         console_ip_addr = None | ||||
|     else: | ||||
|         app['joycons_info'][serial]['pairing_code'] = '' | ||||
| 
 | ||||
|     joycon = ButtonEventJoyCon(vendor_id, product_id, serial) | ||||
| 
 | ||||
|     if pairing_method == PairingMethod.DEFAULT.value: | ||||
|         console_ip_addr = None | ||||
| 
 | ||||
|     if pairing_method == PairingMethod.OLD.value: | ||||
|         protocol_version = WsSubprotocolVersion.V1 | ||||
|     else: | ||||
|  | @ -234,7 +233,12 @@ def is_valid_ip_address(val): | |||
| 
 | ||||
| 
 | ||||
| def is_valid_pairing_method(val): | ||||
|     return val in [PairingMethod.DEFAULT.value, PairingMethod.FAST.value, PairingMethod.OLD.value] | ||||
|     return val in [ | ||||
|         PairingMethod.DEFAULT.value, | ||||
|         PairingMethod.FAST.value, | ||||
|         PairingMethod.STADIA.value, | ||||
|         PairingMethod.OLD.value, | ||||
|     ] | ||||
| 
 | ||||
| 
 | ||||
| def get_host_ip(): | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import socket | |||
| import ssl | ||||
| import time | ||||
| from enum import Enum | ||||
| from urllib.parse import urlparse | ||||
| 
 | ||||
| import aiohttp | ||||
| import websockets | ||||
|  | @ -56,6 +57,7 @@ class JoyDance: | |||
|         self.host_ip_addr = host_ip_addr | ||||
|         self.console_ip_addr = console_ip_addr | ||||
|         self.host_port = self.get_random_port() | ||||
|         self.tls_certificate = None | ||||
| 
 | ||||
|         self.accel_acquisition_freq_hz = accel_acquisition_freq_hz | ||||
|         self.accel_acquisition_latency = accel_acquisition_latency | ||||
|  | @ -114,7 +116,17 @@ class JoyDance: | |||
|                     raise Exception('ERROR: Invalid pairing code!') | ||||
| 
 | ||||
|                 json_body = await resp.json() | ||||
|                 self.pairing_url = json_body['pairingUrl'].replace('https://', 'wss://') + 'smartphone' | ||||
|                 import pprint | ||||
|                 pprint.pprint(json_body) | ||||
| 
 | ||||
|                 self.pairing_url = json_body['pairingUrl'].replace('https://', 'wss://') | ||||
|                 if not self.pairing_url.endswith('/'): | ||||
|                     self.pairing_url += '/' | ||||
|                 self.pairing_url += 'smartphone' | ||||
| 
 | ||||
|                 self.tls_certificate = json_body['tlsCertificate'] | ||||
| 
 | ||||
|                 print(self.pairing_url) | ||||
|                 self.requires_punch_pairing = json_body.get('requiresPunchPairing', False) | ||||
| 
 | ||||
|     async def send_initiate_punch_pairing(self): | ||||
|  | @ -205,7 +217,7 @@ class JoyDance: | |||
|             await self.send_message('JD_CancelKeyboard_PhoneCommandData') | ||||
|         elif __class == 'JD_PhoneUiSetupData': | ||||
|             self.is_input_allowed = True | ||||
|             shortcuts = set() | ||||
|             self.available_shortcuts = set() | ||||
|             if message.get('setupData', {}).get('gameplaySetup', {}).get('pauseSlider', {}): | ||||
|                 self.available_shortcuts.add(Command.PAUSE) | ||||
| 
 | ||||
|  | @ -274,7 +286,7 @@ class JoyDance: | |||
|             delta_time += (end - start) * 1000 | ||||
| 
 | ||||
|     async def send_command(self): | ||||
|         ''' Capture Joycon's input and send to console. Only works on procol v2 ''' | ||||
|         ''' Capture Joycon's input and send to console. Only works on protocol v2 ''' | ||||
|         if self.protocol_version == WsSubprotocolVersion.V1: | ||||
|             return | ||||
| 
 | ||||
|  | @ -351,12 +363,15 @@ class JoyDance: | |||
|                         await asyncio.sleep(0.01) | ||||
|             except Exception as e: | ||||
|                 print(e) | ||||
|                 import traceback | ||||
|                 traceback.print_exc() | ||||
|                 await self.disconnect() | ||||
| 
 | ||||
|     async def connect_ws(self): | ||||
|         server_hostname = None | ||||
| 
 | ||||
|         if self.protocol_version == WsSubprotocolVersion.V1: | ||||
|             ssl_context = None | ||||
|             server_hostname = None | ||||
|         else: | ||||
|             ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | ||||
|             ssl_context.set_ciphers('ALL') | ||||
|  | @ -364,7 +379,16 @@ class JoyDance: | |||
|             ssl_context.check_hostname = False | ||||
|             ssl_context.verify_mode = ssl.CERT_NONE | ||||
| 
 | ||||
|             server_hostname = self.console_conn.getpeername()[0] if self.console_conn else None | ||||
|             if self.tls_certificate: | ||||
|                 ssl_context.load_verify_locations(cadata=self.tls_certificate) | ||||
| 
 | ||||
|             if '192.168' in self.pairing_url: | ||||
|                 if self.console_conn: | ||||
|                     server_hostname = self.console_conn.getpeername()[0] | ||||
|             else: | ||||
|                 # Stadia | ||||
|                 tmp = urlparse(self.pairing_url) | ||||
|                 server_hostname = tmp.hostname | ||||
| 
 | ||||
|         subprotocol = WS_SUBPROTOCOLS[self.protocol_version.value] | ||||
|         try: | ||||
|  | @ -387,7 +411,8 @@ class JoyDance: | |||
|                 except websockets.ConnectionClosed: | ||||
|                     await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) | ||||
|                     await self.disconnect(close_ws=False) | ||||
|         except Exception: | ||||
|         except Exception as e: | ||||
|             print(e) | ||||
|             await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) | ||||
|             await self.disconnect(close_ws=False) | ||||
| 
 | ||||
|  |  | |||
|  | @ -129,8 +129,8 @@ SHORTCUT_MAPPING = { | |||
| } | ||||
| 
 | ||||
| # Same with Joy-Con (L) | ||||
| SHORTCUT_MAPPING[JoyConButton.UP] = JoyConButton.X | ||||
| SHORTCUT_MAPPING[JoyConButton.LEFT] = JoyConButton.Y | ||||
| SHORTCUT_MAPPING[JoyConButton.MINUS] = JoyConButton.PLUS | ||||
| SHORTCUT_MAPPING[JoyConButton.L] = JoyConButton.R | ||||
| SHORTCUT_MAPPING[JoyConButton.ZL] = JoyConButton.ZR | ||||
| SHORTCUT_MAPPING[JoyConButton.UP] = SHORTCUT_MAPPING[JoyConButton.X] | ||||
| SHORTCUT_MAPPING[JoyConButton.LEFT] = SHORTCUT_MAPPING[JoyConButton.Y] | ||||
| SHORTCUT_MAPPING[JoyConButton.MINUS] = SHORTCUT_MAPPING[JoyConButton.PLUS] | ||||
| SHORTCUT_MAPPING[JoyConButton.L] = SHORTCUT_MAPPING[JoyConButton.R] | ||||
| SHORTCUT_MAPPING[JoyConButton.ZL] = SHORTCUT_MAPPING[JoyConButton.ZR] | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ const BATTERY_LEVEL = { | |||
| const PairingMethod = { | ||||
|     DEFAULT: 'default', | ||||
|     FAST: 'fast', | ||||
|     STADIA: 'stadia', | ||||
|     OLD: 'old', | ||||
| } | ||||
| 
 | ||||
|  | @ -85,8 +86,9 @@ class PairingMethodPicker extends Component { | |||
|             <label for="stacked-state">Pairing Method</label> | ||||
|             <select id="stacked-state" onChange=${this.onChange} value=${props.pairing_method}> | ||||
|                 <optgroup label="JD 2020 and later"> | ||||
|                     <option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</option> | ||||
|                     <option value="${PairingMethod.DEFAULT}">Default: All platforms except Stadia</option> | ||||
|                     <option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option> | ||||
|                     <option value="${PairingMethod.STADIA}">Stadia: for Stadia, obviously</option> | ||||
|                 </optgroup> | ||||
|                 <optgroup label="JD 2016-2019"> | ||||
|                     <option value="${PairingMethod.OLD}">Old: All platforms (incl. PC)</option> | ||||
|  | @ -147,15 +149,19 @@ class PrivateIpAddress extends Component { | |||
|         const addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr | ||||
|         return html` | ||||
|             <label> | ||||
|                 ${pairing_method == PairingMethod.DEFAULT && html`Host's Private IP Address`} | ||||
|                 ${pairing_method != PairingMethod.DEFAULT && html`Console's Private IP Address`} | ||||
|                 ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) > -1 && html`Host's Private IP Address`} | ||||
|                 ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) == -1 && html`Console's Private IP Address`} | ||||
|             </label> | ||||
| 
 | ||||
|             ${pairing_method == PairingMethod.DEFAULT && state.lock_host && html` | ||||
|                 <input readonly required id="ipAddr" type="text" size="15" placeholder="${addr}" /> | ||||
|             ${(pairing_method == PairingMethod.STADIA) && html` | ||||
|                 <input readonly id="ipAddr" type="text" size="15" placeholder="Not Required" /> | ||||
|             `}
 | ||||
| 
 | ||||
|             ${(pairing_method != PairingMethod.DEFAULT || !state.lock_host) && html` | ||||
|             ${(pairing_method == PairingMethod.DEFAULT && state.lock_host) && html` | ||||
|                 <input readonly id="ipAddr" type="text" size="15" placeholder="${addr}" /> | ||||
|             `}
 | ||||
| 
 | ||||
|             ${([PairingMethod.FAST, PairingMethod.OLD].indexOf(pairing_method) > -1 || (pairing_method == PairingMethod.DEFAULT && !state.lock_host)) && html` | ||||
|                 <input required id="ipAddr" type="text" inputmode="decimal" size="15" maxlength="15" placeholder="192.168.x.x" pattern="^192\\.168\\.((\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.)(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$" value=${addr} onKeyPress=${this.onKeyPress} onChange="${this.onChange}" /> | ||||
|             `}
 | ||||
| 
 | ||||
|  | @ -183,12 +189,13 @@ class PairingCode extends Component { | |||
|     } | ||||
| 
 | ||||
|     render(props, state) { | ||||
|         const pairing_method = props.pairing_method | ||||
|         return html` | ||||
|             <label>Pairing Code</label> | ||||
|             ${props.pairing_method == PairingMethod.DEFAULT && html` | ||||
|             ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) > -1 && html` | ||||
|                 <input required id="pairingCode" type="text" inputmode="decimal" value=${state.pairing_code} placeholder="000000" maxlength="6" size="6" pattern="[0-9]{6}" onKeyPress=${(e) => !/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} /> | ||||
|             `}
 | ||||
|             ${props.pairing_method != PairingMethod.DEFAULT && html` | ||||
|             ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) == -1 && html` | ||||
|                 <input type="text" id="pairingCode" value="" readonly placeholder="Not Required" size="12" /> | ||||
|             `}
 | ||||
|         ` | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue