@lampdotdev/lamp 0.0.0-stage → 0.36.2-nightly.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +397 -2
- package/Resources/Devices/roku.json +410 -0
- package/Resources/Devices/xbox-one.json +367 -0
- package/Resources/GameKit/NOTICE.md +63 -0
- package/Resources/GameKit/apple/LampPage.swift +1813 -0
- package/Resources/GameKit/apple/LampPlayer.swift +46 -0
- package/Resources/GameKit/apple/LampWebApp.swift +63 -0
- package/Resources/GameKit/apple/LampWebGame.swift +87 -0
- package/Resources/GameKit/apple/Native.swift +12 -0
- package/Resources/GameKit/build.sh +55 -0
- package/Resources/GameKit/lamp_game.h +116 -0
- package/Resources/GameKit/lamp_host.h +19 -0
- package/Resources/GameKit/lamp_kit.cpp +61 -0
- package/Resources/GameKit/lamp_sound.cpp +455 -0
- package/Resources/GameKit/lamp_xbox.cpp +374 -0
- package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
- package/Resources/GameKit/libraries/cgltf.h +7240 -0
- package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
- package/Resources/GameKit/libraries/dr_wav.h +9209 -0
- package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
- package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
- package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
- package/Resources/GameKit/libraries/sokol_log.h +335 -0
- package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
- package/Resources/GameKit/libraries/stb_image.h +7988 -0
- package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
- package/Resources/GameKit/page/lamp-live.js +672 -0
- package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
- package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
- package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
- package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
- package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
- package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
- package/Resources/GameKit/web/lamp-game.js +3042 -0
- package/Resources/GameKit/web/lamp-phaser.js +128 -0
- package/Resources/WebHost/App.exe +0 -0
- package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
- package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
- package/Resources/WebHost/lamp_controllers.h +1506 -0
- package/Resources/WebHost/lamp_web_host.cpp +1173 -0
- package/Resources/WebLibraries/earlier.txt +318 -0
- package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
- package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
- package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
- package/Resources/WebLibraries/qrcode/LICENSE +21 -0
- package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
- package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
- package/Resources/WebLibraries/rapier/LICENSE +201 -0
- package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
- package/Resources/WebLibraries/rapier/rapier.js +2 -0
- package/Resources/WebLibraries/three/LICENSE +21 -0
- package/Resources/WebLibraries/three/NOTICE.md +4 -0
- package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
- package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
- package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
- package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
- package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
- package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
- package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
- package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
- package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
- package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
- package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
- package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
- package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
- package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
- package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
- package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
- package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
- package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
- package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
- package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
- package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
- package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
- package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
- package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
- package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
- package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
- package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
- package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
- package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
- package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
- package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
- package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
- package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
- package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
- package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
- package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
- package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
- package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
- package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
- package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
- package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
- package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
- package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
- package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
- package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
- package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
- package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
- package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
- package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
- package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
- package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
- package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
- package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
- package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
- package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
- package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
- package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
- package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
- package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
- package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
- package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
- package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
- package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
- package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
- package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
- package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
- package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
- package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
- package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
- package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
- package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
- package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
- package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
- package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
- package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
- package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
- package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
- package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
- package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
- package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
- package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
- package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
- package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
- package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
- package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
- package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
- package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
- package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
- package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
- package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
- package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
- package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
- package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
- package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
- package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
- package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
- package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
- package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
- package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
- package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
- package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
- package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
- package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
- package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
- package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
- package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
- package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
- package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
- package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
- package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
- package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
- package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
- package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
- package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
- package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
- package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
- package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
- package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
- package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
- package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
- package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
- package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
- package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
- package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
- package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
- package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
- package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
- package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
- package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
- package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
- package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
- package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
- package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
- package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
- package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
- package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
- package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
- package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
- package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
- package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
- package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
- package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
- package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
- package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
- package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
- package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
- package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
- package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
- package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
- package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
- package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
- package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
- package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
- package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
- package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
- package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
- package/Resources/WebLibraries/three/three.core.js +61162 -0
- package/Resources/WebLibraries/three/three.module.js +19537 -0
- package/Resources/WebLibraries/three/three.tsl.js +41903 -0
- package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
- package/Resources/share-rules.json +64 -0
- package/Resources/skills/2d-game/SKILL.md +305 -0
- package/Resources/skills/3d-game/SKILL.md +274 -0
- package/Resources/skills/accounts/SKILL.md +41 -0
- package/Resources/skills/android-design/SKILL.md +159 -0
- package/Resources/skills/app-ai/SKILL.md +154 -0
- package/Resources/skills/app-intents/SKILL.md +31 -0
- package/Resources/skills/app-kinds/SKILL.md +122 -0
- package/Resources/skills/apple-watch/SKILL.md +43 -0
- package/Resources/skills/assets/SKILL.md +183 -0
- package/Resources/skills/building-blocks/SKILL.md +97 -0
- package/Resources/skills/device-control/SKILL.md +56 -0
- package/Resources/skills/device-integrations/SKILL.md +278 -0
- package/Resources/skills/finder-files/SKILL.md +19 -0
- package/Resources/skills/game/SKILL.md +416 -0
- package/Resources/skills/game-3d/SKILL.md +12 -0
- package/Resources/skills/game-art/SKILL.md +11 -0
- package/Resources/skills/game-feel/SKILL.md +13 -0
- package/Resources/skills/game-levels/SKILL.md +12 -0
- package/Resources/skills/game-live/SKILL.md +12 -0
- package/Resources/skills/game-physics/SKILL.md +13 -0
- package/Resources/skills/game-playtest/SKILL.md +26 -0
- package/Resources/skills/game-ui/SKILL.md +278 -0
- package/Resources/skills/github-projects/SKILL.md +62 -0
- package/Resources/skills/icloud-sync/SKILL.md +30 -0
- package/Resources/skills/iphone-hardware/SKILL.md +57 -0
- package/Resources/skills/iphone-widgets/SKILL.md +53 -0
- package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
- package/Resources/skills/mac-widgets/SKILL.md +60 -0
- package/Resources/skills/maps/SKILL.md +8 -0
- package/Resources/skills/menu-bar/SKILL.md +36 -0
- package/Resources/skills/more-apps/SKILL.md +53 -0
- package/Resources/skills/phone-controllers/SKILL.md +79 -0
- package/Resources/skills/phone-pages/SKILL.md +173 -0
- package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
- package/Resources/skills/scene-3d/SKILL.md +27 -0
- package/Resources/skills/scripting/SKILL.md +17 -0
- package/Resources/skills/share-extension/SKILL.md +22 -0
- package/Resources/skills/tv-design/SKILL.md +72 -0
- package/Resources/skills/web-app/SKILL.md +170 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +108 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/ai.mjs +237 -0
- package/cli/android.mjs +941 -0
- package/cli/app-ai.mjs +473 -0
- package/cli/browser.mjs +284 -0
- package/cli/calls.mjs +594 -0
- package/cli/dev.mjs +820 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1326 -0
- package/cli/lampapp.mjs +332 -0
- package/cli/lamps/android-host/LampPageGateway.kt +345 -0
- package/cli/lamps/android-host/app/build.gradle.kts +40 -0
- package/cli/lamps/android-host/build.gradle.kts +4 -0
- package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
- package/cli/lamps/android-host/game/MainActivity.kt +173 -0
- package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
- package/cli/lamps/android-host/gradle.properties +8 -0
- package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
- package/cli/lamps/android-host/page/MainActivity.kt +193 -0
- package/cli/lamps/android-host/settings.gradle.kts +30 -0
- package/cli/lamps/android-host/tools.json +43 -0
- package/cli/lamps/lamp-controllers.js +2 -0
- package/cli/lamps/lamp-for-games.js +6922 -0
- package/cli/lamps/lamp.js +7082 -0
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
- package/cli/lamps/mac-starter/APP.md +12 -0
- package/cli/lamps/mac-starter/Package.swift +8 -0
- package/cli/lamps/mac-starter/Sources/App.swift +11 -0
- package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +18215 -0
- package/cli/lamps/mac-starter/build.sh +336 -0
- package/cli/lamps/mac-starter/gitignore +3 -0
- package/cli/lamps/mac-starter/lamp-starter.json +6 -0
- package/cli/lamps/web-starter/APP.md +8 -0
- package/cli/lamps/web-starter/app.js +1 -0
- package/cli/lamps/web-starter/index.html +17 -0
- package/cli/lamps/web-starter/lamp-starter.json +5 -0
- package/cli/lamps/web-starter/lamp.js +7082 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +202 -0
- package/cli/local-ai.mjs +193 -0
- package/cli/mac.mjs +621 -0
- package/cli/mdns.mjs +205 -0
- package/cli/msix.mjs +413 -0
- package/cli/network.mjs +209 -0
- package/cli/openrouter.mjs +133 -0
- package/cli/outside.mjs +203 -0
- package/cli/page.js +317 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +270 -0
- package/cli/skill/SKILL.md +280 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +176 -0
- package/cli/update.mjs +162 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +1727 -0
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
// Lamp's web host on the Xbox: the same program for every web app, beside the app's Web/ folder in its package.
|
|
2
|
+
//
|
|
3
|
+
// A full-screen CoreWindow holding a WebView2 (Chromium, which the console keeps up to date) that shows Web/index.html
|
|
4
|
+
// from the package, served at https://lamp.example/ by WebView2's own mapping of a host name to a folder, so pages load
|
|
5
|
+
// their files with fetch as they would from a site. No XAML: the page is drawn into a composition visual, as WinUI's
|
|
6
|
+
// control does it. The page's console, its uncaught errors, text it posts with chrome.webview.postMessage and what the
|
|
7
|
+
// host itself has to say go to log.txt in the app's own folder, which Device Portal reads back.
|
|
8
|
+
//
|
|
9
|
+
// lamp.js reaches Lamp on the Mac through the host (`http` messages): the page is https and Lamp's gateway plain http
|
|
10
|
+
// on the home network, which Chromium won't let the page call itself. Only the app's own page may ask, only Lamp's own
|
|
11
|
+
// paths are passed on, and an answer goes only to the page that asked: the app's token and its AI's answers are in
|
|
12
|
+
// them. A link to anywhere else opens in the console's browser, never in this web view.
|
|
13
|
+
//
|
|
14
|
+
// A game's or a screen's page that's one of a wish's apps talks to the others over plain ws:// links (to Lamp's relay
|
|
15
|
+
// room, and straight to the apps that listen), which an https page may not open. The host opens them for the page and
|
|
16
|
+
// passes each frame both ways as it is (`ws` messages), while the page keeps the wish's handshake and seals: the host
|
|
17
|
+
// never holds the wish's key and can't read what goes. Only the app's own page may ask, and only for a ws:// link to
|
|
18
|
+
// this network (a private or link-local address, or a .local name), never to the console itself or the internet.
|
|
19
|
+
//
|
|
20
|
+
// Chromium's web view never shakes the controller, so lamp.js sends a page's vibrationActuator calls here (`rumble`
|
|
21
|
+
// messages), and the host plays them with Windows.Gaming.Input, as a native game's lamp_rumble does.
|
|
22
|
+
//
|
|
23
|
+
// A game that chose its own host for its phones (`Lamp.controllers.start({ host: 'game' })`) plays with no Lamp
|
|
24
|
+
// (lamp_controllers.h): the host serves the game's phone page from the package's Phone folder and runs the relay its
|
|
25
|
+
// phones join, each on a port the system gives it, with a key made for that session and kept only in memory. The page
|
|
26
|
+
// asks the host for its session (`controllers` messages), and its own link to the relay is carried over the host's
|
|
27
|
+
// pipe straight into the relay, never over the network, so only the page can be the game's host. Stopping, the console setting the app aside, the page going
|
|
28
|
+
// away, and no ask from it for 45 s end the session: listeners close, players are let go and the key is forgotten;
|
|
29
|
+
// the page's next ask opens a new one.
|
|
30
|
+
//
|
|
31
|
+
// The page is laid out at 1920x1080 CSS pixels, as in Lamp's preview on the Mac and as the rules say: the console's
|
|
32
|
+
// web view would otherwise lay it out at 1280x720 and scale it up, so the host zooms it out to match.
|
|
33
|
+
//
|
|
34
|
+
// Built by scripts/build-web-host.sh; Lamp ships the program it makes (Resources/WebHost/App.exe).
|
|
35
|
+
#define WIN32_LEAN_AND_MEAN
|
|
36
|
+
#include <windows.h>
|
|
37
|
+
#include <winrt/base.h>
|
|
38
|
+
#include <winrt/Windows.Foundation.h>
|
|
39
|
+
#include <winrt/Windows.Foundation.Collections.h>
|
|
40
|
+
#include <winrt/Windows.ApplicationModel.h>
|
|
41
|
+
#include <winrt/Windows.ApplicationModel.Core.h>
|
|
42
|
+
#include <winrt/Windows.UI.Core.h>
|
|
43
|
+
#include <winrt/Windows.UI.ViewManagement.h>
|
|
44
|
+
#include <winrt/Windows.Storage.h>
|
|
45
|
+
#include <winrt/Windows.UI.Composition.h>
|
|
46
|
+
#include <winrt/Windows.Data.Json.h>
|
|
47
|
+
#include <winrt/Windows.Web.Http.h>
|
|
48
|
+
#include <winrt/Windows.Web.Http.Filters.h>
|
|
49
|
+
#include <winrt/Windows.Web.Http.Headers.h>
|
|
50
|
+
#include <winrt/Windows.Storage.Streams.h>
|
|
51
|
+
#include <winrt/Windows.System.h>
|
|
52
|
+
#include <winrt/Windows.System.Threading.h>
|
|
53
|
+
#include <winrt/Windows.Gaming.Input.h>
|
|
54
|
+
#include <winrt/Windows.Networking.h>
|
|
55
|
+
#include <winrt/Windows.Networking.Sockets.h>
|
|
56
|
+
#include <winrt/Windows.Networking.Connectivity.h>
|
|
57
|
+
#include <winrt/Windows.Security.Cryptography.h>
|
|
58
|
+
#include <winrt/Windows.Security.Cryptography.Core.h>
|
|
59
|
+
#include <winrt/Microsoft.Web.WebView2.Core.h>
|
|
60
|
+
#include <algorithm>
|
|
61
|
+
#include <atomic>
|
|
62
|
+
#include <mutex>
|
|
63
|
+
#include <cstdio>
|
|
64
|
+
#include <deque>
|
|
65
|
+
#include <map>
|
|
66
|
+
#include <memory>
|
|
67
|
+
#include <string>
|
|
68
|
+
#include "lamp_controllers.h"
|
|
69
|
+
|
|
70
|
+
using namespace winrt;
|
|
71
|
+
using namespace Windows::ApplicationModel::Core;
|
|
72
|
+
using namespace Windows::UI::Core;
|
|
73
|
+
using namespace Microsoft::Web::WebView2::Core;
|
|
74
|
+
using Windows::Data::Json::JsonObject;
|
|
75
|
+
using Windows::Data::Json::JsonValue;
|
|
76
|
+
|
|
77
|
+
static FILE* g_log = nullptr;
|
|
78
|
+
static const wchar_t* g_origin = L"https://lamp.example/";
|
|
79
|
+
|
|
80
|
+
static bool is_app_page(std::wstring_view url) { return url.rfind(g_origin, 0) == 0; }
|
|
81
|
+
|
|
82
|
+
static void say_line(std::wstring_view text);
|
|
83
|
+
|
|
84
|
+
// An IPv4 address on a home network: private (10/8, 172.16/12, 192.168/16) or link-local (169.254/16).
|
|
85
|
+
static bool is_private_v4(std::wstring_view host) {
|
|
86
|
+
unsigned a, b, c, d;
|
|
87
|
+
wchar_t rest;
|
|
88
|
+
if (swscanf_s(std::wstring(host).c_str(), L"%u.%u.%u.%u%lc", &a, &b, &c, &d, &rest, 1) != 4 || a > 255 || b > 255 || c > 255 || d > 255) return false;
|
|
89
|
+
return a == 10 || (a == 172 && b >= 16 && b < 32) || (a == 192 && b == 168) || (a == 169 && b == 254);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Where the page may open a link to: ws:// on this network, at a port of a program's own, with nothing but a host and a
|
|
93
|
+
// port. The wish's apps meet at Lamp's relay on the Mac and at each other's listeners, all on the home network; the
|
|
94
|
+
// console's own services (its loopback) and anywhere beyond the network are never reached through the host.
|
|
95
|
+
static bool may_dial(Windows::Foundation::Uri const& url) {
|
|
96
|
+
if (url.SchemeName() != L"ws" || !url.UserName().empty() || !url.Password().empty() || url.Port() < 1024 || url.Port() > 65535) return false;
|
|
97
|
+
std::wstring host(url.Host());
|
|
98
|
+
std::transform(host.begin(), host.end(), host.begin(), ::towlower);
|
|
99
|
+
if (is_private_v4(host)) return true;
|
|
100
|
+
// An IPv6 link-local or unique local address.
|
|
101
|
+
if (host.size() > 2 && host.front() == L'[' && host.back() == L']')
|
|
102
|
+
return host.rfind(L"[fe8", 0) == 0 || host.rfind(L"[fe9", 0) == 0 || host.rfind(L"[fea", 0) == 0 || host.rfind(L"[feb", 0) == 0
|
|
103
|
+
|| host.rfind(L"[fc", 0) == 0 || host.rfind(L"[fd", 0) == 0;
|
|
104
|
+
// A Mac's Bonjour name, which only this network answers.
|
|
105
|
+
bool plain = !host.empty() && std::all_of(host.begin(), host.end(), [](wchar_t ch) { return iswalnum(ch) || ch == L'-' || ch == L'.'; });
|
|
106
|
+
return plain && host.size() > 6 && host.compare(host.size() - 6, 6, L".local") == 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static void say(const wchar_t* format, ...) {
|
|
110
|
+
wchar_t line[1024];
|
|
111
|
+
va_list args;
|
|
112
|
+
va_start(args, format);
|
|
113
|
+
_vsnwprintf_s(line, _TRUNCATE, format, args);
|
|
114
|
+
va_end(args);
|
|
115
|
+
say_line(line);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static void say_line(std::wstring_view text) {
|
|
119
|
+
if (!g_log) return;
|
|
120
|
+
// UTF-8 in the file, so Device Portal hands back plain text whatever the page wrote.
|
|
121
|
+
int n = WideCharToMultiByte(CP_UTF8, 0, text.data(), (int)text.size(), nullptr, 0, nullptr, nullptr);
|
|
122
|
+
std::string out(n, '\0');
|
|
123
|
+
WideCharToMultiByte(CP_UTF8, 0, text.data(), (int)text.size(), out.data(), n, nullptr, nullptr);
|
|
124
|
+
fwrite(out.data(), 1, out.size(), g_log);
|
|
125
|
+
fputc('\n', g_log);
|
|
126
|
+
fflush(g_log);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: Phones as controllers: the console's side of lamp_controllers.h
|
|
130
|
+
|
|
131
|
+
namespace lampc::platform {
|
|
132
|
+
using Windows::Security::Cryptography::CryptographicBuffer;
|
|
133
|
+
|
|
134
|
+
static Windows::Storage::Streams::IBuffer buffer_of(std::string_view data) {
|
|
135
|
+
return CryptographicBuffer::CreateFromByteArray({ reinterpret_cast<uint8_t const*>(data.data()), reinterpret_cast<uint8_t const*>(data.data()) + data.size() });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
void random_bytes(uint8_t* out, size_t count) {
|
|
139
|
+
auto random = CryptographicBuffer::GenerateRandom((uint32_t)count);
|
|
140
|
+
memcpy(out, random.data(), count);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
std::array<uint8_t, 32> hmac_sha256(std::string_view key, std::string_view data) {
|
|
144
|
+
using namespace Windows::Security::Cryptography::Core;
|
|
145
|
+
static auto provider = MacAlgorithmProvider::OpenAlgorithm(MacAlgorithmNames::HmacSha256());
|
|
146
|
+
auto signature = CryptographicEngine::Sign(provider.CreateKey(buffer_of(key)), buffer_of(data));
|
|
147
|
+
std::array<uint8_t, 32> out{};
|
|
148
|
+
memcpy(out.data(), signature.data(), std::min<size_t>(out.size(), signature.Length()));
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
std::array<uint8_t, 20> sha1(std::string_view data) {
|
|
153
|
+
using namespace Windows::Security::Cryptography::Core;
|
|
154
|
+
static auto provider = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1());
|
|
155
|
+
auto digest = provider.HashData(buffer_of(data));
|
|
156
|
+
std::array<uint8_t, 20> out{};
|
|
157
|
+
memcpy(out.data(), digest.data(), std::min<size_t>(out.size(), digest.Length()));
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Where a phone's page goes: the page-only link the host carries into its relay (`pipe`), never dialed.
|
|
163
|
+
static const wchar_t* g_relay_link = L"ws://lamp-controllers.invalid/";
|
|
164
|
+
|
|
165
|
+
// The console's address a phone's link carries: one on the home network, the one the console reaches the internet by
|
|
166
|
+
// when it has several; nil on none, so a phone is never given a link to itself.
|
|
167
|
+
static std::optional<std::wstring> link_host() {
|
|
168
|
+
using namespace Windows::Networking::Connectivity;
|
|
169
|
+
std::optional<std::wstring> any, internet;
|
|
170
|
+
try {
|
|
171
|
+
hstring adapter;
|
|
172
|
+
if (auto profile = NetworkInformation::GetInternetConnectionProfile(); profile && profile.NetworkAdapter())
|
|
173
|
+
adapter = to_hstring(profile.NetworkAdapter().NetworkAdapterId());
|
|
174
|
+
for (auto const& name : NetworkInformation::GetHostNames()) {
|
|
175
|
+
if (name.Type() != Windows::Networking::HostNameType::Ipv4 || !is_private_v4(name.CanonicalName())) continue;
|
|
176
|
+
std::wstring address(name.CanonicalName());
|
|
177
|
+
if (!any) any = address;
|
|
178
|
+
auto info = name.IPInformation();
|
|
179
|
+
if (!internet && info && info.NetworkAdapter() && !adapter.empty() && to_hstring(info.NetworkAdapter().NetworkAdapterId()) == adapter) internet = address;
|
|
180
|
+
}
|
|
181
|
+
} catch (...) {}
|
|
182
|
+
return internet ? internet : any;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The console's names and addresses, lowercased: the only ones a phone's page is served as.
|
|
186
|
+
static std::set<std::string> host_names() {
|
|
187
|
+
std::set<std::string> names;
|
|
188
|
+
try {
|
|
189
|
+
for (auto const& name : Windows::Networking::Connectivity::NetworkInformation::GetHostNames()) {
|
|
190
|
+
std::string text = lampc::lower(to_string(name.CanonicalName()));
|
|
191
|
+
if (auto zone = text.find('%'); zone != std::string::npos) text = text.substr(0, zone);
|
|
192
|
+
if (!text.empty()) names.insert(text);
|
|
193
|
+
}
|
|
194
|
+
} catch (...) {}
|
|
195
|
+
return names;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Which phone a connection is from, for the limits on how many it may hold (`lampc::source_of`).
|
|
199
|
+
static std::string remote_of(Windows::Networking::Sockets::StreamSocket const& socket) {
|
|
200
|
+
try { return lampc::source_of(to_string(socket.Information().RemoteAddress().CanonicalName())); }
|
|
201
|
+
catch (...) { return "?"; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A StreamSocket under one of lampc's WebSockets: each write handed to the network, and `done` on the window's thread.
|
|
205
|
+
struct StreamTransport : lampc::Transport {
|
|
206
|
+
Windows::Networking::Sockets::StreamSocket socket;
|
|
207
|
+
Windows::Storage::Streams::DataWriter writer;
|
|
208
|
+
CoreDispatcher dispatcher;
|
|
209
|
+
StreamTransport(Windows::Networking::Sockets::StreamSocket s, Windows::Storage::Streams::DataWriter w, CoreDispatcher d)
|
|
210
|
+
: socket(std::move(s)), writer(std::move(w)), dispatcher(std::move(d)) {}
|
|
211
|
+
void write(std::string bytes, std::function<void(bool)> done) override { send(writer, dispatcher, std::move(bytes), std::move(done)); }
|
|
212
|
+
void shutdown() override { try { socket.Close(); } catch (...) {} }
|
|
213
|
+
|
|
214
|
+
// Touches nothing of the transport after it waits, which may be gone by then.
|
|
215
|
+
static fire_and_forget send(Windows::Storage::Streams::DataWriter writer, CoreDispatcher dispatcher, std::string bytes, std::function<void(bool)> done) {
|
|
216
|
+
bool ok = true;
|
|
217
|
+
try {
|
|
218
|
+
writer.WriteBytes({ reinterpret_cast<uint8_t const*>(bytes.data()), reinterpret_cast<uint8_t const*>(bytes.data()) + bytes.size() });
|
|
219
|
+
co_await writer.StoreAsync();
|
|
220
|
+
} catch (...) { ok = false; }
|
|
221
|
+
co_await resume_foreground(dispatcher);
|
|
222
|
+
done(ok);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
struct View : implements<View, IFrameworkViewSource, IFrameworkView> {
|
|
227
|
+
CoreWindow window{ nullptr };
|
|
228
|
+
CoreWebView2Controller controller{ nullptr };
|
|
229
|
+
CoreWebView2 web{ nullptr };
|
|
230
|
+
CoreWebView2DevToolsProtocolEventReceiver consoleCalls{ nullptr }, uncaught{ nullptr };
|
|
231
|
+
// The page is drawn into this visual, the window's only content: WebView2 on the Xbox is hosted the way WinUI's
|
|
232
|
+
// control hosts it, through composition, since a controller given the CoreWindow itself ends the app.
|
|
233
|
+
Windows::UI::Composition::Compositor compositor{ nullptr };
|
|
234
|
+
Windows::UI::Composition::CompositionTarget target{ nullptr };
|
|
235
|
+
Windows::UI::Composition::ContainerVisual root{ nullptr };
|
|
236
|
+
std::wstring installed;
|
|
237
|
+
// One effect at a time on each controller: a new one, or a reset, takes the place of what was playing.
|
|
238
|
+
// Each of the page's gamepads by its index: the controller it was found to be, and what's playing on it.
|
|
239
|
+
struct Rumble {
|
|
240
|
+
Windows::Gaming::Input::Gamepad pad{ nullptr };
|
|
241
|
+
std::atomic<uint64_t> effect{ 0 };
|
|
242
|
+
Windows::System::Threading::ThreadPoolTimer start{ nullptr }, stop{ nullptr };
|
|
243
|
+
};
|
|
244
|
+
Rumble rumbles[4];
|
|
245
|
+
// Held over each check of an effect's generation and the write that follows it, from the timers and from `still`, so
|
|
246
|
+
// a timer can't write an effect's strength after a reset has stilled the controller.
|
|
247
|
+
std::mutex shaking;
|
|
248
|
+
// Counts the page's top-level navigations, so an answer to a page that has since gone is dropped.
|
|
249
|
+
uint64_t page = 0;
|
|
250
|
+
struct PagePeer;
|
|
251
|
+
// The page's links to the wish's other apps, by the page's number for each; touched only on the window's thread.
|
|
252
|
+
struct Pipe {
|
|
253
|
+
Windows::Networking::Sockets::MessageWebSocket socket{ nullptr };
|
|
254
|
+
Windows::Storage::Streams::DataWriter writer{ nullptr };
|
|
255
|
+
// What the page sent while the link opened or a frame before it was still going: text, or bytes in base64.
|
|
256
|
+
std::deque<std::pair<bool, hstring>> queue;
|
|
257
|
+
bool open = false, writing = false, over = false;
|
|
258
|
+
uint64_t page = 0;
|
|
259
|
+
// The page's link to the game's own relay, carried in the host rather than dialed.
|
|
260
|
+
std::shared_ptr<PagePeer> local;
|
|
261
|
+
};
|
|
262
|
+
std::map<double, std::shared_ptr<Pipe>> pipes;
|
|
263
|
+
|
|
264
|
+
IFrameworkView CreateView() { return *this; }
|
|
265
|
+
|
|
266
|
+
void Initialize(CoreApplicationView const&) {
|
|
267
|
+
installed = std::wstring(Windows::ApplicationModel::Package::Current().InstalledLocation().Path());
|
|
268
|
+
CoreApplication::Suspending([this](auto const&, Windows::ApplicationModel::SuspendingEventArgs const& args) {
|
|
269
|
+
say_line(L"Lamp: the console set the app aside.");
|
|
270
|
+
for (int i = 0; i < 4; i++) still(i);
|
|
271
|
+
// Phones' session ends with it: nothing listens while the app is set aside, and coming back opens a new one.
|
|
272
|
+
if (!window) return;
|
|
273
|
+
auto deferral = args.SuspendingOperation().GetDeferral();
|
|
274
|
+
window.Dispatcher().RunAsync(CoreDispatcherPriority::High, [this, deferral] {
|
|
275
|
+
phones_close(L"the console set the app aside");
|
|
276
|
+
deferral.Complete();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
CoreApplication::Resuming([this](auto const&, auto const&) {
|
|
280
|
+
say_line(L"Lamp: the app came back.");
|
|
281
|
+
// Links held over a suspension may be gone without a word: the page is told they're closed, and opens them
|
|
282
|
+
// again as it does any link lost.
|
|
283
|
+
if (window) window.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this] { end_pipes(1001); });
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
void Load(hstring const&) {}
|
|
288
|
+
void Uninitialize() {}
|
|
289
|
+
|
|
290
|
+
void SetWindow(CoreWindow const& w) {
|
|
291
|
+
window = w;
|
|
292
|
+
// The whole screen is the page's: no border kept for the TV's edges, as a web app would expect.
|
|
293
|
+
Windows::UI::ViewManagement::ApplicationView::GetForCurrentView().SetDesiredBoundsMode(
|
|
294
|
+
Windows::UI::ViewManagement::ApplicationViewBoundsMode::UseCoreWindow);
|
|
295
|
+
// B on the controller is the page's (Escape for its menus, a game's own use): without this the console takes it
|
|
296
|
+
// as Back and leaves the app.
|
|
297
|
+
SystemNavigationManager::GetForCurrentView().BackRequested([](auto const&, BackRequestedEventArgs const& args) { args.Handled(true); });
|
|
298
|
+
window.SizeChanged([this](auto const&, auto const&) { fit(); });
|
|
299
|
+
// Windows lists controllers only once something listens for them: listening from the start, the first effect
|
|
300
|
+
// finds them there.
|
|
301
|
+
Windows::Gaming::Input::Gamepad::GamepadAdded([](auto const&, auto const&) {
|
|
302
|
+
say(L"Lamp: %u controllers connected.", Windows::Gaming::Input::Gamepad::Gamepads().Size());
|
|
303
|
+
});
|
|
304
|
+
window.VisibilityChanged([this](auto const&, VisibilityChangedEventArgs const& args) {
|
|
305
|
+
if (controller) controller.IsVisible(args.Visible());
|
|
306
|
+
// A game set aside mid-effect doesn't leave the controller shaking in the person's hands.
|
|
307
|
+
if (!args.Visible()) for (int i = 0; i < 4; i++) still(i);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
void fit() {
|
|
312
|
+
if (!controller || !window) return;
|
|
313
|
+
auto b = window.Bounds();
|
|
314
|
+
controller.Bounds({ 0, 0, b.Width, b.Height });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fire_and_forget start() {
|
|
318
|
+
auto lifetime = get_strong();
|
|
319
|
+
try {
|
|
320
|
+
// A console app plays its sound as it opens, as a game's title music does: WebView2, like Chrome, would hold
|
|
321
|
+
// a page's sound back until the first press of a button, so that rule is turned off for the app's own page.
|
|
322
|
+
CoreWebView2EnvironmentOptions options;
|
|
323
|
+
options.AdditionalBrowserArguments(L"--autoplay-policy=no-user-gesture-required");
|
|
324
|
+
auto env = co_await CoreWebView2Environment::CreateWithOptionsAsync(L"", L"", options);
|
|
325
|
+
say(L"Lamp: WebView2 %ls.", env.BrowserVersionString().c_str());
|
|
326
|
+
compositor = Windows::UI::Composition::Compositor();
|
|
327
|
+
target = compositor.CreateTargetForCurrentView();
|
|
328
|
+
root = compositor.CreateContainerVisual();
|
|
329
|
+
root.RelativeSizeAdjustment({ 1, 1 });
|
|
330
|
+
target.Root(root);
|
|
331
|
+
auto composed = co_await env.CreateCoreWebView2CompositionControllerAsync(CoreWebView2ControllerWindowReference::CreateFromCoreWindow(window));
|
|
332
|
+
composed.RootVisualTarget(root);
|
|
333
|
+
controller = composed;
|
|
334
|
+
controller.DefaultBackgroundColor(Windows::UI::Color{ 255, 0, 0, 0 });
|
|
335
|
+
fit();
|
|
336
|
+
controller.IsVisible(true);
|
|
337
|
+
auto b = window.Bounds();
|
|
338
|
+
say(L"Lamp: the window is %.0f by %.0f, the page drawn at %.2f.", b.Width, b.Height, controller.RasterizationScale());
|
|
339
|
+
// Focus stays in the page: leaving it would leave nothing on screen to take the controller.
|
|
340
|
+
controller.MoveFocusRequested([](auto const&, CoreWebView2MoveFocusRequestedEventArgs const& args) { args.Handled(true); });
|
|
341
|
+
|
|
342
|
+
// Kept for the app's life: WebView2's WinRT objects drop the handlers added to them once they're let go.
|
|
343
|
+
web = controller.CoreWebView2();
|
|
344
|
+
auto settings = web.Settings();
|
|
345
|
+
settings.AreDefaultContextMenusEnabled(false);
|
|
346
|
+
settings.IsStatusBarEnabled(false);
|
|
347
|
+
settings.IsGeneralAutofillEnabled(false);
|
|
348
|
+
settings.IsPasswordAutosaveEnabled(false);
|
|
349
|
+
settings.IsZoomControlEnabled(false);
|
|
350
|
+
settings.IsPinchZoomEnabled(false);
|
|
351
|
+
// The page is the app's own, from its package, so nothing it opens needs SmartScreen's look first.
|
|
352
|
+
settings.IsReputationCheckingRequired(false);
|
|
353
|
+
web.SetVirtualHostNameToFolderMapping(L"lamp.example", hstring(installed + L"\\Web"), CoreWebView2HostResourceAccessKind::Allow);
|
|
354
|
+
web.WebMessageReceived([this](auto const&, CoreWebView2WebMessageReceivedEventArgs const& args) {
|
|
355
|
+
JsonObject message{ nullptr };
|
|
356
|
+
if (JsonObject::TryParse(args.WebMessageAsJson(), message) && message.GetNamedString(L"lamp", L"") == L"http") {
|
|
357
|
+
if (is_app_page(args.Source())) relay(message);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (message && message.GetNamedString(L"lamp", L"") == L"rumble") {
|
|
361
|
+
if (is_app_page(args.Source())) rumble(message);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (message && message.GetNamedString(L"lamp", L"") == L"ws") {
|
|
365
|
+
if (is_app_page(args.Source())) pipe(message);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (message && message.GetNamedString(L"lamp", L"") == L"controllers") {
|
|
369
|
+
if (is_app_page(args.Source()))
|
|
370
|
+
phone_ask({ message.GetNamedString(L"op", L"") == L"stop", message.GetNamedNumber(L"id", 0), page });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try { say_line(std::wstring(L"page: ") + std::wstring(args.TryGetWebMessageAsString())); }
|
|
374
|
+
catch (...) { say_line(std::wstring(L"page: ") + std::wstring(args.WebMessageAsJson()).substr(0, 2000)); }
|
|
375
|
+
});
|
|
376
|
+
listen_to_console();
|
|
377
|
+
web.NavigationCompleted([](auto const&, CoreWebView2NavigationCompletedEventArgs const& args) {
|
|
378
|
+
if (args.IsSuccess()) say_line(L"Lamp: the page opened.");
|
|
379
|
+
else say(L"Lamp: the page didn't open (%d).", (int)args.WebErrorStatus());
|
|
380
|
+
});
|
|
381
|
+
web.ProcessFailed([](auto const&, CoreWebView2ProcessFailedEventArgs const& args) {
|
|
382
|
+
say(L"Lamp: a WebView2 process stopped (kind %d, reason %d, exit %d).", (int)args.ProcessFailedKind(), (int)args.Reason(), args.ExitCode());
|
|
383
|
+
});
|
|
384
|
+
// The app's pages stay in this window; anything else opens in the console's browser, so no other site's page
|
|
385
|
+
// is ever where the app's answers go.
|
|
386
|
+
web.NavigationStarting([this](auto const&, CoreWebView2NavigationStartingEventArgs const& args) {
|
|
387
|
+
std::wstring uri(args.Uri());
|
|
388
|
+
if (is_app_page(uri) || uri == L"about:blank") { page++; end_pipes(0); phones_close(L"the page went away"); return; }
|
|
389
|
+
args.Cancel(true);
|
|
390
|
+
elsewhere(uri);
|
|
391
|
+
});
|
|
392
|
+
web.NewWindowRequested([this](auto const& sender, CoreWebView2NewWindowRequestedEventArgs const& args) {
|
|
393
|
+
args.Handled(true);
|
|
394
|
+
std::wstring uri(args.Uri());
|
|
395
|
+
if (is_app_page(uri)) sender.Navigate(args.Uri()); else elsewhere(uri);
|
|
396
|
+
});
|
|
397
|
+
co_await fit_page();
|
|
398
|
+
web.Navigate(L"https://lamp.example/index.html");
|
|
399
|
+
controller.MoveFocus(CoreWebView2MoveFocusReason::Programmatic);
|
|
400
|
+
} catch (hresult_error const& e) {
|
|
401
|
+
say(L"Lamp: WebView2 couldn't start (0x%08x): %ls", (unsigned)e.code(), e.message().c_str());
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// The page's console and uncaught errors, through the DevTools protocol, so the log says what went wrong in it.
|
|
406
|
+
fire_and_forget listen_to_console() {
|
|
407
|
+
auto lifetime = get_strong();
|
|
408
|
+
auto line = [](JsonObject const& o) {
|
|
409
|
+
std::wstring text;
|
|
410
|
+
for (auto const& value : o.GetNamedArray(L"args", Windows::Data::Json::JsonArray())) {
|
|
411
|
+
auto arg = value.GetObject();
|
|
412
|
+
if (!text.empty()) text += L" ";
|
|
413
|
+
if (arg.HasKey(L"value")) {
|
|
414
|
+
auto v = arg.GetNamedValue(L"value");
|
|
415
|
+
text += v.ValueType() == Windows::Data::Json::JsonValueType::String ? std::wstring(v.GetString()) : std::wstring(v.Stringify());
|
|
416
|
+
} else text += std::wstring(arg.GetNamedString(L"description", arg.GetNamedString(L"type", L"")));
|
|
417
|
+
}
|
|
418
|
+
return text.substr(0, 2000);
|
|
419
|
+
};
|
|
420
|
+
consoleCalls = web.GetDevToolsProtocolEventReceiver(L"Runtime.consoleAPICalled");
|
|
421
|
+
consoleCalls.DevToolsProtocolEventReceived(
|
|
422
|
+
[line](auto const&, CoreWebView2DevToolsProtocolEventReceivedEventArgs const& args) {
|
|
423
|
+
JsonObject o{ nullptr };
|
|
424
|
+
if (!JsonObject::TryParse(args.ParameterObjectAsJson(), o)) return;
|
|
425
|
+
say_line(L"console." + std::wstring(o.GetNamedString(L"type", L"log")) + L": " + line(o));
|
|
426
|
+
});
|
|
427
|
+
uncaught = web.GetDevToolsProtocolEventReceiver(L"Runtime.exceptionThrown");
|
|
428
|
+
uncaught.DevToolsProtocolEventReceived(
|
|
429
|
+
[](auto const&, CoreWebView2DevToolsProtocolEventReceivedEventArgs const& args) {
|
|
430
|
+
JsonObject o{ nullptr };
|
|
431
|
+
if (!JsonObject::TryParse(args.ParameterObjectAsJson(), o)) return;
|
|
432
|
+
auto details = o.GetNamedObject(L"exceptionDetails", JsonObject());
|
|
433
|
+
auto exception = details.GetNamedObject(L"exception", JsonObject());
|
|
434
|
+
std::wstring text(exception.GetNamedString(L"description", details.GetNamedString(L"text", L"")));
|
|
435
|
+
say_line(L"error: " + text.substr(0, 2000) + L" (line " + std::to_wstring((int)details.GetNamedNumber(L"lineNumber", 0) + 1) + L")");
|
|
436
|
+
});
|
|
437
|
+
try { co_await web.CallDevToolsProtocolMethodAsync(L"Runtime.enable", L"{}"); }
|
|
438
|
+
catch (hresult_error const& e) { say(L"Lamp: the page's console can't be read (0x%08x).", (unsigned)e.code()); }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// One of lamp.js's calls to Lamp on the Mac: a POST with JSON to Lamp's own paths over plain http, answered with
|
|
442
|
+
// the status and body, or with an error the page reads as "unreachable".
|
|
443
|
+
fire_and_forget relay(JsonObject message) {
|
|
444
|
+
auto lifetime = get_strong();
|
|
445
|
+
auto dispatcher = window.Dispatcher();
|
|
446
|
+
uint64_t asked = page;
|
|
447
|
+
double id = message.GetNamedNumber(L"id", 0);
|
|
448
|
+
JsonObject answer;
|
|
449
|
+
answer.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"http"));
|
|
450
|
+
answer.SetNamedValue(L"id", JsonValue::CreateNumberValue(id));
|
|
451
|
+
try {
|
|
452
|
+
Windows::Foundation::Uri url(message.GetNamedString(L"url", L""));
|
|
453
|
+
std::wstring path(url.Path());
|
|
454
|
+
bool lamps = path.rfind(L"/ai", 0) == 0 || path.rfind(L"/app/", 0) == 0;
|
|
455
|
+
if (url.SchemeName() != L"http" || !lamps || !url.UserName().empty()) throw hresult_invalid_argument(L"not one of Lamp's paths");
|
|
456
|
+
auto body = message.GetNamedString(L"body", L"");
|
|
457
|
+
if (body.size() > 8 * 1024 * 1024) throw hresult_invalid_argument(L"too big");
|
|
458
|
+
double timeout = message.GetNamedNumber(L"timeout", 240000);
|
|
459
|
+
co_await resume_background();
|
|
460
|
+
Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
|
|
461
|
+
filter.AllowAutoRedirect(false);
|
|
462
|
+
filter.CacheControl().ReadBehavior(Windows::Web::Http::Filters::HttpCacheReadBehavior::NoCache);
|
|
463
|
+
Windows::Web::Http::HttpClient client(filter);
|
|
464
|
+
Windows::Web::Http::HttpRequestMessage request(Windows::Web::Http::HttpMethod::Post(), url);
|
|
465
|
+
request.Content(Windows::Web::Http::HttpStringContent(body, Windows::Storage::Streams::UnicodeEncoding::Utf8, L"application/json"));
|
|
466
|
+
auto token = message.GetNamedString(L"token", L"");
|
|
467
|
+
if (!token.empty()) request.Headers().Authorization(Windows::Web::Http::Headers::HttpCredentialsHeaderValue(L"Bearer", token));
|
|
468
|
+
auto sending = client.SendRequestAsync(request);
|
|
469
|
+
if (sending.wait_for(std::chrono::milliseconds((int64_t)timeout)) != Windows::Foundation::AsyncStatus::Completed) {
|
|
470
|
+
sending.Cancel();
|
|
471
|
+
throw hresult_error(E_ABORT, L"no answer");
|
|
472
|
+
}
|
|
473
|
+
auto response = sending.GetResults();
|
|
474
|
+
auto text = response.Content().ReadAsStringAsync().get();
|
|
475
|
+
answer.SetNamedValue(L"status", JsonValue::CreateNumberValue((double)(int)response.StatusCode()));
|
|
476
|
+
say(L"Lamp: %ls%ls answered %d.", url.Host().c_str(), path.c_str(), (int)response.StatusCode());
|
|
477
|
+
answer.SetNamedValue(L"body", JsonValue::CreateStringValue(text));
|
|
478
|
+
} catch (hresult_error const& e) {
|
|
479
|
+
say(L"Lamp: a call to Lamp on the Mac failed (0x%08x): %ls", (unsigned)e.code(), e.message().c_str());
|
|
480
|
+
answer.SetNamedValue(L"error", JsonValue::CreateStringValue(e.message()));
|
|
481
|
+
}
|
|
482
|
+
co_await resume_foreground(dispatcher);
|
|
483
|
+
if (web && page == asked && is_app_page(web.Source())) web.PostWebMessageAsJson(answer.Stringify());
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// What the page asks of its links: open one, send a frame on it, close it, or say this console's addresses.
|
|
487
|
+
void pipe(JsonObject const& m) {
|
|
488
|
+
auto op = m.GetNamedString(L"op", L"");
|
|
489
|
+
double id = m.GetNamedNumber(L"id", 0);
|
|
490
|
+
if (op == L"here") return here();
|
|
491
|
+
if (op == L"open") {
|
|
492
|
+
if (m.GetNamedString(L"url", L"") == g_relay_link) return open_local(id);
|
|
493
|
+
std::shared_ptr<Pipe> p;
|
|
494
|
+
try {
|
|
495
|
+
Windows::Foundation::Uri url(m.GetNamedString(L"url", L""));
|
|
496
|
+
// A sweep of the network opens a few dozen at once; more than that is something gone wrong in the page.
|
|
497
|
+
if (pipes.count(id) || pipes.size() >= 64) throw hresult_invalid_argument(L"too many links");
|
|
498
|
+
if (!may_dial(url)) throw hresult_invalid_argument(L"not a link to this network");
|
|
499
|
+
p = std::make_shared<Pipe>();
|
|
500
|
+
p->page = page;
|
|
501
|
+
pipes[id] = p;
|
|
502
|
+
dial(id, p, url);
|
|
503
|
+
} catch (hresult_error const& e) {
|
|
504
|
+
say(L"Lamp: the page's link wasn't opened: %ls", e.message().c_str());
|
|
505
|
+
if (p) pipes.erase(id);
|
|
506
|
+
tell(id, page, L"close", 1006);
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
auto found = pipes.find(id);
|
|
511
|
+
if (found == pipes.end()) return;
|
|
512
|
+
auto p = found->second;
|
|
513
|
+
if (op == L"close") return end_pipe(id, p, 0, false);
|
|
514
|
+
if (op != L"send") return;
|
|
515
|
+
bool binary = m.HasKey(L"data");
|
|
516
|
+
// Held to what a file's piece takes, sealed, in base64, and to a queue the page's own flow keeps short.
|
|
517
|
+
auto payload = binary ? m.GetNamedString(L"data", L"") : m.GetNamedString(L"text", L"");
|
|
518
|
+
if (payload.size() > 128 * 1024 || p->queue.size() >= 256) return end_pipe(id, p, 1009, true);
|
|
519
|
+
if (p->local) return to_local(id, p, binary, payload);
|
|
520
|
+
p->queue.emplace_back(binary, payload);
|
|
521
|
+
pump(id, p);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Opens one link, and passes what comes on it to the page, in order.
|
|
525
|
+
fire_and_forget dial(double id, std::shared_ptr<Pipe> p, Windows::Foundation::Uri url) {
|
|
526
|
+
auto lifetime = get_strong();
|
|
527
|
+
auto dispatcher = window.Dispatcher();
|
|
528
|
+
using namespace Windows::Networking::Sockets;
|
|
529
|
+
std::weak_ptr<Pipe> weak = p;
|
|
530
|
+
bool failed = false;
|
|
531
|
+
try {
|
|
532
|
+
MessageWebSocket socket;
|
|
533
|
+
socket.Control().MessageType(SocketMessageType::Utf8);
|
|
534
|
+
p->socket = socket;
|
|
535
|
+
socket.MessageReceived([this, id, weak, dispatcher](auto const&, MessageWebSocketMessageReceivedEventArgs const& args) {
|
|
536
|
+
JsonObject out;
|
|
537
|
+
out.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"ws"));
|
|
538
|
+
out.SetNamedValue(L"id", JsonValue::CreateNumberValue(id));
|
|
539
|
+
out.SetNamedValue(L"ev", JsonValue::CreateStringValue(L"message"));
|
|
540
|
+
bool lost = false;
|
|
541
|
+
try {
|
|
542
|
+
auto reader = args.GetDataReader();
|
|
543
|
+
uint32_t n = reader.UnconsumedBufferLength();
|
|
544
|
+
if (args.MessageType() == SocketMessageType::Utf8) {
|
|
545
|
+
reader.UnicodeEncoding(Windows::Storage::Streams::UnicodeEncoding::Utf8);
|
|
546
|
+
out.SetNamedValue(L"text", JsonValue::CreateStringValue(n ? reader.ReadString(n) : hstring()));
|
|
547
|
+
} else {
|
|
548
|
+
out.SetNamedValue(L"data", JsonValue::CreateStringValue(Windows::Security::Cryptography::CryptographicBuffer::EncodeToBase64String(reader.ReadBuffer(n))));
|
|
549
|
+
}
|
|
550
|
+
} catch (...) { lost = true; }
|
|
551
|
+
auto text = out.Stringify();
|
|
552
|
+
dispatcher.RunAsync(CoreDispatcherPriority::Normal, [this, id, weak, lost, text] {
|
|
553
|
+
auto p = weak.lock();
|
|
554
|
+
if (!p || p->over) return;
|
|
555
|
+
if (lost) return end_pipe(id, p, 1006, true);
|
|
556
|
+
if (web && p->page == page && is_app_page(web.Source())) web.PostWebMessageAsJson(text);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
socket.Closed([this, id, weak, dispatcher](auto const&, WebSocketClosedEventArgs const& args) {
|
|
560
|
+
int code = args.Code();
|
|
561
|
+
dispatcher.RunAsync(CoreDispatcherPriority::Normal, [this, id, weak, code] {
|
|
562
|
+
if (auto p = weak.lock()) end_pipe(id, p, code ? code : 1006, true);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
co_await socket.ConnectAsync(url);
|
|
566
|
+
} catch (...) { failed = true; }
|
|
567
|
+
co_await resume_foreground(dispatcher);
|
|
568
|
+
if (p->over) co_return;
|
|
569
|
+
if (failed) co_return end_pipe(id, p, 1006, true);
|
|
570
|
+
p->writer = Windows::Storage::Streams::DataWriter(p->socket.OutputStream());
|
|
571
|
+
p->open = true;
|
|
572
|
+
tell(id, p->page, L"open", 0);
|
|
573
|
+
pump(id, p);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Sends what the page queued on a link, one frame at a time, telling the page as each has gone so its count of
|
|
577
|
+
// what's still to go (a file's flow) stays right.
|
|
578
|
+
fire_and_forget pump(double id, std::shared_ptr<Pipe> p) {
|
|
579
|
+
if (!p->open || p->writing) co_return;
|
|
580
|
+
auto lifetime = get_strong();
|
|
581
|
+
auto dispatcher = window.Dispatcher();
|
|
582
|
+
using namespace Windows::Networking::Sockets;
|
|
583
|
+
p->writing = true;
|
|
584
|
+
while (!p->over && !p->queue.empty()) {
|
|
585
|
+
auto [binary, payload] = p->queue.front();
|
|
586
|
+
p->queue.pop_front();
|
|
587
|
+
bool failed = false;
|
|
588
|
+
try {
|
|
589
|
+
p->socket.Control().MessageType(binary ? SocketMessageType::Binary : SocketMessageType::Utf8);
|
|
590
|
+
if (binary) p->writer.WriteBuffer(Windows::Security::Cryptography::CryptographicBuffer::DecodeFromBase64String(payload));
|
|
591
|
+
else p->writer.WriteString(payload);
|
|
592
|
+
co_await p->writer.StoreAsync();
|
|
593
|
+
} catch (...) { failed = true; }
|
|
594
|
+
co_await resume_foreground(dispatcher);
|
|
595
|
+
if (p->over) break;
|
|
596
|
+
if (failed) { end_pipe(id, p, 1006, true); break; }
|
|
597
|
+
tell(id, p->page, L"sent", 0);
|
|
598
|
+
}
|
|
599
|
+
p->writing = false;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Ends a link: closed by the page (nothing said back), or lost or closed by the other side (the page is told).
|
|
603
|
+
void end_pipe(double id, std::shared_ptr<Pipe> const& p, int code, bool say_so) {
|
|
604
|
+
if (p->over) return;
|
|
605
|
+
p->over = true;
|
|
606
|
+
auto found = pipes.find(id);
|
|
607
|
+
if (found != pipes.end() && found->second == p) pipes.erase(found);
|
|
608
|
+
if (auto local = p->local) { p->local = nullptr; local->lost(); }
|
|
609
|
+
try { if (p->socket) p->socket.Close(1000, L""); } catch (...) {}
|
|
610
|
+
p->socket = nullptr;
|
|
611
|
+
p->writer = nullptr;
|
|
612
|
+
p->queue.clear();
|
|
613
|
+
if (say_so) tell(id, p->page, L"close", code);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Every link ends: the page that opened them has gone (code 0, nothing said), or they may have (said).
|
|
617
|
+
void end_pipes(int code) {
|
|
618
|
+
auto all = pipes;
|
|
619
|
+
for (auto const& [id, p] : all) end_pipe(id, p, code, code != 0);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
void tell(double id, uint64_t asked, const wchar_t* ev, int code) {
|
|
623
|
+
if (!web || asked != page || !is_app_page(web.Source())) return;
|
|
624
|
+
JsonObject out;
|
|
625
|
+
out.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"ws"));
|
|
626
|
+
out.SetNamedValue(L"id", JsonValue::CreateNumberValue(id));
|
|
627
|
+
out.SetNamedValue(L"ev", JsonValue::CreateStringValue(ev));
|
|
628
|
+
if (code) out.SetNamedValue(L"code", JsonValue::CreateNumberValue(code));
|
|
629
|
+
web.PostWebMessageAsJson(out.Stringify());
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// This console's addresses on the home network, so a page that remembers nothing looks for the others on it.
|
|
633
|
+
void here() {
|
|
634
|
+
Windows::Data::Json::JsonArray list;
|
|
635
|
+
try {
|
|
636
|
+
for (auto const& name : Windows::Networking::Connectivity::NetworkInformation::GetHostNames())
|
|
637
|
+
if (name.Type() == Windows::Networking::HostNameType::Ipv4 && is_private_v4(name.CanonicalName()))
|
|
638
|
+
list.Append(JsonValue::CreateStringValue(name.CanonicalName()));
|
|
639
|
+
} catch (...) {}
|
|
640
|
+
if (!web || !is_app_page(web.Source())) return;
|
|
641
|
+
JsonObject out;
|
|
642
|
+
out.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"ws"));
|
|
643
|
+
out.SetNamedValue(L"ev", JsonValue::CreateStringValue(L"here"));
|
|
644
|
+
out.SetNamedValue(L"addresses", list);
|
|
645
|
+
web.PostWebMessageAsJson(out.Stringify());
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// MARK: Phones as controllers
|
|
649
|
+
|
|
650
|
+
// The page's own link to the relay, over the pipe: what the relay says goes to the page as the link's messages.
|
|
651
|
+
struct PagePeer : lampc::Peer {
|
|
652
|
+
View* view = nullptr;
|
|
653
|
+
double id = 0;
|
|
654
|
+
uint64_t page = 0;
|
|
655
|
+
bool open = true;
|
|
656
|
+
bool is_open() const override { return open; }
|
|
657
|
+
size_t pending_text() const override { return 0; }
|
|
658
|
+
void send_text(std::string const& text) override { if (open) view->tell_link(id, page, &text, nullptr); }
|
|
659
|
+
bool send_piece(std::string piece) override {
|
|
660
|
+
if (!open) return false;
|
|
661
|
+
view->tell_link(id, page, nullptr, &piece);
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
void close(int code) override {
|
|
665
|
+
if (!open) return;
|
|
666
|
+
open = false;
|
|
667
|
+
view->end_local(id, code);
|
|
668
|
+
on_close();
|
|
669
|
+
}
|
|
670
|
+
// The page closed it, or went away.
|
|
671
|
+
void lost() {
|
|
672
|
+
if (!open) return;
|
|
673
|
+
open = false;
|
|
674
|
+
on_close();
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// A connection to the page or the relay: its request's head until it's answered, then the WebSocket it became.
|
|
679
|
+
struct Conn {
|
|
680
|
+
Windows::Networking::Sockets::StreamSocket socket{ nullptr };
|
|
681
|
+
Windows::Storage::Streams::DataWriter writer{ nullptr };
|
|
682
|
+
std::string from, buffer;
|
|
683
|
+
int64_t since = lampc::wall_ms();
|
|
684
|
+
bool relay = false, answered = false;
|
|
685
|
+
std::shared_ptr<lampc::SocketPeer> peer;
|
|
686
|
+
};
|
|
687
|
+
// How long an answered page request may take to go: a phone on a slow network gets the page, one that stops reading
|
|
688
|
+
// doesn't keep its connection (and the file held for it) for good.
|
|
689
|
+
static constexpr int64_t phone_send_deadline_ms = 60000;
|
|
690
|
+
|
|
691
|
+
struct PhoneAsk {
|
|
692
|
+
bool stop = false;
|
|
693
|
+
double id = 0;
|
|
694
|
+
uint64_t page = 0;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// All of it touched only on the window's thread. A session is the key, the relay and both listeners; its number
|
|
698
|
+
// changes as it closes, so what an earlier one had started is dropped.
|
|
699
|
+
std::unique_ptr<lampc::Relay> phones;
|
|
700
|
+
std::string phone_key, phone_app;
|
|
701
|
+
std::set<std::string> phone_hosts;
|
|
702
|
+
Windows::Networking::Sockets::StreamSocketListener relay_listener{ nullptr }, page_listener{ nullptr };
|
|
703
|
+
std::optional<uint16_t> relay_port, page_port;
|
|
704
|
+
std::vector<std::shared_ptr<Conn>> conns;
|
|
705
|
+
Windows::System::Threading::ThreadPoolTimer phone_ticks{ nullptr };
|
|
706
|
+
uint64_t phone_session = 0;
|
|
707
|
+
// The page's asks, one at a time, so a stop and the start after it happen in the order they were asked.
|
|
708
|
+
std::deque<PhoneAsk> phone_asks;
|
|
709
|
+
bool phone_busy = false;
|
|
710
|
+
// When the page last asked for its session (every 15 s while it plays): a session no page asks for any more is
|
|
711
|
+
// ended, even if it never had a host, so a page gone without stopping leaves nothing listening.
|
|
712
|
+
int64_t phone_asked_at = 0;
|
|
713
|
+
static constexpr int64_t phone_unasked_ms = 45000;
|
|
714
|
+
|
|
715
|
+
void phone_ask(PhoneAsk ask) {
|
|
716
|
+
phone_asks.push_back(ask);
|
|
717
|
+
if (!phone_busy) phone_run();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
fire_and_forget phone_run() {
|
|
721
|
+
auto lifetime = get_strong();
|
|
722
|
+
phone_busy = true;
|
|
723
|
+
while (!phone_asks.empty()) {
|
|
724
|
+
auto ask = phone_asks.front();
|
|
725
|
+
phone_asks.pop_front();
|
|
726
|
+
// An ask from a page that has gone since opens nothing.
|
|
727
|
+
if (ask.page != page) continue;
|
|
728
|
+
if (ask.stop) {
|
|
729
|
+
phones_close(L"the game stopped its controllers");
|
|
730
|
+
answer_phones(ask, 200, JsonObject());
|
|
731
|
+
} else {
|
|
732
|
+
co_await phones_open(ask);
|
|
733
|
+
co_await resume_foreground(window.Dispatcher());
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
phone_busy = false;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
void answer_phones(PhoneAsk const& ask, int status, JsonObject const& body) {
|
|
740
|
+
if (!web || ask.page != page || !is_app_page(web.Source())) return;
|
|
741
|
+
JsonObject out;
|
|
742
|
+
out.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"controllers"));
|
|
743
|
+
out.SetNamedValue(L"id", JsonValue::CreateNumberValue(ask.id));
|
|
744
|
+
out.SetNamedValue(L"status", JsonValue::CreateNumberValue(status));
|
|
745
|
+
out.SetNamedValue(L"body", body);
|
|
746
|
+
web.PostWebMessageAsJson(out.Stringify());
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
void refuse_phones(PhoneAsk const& ask, int status, const wchar_t* error) {
|
|
750
|
+
JsonObject body;
|
|
751
|
+
body.SetNamedValue(L"error", JsonValue::CreateStringValue(error));
|
|
752
|
+
answer_phones(ask, status, body);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// The game's session, opened if it isn't: where its page's link goes, with a host's credential. Asked as
|
|
756
|
+
// Lamp.controllers starts and every few seconds after, which also tells the relay where the page is now.
|
|
757
|
+
Windows::Foundation::IAsyncAction phones_open(PhoneAsk ask) {
|
|
758
|
+
auto lifetime = get_strong();
|
|
759
|
+
auto dispatcher = window.Dispatcher();
|
|
760
|
+
WIN32_FILE_ATTRIBUTE_DATA index{};
|
|
761
|
+
if (!GetFileAttributesExW((installed + L"\\Phone\\index.html").c_str(), GetFileExInfoStandard, &index)) {
|
|
762
|
+
refuse_phones(ask, 409, L"The game has no phone page yet: put one in Phone/index.html.");
|
|
763
|
+
co_return;
|
|
764
|
+
}
|
|
765
|
+
// Never a link to the console itself: a phone opening it would reach itself.
|
|
766
|
+
auto link = link_host();
|
|
767
|
+
if (!link) {
|
|
768
|
+
phones_close(L"the console isn't on a network");
|
|
769
|
+
refuse_phones(ask, 503, L"This console isn't on a network phones can join. Join one, and phones can play again.");
|
|
770
|
+
co_return;
|
|
771
|
+
}
|
|
772
|
+
uint64_t session = phone_session;
|
|
773
|
+
phone_asked_at = lampc::wall_ms();
|
|
774
|
+
if (!phones) {
|
|
775
|
+
if (phone_app.empty()) phone_app = to_string(Windows::ApplicationModel::Package::Current().Id().Name());
|
|
776
|
+
phone_key = lampc::random_text(32);
|
|
777
|
+
phones = std::make_unique<lampc::Relay>(phone_app, phone_key);
|
|
778
|
+
say_line(L"Lamp: phones' session opened.");
|
|
779
|
+
}
|
|
780
|
+
for (bool relay : { true, false }) {
|
|
781
|
+
auto& listener = relay ? relay_listener : page_listener;
|
|
782
|
+
if (listener) continue;
|
|
783
|
+
Windows::Networking::Sockets::StreamSocketListener made;
|
|
784
|
+
made.ConnectionReceived([this, relay, session, dispatcher](auto const&, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs const& args) {
|
|
785
|
+
auto socket = args.Socket();
|
|
786
|
+
dispatcher.RunAsync(CoreDispatcherPriority::Normal, [this, socket, relay, session] { phone_connected(socket, relay, session); });
|
|
787
|
+
});
|
|
788
|
+
bool bound = true;
|
|
789
|
+
try { co_await made.BindServiceNameAsync(L""); } catch (hresult_error const& e) {
|
|
790
|
+
say(L"Lamp: the %ls for phones couldn't listen (0x%08x).", relay ? L"relay" : L"page", (unsigned)e.code());
|
|
791
|
+
bound = false;
|
|
792
|
+
}
|
|
793
|
+
co_await resume_foreground(dispatcher);
|
|
794
|
+
if (session != phone_session || !bound) {
|
|
795
|
+
try { made.Close(); } catch (...) {}
|
|
796
|
+
if (session == phone_session) phones_close(L"a listener couldn't open");
|
|
797
|
+
refuse_phones(ask, 503, relay ? L"The link for phones couldn't open on this console." : L"The phone page couldn't open on this console.");
|
|
798
|
+
co_return;
|
|
799
|
+
}
|
|
800
|
+
uint16_t port = (uint16_t)_wtoi(made.Information().LocalPort().c_str());
|
|
801
|
+
(relay ? relay_port : page_port) = port;
|
|
802
|
+
listener = made;
|
|
803
|
+
say(L"Lamp: the %ls for phones listens at port %u.", relay ? L"relay" : L"page", (unsigned)port);
|
|
804
|
+
}
|
|
805
|
+
if (!phone_ticks) {
|
|
806
|
+
phone_ticks = Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer([this, session, dispatcher](auto const&) {
|
|
807
|
+
dispatcher.RunAsync(CoreDispatcherPriority::Normal, [this, session] { phone_tick(session); });
|
|
808
|
+
}, std::chrono::seconds(1));
|
|
809
|
+
}
|
|
810
|
+
phone_hosts = host_names();
|
|
811
|
+
std::string address = "http://" + to_string(*link) + ":" + std::to_string(*page_port) + "/";
|
|
812
|
+
phones->configure(address, page_port, phone_hosts);
|
|
813
|
+
JsonObject relay;
|
|
814
|
+
relay.SetNamedValue(L"kind", JsonValue::CreateStringValue(L"relay"));
|
|
815
|
+
relay.SetNamedValue(L"socket", JsonValue::CreateStringValue(g_relay_link));
|
|
816
|
+
relay.SetNamedValue(L"app", JsonValue::CreateStringValue(to_hstring(phone_app)));
|
|
817
|
+
relay.SetNamedValue(L"credential", JsonValue::CreateStringValue(to_hstring(phones->host_credential())));
|
|
818
|
+
Windows::Data::Json::JsonArray links;
|
|
819
|
+
links.Append(relay);
|
|
820
|
+
JsonObject body;
|
|
821
|
+
body.SetNamedValue(L"links", links);
|
|
822
|
+
body.SetNamedValue(L"renew", JsonValue::CreateNumberValue(15));
|
|
823
|
+
answer_phones(ask, 200, body);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Ends the session: every phone and the page's link let go, both listeners closed and the key forgotten.
|
|
827
|
+
void phones_close(const wchar_t* why) {
|
|
828
|
+
bool was = phones != nullptr;
|
|
829
|
+
phone_session++;
|
|
830
|
+
if (phone_ticks) { phone_ticks.Cancel(); phone_ticks = nullptr; }
|
|
831
|
+
if (phones) { phones->stop(); phones.reset(); }
|
|
832
|
+
for (auto* listener : { &relay_listener, &page_listener }) {
|
|
833
|
+
if (*listener) { try { listener->Close(); } catch (...) {} *listener = nullptr; }
|
|
834
|
+
}
|
|
835
|
+
relay_port.reset();
|
|
836
|
+
page_port.reset();
|
|
837
|
+
std::fill(phone_key.begin(), phone_key.end(), '\0');
|
|
838
|
+
phone_key.clear();
|
|
839
|
+
// A phone's socket saying why it closes gets a moment for it to go; everything else goes now.
|
|
840
|
+
auto left = std::move(conns);
|
|
841
|
+
conns.clear();
|
|
842
|
+
std::vector<Windows::Networking::Sockets::StreamSocket> closing;
|
|
843
|
+
for (auto& conn : left) {
|
|
844
|
+
if (conn->peer && conn->peer->transport) closing.push_back(conn->socket);
|
|
845
|
+
else try { conn->socket.Close(); } catch (...) {}
|
|
846
|
+
}
|
|
847
|
+
if (!closing.empty()) Windows::System::Threading::ThreadPoolTimer::CreateTimer([closing](auto const&) {
|
|
848
|
+
for (auto const& socket : closing) try { socket.Close(); } catch (...) {}
|
|
849
|
+
}, std::chrono::seconds(2));
|
|
850
|
+
if (was) say(L"Lamp: phones' session ended: %ls.", why);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
void phone_tick(uint64_t session) {
|
|
854
|
+
if (session != phone_session || !phones) return;
|
|
855
|
+
int64_t now = lampc::wall_ms();
|
|
856
|
+
if (now - phone_asked_at > phone_unasked_ms) return phones_close(L"the page stopped asking for it");
|
|
857
|
+
phones->tick();
|
|
858
|
+
for (auto const& conn : std::vector<std::shared_ptr<Conn>>(conns)) {
|
|
859
|
+
if (conn->peer) conn->peer->tick(now);
|
|
860
|
+
// A request never finished, or an answer never taken, is let go before it can use up what the game needs for phones.
|
|
861
|
+
else if (now - conn->since > (conn->answered ? phone_send_deadline_ms : lampc::page::head_deadline_ms)) { try { conn->socket.Close(); } catch (...) {} }
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
void phone_connected(Windows::Networking::Sockets::StreamSocket const& socket, bool relay, uint64_t session) {
|
|
866
|
+
if (session != phone_session || !phones) { try { socket.Close(); } catch (...) {} return; }
|
|
867
|
+
auto conn = std::make_shared<Conn>();
|
|
868
|
+
conn->socket = socket;
|
|
869
|
+
conn->writer = Windows::Storage::Streams::DataWriter(socket.OutputStream());
|
|
870
|
+
conn->from = remote_of(socket);
|
|
871
|
+
conn->relay = relay;
|
|
872
|
+
size_t mine = std::count_if(conns.begin(), conns.end(), [&](auto const& c) { return c->from == conn->from; });
|
|
873
|
+
if (conns.size() >= lampc::page::connection_limit_all || mine >= lampc::page::connection_limit_each) { try { socket.Close(); } catch (...) {} return; }
|
|
874
|
+
conns.push_back(conn);
|
|
875
|
+
phone_read(conn, session);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
fire_and_forget phone_read(std::shared_ptr<Conn> conn, uint64_t session) {
|
|
879
|
+
auto lifetime = get_strong();
|
|
880
|
+
auto dispatcher = window.Dispatcher();
|
|
881
|
+
Windows::Storage::Streams::Buffer buffer(16384);
|
|
882
|
+
try {
|
|
883
|
+
for (;;) {
|
|
884
|
+
auto got = co_await conn->socket.InputStream().ReadAsync(buffer, buffer.Capacity(), Windows::Storage::Streams::InputStreamOptions::Partial);
|
|
885
|
+
co_await resume_foreground(dispatcher);
|
|
886
|
+
if (got.Length() == 0) break;
|
|
887
|
+
phone_heard(conn, std::string_view(reinterpret_cast<const char*>(got.data()), got.Length()), session);
|
|
888
|
+
}
|
|
889
|
+
} catch (...) {}
|
|
890
|
+
co_await resume_foreground(dispatcher);
|
|
891
|
+
conns.erase(std::remove(conns.begin(), conns.end(), conn), conns.end());
|
|
892
|
+
if (conn->peer) conn->peer->lost();
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
void phone_write_then_close(std::shared_ptr<Conn> const& conn, std::string bytes) {
|
|
896
|
+
StreamTransport::send(conn->writer, window.Dispatcher(), std::move(bytes), [conn](bool) { try { conn->socket.Close(); } catch (...) {} });
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
void phone_heard(std::shared_ptr<Conn> const& conn, std::string_view bytes, uint64_t session) {
|
|
900
|
+
if (conn->peer) return conn->peer->received(bytes);
|
|
901
|
+
if (conn->answered) return;
|
|
902
|
+
conn->buffer.append(bytes.data(), bytes.size());
|
|
903
|
+
auto end = conn->buffer.find("\r\n\r\n");
|
|
904
|
+
if (end == std::string::npos) {
|
|
905
|
+
if (conn->buffer.size() > lampc::page::head_limit) { try { conn->socket.Close(); } catch (...) {} }
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
std::string head = conn->buffer.substr(0, end), rest = conn->buffer.substr(end + 4);
|
|
909
|
+
conn->buffer.clear();
|
|
910
|
+
conn->answered = true;
|
|
911
|
+
if (session != phone_session || !phones) { try { conn->socket.Close(); } catch (...) {} return; }
|
|
912
|
+
if (!conn->relay) {
|
|
913
|
+
auto answer = lampc::page::answer(head, phone_hosts, relay_port, phone_app, [this](auto const& steps) { return phone_file(steps); },
|
|
914
|
+
[this](auto const& steps) { return phone_exists(steps); });
|
|
915
|
+
return phone_write_then_close(conn, lampc::page::response(answer));
|
|
916
|
+
}
|
|
917
|
+
auto upgrade = lampc::ws::upgrade(head);
|
|
918
|
+
if (!upgrade) return phone_write_then_close(conn, lampc::ws::refused(400));
|
|
919
|
+
if (!phones->admits(upgrade->origin)) return phone_write_then_close(conn, lampc::ws::refused(403));
|
|
920
|
+
auto peer = std::make_shared<lampc::SocketPeer>(std::make_unique<StreamTransport>(conn->socket, conn->writer, window.Dispatcher()));
|
|
921
|
+
if (!phones->accept(peer, conn->from, false)) { try { conn->socket.Close(); } catch (...) {} return; }
|
|
922
|
+
conn->peer = peer;
|
|
923
|
+
peer->queue_text(lampc::ws::accepted(upgrade->key));
|
|
924
|
+
if (!rest.empty()) peer->received(rest);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// A file of the phone page in the package, by its checked steps: a plain file, reached through no link.
|
|
928
|
+
std::optional<std::string> phone_file(std::vector<std::string> const& steps) {
|
|
929
|
+
std::wstring path = installed + L"\\Phone";
|
|
930
|
+
WIN32_FILE_ATTRIBUTE_DATA data{};
|
|
931
|
+
for (auto const& step : steps) {
|
|
932
|
+
path += L"\\" + std::wstring(to_hstring(step));
|
|
933
|
+
if (!GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data)
|
|
934
|
+
|| (data.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) return std::nullopt;
|
|
935
|
+
}
|
|
936
|
+
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) return std::nullopt;
|
|
937
|
+
uint64_t size = (uint64_t)data.nFileSizeHigh << 32 | data.nFileSizeLow;
|
|
938
|
+
if (size > lampc::page::file_limit) return std::nullopt;
|
|
939
|
+
FILE* file = _wfopen(path.c_str(), L"rb");
|
|
940
|
+
if (!file) return std::nullopt;
|
|
941
|
+
std::string out((size_t)size, '\0');
|
|
942
|
+
size_t read = size ? fread(out.data(), 1, out.size(), file) : 0;
|
|
943
|
+
fclose(file);
|
|
944
|
+
if (read != out.size()) return std::nullopt;
|
|
945
|
+
return out;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Whether anything is at the steps in the package, readable or not: only a name that isn't there at all is absent, so
|
|
949
|
+
// a reach file that can't be read holds the page to no sites.
|
|
950
|
+
bool phone_exists(std::vector<std::string> const& steps) {
|
|
951
|
+
std::wstring path = installed + L"\\Phone";
|
|
952
|
+
for (auto const& step : steps) path += L"\\" + std::wstring(to_hstring(step));
|
|
953
|
+
if (GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES) return true;
|
|
954
|
+
DWORD error = GetLastError();
|
|
955
|
+
return error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// The page opening its link to the relay: the link goes into the relay here, as the one peer that may be the game's host.
|
|
959
|
+
void open_local(double id) {
|
|
960
|
+
if (pipes.count(id) || pipes.size() >= 64 || !phones) return tell(id, page, L"close", 1006);
|
|
961
|
+
auto p = std::make_shared<Pipe>();
|
|
962
|
+
p->page = page;
|
|
963
|
+
p->open = true;
|
|
964
|
+
auto peer = std::make_shared<PagePeer>();
|
|
965
|
+
peer->view = this;
|
|
966
|
+
peer->id = id;
|
|
967
|
+
peer->page = page;
|
|
968
|
+
p->local = peer;
|
|
969
|
+
pipes[id] = p;
|
|
970
|
+
if (!phones->accept(peer, "page", true)) {
|
|
971
|
+
pipes.erase(id);
|
|
972
|
+
return tell(id, page, L"close", 1006);
|
|
973
|
+
}
|
|
974
|
+
tell(id, page, L"open", 0);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// What the page sent on its link to the relay.
|
|
978
|
+
void to_local(double id, std::shared_ptr<Pipe> const& p, bool binary, hstring const& payload) {
|
|
979
|
+
auto peer = p->local;
|
|
980
|
+
if (!peer->open) return;
|
|
981
|
+
peer->heard = lampc::wall_ms();
|
|
982
|
+
if (binary) {
|
|
983
|
+
std::string bytes;
|
|
984
|
+
try {
|
|
985
|
+
auto decoded = Windows::Security::Cryptography::CryptographicBuffer::DecodeFromBase64String(payload);
|
|
986
|
+
bytes.assign(reinterpret_cast<const char*>(decoded.data()), decoded.Length());
|
|
987
|
+
} catch (...) { return end_pipe(id, p, 1007, true); }
|
|
988
|
+
peer->on_binary(bytes);
|
|
989
|
+
} else {
|
|
990
|
+
std::string text = to_string(payload);
|
|
991
|
+
if (text.size() > lampc::wire::message) return peer->close(1009);
|
|
992
|
+
peer->on_text(text);
|
|
993
|
+
}
|
|
994
|
+
tell(id, p->page, L"sent", 0);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// The relay closing the page's link: the page is told, with the relay's reason.
|
|
998
|
+
void end_local(double id, int code) {
|
|
999
|
+
auto found = pipes.find(id);
|
|
1000
|
+
if (found == pipes.end() || !found->second->local) return;
|
|
1001
|
+
auto p = found->second;
|
|
1002
|
+
p->local = nullptr;
|
|
1003
|
+
end_pipe(id, p, code, true);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
void tell_link(double id, uint64_t asked, std::string const* text, std::string const* piece) {
|
|
1007
|
+
if (!web || asked != page || !is_app_page(web.Source())) return;
|
|
1008
|
+
JsonObject out;
|
|
1009
|
+
out.SetNamedValue(L"lamp", JsonValue::CreateStringValue(L"ws"));
|
|
1010
|
+
out.SetNamedValue(L"id", JsonValue::CreateNumberValue(id));
|
|
1011
|
+
out.SetNamedValue(L"ev", JsonValue::CreateStringValue(L"message"));
|
|
1012
|
+
if (text) out.SetNamedValue(L"text", JsonValue::CreateStringValue(to_hstring(*text)));
|
|
1013
|
+
else out.SetNamedValue(L"data", JsonValue::CreateStringValue(to_hstring(lampc::base64(*piece, false))));
|
|
1014
|
+
web.PostWebMessageAsJson(out.Stringify());
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// How far a controller's reading is from what the page showed on its gamepad, in the standard mapping: the sum of
|
|
1018
|
+
// the differences of its buttons and sticks.
|
|
1019
|
+
static double distance(Windows::Gaming::Input::GamepadReading const& r, JsonObject const& m) {
|
|
1020
|
+
using Windows::Gaming::Input::GamepadButtons;
|
|
1021
|
+
static const GamepadButtons order[] = { GamepadButtons::A, GamepadButtons::B, GamepadButtons::X, GamepadButtons::Y,
|
|
1022
|
+
GamepadButtons::LeftShoulder, GamepadButtons::RightShoulder, GamepadButtons::None, GamepadButtons::None,
|
|
1023
|
+
GamepadButtons::View, GamepadButtons::Menu, GamepadButtons::LeftThumbstick, GamepadButtons::RightThumbstick,
|
|
1024
|
+
GamepadButtons::DPadUp, GamepadButtons::DPadDown, GamepadButtons::DPadLeft, GamepadButtons::DPadRight };
|
|
1025
|
+
auto buttons = m.GetNamedArray(L"buttons", Windows::Data::Json::JsonArray());
|
|
1026
|
+
auto axes = m.GetNamedArray(L"axes", Windows::Data::Json::JsonArray());
|
|
1027
|
+
double sum = 0;
|
|
1028
|
+
for (uint32_t i = 0; i < buttons.Size() && i < 16; i++) {
|
|
1029
|
+
double mine = i == 6 ? r.LeftTrigger : i == 7 ? r.RightTrigger : (r.Buttons & order[i]) == order[i] ? 1.0 : 0.0;
|
|
1030
|
+
sum += std::abs(buttons.GetNumberAt(i) - mine);
|
|
1031
|
+
}
|
|
1032
|
+
// The page's sticks point down where the controller's point up.
|
|
1033
|
+
const double sticks[] = { r.LeftThumbstickX, -r.LeftThumbstickY, r.RightThumbstickX, -r.RightThumbstickY };
|
|
1034
|
+
for (uint32_t i = 0; i < axes.Size() && i < 4; i++) sum += std::abs(axes.GetNumberAt(i) - sticks[i]);
|
|
1035
|
+
return sum;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// The controller the page knows by this index. The page's web view numbers controllers in its own order, so the one
|
|
1039
|
+
// whose reading matches what the page showed is it, kept for later effects asked while nothing tells two apart (no
|
|
1040
|
+
// button held, sticks at rest). Until one is found: the only controller, or the same place in the console's list.
|
|
1041
|
+
Windows::Gaming::Input::Gamepad pad(int index, JsonObject const& m) {
|
|
1042
|
+
auto list = Windows::Gaming::Input::Gamepad::Gamepads();
|
|
1043
|
+
auto& known = rumbles[index].pad;
|
|
1044
|
+
bool there = false;
|
|
1045
|
+
for (auto const& p : list) there = there || p == known;
|
|
1046
|
+
if (!there) known = nullptr;
|
|
1047
|
+
if (list.Size() > 1) {
|
|
1048
|
+
double best = 1e9, next = 1e9;
|
|
1049
|
+
Windows::Gaming::Input::Gamepad found{ nullptr };
|
|
1050
|
+
for (auto const& p : list) {
|
|
1051
|
+
double d = distance(p.GetCurrentReading(), m);
|
|
1052
|
+
if (d < best) { next = best; best = d; found = p; } else if (d < next) next = d;
|
|
1053
|
+
}
|
|
1054
|
+
if (found && best < 0.5 && next - best > 0.5) {
|
|
1055
|
+
uint32_t at = 0;
|
|
1056
|
+
if (found != known && list.IndexOf(found, at)) say(L"Lamp: the page's controller %d is the console's %u.", index, at);
|
|
1057
|
+
known = found;
|
|
1058
|
+
// Another of the page's gamepads that was taken for this controller ends what it played there first, so its
|
|
1059
|
+
// timers neither stop this effect early nor are left with no controller to still.
|
|
1060
|
+
for (int i = 0; i < 4; i++) if (i != index && rumbles[i].pad == found) { still(i); rumbles[i].pad = nullptr; }
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (known) return known;
|
|
1064
|
+
if (list.Size() == 1) return list.GetAt(0);
|
|
1065
|
+
return (uint32_t)index < list.Size() ? list.GetAt(index) : nullptr;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Ends what's playing for the page's controller, now.
|
|
1069
|
+
void still(int index) {
|
|
1070
|
+
auto& r = rumbles[index];
|
|
1071
|
+
if (r.start) r.start.Cancel();
|
|
1072
|
+
if (r.stop) r.stop.Cancel();
|
|
1073
|
+
std::lock_guard held(shaking);
|
|
1074
|
+
r.effect++;
|
|
1075
|
+
if (r.pad) r.pad.Vibration({});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// One of the page's effects: its motors and triggers at the strengths asked, after its delay, for its duration.
|
|
1079
|
+
void rumble(JsonObject const& m) {
|
|
1080
|
+
int index = (int)m.GetNamedNumber(L"pad", 0);
|
|
1081
|
+
if (index < 0 || index > 3) return;
|
|
1082
|
+
auto amount = [&](const wchar_t* key) { return (double)std::clamp(m.GetNamedNumber(key, 0), 0.0, 1.0); };
|
|
1083
|
+
auto ms = [&](const wchar_t* key) { return std::chrono::milliseconds((int64_t)std::clamp(m.GetNamedNumber(key, 0), 0.0, 5000.0)); };
|
|
1084
|
+
Windows::Gaming::Input::GamepadVibration shake{};
|
|
1085
|
+
shake.LeftMotor = amount(L"strong");
|
|
1086
|
+
shake.RightMotor = amount(L"weak");
|
|
1087
|
+
shake.LeftTrigger = amount(L"leftTrigger");
|
|
1088
|
+
shake.RightTrigger = amount(L"rightTrigger");
|
|
1089
|
+
auto delay = ms(L"delay"), duration = ms(L"duration");
|
|
1090
|
+
still(index);
|
|
1091
|
+
auto& r = rumbles[index];
|
|
1092
|
+
auto target = pad(index, m);
|
|
1093
|
+
// Kept as the one playing even before it's matched, so a reset stops what was started on it.
|
|
1094
|
+
if (!r.pad) r.pad = target;
|
|
1095
|
+
if (duration.count() == 0) return;
|
|
1096
|
+
if (!target) { say(L"Lamp: the page asked to shake controller %d, and none is connected.", index); return; }
|
|
1097
|
+
uint64_t effect = r.effect;
|
|
1098
|
+
using Windows::System::Threading::ThreadPoolTimer;
|
|
1099
|
+
// The timers' work is skipped once another effect has taken this one's place.
|
|
1100
|
+
auto set = [this, index, effect, target](Windows::Gaming::Input::GamepadVibration v) {
|
|
1101
|
+
std::lock_guard held(shaking);
|
|
1102
|
+
if (rumbles[index].effect == effect) target.Vibration(v);
|
|
1103
|
+
};
|
|
1104
|
+
if (delay.count() == 0) set(shake);
|
|
1105
|
+
else r.start = ThreadPoolTimer::CreateTimer([set, shake](auto const&) { set(shake); }, delay);
|
|
1106
|
+
r.stop = ThreadPoolTimer::CreateTimer([set](auto const&) { set({}); }, delay + duration);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// A link out of the app, in the console's own browser.
|
|
1110
|
+
fire_and_forget elsewhere(std::wstring uri) {
|
|
1111
|
+
try {
|
|
1112
|
+
Windows::Foundation::Uri target(uri);
|
|
1113
|
+
if (target.SchemeName() != L"https" && target.SchemeName() != L"http") co_return;
|
|
1114
|
+
co_await Windows::System::Launcher::LaunchUriAsync(target);
|
|
1115
|
+
} catch (...) {}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Opens a blank page first to learn the CSS width the web view gives the window, then zooms so the app's page
|
|
1119
|
+
// is 1920 CSS pixels wide, before the app's page opens.
|
|
1120
|
+
Windows::Foundation::IAsyncAction fit_page() {
|
|
1121
|
+
auto lifetime = get_strong();
|
|
1122
|
+
try {
|
|
1123
|
+
winrt::handle opened{ CreateEventW(nullptr, TRUE, FALSE, nullptr) };
|
|
1124
|
+
auto token = web.NavigationCompleted([&](auto const&, auto const&) { SetEvent(opened.get()); });
|
|
1125
|
+
web.Navigate(L"about:blank");
|
|
1126
|
+
co_await resume_on_signal(opened.get(), std::chrono::seconds(5));
|
|
1127
|
+
co_await resume_foreground(window.Dispatcher());
|
|
1128
|
+
web.NavigationCompleted(token);
|
|
1129
|
+
auto width = co_await web.ExecuteScriptAsync(L"window.innerWidth");
|
|
1130
|
+
double css = _wtof(width.c_str());
|
|
1131
|
+
if (css > 0) {
|
|
1132
|
+
double zoom = controller.ZoomFactor() * css / 1920.0;
|
|
1133
|
+
controller.ZoomFactor(zoom);
|
|
1134
|
+
say(L"Lamp: the page is 1920 CSS pixels wide (the web view gave %.0f, zoomed to %.3f).", css, zoom);
|
|
1135
|
+
}
|
|
1136
|
+
} catch (hresult_error const& e) {
|
|
1137
|
+
say(L"Lamp: the page's size couldn't be set (0x%08x).", (unsigned)e.code());
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
void Run() {
|
|
1142
|
+
window.Activate();
|
|
1143
|
+
start();
|
|
1144
|
+
window.Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// Anything that stops the host before or outside WinRT's own error handling goes to the log too.
|
|
1149
|
+
static LONG WINAPI last_words(EXCEPTION_POINTERS* info) {
|
|
1150
|
+
say(L"Lamp: the app stopped on exception 0x%08x.", (unsigned)info->ExceptionRecord->ExceptionCode);
|
|
1151
|
+
return EXCEPTION_CONTINUE_SEARCH;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
|
|
1155
|
+
init_apartment();
|
|
1156
|
+
try {
|
|
1157
|
+
std::wstring local(Windows::Storage::ApplicationData::Current().LocalFolder().Path());
|
|
1158
|
+
g_log = _wfopen((local + L"\\log.txt").c_str(), L"wb");
|
|
1159
|
+
} catch (...) {}
|
|
1160
|
+
say_line(L"Lamp: the app started.");
|
|
1161
|
+
// An Xbox gives a package installed as a game about 5 GB and one installed as an app 1 GB: which it got.
|
|
1162
|
+
try {
|
|
1163
|
+
say(L"Lamp: the console lets the app use %llu MB of memory.",
|
|
1164
|
+
(unsigned long long)(Windows::System::MemoryManager::AppMemoryUsageLimit() >> 20));
|
|
1165
|
+
} catch (...) {}
|
|
1166
|
+
SetUnhandledExceptionFilter(last_words);
|
|
1167
|
+
try {
|
|
1168
|
+
CoreApplication::Run(make<View>());
|
|
1169
|
+
} catch (hresult_error const& e) {
|
|
1170
|
+
say(L"Lamp: the app couldn't run (0x%08x): %ls", (unsigned)e.code(), e.message().c_str());
|
|
1171
|
+
}
|
|
1172
|
+
return 0;
|
|
1173
|
+
}
|