Files
DroidDeck/tools/xcloud-player/greenlight/lib/input/touch.ts
The412Banner c5d7cdc058 Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation (#1531)
Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation

Stream Xbox games with Xbox Cloud Gaming (Game Pass) from DroidDeck, outside
the Linux session.

- Backend: `xodus-cli cloud-token` (tools/xbox/droiddeck-cloud-token.patch)
  gets the streaming tokens; they stay in memory.
- Web player (default): the Greenlight player page (tools/xcloud-player, MIT)
  in a WebView, plus Microsoft's own xbox.com/play player. In-stream menu with
  screen effects (sharpen, colour, deband, FXAA, CRT, NTSC, Toon, fps cap),
  stream quality, performance overlay (level, layout, top right / top left)
  and rumble.
- Native player (opt-in, Android 10+, Player engine: Native): Google's WebRTC
  (io.github.webrtc-sdk 125.6422.07, BSD) with the Greenlight protocol ported
  to Kotlin, in its own process (:xcloudnative) with its own instance of
  DroidDeck's compositor. Decoded frames reach the compositor as
  AHardwareBuffers (new stream mode, VK_ANDROID_external_memory_android_
  hardware_buffer import), so DDFG 2x and LSFG 2x/3x/4x run on them. Live
  frame generation, frame-rate target (presets and a 10-165 fps slider, even
  pacing), zero-copy picture, small network buffer, fast decoder, instant
  controller input, overlay with engine and on-screen fps, background end,
  restart and web fallback. It takes the main process's tokens through
  XboxCloudTokenService; they are fetched ahead while a cloud game is shown.
- The compositor requests the dma-buf import extensions only where the driver
  has them (the system driver has none).
- Catalog: cloud availability, Game Pass plan CFQ7TTC0K6L8 counted, library
  filter All / Owned / Game Pass / Cloud, Stream and Add to Games on the store
  page, stream-only Games-tab entries (Stream + Download / Play + Stream).
- Xbox games get VC++ 2015-2022 and the ARM64 runtime registry entries
  (vcredist2022_arm64, built in until the catalog has it) before each launch.
- Controller focus reaches pickers opened from a game page (install location,
  uninstall); Add / Remove from Games keep the focus.

Credits: Greenlight by UnknownSKL (Jim Kroon), MIT, player and protocol;
Better xCloud for the resolution / OS mapping; MGamer404's Bannerlator #627
for the ARM64 runtime approach. Ported from the412banner's Bannerlator.
2026-10-24 23:17:57 -04:00

70 lines
2.4 KiB
TypeScript

import xCloudPlayer from '../player'
export default class Touch {
private _player: xCloudPlayer | undefined
private _index: number
private _pointerEvents = {}
private _lastPointerId = -1
private _mouseDown = false
private _listener = {
onPointer: this.onPointer.bind(this),
}
constructor(index:number){
this._index = index
}
attach(xCloudPlayer:xCloudPlayer){
this._player = xCloudPlayer
this._player._channels.control.sendGamepadState(this._index, true, this)
// Mod the video UI to capture MKB input
if(this._player.getVideoElement() === undefined){
console.log('VideoComponent is not attached. this._player._videoComponent is:', this._player.getVideoElement())
return
} else {
this._player.getVideoElement()?.addEventListener('pointermove', this._listener.onPointer, { passive: false })
this._player.getVideoElement()?.addEventListener('pointerdown', this._listener.onPointer, { passive: false })
this._player.getVideoElement()?.addEventListener('pointerup', this._listener.onPointer, { passive: false })
}
}
detach(){
if(this._player === undefined){
console.log('Player is not attached. this._player is:', this._player)
return
}
this._player._channels.control.sendGamepadState(this._index, false)
this._player.getVideoElement()?.removeEventListener('pointermove', this._listener.onPointer)
this._player.getVideoElement()?.removeEventListener('pointerdown', this._listener.onPointer)
this._player.getVideoElement()?.removeEventListener('pointerup', this._listener.onPointer)
}
onPointer(event:PointerEvent) {
this._lastPointerId = event.pointerId
if(event.pointerType === 'mouse' && event.type === 'pointerdown'){
this._mouseDown = true
}
if(event.pointerType === 'mouse' && event.type === 'pointerup'){
this._mouseDown = false
}
if(event.pointerType === 'mouse' && event.type === 'pointermove' && this._mouseDown === false){
// ignore events if the pointer type is mouse and is not pressed.
} else {
this._player?._channels.input.queuePointerFrame({
events: [event],
})
}
event.preventDefault()
}
}