@lampdotdev/lamp 0.0.0-stage → 0.35.0
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 +357 -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 +1646 -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/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 +3036 -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/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 +414 -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 +59 -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 +136 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +103 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/android.mjs +941 -0
- package/cli/browser.mjs +268 -0
- package/cli/calls.mjs +390 -0
- package/cli/dev.mjs +773 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1016 -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 +6863 -0
- package/cli/lamps/lamp.js +7023 -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 +18097 -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 +7023 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +138 -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/outside.mjs +140 -0
- package/cli/page.js +165 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +202 -0
- package/cli/skill/SKILL.md +255 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +173 -0
- package/cli/update.mjs +57 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +996 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// Written by Lamp before every build; don't edit. The page's LampAI (Web/lamp.js), answered through this app.
|
|
2
|
+
package {{PACKAGE}}
|
|
3
|
+
|
|
4
|
+
import android.webkit.WebView
|
|
5
|
+
import androidx.webkit.WebViewCompat
|
|
6
|
+
import androidx.webkit.WebViewFeature
|
|
7
|
+
import java.io.File
|
|
8
|
+
import java.net.InetSocketAddress
|
|
9
|
+
import java.net.Socket
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
import kotlin.concurrent.thread
|
|
12
|
+
|
|
13
|
+
object LampPageGateway {
|
|
14
|
+
private val routes = setOf("/ai", "/ai/status", "/ai/image", "/ai/decide", "/ai/act", "/ai/hear")
|
|
15
|
+
|
|
16
|
+
/** Answers the page at `origin`, the page's own, in its main frame: no other page or frame reaches Lamp. */
|
|
17
|
+
fun attach(web: WebView, origin: String) {
|
|
18
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) return
|
|
19
|
+
val files = web.context.filesDir
|
|
20
|
+
WebViewCompat.addWebMessageListener(web, "lampGatewayHost", setOf(origin)) { _, message, _, isMainFrame, reply ->
|
|
21
|
+
val call = try { JSONObject(message.data ?: "") } catch (e: Exception) { null }
|
|
22
|
+
if (!isMainFrame || call == null) return@addWebMessageListener
|
|
23
|
+
val id = call.optInt("id")
|
|
24
|
+
val path = call.optString("path")
|
|
25
|
+
val body = call.optString("body", "{}")
|
|
26
|
+
// The page's own wait for an answer (a status check's few seconds, a question's minutes).
|
|
27
|
+
val timeout = call.optInt("timeout", 240_000).coerceIn(1_000, 240_000)
|
|
28
|
+
thread {
|
|
29
|
+
val (status, text) = if (path in routes) post(files, path, body, timeout) else 404 to JSONObject().put("error", "Not found").toString()
|
|
30
|
+
val answer = JSONObject().put("id", id).put("status", status).put("body", text).toString()
|
|
31
|
+
web.post { reply.postMessage(answer) }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private fun failure(message: String) = 503 to JSONObject().put("error", message).toString()
|
|
37
|
+
|
|
38
|
+
/** Plain HTTP on the Mac's own network, on a socket, as the Kotlin building blocks speak it: each way to the Mac
|
|
39
|
+
* Lamp wrote down in turn, and all of them once more after a moment, in case Lamp is just starting. */
|
|
40
|
+
private fun post(files: File, path: String, body: String, timeout: Int): Pair<Int, String> {
|
|
41
|
+
val deadline = System.currentTimeMillis() + timeout
|
|
42
|
+
val install = try { JSONObject(File(files, "lamp-install.json").readText()) } catch (e: Exception) {
|
|
43
|
+
return failure("This copy of the app wasn't opened from Lamp, so it can't use AI. Open it from Lamp on your Mac.")
|
|
44
|
+
}
|
|
45
|
+
val token = install.optString("token")
|
|
46
|
+
val list = install.optJSONArray("routes") ?: return failure("Couldn't reach Lamp. Open Lamp on your Mac and try again.")
|
|
47
|
+
val bytes = body.toByteArray(Charsets.UTF_8)
|
|
48
|
+
// What the person said is paid for once heard: tried on each way to the Mac once, never again after a moment
|
|
49
|
+
// (Lamp knows the recording by the id lamp.js gave it, so two ways reaching it still pay once).
|
|
50
|
+
for (attempt in 0 until (if (path == "/ai/hear") 1 else 2)) {
|
|
51
|
+
for (i in 0 until list.length()) {
|
|
52
|
+
val route = list.optJSONObject(i) ?: continue
|
|
53
|
+
val left = (deadline - System.currentTimeMillis()).toInt()
|
|
54
|
+
if (left <= 0) break
|
|
55
|
+
try {
|
|
56
|
+
return exchange(route, path, token, bytes, left)
|
|
57
|
+
} catch (e: Exception) {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (attempt == 0 && deadline - System.currentTimeMillis() > 3000) Thread.sleep(3000) else break
|
|
62
|
+
}
|
|
63
|
+
return failure(if (install.optBoolean("device")) "Couldn't reach Lamp on your Mac. Keep this device on the same Wi‑Fi as the Mac, with Lamp open, and try again."
|
|
64
|
+
else "Couldn't reach Lamp. Open Lamp on your Mac and try again.")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private fun exchange(route: JSONObject, path: String, token: String, body: ByteArray, timeout: Int): Pair<Int, String> {
|
|
68
|
+
Socket().use { socket ->
|
|
69
|
+
socket.connect(InetSocketAddress(route.getString("address"), route.getInt("port")), minOf(4000, timeout))
|
|
70
|
+
socket.soTimeout = timeout
|
|
71
|
+
val head = "POST $path HTTP/1.1\r\nHost: ${route.getString("host")}\r\nAuthorization: Bearer $token\r\n" +
|
|
72
|
+
"Content-Type: application/json\r\nContent-Length: ${body.size}\r\nConnection: close\r\n\r\n"
|
|
73
|
+
socket.getOutputStream().apply { write(head.toByteArray()); write(body); flush() }
|
|
74
|
+
val all = socket.getInputStream().readBytes()
|
|
75
|
+
val split = (0..all.size - 4).firstOrNull { all[it] == '\r'.code.toByte() && all[it + 1] == '\n'.code.toByte() && all[it + 2] == '\r'.code.toByte() && all[it + 3] == '\n'.code.toByte() }
|
|
76
|
+
?: throw java.io.IOException("Lamp's answer was cut short.")
|
|
77
|
+
val status = String(all, 0, split).split("\r\n").first().split(" ").getOrNull(1)?.toIntOrNull()
|
|
78
|
+
?: throw java.io.IOException("Lamp's answer couldn't be read.")
|
|
79
|
+
return status to String(all, split + 4, all.size - split - 4)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The page's LampVoice (Web/lamp.js's `lampVoiceHost`): what the person says, recorded by whatever microphone this
|
|
85
|
+
* device gives an app (a TV remote's included, which Android hands over as the voice-recognition source), as
|
|
86
|
+
* 16 kHz mono 16-bit PCM in a WAV for the page to have transcribed (`LampAI.hear`); and what the page says back,
|
|
87
|
+
* read out. Asking what's possible never prompts: only `listen` asks for the microphone. One recording at a time,
|
|
88
|
+
* each with its own stop (`LampVoiceTake`), tagged with the page's session; a stop keeps it, a cancel throws it
|
|
89
|
+
* away, and the app leaving the screen or the page loading again cancels it whether the page says so or not. Every
|
|
90
|
+
* answer goes back to the page that asked, and none to a page loaded since. Only the page's own origin, in its
|
|
91
|
+
* main frame. */
|
|
92
|
+
object LampPageVoice {
|
|
93
|
+
private const val RATE = 16000
|
|
94
|
+
/** One call from the page: answered on its own page's channel, and only while that page is still the one shown. */
|
|
95
|
+
private class Call(val id: Int, val proxy: androidx.webkit.JavaScriptReplyProxy, val page: Int, val tag: String = "")
|
|
96
|
+
private var activity: android.app.Activity? = null
|
|
97
|
+
private var web: WebView? = null
|
|
98
|
+
@Volatile private var page = 0 // counts the pages loaded
|
|
99
|
+
@Volatile private var take: LampVoiceTake? = null // the recording now
|
|
100
|
+
private var worker: Thread? = null
|
|
101
|
+
private var waiting: Call? = null // the listen asking for the microphone
|
|
102
|
+
private var tts: android.speech.tts.TextToSpeech? = null
|
|
103
|
+
private var ttsReady = false
|
|
104
|
+
private var speeches = 0 // a later speak, hush or cancel overtakes an earlier one
|
|
105
|
+
private var queued: Pair<Call, () -> Unit>? = null // a speak waiting for the voice to start
|
|
106
|
+
private val spoken = java.util.concurrent.ConcurrentHashMap<String, Call>()
|
|
107
|
+
|
|
108
|
+
fun attach(activity: android.app.Activity, web: WebView, origin: String) {
|
|
109
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) return
|
|
110
|
+
this.activity = activity; this.web = web
|
|
111
|
+
WebViewCompat.addWebMessageListener(web, "lampVoiceHost", setOf(origin)) { _, message, _, isMainFrame, proxy ->
|
|
112
|
+
val msg = try { JSONObject(message.data ?: "") } catch (e: Exception) { null }
|
|
113
|
+
if (!isMainFrame || msg == null) return@addWebMessageListener
|
|
114
|
+
val tag = msg.optString("session")
|
|
115
|
+
val call = Call(msg.optInt("id"), proxy, page, tag)
|
|
116
|
+
when (msg.optString("cmd")) {
|
|
117
|
+
"status" -> answer(call, status())
|
|
118
|
+
"listen" -> listen(call)
|
|
119
|
+
"stop" -> { take?.takeIf { it.tag == tag }?.stop("stopped"); answer(call, JSONObject().put("ok", true)) }
|
|
120
|
+
"cancel" -> {
|
|
121
|
+
take?.takeIf { it.tag == tag }?.stop("cancelled")
|
|
122
|
+
waiting?.takeIf { it.tag == tag }?.let { waiting = null; answer(it, JSONObject().put("ended", "cancelled")) }
|
|
123
|
+
answer(call, JSONObject().put("ok", true))
|
|
124
|
+
}
|
|
125
|
+
"speak" -> speak(msg.optString("text"), call)
|
|
126
|
+
"hush" -> { hush(); answer(call, JSONObject().put("ok", true)) }
|
|
127
|
+
else -> answer(call, JSONObject().put("error", "Unknown."))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The app left the screen: nothing goes on being heard or said, and a listen waiting for the microphone is
|
|
133
|
+
* answered as cancelled. */
|
|
134
|
+
fun cancel() {
|
|
135
|
+
waiting?.let { waiting = null; answer(it, JSONObject().put("ended", "cancelled")) }
|
|
136
|
+
take?.stop("cancelled")
|
|
137
|
+
hush()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The page loads again: as leaving the screen, and nothing still under way answers the new page. */
|
|
141
|
+
fun newPage() { cancel(); page++ }
|
|
142
|
+
|
|
143
|
+
private fun hush() {
|
|
144
|
+
speeches++
|
|
145
|
+
queued?.let { queued = null; answer(it.first, JSONObject().put("done", false)) }
|
|
146
|
+
tts?.stop()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private fun granted() = activity?.checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
150
|
+
|
|
151
|
+
fun status(): JSONObject {
|
|
152
|
+
val a = activity
|
|
153
|
+
val permission = when {
|
|
154
|
+
granted() -> "granted"
|
|
155
|
+
a != null && a.shouldShowRequestPermissionRationale(android.Manifest.permission.RECORD_AUDIO) -> "denied"
|
|
156
|
+
else -> "ask"
|
|
157
|
+
}
|
|
158
|
+
val inputs = (a?.getSystemService(android.content.Context.AUDIO_SERVICE) as? android.media.AudioManager)
|
|
159
|
+
?.getDevices(android.media.AudioManager.GET_DEVICES_INPUTS)?.size ?: 0
|
|
160
|
+
return JSONObject().put("listen", inputs > 0).put("speak", true).put("permission", permission)
|
|
161
|
+
.put("why", if (inputs > 0) "" else "This device has no microphone.").put("maxSeconds", LampVoiceEnd.MAX)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private fun listen(call: Call) {
|
|
165
|
+
cancel()
|
|
166
|
+
if (!granted()) {
|
|
167
|
+
waiting = call
|
|
168
|
+
activity?.requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 7307)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
record(call)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The person's answer to the system's question (MainActivity passes it on). Closed meanwhile: nothing starts. */
|
|
175
|
+
fun permissionResult(code: Int, allowed: Boolean) {
|
|
176
|
+
if (code != 7307) return
|
|
177
|
+
val call = waiting ?: return
|
|
178
|
+
waiting = null
|
|
179
|
+
if (call.page != page) return
|
|
180
|
+
if (!allowed) { answer(call, JSONObject().put("ended", "denied")); return }
|
|
181
|
+
record(call)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private fun record(call: Call) {
|
|
185
|
+
val mine = LampVoiceTake(call.tag)
|
|
186
|
+
val before = worker
|
|
187
|
+
take = mine
|
|
188
|
+
worker = thread {
|
|
189
|
+
// The recording before lets the microphone go first (it ends within a frame of being cancelled).
|
|
190
|
+
before?.join(2000)
|
|
191
|
+
val result = try { capture(mine, call) } catch (e: Exception) { JSONObject().put("ended", "error").put("error", e.message ?: "The microphone failed.") }
|
|
192
|
+
web?.post { if (take === mine) take = null }
|
|
193
|
+
answer(call, result)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Records until the person stops speaking, a stop, a cancel or the limit; levels go to the page as it goes. */
|
|
198
|
+
private fun capture(mine: LampVoiceTake, call: Call): JSONObject {
|
|
199
|
+
mine.stopAs?.let { return JSONObject().put("ended", it) }
|
|
200
|
+
val min = android.media.AudioRecord.getMinBufferSize(RATE, android.media.AudioFormat.CHANNEL_IN_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT)
|
|
201
|
+
@Suppress("MissingPermission")
|
|
202
|
+
val rec = android.media.AudioRecord(android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION, RATE,
|
|
203
|
+
android.media.AudioFormat.CHANNEL_IN_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT, maxOf(min, RATE))
|
|
204
|
+
try {
|
|
205
|
+
if (rec.state != android.media.AudioRecord.STATE_INITIALIZED) return JSONObject().put("ended", "error").put("error", "The microphone didn't open.")
|
|
206
|
+
rec.startRecording()
|
|
207
|
+
if (rec.recordingState != android.media.AudioRecord.RECORDSTATE_RECORDING) return JSONObject().put("ended", "error").put("error", "The microphone didn't start.")
|
|
208
|
+
val routed = rec.routedDevice?.type ?: -1
|
|
209
|
+
// Never waits for audio: the clock ends it on time, and a cancel within a frame, even when nothing comes.
|
|
210
|
+
val ended = mine.run({ buf, from -> rec.read(buf, from, buf.size - from, android.media.AudioRecord.READ_NON_BLOCKING) }, System::nanoTime) { level, speech ->
|
|
211
|
+
event(call, JSONObject().put("event", "level").put("session", mine.tag).put("level", minOf(1.0, level * 4)).put("speech", speech))
|
|
212
|
+
}
|
|
213
|
+
val audio = mine.pcm.toByteArray()
|
|
214
|
+
// The microphone stopped sending mid-way: what it heard may be cut off, so it's an error, not something to act on.
|
|
215
|
+
if (ended == "lost") return JSONObject().put("ended", "error").put("error", "The microphone stopped sending sound. Try again.")
|
|
216
|
+
.put("peak", mine.end.peak).put("speech", mine.end.speechAt >= 0).put("input", routed)
|
|
217
|
+
val out = JSONObject().put("ended", ended).put("seconds", audio.size / 2.0 / RATE).put("peak", mine.end.peak)
|
|
218
|
+
.put("speech", mine.end.speechAt >= 0).put("input", routed)
|
|
219
|
+
if (ended == "stopped" || ended == "quiet" || ended == "cap") out.put("audio", android.util.Base64.encodeToString(wav(audio), android.util.Base64.NO_WRAP))
|
|
220
|
+
return out
|
|
221
|
+
} finally {
|
|
222
|
+
try { rec.stop() } catch (e: Exception) {}
|
|
223
|
+
rec.release()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private fun wav(pcm: ByteArray): ByteArray {
|
|
228
|
+
val h = java.nio.ByteBuffer.allocate(44).order(java.nio.ByteOrder.LITTLE_ENDIAN)
|
|
229
|
+
h.put("RIFF".toByteArray()).putInt(36 + pcm.size).put("WAVE".toByteArray()).put("fmt ".toByteArray()).putInt(16).putShort(1).putShort(1)
|
|
230
|
+
.putInt(RATE).putInt(RATE * 2).putShort(2).putShort(16).put("data".toByteArray()).putInt(pcm.size)
|
|
231
|
+
return h.array() + pcm
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Reads the words out; resolves once they're said to the end, or false when they couldn't be or were cut off. */
|
|
235
|
+
private fun speak(text: String, call: Call) {
|
|
236
|
+
val a = activity ?: return answer(call, JSONObject().put("done", false))
|
|
237
|
+
if (text.isEmpty() || take != null) return answer(call, JSONObject().put("done", false))
|
|
238
|
+
hush()
|
|
239
|
+
val asked = speeches
|
|
240
|
+
val say = {
|
|
241
|
+
val u = "u${call.page}-${call.id}"
|
|
242
|
+
if (asked != speeches || call.page != page) answer(call, JSONObject().put("done", false))
|
|
243
|
+
else {
|
|
244
|
+
spoken[u] = call
|
|
245
|
+
if (tts?.speak(text, android.speech.tts.TextToSpeech.QUEUE_FLUSH, null, u) != android.speech.tts.TextToSpeech.SUCCESS)
|
|
246
|
+
spoken.remove(u)?.let { answer(it, JSONObject().put("done", false)) }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (tts != null && ttsReady) return say()
|
|
250
|
+
queued = call to say
|
|
251
|
+
if (tts != null) return // already starting: it says the latest when it's ready
|
|
252
|
+
tts = android.speech.tts.TextToSpeech(a) { status ->
|
|
253
|
+
web?.post {
|
|
254
|
+
ttsReady = status == android.speech.tts.TextToSpeech.SUCCESS
|
|
255
|
+
if (ttsReady) tts?.setOnUtteranceProgressListener(object : android.speech.tts.UtteranceProgressListener() {
|
|
256
|
+
override fun onStart(u: String) {}
|
|
257
|
+
override fun onDone(u: String) { spoken.remove(u)?.let { answer(it, JSONObject().put("done", true)) } }
|
|
258
|
+
@Deprecated("Deprecated in Java") override fun onError(u: String) { spoken.remove(u)?.let { answer(it, JSONObject().put("done", false)) } }
|
|
259
|
+
override fun onStop(u: String, interrupted: Boolean) { spoken.remove(u)?.let { answer(it, JSONObject().put("done", false)) } }
|
|
260
|
+
}) else { tts?.shutdown(); tts = null }
|
|
261
|
+
val next = queued; queued = null
|
|
262
|
+
if (next != null) { if (ttsReady) next.second() else answer(next.first, JSONObject().put("done", false)) }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private fun answer(call: Call, body: JSONObject) = post(call, body.put("id", call.id))
|
|
268
|
+
private fun event(call: Call, body: JSONObject) = post(call, body)
|
|
269
|
+
private fun post(call: Call, body: JSONObject) {
|
|
270
|
+
val w = web ?: return
|
|
271
|
+
w.post { if (call.page == page) try { call.proxy.postMessage(body.toString()) } catch (e: Exception) {} }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** When one recording ends, from its levels and the clock alone (the same rule as LampVoiceEnd in LampPage.swift).
|
|
276
|
+
* Speech is a level three times the room's, and above a floor. The room is the quietest it has been lately: it
|
|
277
|
+
* follows a dip at once and, until speech is heard, rises back over a couple of seconds, so speech from the very
|
|
278
|
+
* first moment counts once its first pause shows the room; once speech is heard the room stays put, so talking on
|
|
279
|
+
* without a pause is never taken for the room. A recording ends as finished (`quiet`) only after 1.2 s of quiet
|
|
280
|
+
* that was received: audio under the speech level after speech, or under the floor (past 6 s) when there was only
|
|
281
|
+
* sound. Missing audio is never quiet: a stream that stops coming for a second ends as `lost`, which nothing acts
|
|
282
|
+
* on. With no sound at all by 6 s it's `nothing`. Anything still going at 15 s is `cap`, which is never acted on.
|
|
283
|
+
* Asked every frame whether audio comes or not, so these hold when audio stops. A rule of thumb on levels, not a
|
|
284
|
+
* way of telling speech from a television. */
|
|
285
|
+
class LampVoiceEnd {
|
|
286
|
+
companion object { const val MAX = 15.0; const val WAIT = 6.0; const val QUIET = 1.2; const val FLOOR = 0.02; const val RISE = 2.0; const val STALL = 1.0 }
|
|
287
|
+
var speechAt = -1.0; private set
|
|
288
|
+
var peak = 0.0; private set
|
|
289
|
+
private var room = -1.0; private var at = 0.0; private var lastHeard = -1.0; private var sound = false
|
|
290
|
+
// When the quiet that's going on now began, by the audio received: under the speech level, and under the floor.
|
|
291
|
+
private var quietFrom = -1.0; private var silentFrom = -1.0
|
|
292
|
+
@Synchronized fun heard(level: Double, top: Double, t: Double) {
|
|
293
|
+
peak = maxOf(peak, top)
|
|
294
|
+
if (room < 0 || level < room) room = level
|
|
295
|
+
else if (speechAt < 0) room += (level - room) * minOf(1.0, (t - at) / RISE)
|
|
296
|
+
at = t
|
|
297
|
+
lastHeard = t
|
|
298
|
+
if (level > FLOOR) { sound = true; silentFrom = -1.0 } else if (silentFrom < 0) silentFrom = t
|
|
299
|
+
if (level > maxOf(room * 3, FLOOR)) { if (speechAt < 0) speechAt = t; quietFrom = -1.0 }
|
|
300
|
+
else if (quietFrom < 0) quietFrom = t
|
|
301
|
+
}
|
|
302
|
+
@Synchronized fun ended(t: Double): String? = when {
|
|
303
|
+
t >= MAX -> "cap"
|
|
304
|
+
lastHeard < 0 -> if (t >= WAIT) "nothing" else null
|
|
305
|
+
speechAt >= 0 && quietFrom >= 0 && lastHeard - quietFrom >= QUIET -> "quiet"
|
|
306
|
+
speechAt < 0 && sound && t >= WAIT && silentFrom >= 0 && lastHeard - silentFrom >= QUIET -> "quiet"
|
|
307
|
+
speechAt < 0 && !sound && t >= WAIT -> "nothing"
|
|
308
|
+
// Audio that has stopped coming isn't quiet: what was heard may have been cut off.
|
|
309
|
+
t - lastHeard >= STALL -> "lost"
|
|
310
|
+
else -> null
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** One recording, with its own stop: a stop or cancel meant for one never reaches another. `run` reads 40 ms
|
|
315
|
+
* frames from `read` (what's there, without waiting: 0 when nothing has come) until a stop, a cancel or
|
|
316
|
+
* LampVoiceEnd says it's over, checking both between every read. */
|
|
317
|
+
class LampVoiceTake(val tag: String) {
|
|
318
|
+
@Volatile var stopAs: String? = null; private set
|
|
319
|
+
val end = LampVoiceEnd()
|
|
320
|
+
val pcm = java.io.ByteArrayOutputStream()
|
|
321
|
+
fun stop(how: String) { if (stopAs == null) stopAs = how }
|
|
322
|
+
fun run(read: (ShortArray, Int) -> Int, now: () -> Long, level: (Double, Boolean) -> Unit): String {
|
|
323
|
+
val started = now()
|
|
324
|
+
val frame = ShortArray(640)
|
|
325
|
+
var filled = 0
|
|
326
|
+
while (true) {
|
|
327
|
+
stopAs?.let { return it }
|
|
328
|
+
end.ended((now() - started) / 1e9)?.let { return it }
|
|
329
|
+
val n = read(frame, filled)
|
|
330
|
+
if (n < 0) throw java.io.IOException("The microphone stopped working ($n).")
|
|
331
|
+
if (n == 0) { Thread.sleep(10); continue }
|
|
332
|
+
filled += n
|
|
333
|
+
if (filled < frame.size) continue
|
|
334
|
+
filled = 0
|
|
335
|
+
var sum = 0.0; var top = 0.0
|
|
336
|
+
for (v in frame) {
|
|
337
|
+
val x = v / 32768.0; sum += x * x; top = maxOf(top, kotlin.math.abs(x))
|
|
338
|
+
pcm.write(v.toInt() and 0xff); pcm.write((v.toInt() shr 8) and 0xff)
|
|
339
|
+
}
|
|
340
|
+
val l = kotlin.math.sqrt(sum / frame.size)
|
|
341
|
+
end.heard(l, top, (now() - started) / 1e9)
|
|
342
|
+
level(l, end.speechAt >= 0)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Written by Lamp before every build; don't edit.
|
|
2
|
+
plugins {
|
|
3
|
+
alias(libs.plugins.android.application)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
android {
|
|
7
|
+
namespace = "{{ANDROID_PACKAGE}}"
|
|
8
|
+
compileSdk = 36
|
|
9
|
+
defaultConfig {
|
|
10
|
+
applicationId = "{{ANDROID_PACKAGE}}"
|
|
11
|
+
minSdk = 26
|
|
12
|
+
targetSdk = 36
|
|
13
|
+
versionCode = 1
|
|
14
|
+
versionName = "1.0"
|
|
15
|
+
}
|
|
16
|
+
compileOptions {
|
|
17
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
18
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
19
|
+
}
|
|
20
|
+
buildFeatures { buildConfig = false }
|
|
21
|
+
sourceSets["main"].kotlin.srcDirs("src/main/kotlin")
|
|
22
|
+
// The page and the icon, which build.sh puts in the game's build/ before Gradle runs.
|
|
23
|
+
sourceSets["main"].assets.srcDirs("../../../build/android-assets")
|
|
24
|
+
sourceSets["main"].res.srcDirs("../../../build/android-res")
|
|
25
|
+
// The page's files are served as they are: a compressed asset can't be streamed to the WebView. And every
|
|
26
|
+
// folder of the page's goes in: Android's own list of what an app's assets leave out drops any folder whose
|
|
27
|
+
// name starts with an underscore, which is where a built site keeps its scripts (_next, _astro).
|
|
28
|
+
androidResources {
|
|
29
|
+
noCompress += listOf("js", "html", "css", "json", "glb", "gltf", "bin", "png", "jpg", "wav", "ogg", "mp3", "m4a")
|
|
30
|
+
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
kotlin {
|
|
35
|
+
jvmToolchain(17)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dependencies {
|
|
39
|
+
implementation(libs.androidx.webkit)
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Written by Lamp before every build; don't edit. -->
|
|
3
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
4
|
+
<!-- Back reaches the page as a key (the game's B, a TV page's Back), not Android's predictive back, which would
|
|
5
|
+
leave the app before the page says what going back means. -->
|
|
6
|
+
<!-- One app for phones and TVs: no touch screen needed, and on a TV's home screen through the leanback launcher. -->
|
|
7
|
+
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
|
8
|
+
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
|
9
|
+
<uses-feature android:name="android.software.leanback" android:required="false" />
|
|
10
|
+
<!-- A page may read the web (a screen's weather, a game's scores), as it does in every other place. -->
|
|
11
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
12
|
+
<application
|
|
13
|
+
android:allowBackup="true"
|
|
14
|
+
android:banner="@drawable/lamp_banner"
|
|
15
|
+
android:enableOnBackInvokedCallback="false"
|
|
16
|
+
android:icon="@mipmap/ic_launcher"
|
|
17
|
+
android:label="{{NAME}}"
|
|
18
|
+
android:hardwareAccelerated="true"
|
|
19
|
+
android:isGame="true"
|
|
20
|
+
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
|
21
|
+
<activity
|
|
22
|
+
android:name=".MainActivity"
|
|
23
|
+
android:exported="true"
|
|
24
|
+
android:screenOrientation="sensorLandscape"
|
|
25
|
+
android:configChanges="orientation|screenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode">
|
|
26
|
+
<intent-filter>
|
|
27
|
+
<action android:name="android.intent.action.MAIN" />
|
|
28
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
29
|
+
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
|
30
|
+
</intent-filter>
|
|
31
|
+
</activity>
|
|
32
|
+
</application>
|
|
33
|
+
</manifest>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Written by Lamp before every build; don't edit. The game is the page in Web/.
|
|
2
|
+
package {{PACKAGE}}
|
|
3
|
+
|
|
4
|
+
import android.annotation.SuppressLint
|
|
5
|
+
import android.app.Activity
|
|
6
|
+
import android.hardware.input.InputManager
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import android.view.InputDevice
|
|
10
|
+
import android.view.KeyEvent
|
|
11
|
+
import android.view.MotionEvent
|
|
12
|
+
import android.view.View
|
|
13
|
+
import android.view.WindowInsets
|
|
14
|
+
import android.view.WindowInsetsController
|
|
15
|
+
import android.webkit.ConsoleMessage
|
|
16
|
+
import android.webkit.WebChromeClient
|
|
17
|
+
import android.webkit.WebResourceRequest
|
|
18
|
+
import android.webkit.WebResourceResponse
|
|
19
|
+
import android.webkit.WebView
|
|
20
|
+
import android.webkit.WebViewClient
|
|
21
|
+
import kotlin.math.abs
|
|
22
|
+
|
|
23
|
+
class MainActivity : Activity(), InputManager.InputDeviceListener {
|
|
24
|
+
private lateinit var web: WebView
|
|
25
|
+
// The first controller, as the page's pad 0 is: sticks -1 to 1 (up positive), triggers 0 to 1, buttons as Lamp's bits.
|
|
26
|
+
private var lx = 0f; private var ly = 0f; private var rx = 0f; private var ry = 0f; private var lt = 0f; private var rt = 0f
|
|
27
|
+
private var down = 0
|
|
28
|
+
// The controller's USB vendor, so the page names its buttons as they're marked (a PlayStation's shapes).
|
|
29
|
+
private var vendor = 0
|
|
30
|
+
private var sent = ""
|
|
31
|
+
|
|
32
|
+
@SuppressLint("SetJavaScriptEnabled")
|
|
33
|
+
override fun onCreate(saved: Bundle?) {
|
|
34
|
+
super.onCreate(saved)
|
|
35
|
+
web = WebView(this)
|
|
36
|
+
web.setBackgroundColor(0xFF000000.toInt())
|
|
37
|
+
web.settings.javaScriptEnabled = true
|
|
38
|
+
web.settings.domStorageEnabled = true
|
|
39
|
+
web.settings.mediaPlaybackRequiresUserGesture = false
|
|
40
|
+
web.settings.allowFileAccess = false
|
|
41
|
+
web.webViewClient = object : WebViewClient() {
|
|
42
|
+
// The page's files, from the app's own assets, at an address of the game's own.
|
|
43
|
+
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
|
44
|
+
val url = request.url
|
|
45
|
+
if (url.host != HOST) return null
|
|
46
|
+
val path = (url.path ?: "/").trimStart('/').ifEmpty { "index.html" }
|
|
47
|
+
if (path.split('/').any { it == ".." }) return notFound()
|
|
48
|
+
return try {
|
|
49
|
+
WebResourceResponse(type(path), "utf-8", assets.open("Web/$path"))
|
|
50
|
+
} catch (e: Exception) { notFound() }
|
|
51
|
+
}
|
|
52
|
+
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = request.url.host != HOST
|
|
53
|
+
}
|
|
54
|
+
web.webChromeClient = object : WebChromeClient() {
|
|
55
|
+
override fun onConsoleMessage(message: ConsoleMessage): Boolean {
|
|
56
|
+
Log.i("Lamp", message.message())
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
setContentView(web)
|
|
61
|
+
LampPageGateway.attach(web, "https://$HOST")
|
|
62
|
+
(getSystemService(INPUT_SERVICE) as InputManager).registerInputDeviceListener(this, null)
|
|
63
|
+
immerse()
|
|
64
|
+
web.loadUrl("https://$HOST/index.html")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun onWindowFocusChanged(focused: Boolean) {
|
|
68
|
+
super.onWindowFocusChanged(focused)
|
|
69
|
+
if (focused) immerse()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun immerse() {
|
|
73
|
+
if (android.os.Build.VERSION.SDK_INT >= 30) {
|
|
74
|
+
window.insetsController?.let {
|
|
75
|
+
it.hide(WindowInsets.Type.systemBars())
|
|
76
|
+
it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
@Suppress("DEPRECATION")
|
|
80
|
+
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun onPause() {
|
|
85
|
+
super.onPause()
|
|
86
|
+
web.onPause()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// A controller that goes away mid-press leaves nothing held: the page sees it let go, and its touch controls
|
|
90
|
+
// can come back.
|
|
91
|
+
override fun onInputDeviceRemoved(id: Int) {
|
|
92
|
+
lx = 0f; ly = 0f; rx = 0f; ry = 0f; lt = 0f; rt = 0f; down = 0
|
|
93
|
+
send()
|
|
94
|
+
}
|
|
95
|
+
override fun onInputDeviceAdded(id: Int) {}
|
|
96
|
+
override fun onInputDeviceChanged(id: Int) {}
|
|
97
|
+
override fun onDestroy() {
|
|
98
|
+
(getSystemService(INPUT_SERVICE) as InputManager).unregisterInputDeviceListener(this)
|
|
99
|
+
super.onDestroy()
|
|
100
|
+
}
|
|
101
|
+
override fun onResume() { super.onResume(); web.onResume() }
|
|
102
|
+
|
|
103
|
+
// MARK: The controller
|
|
104
|
+
|
|
105
|
+
private fun bit(code: Int): Int = when (code) {
|
|
106
|
+
KeyEvent.KEYCODE_BUTTON_A -> 1; KeyEvent.KEYCODE_BUTTON_B -> 2; KeyEvent.KEYCODE_BUTTON_X -> 4; KeyEvent.KEYCODE_BUTTON_Y -> 8
|
|
107
|
+
KeyEvent.KEYCODE_BUTTON_L1 -> 16; KeyEvent.KEYCODE_BUTTON_R1 -> 32
|
|
108
|
+
KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_MENU -> 64; KeyEvent.KEYCODE_BUTTON_SELECT -> 128
|
|
109
|
+
KeyEvent.KEYCODE_DPAD_UP -> 256; KeyEvent.KEYCODE_DPAD_DOWN -> 512; KeyEvent.KEYCODE_DPAD_LEFT -> 1024; KeyEvent.KEYCODE_DPAD_RIGHT -> 2048
|
|
110
|
+
KeyEvent.KEYCODE_BUTTON_THUMBL -> 4096; KeyEvent.KEYCODE_BUTTON_THUMBR -> 8192
|
|
111
|
+
else -> 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A TV remote's d-pad is a controller's to a game: its arrows the d-pad, OK the A button, play/pause Start.
|
|
115
|
+
private fun fromController(event: KeyEvent) =
|
|
116
|
+
event.isFromSource(InputDevice.SOURCE_GAMEPAD) || event.isFromSource(InputDevice.SOURCE_JOYSTICK) || KeyEvent.isGamepadButton(event.keyCode)
|
|
117
|
+
|| event.isFromSource(InputDevice.SOURCE_DPAD) || event.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|
|
118
|
+
private fun remote(code: Int) = when (code) {
|
|
119
|
+
KeyEvent.KEYCODE_DPAD_CENTER -> KeyEvent.KEYCODE_BUTTON_A; KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> KeyEvent.KEYCODE_BUTTON_START; else -> code
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
|
124
|
+
// Back is the game's B, as on a console: the game says what going back means, and stays open.
|
|
125
|
+
val code = if (event.keyCode == KeyEvent.KEYCODE_BACK) KeyEvent.KEYCODE_BUTTON_B else remote(event.keyCode)
|
|
126
|
+
val b = bit(code)
|
|
127
|
+
if (b != 0 && (fromController(event) || event.keyCode == KeyEvent.KEYCODE_BACK)) {
|
|
128
|
+
if (fromController(event)) vendor = event.device?.vendorId ?: 0
|
|
129
|
+
down = if (event.action == KeyEvent.ACTION_DOWN) down or b else down and b.inv()
|
|
130
|
+
send()
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
return super.dispatchKeyEvent(event)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun dead(v: Float) = if (abs(v) < 0.15f) 0f else (v - Math.signum(v) * 0.15f) / 0.85f
|
|
137
|
+
|
|
138
|
+
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
|
139
|
+
if (!event.isFromSource(InputDevice.SOURCE_JOYSTICK) || event.action != MotionEvent.ACTION_MOVE) return super.dispatchGenericMotionEvent(event)
|
|
140
|
+
vendor = event.device?.vendorId ?: 0
|
|
141
|
+
lx = dead(event.getAxisValue(MotionEvent.AXIS_X)); ly = -dead(event.getAxisValue(MotionEvent.AXIS_Y))
|
|
142
|
+
rx = dead(event.getAxisValue(MotionEvent.AXIS_Z)); ry = -dead(event.getAxisValue(MotionEvent.AXIS_RZ))
|
|
143
|
+
lt = maxOf(event.getAxisValue(MotionEvent.AXIS_LTRIGGER), event.getAxisValue(MotionEvent.AXIS_BRAKE))
|
|
144
|
+
rt = maxOf(event.getAxisValue(MotionEvent.AXIS_RTRIGGER), event.getAxisValue(MotionEvent.AXIS_GAS))
|
|
145
|
+
// A d-pad that reports as a hat.
|
|
146
|
+
val hx = event.getAxisValue(MotionEvent.AXIS_HAT_X); val hy = event.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
|
147
|
+
down = down and (256 or 512 or 1024 or 2048).inv() or
|
|
148
|
+
(if (hy < -0.5f) 256 else 0) or (if (hy > 0.5f) 512 else 0) or (if (hx < -0.5f) 1024 else 0) or (if (hx > 0.5f) 2048 else 0)
|
|
149
|
+
send()
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private fun send() {
|
|
154
|
+
val now = "$lx,$ly,$rx,$ry,$lt,$rt,$down,$vendor"
|
|
155
|
+
if (now == sent) return
|
|
156
|
+
sent = now
|
|
157
|
+
web.evaluateJavascript("window.lampNativePad&&lampNativePad($now)", null)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun type(path: String) = when (path.substringAfterLast('.', "").lowercase()) {
|
|
161
|
+
"html", "htm" -> "text/html"; "js", "mjs" -> "text/javascript"; "css" -> "text/css"; "json" -> "application/json"
|
|
162
|
+
"png" -> "image/png"; "jpg", "jpeg" -> "image/jpeg"; "webp" -> "image/webp"; "svg" -> "image/svg+xml"
|
|
163
|
+
"glb" -> "model/gltf-binary"; "gltf" -> "model/gltf+json"; "wav" -> "audio/wav"; "ogg" -> "audio/ogg"
|
|
164
|
+
"mp3" -> "audio/mpeg"; "m4a" -> "audio/mp4"; "wasm" -> "application/wasm"
|
|
165
|
+
else -> "application/octet-stream"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun notFound() = WebResourceResponse("text/plain", "utf-8", 404, "Not Found", emptyMap(), "".byteInputStream())
|
|
169
|
+
|
|
170
|
+
companion object {
|
|
171
|
+
const val HOST = "lamp.game"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Written by Lamp before every build; don't edit. Add other libraries in app/build.gradle.kts with their full names.
|
|
2
|
+
[versions]
|
|
3
|
+
androidGradlePlugin = "9.0.1"
|
|
4
|
+
androidxCore = "1.18.0"
|
|
5
|
+
androidxLifecycle = "2.10.0"
|
|
6
|
+
androidxActivity = "1.13.0"
|
|
7
|
+
androidxComposeBom = "2026.03.01"
|
|
8
|
+
androidxNavigation3 = "1.0.1"
|
|
9
|
+
androidxDatastore = "1.1.7"
|
|
10
|
+
androidxWebkit = "1.14.0"
|
|
11
|
+
androidxTvMaterial = "1.1.0"
|
|
12
|
+
coroutines = "1.10.2"
|
|
13
|
+
kotlin = "2.3.20"
|
|
14
|
+
serialization = "1.9.0"
|
|
15
|
+
|
|
16
|
+
[libraries]
|
|
17
|
+
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
|
18
|
+
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
|
|
19
|
+
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
|
|
20
|
+
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
21
|
+
androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
|
22
|
+
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
23
|
+
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
24
|
+
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
25
|
+
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
|
|
26
|
+
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
|
27
|
+
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
|
|
28
|
+
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "androidxNavigation3" }
|
|
29
|
+
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "androidxNavigation3" }
|
|
30
|
+
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidxDatastore" }
|
|
31
|
+
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
|
32
|
+
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
|
|
33
|
+
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "androidxWebkit" }
|
|
34
|
+
androidx-tv-material = { module = "androidx.tv:tv-material", version.ref = "androidxTvMaterial" }
|
|
35
|
+
|
|
36
|
+
[plugins]
|
|
37
|
+
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
|
|
38
|
+
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
39
|
+
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Written by Lamp before every build; don't edit.
|
|
2
|
+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -Dlamp.app={{SLUG}}
|
|
3
|
+
org.gradle.daemon.idletimeout=600000
|
|
4
|
+
org.gradle.caching=true
|
|
5
|
+
org.gradle.configuration-cache=true
|
|
6
|
+
android.useAndroidX=true
|
|
7
|
+
kotlin.code.style=official
|
|
8
|
+
android.nonTransitiveRClass=true
|