xbintsc 0.3.68 → 0.3.79

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.
Files changed (100) hide show
  1. package/README.md +59 -4
  2. package/README.zh-CN.md +52 -2
  3. package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
  4. package/dist/tests/e2e/gui-advanced.test.js +261 -0
  5. package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
  6. package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
  7. package/dist/tests/e2e/gui-canvas.test.js +233 -0
  8. package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
  9. package/dist/tests/e2e/gui-example.test.d.ts +1 -1
  10. package/dist/tests/e2e/gui-example.test.js +2 -2
  11. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  12. package/dist/tests/e2e/gui-helpers.js +5 -0
  13. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  14. package/dist/tests/e2e/gui-layout.test.js +73 -0
  15. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  16. package/dist/tests/e2e/gui-media.test.d.ts +8 -0
  17. package/dist/tests/e2e/gui-media.test.js +119 -0
  18. package/dist/tests/e2e/gui-media.test.js.map +1 -0
  19. package/dist/tests/e2e/gui-paint.test.js +40 -0
  20. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  21. package/doc/ai/extensions.md +16 -3
  22. package/doc/ai/zh-CN/extensions.md +10 -3
  23. package/doc/gui/animation.md +46 -0
  24. package/doc/gui/api.md +92 -0
  25. package/doc/gui/build.md +91 -0
  26. package/doc/gui/canvas.md +98 -0
  27. package/doc/gui/dom.md +123 -0
  28. package/doc/gui/input.md +22 -0
  29. package/doc/gui/known-issues.md +66 -0
  30. package/doc/gui/layout.md +72 -0
  31. package/doc/gui/media.md +87 -0
  32. package/doc/gui/rendering.md +98 -0
  33. package/doc/gui/runtime.md +88 -0
  34. package/doc/gui-scripts.md +7 -3
  35. package/doc/gui.md +80 -612
  36. package/doc/implemented.md +1 -0
  37. package/doc/zh-CN/gui/animation.md +46 -0
  38. package/doc/zh-CN/gui/api.md +88 -0
  39. package/doc/zh-CN/gui/build.md +84 -0
  40. package/doc/zh-CN/gui/canvas.md +80 -0
  41. package/doc/zh-CN/gui/dom.md +114 -0
  42. package/doc/zh-CN/gui/input.md +21 -0
  43. package/doc/zh-CN/gui/known-issues.md +50 -0
  44. package/doc/zh-CN/gui/layout.md +60 -0
  45. package/doc/zh-CN/gui/media.md +71 -0
  46. package/doc/zh-CN/gui/rendering.md +81 -0
  47. package/doc/zh-CN/gui/runtime.md +76 -0
  48. package/doc/zh-CN/gui-scripts.md +6 -2
  49. package/doc/zh-CN/gui.md +64 -532
  50. package/doc/zh-CN/implemented.md +1 -0
  51. package/doc/zh-CN/post/xbintsc-build-pelican.md +2 -2
  52. package/llms.txt +15 -2
  53. package/package.json +1 -1
  54. package/runtime/ext_gui/canvas.cpp +580 -0
  55. package/runtime/ext_gui/canvas.h +170 -0
  56. package/runtime/ext_gui/canvas_internal.h +59 -0
  57. package/runtime/ext_gui/canvas_raster.cpp +254 -0
  58. package/runtime/ext_gui/document.cpp +24 -1
  59. package/runtime/ext_gui/document.h +11 -0
  60. package/runtime/ext_gui/dom_api.cpp +3 -0
  61. package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
  62. package/runtime/ext_gui/dom_api_internal.h +73 -0
  63. package/runtime/ext_gui/dom_api_media.cpp +274 -0
  64. package/runtime/ext_gui/dom_api_proto.cpp +80 -0
  65. package/runtime/ext_gui/gui.cpp +6 -0
  66. package/runtime/ext_gui/gui_canvas.cpp +95 -0
  67. package/runtime/ext_gui/gui_engine.h +49 -0
  68. package/runtime/ext_gui/gui_events.cpp +21 -5
  69. package/runtime/ext_gui/gui_media.cpp +193 -0
  70. package/runtime/ext_gui/image.cpp +31 -12
  71. package/runtime/ext_gui/image.h +20 -7
  72. package/runtime/ext_gui/layout.cpp +52 -5
  73. package/runtime/ext_gui/layout.h +33 -1
  74. package/runtime/ext_gui/layout_flex.cpp +263 -99
  75. package/runtime/ext_gui/layout_flow.cpp +85 -9
  76. package/runtime/ext_gui/layout_flow_internal.h +84 -5
  77. package/runtime/ext_gui/layout_inline.cpp +54 -37
  78. package/runtime/ext_gui/media.cpp +514 -0
  79. package/runtime/ext_gui/media.h +104 -0
  80. package/runtime/ext_gui/nanosvg.h +3278 -0
  81. package/runtime/ext_gui/nanosvgrast.h +1472 -0
  82. package/runtime/ext_gui/paint.cpp +123 -38
  83. package/runtime/ext_gui/paint.h +36 -6
  84. package/runtime/ext_gui/renderer.cpp +113 -10
  85. package/runtime/ext_gui/renderer.h +49 -9
  86. package/runtime/ext_gui/renderer_image.cpp +203 -77
  87. package/runtime/ext_gui/renderer_internal.h +6 -8
  88. package/runtime/ext_gui/renderer_text.cpp +11 -26
  89. package/runtime/ext_gui/style.h +11 -0
  90. package/runtime/ext_gui/style_print.cpp +11 -0
  91. package/runtime/ext_gui/style_ua.cpp +7 -0
  92. package/runtime/ext_gui/style_values.cpp +40 -1
  93. package/runtime/ext_gui/svg.cpp +182 -0
  94. package/runtime/ext_gui/svg.h +46 -0
  95. package/runtime/ext_gui/text.cpp +156 -25
  96. package/runtime/ext_gui/text.h +11 -2
  97. package/runtime/ext_gui/window.cpp +4 -1
  98. package/scripts/build-gui.ts +148 -6
  99. package/scripts/check-file-length.ts +5 -1
  100. package/scripts/install-gui-deps.sh +28 -0
@@ -13,6 +13,8 @@
13
13
  * 1. fetch the pinned SDL3 source into `vendor/SDL` (gitignored),
14
14
  * 2. build `libSDL3.a` with CMake,
15
15
  * 2b. fetch + build static FreeType and HarfBuzz for text,
16
+ * 2c. fetch + build a minimal static FFmpeg for `<video>`/`<audio>`
17
+ * (skipped on Windows, where media support is compiled out),
16
18
  * 3. compile the engine translation units,
17
19
  * 4. merge everything into `gui.a` (libtool on macOS, `ar -M` elsewhere;
18
20
  * `gui.lib` on Windows, built from MSVC-compatible COFF objects).
@@ -31,6 +33,7 @@
31
33
  */
32
34
 
33
35
  import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "node:fs";
36
+ import { availableParallelism } from "node:os";
34
37
  import { spawnSync, type SpawnSyncReturns } from "node:child_process";
35
38
  import { dirname, join, resolve } from "node:path";
36
39
  import { fileURLToPath } from "node:url";
@@ -41,11 +44,23 @@ const runtimeDir = join(root, "runtime");
41
44
  const engineDir = join(runtimeDir, "ext_gui");
42
45
  const vendorDir = join(root, "vendor");
43
46
  const sdlSrc = join(vendorDir, "SDL");
44
- const sdlBuild = join(sdlSrc, "build");
47
+ /* SDL's build tree is a *sibling* of its source, like FreeType's and HarfBuzz's,
48
+ * never inside it: the CI cache keyed on a vendored library ships the whole
49
+ * `vendor/SDL` tree, and a CMake build tree is not relocatable across machines
50
+ * or platforms. `CMakeCache.txt` records the host's compiler and probes, and
51
+ * SDL's video options are `option()`s — the first configure to run wins, so a
52
+ * cached `SDL_X11=OFF` (e.g. from a Windows configure, where the option's
53
+ * default is OFF) survives into a Linux configure and SDL then aborts with
54
+ * "SDL could not find X11 or Wayland development libraries". Keeping the build
55
+ * tree out of the cached source path makes each machine configure its own. */
56
+ const sdlBuild = join(vendorDir, "sdl-build");
45
57
  const freetypeSrc = join(vendorDir, "freetype");
46
58
  const freetypeBuild = join(vendorDir, "freetype-build");
47
59
  const harfbuzzSrc = join(vendorDir, "harfbuzz");
48
60
  const harfbuzzBuild = join(vendorDir, "harfbuzz-build");
61
+ const ffmpegSrc = join(vendorDir, "ffmpeg");
62
+ const ffmpegBuild = join(vendorDir, "ffmpeg-build");
63
+ const ffmpegInstall = join(ffmpegBuild, "dist");
49
64
  const objDir = join(root, "build", "gui-obj");
50
65
  const outDir = join(runtimeDir, "lib", platformSlug());
51
66
 
@@ -82,8 +97,24 @@ const cmakeGeneratorArgs: readonly string[] = isWindows
82
97
  const SDL_TAG = process.env.SDL3_TAG || "release-3.2.10";
83
98
  const FREETYPE_VERSION = process.env.FREETYPE_VERSION || "2.13.3";
84
99
  const HARFBUZZ_VERSION = process.env.HARFBUZZ_VERSION || "10.1.0";
100
+ const FFMPEG_VERSION = process.env.FFMPEG_VERSION || "7.1";
85
101
  const FREETYPE_URL = `https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz`;
86
102
  const HARFBUZZ_URL = `https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz`;
103
+ const FFMPEG_URL = `https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz`;
104
+
105
+ /* A deliberately minimal FFmpeg: containers and decoders for the common web
106
+ * media formats, file access only, no network, no encoder, no filter graph.
107
+ * Disabling everything first keeps the static libraries small and the build
108
+ * short. `--disable-x86asm` drops the nasm/yasm build dependency (the C
109
+ * fallbacks are plenty for playback). This configuration is LGPL-only, so the
110
+ * resulting `gui.a` stays redistributable under the engine's terms. */
111
+ const FFMPEG_COMPONENTS: readonly string[] = [
112
+ "--enable-protocol=file",
113
+ "--enable-demuxer=mov,matroska,avi,flv,mpegts,mp3,ogg,wav,flac,aac,m4v,asf,mpeg,mpegvideo,h264,hevc,vc1",
114
+ "--enable-decoder=h264,hevc,vp8,vp9,av1,aac,aac_latm,mp3,mp3float,opus,vorbis,flac,alac,pcm_s16le,pcm_s24le,pcm_s32le,pcm_u8,pcm_f32le,pcm_alaw,pcm_mulaw,ac3,eac3,wmav2,mjpeg",
115
+ "--enable-parser=h264,hevc,vp8,vp9,av1,aac,aac_latm,mpegaudio,opus,vorbis,mpeg4video,mjpeg,flac",
116
+ "--enable-bsf=h264_mp4toannexb,hevc_mp4toannexb,aac_adtstoasc,vp9_superframe,vp9_superframe_split,extract_extradata",
117
+ ];
87
118
 
88
119
  function fail(message: string): never {
89
120
  console.error(`xbintsc: ${message}`);
@@ -93,13 +124,14 @@ function fail(message: string): never {
93
124
  function run(
94
125
  command: string,
95
126
  args: readonly string[],
96
- options: { input?: string; quietStderr?: boolean } = {},
127
+ options: { input?: string; quietStderr?: boolean; cwd?: string } = {},
97
128
  ): SpawnSyncReturns<string> {
98
129
  const quiet = options.quietStderr === true;
99
130
  const result = spawnSync(command, args, {
100
131
  stdio: options.input !== undefined ? ["pipe", "inherit", "inherit"] : quiet ? ["ignore", "inherit", "pipe"] : "inherit",
101
132
  encoding: "utf8",
102
133
  input: options.input,
134
+ cwd: options.cwd,
103
135
  });
104
136
  if (result.status !== 0) {
105
137
  if (quiet && result.stderr) process.stderr.write(result.stderr);
@@ -142,12 +174,49 @@ function findCxx(): string {
142
174
  return cxx;
143
175
  }
144
176
 
177
+ function findCc(): string {
178
+ const cc = firstAvailable([process.env.xbintsc_CC, process.env.CC, "clang", "cc", "gcc"]);
179
+ if (!cc) fail("no C compiler found; install clang or set xbintsc_CC");
180
+ return cc;
181
+ }
182
+
145
183
  function findArchiver(): string {
146
184
  const ar = firstAvailable([process.env.xbintsc_AR, "llvm-ar", "ar"]);
147
185
  if (!ar) fail("no archiver found; set xbintsc_AR to llvm-ar or ar");
148
186
  return ar;
149
187
  }
150
188
 
189
+ /** Directories a CMake `find_path` would search, relative to the filesystem
190
+ * root, used to check that a development package is installed. */
191
+ const HEADER_PREFIXES = ["/usr/include", "/usr/local/include", "/opt/homebrew/include"];
192
+
193
+ /** True on Debian and its derivatives, where the message below can name the
194
+ * packages to install. Anywhere else the headers may well live under a prefix
195
+ * this check does not know (`/usr/pkg`, `/nix/store`, …), so it stays quiet
196
+ * instead of failing a build that would have worked. */
197
+ function isDebianLike(): boolean {
198
+ return existsSync("/etc/debian_version");
199
+ }
200
+
201
+ /** Fail with the package names to install when a header SDL needs is missing,
202
+ * rather than letting SDL configure itself without a window backend and then
203
+ * abort with a summary that does not say which package is missing. */
204
+ function requireHeaders(feature: string, headers: readonly string[], packages: string): void {
205
+ if (isWindows || !isDebianLike()) return;
206
+ const missing = headers.filter(
207
+ (header) => !HEADER_PREFIXES.some((prefix) => existsSync(join(prefix, header))),
208
+ );
209
+ if (missing.length === 0) return;
210
+ const hint =
211
+ process.env.xbintsc_SKIP_HEADER_CHECK === "1"
212
+ ? ""
213
+ : " (set xbintsc_SKIP_HEADER_CHECK=1 if they are installed under a prefix this check does not know)";
214
+ fail(
215
+ `${feature} needs ${missing.join(", ")}, which is not installed; ` +
216
+ `install its development package first (Debian/Ubuntu: ${packages})${hint}`,
217
+ );
218
+ }
219
+
151
220
  /** First of `candidates` that exists on disk (CMake generators place static
152
221
  * libraries either at the build root or under `Release/`). */
153
222
  function firstExisting(candidates: readonly string[]): string | undefined {
@@ -187,6 +256,16 @@ function harfbuzzArchive(): string | undefined {
187
256
  return builtLibrary(harfbuzzBuild, ["libharfbuzz.a", "harfbuzz.lib"], /^harfbuzz[-\d.]*\.lib$/);
188
257
  }
189
258
 
259
+ /* FFmpeg's release tarball has no CMake build, so it uses its own configure +
260
+ * make. It also installs its headers under a prefix (rather than in the source
261
+ * tree) because the *source* root contains a file literally named `version`
262
+ * that would shadow the C++ standard header for any `.cpp` that includes it. */
263
+ const FFMPEG_LIBRARY_NAMES = ["libavformat.a", "libavcodec.a", "libswscale.a", "libswresample.a", "libavutil.a"];
264
+ function ffmpegStaticLibraries(): string[] | undefined {
265
+ const libraries = FFMPEG_LIBRARY_NAMES.map((name) => join(ffmpegInstall, "lib", name));
266
+ return libraries.every((library) => existsSync(library)) ? libraries : undefined;
267
+ }
268
+
190
269
  /* -- 1. SDL3 source ------------------------------------------------------- */
191
270
 
192
271
  if (!existsSync(join(sdlSrc, "CMakeLists.txt"))) {
@@ -199,6 +278,28 @@ if (!existsSync(join(sdlSrc, "CMakeLists.txt"))) {
199
278
 
200
279
  const existingSdlArchive = sdlArchive();
201
280
  if (!existingSdlArchive) {
281
+ /* Older trees configured SDL inside its source (`vendor/SDL/build`) and a
282
+ * cache from one of those would be reused by the next configure — the exact
283
+ * failure `sdlBuild` above exists to prevent. */
284
+ const legacyBuild = join(sdlSrc, "build");
285
+ if (existsSync(join(legacyBuild, "CMakeCache.txt"))) {
286
+ console.log("xbintsc: discarding the legacy in-source SDL build tree");
287
+ rmSync(legacyBuild, { recursive: true, force: true });
288
+ }
289
+
290
+ /* SDL enables a window backend from the headers it finds, and aborts when it
291
+ * finds neither X11 nor Wayland, so say which package is missing instead of
292
+ * relaying its summary. */
293
+ requireHeaders(
294
+ "SDL3's X11 video driver",
295
+ /* `Xlib.h` comes from libx11-dev; `Xext.h` is the header SDL gates HAVE_X11
296
+ * on as well, and it comes from libxext-dev. */
297
+ ["X11/Xlib.h", "X11/extensions/Xext.h"],
298
+ "libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxinerama-dev libxfixes-dev",
299
+ );
300
+ requireHeaders("SDL3's Wayland video driver", ["wayland-client-core.h"],
301
+ "libwayland-dev libxkbcommon-dev libdecor-0-dev");
302
+
202
303
  const cmake = findCmake();
203
304
  console.log(`xbintsc: building static SDL3 with ${cmake}`);
204
305
  const configureArgs = [
@@ -221,10 +322,12 @@ if (!existingSdlArchive) {
221
322
 
222
323
  /** Download a release tarball into `vendor/` and extract it under `name`.
223
324
  * Release tarballs are used (not git clones) because they ship generated
224
- * sources, so no ragel/autotools step is needed. */
225
- function ensureTarballSource(name: string, url: string, extractedName: string): string {
325
+ * sources, so no ragel/autotools step is needed. `marker` is a file that only
326
+ * exists once the tarball has been extracted (CMake projects ship
327
+ * `CMakeLists.txt`; FFmpeg ships `configure`). */
328
+ function ensureTarballSource(name: string, url: string, extractedName: string, marker = "CMakeLists.txt"): string {
226
329
  const destination = join(vendorDir, name);
227
- if (existsSync(join(destination, "CMakeLists.txt"))) return destination;
330
+ if (existsSync(join(destination, marker))) return destination;
228
331
  const tarball = join(vendorDir, `${name}.tar.xz`);
229
332
  console.log(`xbintsc: fetching ${name} from ${url}`);
230
333
  mkdirSync(vendorDir, { recursive: true });
@@ -284,6 +387,43 @@ if (!existsSync(harfbuzzArchive() ?? "")) {
284
387
  console.log(`xbintsc: reusing existing ${harfbuzzArchive()}`);
285
388
  }
286
389
 
390
+ /* -- 2c. FFmpeg (media decoding) ------------------------------------------ */
391
+
392
+ /* Windows builds have no `configure`/`make`, so media support is compiled out
393
+ * there; the element still works and reports an `error` event. */
394
+ let ffmpegLibraries: string[] | undefined;
395
+ if (isWindows) {
396
+ console.log("xbintsc: skipping FFmpeg on Windows; <video>/<audio> will report an error");
397
+ } else {
398
+ ensureTarballSource("ffmpeg", FFMPEG_URL, `ffmpeg-${FFMPEG_VERSION}`, "configure");
399
+ ffmpegLibraries = ffmpegStaticLibraries();
400
+ if (!ffmpegLibraries) {
401
+ const cc = findCc();
402
+ const ccxx = findCxx();
403
+ console.log(`xbintsc: building minimal static FFmpeg ${FFMPEG_VERSION} with ${cc}`);
404
+ mkdirSync(ffmpegBuild, { recursive: true });
405
+ run(join(ffmpegSrc, "configure"), [
406
+ `--prefix=${ffmpegInstall}`,
407
+ "--enable-static", "--disable-shared",
408
+ "--disable-programs", "--disable-doc",
409
+ "--disable-htmlpages", "--disable-manpages", "--disable-podpages", "--disable-txtpages",
410
+ "--disable-avdevice", "--disable-avfilter", "--disable-postproc",
411
+ "--disable-everything",
412
+ "--enable-avformat", "--enable-avcodec", "--enable-avutil", "--enable-swscale", "--enable-swresample",
413
+ "--disable-network", "--disable-autodetect", "--disable-x86asm", "--disable-debug", "--enable-pic",
414
+ `--cc=${cc}`,
415
+ `--cxx=${ccxx}`,
416
+ ...FFMPEG_COMPONENTS,
417
+ ], { cwd: ffmpegBuild });
418
+ run("make", ["-j", String(availableParallelism())], { cwd: ffmpegBuild });
419
+ run("make", ["install"], { cwd: ffmpegBuild });
420
+ ffmpegLibraries = ffmpegStaticLibraries();
421
+ } else {
422
+ console.log(`xbintsc: reusing existing ${ffmpegInstall}/lib`);
423
+ }
424
+ if (!ffmpegLibraries) fail("FFmpeg build did not produce the expected static libraries");
425
+ }
426
+
287
427
  /* -- 3. engine objects ---------------------------------------------------- */
288
428
 
289
429
  const cxx = findCxx();
@@ -295,12 +435,14 @@ const includeFlags = [
295
435
  "-I", join(freetypeBuild, "include"),
296
436
  "-I", join(harfbuzzSrc, "src"),
297
437
  ];
438
+ if (ffmpegLibraries) includeFlags.push("-I", join(ffmpegInstall, "include"));
298
439
  const sources = readdirSync(engineDir)
299
440
  .filter((entry) => entry.endsWith(".cpp"))
300
441
  .sort();
301
442
 
302
443
  const objects: string[] = [];
303
444
  const cxxFlags = ["-std=c++17", "-O2", "-Wall", "-Wextra", ...includeFlags];
445
+ if (ffmpegLibraries) cxxFlags.push("-DXT_GUI_WITH_FFMPEG=1");
304
446
  /* The engine is C++ and the MSVC C++ runtime must match the static C CRT that
305
447
  * clang links by default, so let every engine object record `libcpmt` as a
306
448
  * default library. The extension itself only contributes the DLL import library
@@ -324,7 +466,7 @@ const harfbuzz = harfbuzzArchive();
324
466
  if (!sdl || !freetype || !harfbuzz) {
325
467
  fail("one of the SDL3/FreeType/HarfBuzz archives was not found after building");
326
468
  }
327
- const libraries = [sdl, freetype, harfbuzz];
469
+ const libraries = [sdl, freetype, harfbuzz, ...(ffmpegLibraries ?? [])];
328
470
 
329
471
  mkdirSync(outDir, { recursive: true });
330
472
  rmSync(output, { force: true });
@@ -36,7 +36,11 @@ const IGNORED_DIRECTORIES = new Set([
36
36
  ]);
37
37
 
38
38
  /** Vendored third-party sources that ship verbatim and are not ours to split. */
39
- const VENDORED_FILES = new Set([join("runtime", "ext_gui", "stb_image.h")]);
39
+ const VENDORED_FILES = new Set([
40
+ join("runtime", "ext_gui", "stb_image.h"),
41
+ join("runtime", "ext_gui", "nanosvg.h"),
42
+ join("runtime", "ext_gui", "nanosvgrast.h"),
43
+ ]);
40
44
 
41
45
  /** Machine-generated files: they are reproduced from their sources by a script,
42
46
  * so their size is not a sign of a module that needs splitting. */
@@ -0,0 +1,28 @@
1
+ #!/bin/sh
2
+ #
3
+ # Install everything Linux needs to build and exercise the GUI archive.
4
+ #
5
+ # `npm run gui` compiles SDL3 from source, so SDL3 needs to *find* the window
6
+ # system it will use: without the X11/Wayland development headers it either
7
+ # configures itself without a window backend and then aborts ("SDL could not
8
+ # find X11 or Wayland development libraries"), or -- now -- `scripts/build-gui.ts`
9
+ # says which package is missing. `cmake` configures the vendored libraries,
10
+ # `clang` compiles the runtime and the emitted LLVM IR, `libvulkan-dev` plus a
11
+ # driver gives SDL_GPU the Vulkan backend it selects on Linux, and `xvfb` lets
12
+ # the example and the e2e suites run headlessly.
13
+ #
14
+ # One list, one place: every Linux job that runs `npm run gui` (or a GUI
15
+ # example) sources this, so a job cannot quietly miss a package the others
16
+ # install. Requires sudo and a Debian/Ubuntu host.
17
+ #
18
+ # Usage: sudo sh scripts/install-gui-deps.sh
19
+ set -eu
20
+
21
+ apt-get update
22
+ apt-get install -y \
23
+ clang cmake \
24
+ libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev \
25
+ libxinerama-dev libxfixes-dev libxkbcommon-dev libwayland-dev \
26
+ wayland-protocols libdecor-0-dev libasound2-dev libpulse-dev \
27
+ libdbus-1-dev libudev-dev libdrm-dev libgbm-dev libgl1-mesa-dev \
28
+ libegl1-mesa-dev libvulkan-dev mesa-vulkan-drivers xvfb