瀏覽代碼

feat: system-info bindings batch

SDL3Stdinc (malloc/calloc/free), SDL3Filesystem (paths, mkdir,
remove, rename, copy), SDL3Power, SDL3Locale (typed locale-list
pointer), SDL3Clipboard (plain text). New test_sysinfo and
test_clipboard smoke tests; runner and Makefile extended.
Verified with gm2 16.0.1, 7/7 tests green headless.
Eric Streit 2 周之前
父節點
當前提交
ec6be4244d
共有 11 個文件被更改,包括 365 次插入 和 4 次删除
  1. 1 1
      Makefile
  2. 5 0
      README.md
  3. 38 0
      docs/summary-v0.2.0.md
  4. 39 0
      src/SDL3Clipboard.def
  5. 67 0
      src/SDL3Filesystem.def
  6. 31 0
      src/SDL3Locale.def
  7. 28 0
      src/SDL3Power.def
  8. 26 0
      src/SDL3Stdinc.def
  9. 7 3
      tests/run_tests.sh
  10. 49 0
      tests/test_clipboard.mod
  11. 74 0
      tests/test_sysinfo.mod

+ 1 - 1
Makefile

@@ -17,7 +17,7 @@ SDL_LIB := -L$(SDL_BLD) -lSDL3 -Wl,-rpath,$(abspath $(SDL_BLD))
 
 INCLUDES := -I$(SRC_DIR) -I$(LIB_DIR) $(SDL_INC)
 
-TESTS    := test_init test_version test_rect test_window test_key
+TESTS    := test_init test_version test_rect test_window test_key test_sysinfo test_clipboard
 EXAMPLES := hello_window version event_loop renderer_demo
 SHOWCASES := showcase_clear showcase_rectangles showcase_primitives showcase_snake showcase_lines showcase_points showcase_debug_text
 

+ 5 - 0
README.md

@@ -23,6 +23,11 @@ Starter set, verified with gm2 + SDL 3.4.16 on Linux:
 | `SDL3Rect` | `<SDL3/SDL_rect.h>` | pure-M2 `SDLPoint`/`SDLFPoint`/`SDLRect`/`SDLFRect` + helpers (`SHORTREAL` = C float) |
 | `SDL3Render` | `<SDL3/SDL_render.h>` | window+renderer creation, draw color (+float), logical presentation, clear/present, points/lines/rects (float `SDLFRect`), render scale, output size, debug text |
 | `SDL3Key` | `<SDL3/SDL_keycode.h>` | pure-M2 keycode constants (arrows, Q/R, Escape...) |
+| `SDL3Stdinc` | `<SDL3/SDL_stdinc.h>` | `malloc`/`calloc`/`free` (frees SDL-allocated strings) |
+| `SDL3Filesystem` | `<SDL3/SDL_filesystem.h>` | base/pref/user-folder/cwd paths, mkdir/remove/rename/copy (no callbacks) |
+| `SDL3Power` | `<SDL3/SDL_power.h>` | `GetPowerInfo` state + seconds/percent |
+| `SDL3Locale` | `<SDL3/SDL_locale.h>` | preferred-locales list (`SDLLocale` records) |
+| `SDL3Clipboard` | `<SDL3/SDL_clipboard.h>` | plain-text set/get/has (+primary selection; no MIME callbacks) |
 | `SDL3Utils` (`lib/`) | — | `CStrToM2()` + `GetErrorString()`, version decode, `DrainEvents()` quit pump (QUIT + WINDOW_CLOSE_REQUESTED), `PollM2Event()` decoded poll (keycode at offset 28) |
 
 ## Layout

+ 38 - 0
docs/summary-v0.2.0.md

@@ -0,0 +1,38 @@
+# m2SDL3 summary — v0.2.0 (2026-09-24)
+
+Follows v0.1.0 (`ed80b57`): system-info bindings batch
+(audio/camera/gamepad still open — see "Next").
+
+## v0.2.0 — filesystem, power, locale, clipboard
+
+- `src/SDL3Stdinc.def` — `SDL_malloc`/`SDL_calloc`/`SDL_free`
+  (`size_t` = 64 bits, hence `LONGCARD`); SDL-allocated strings
+  must be released with `SDL_free`, not libc free
+- `src/SDL3Filesystem.def` — `<SDL_filesystem.h>` without callbacks:
+  `GetBasePath`/`GetPrefPath`/`GetUserFolder`/`GetCurrentDirectory`
+  (SDL-allocated, possibly `NIL`), `CreateDirectory`, `RemovePath`,
+  `RenamePath`, `CopyFile`; `FolderHome..FolderVideos` constants
+- `src/SDL3Power.def` — `GetPowerInfo` state id + `VAR` seconds/percent
+- `src/SDL3Locale.def` — `GetPreferredLocales` returns a typed
+  `LocaleVecPtr` (pointer to array of `LocalePtr`); single `SDL_free`
+  releases the list; `SDLLocale` = language/country `ADDRESS` pair
+- `src/SDL3Clipboard.def` — plain-text `Set`/`Get`/`HasClipboardText`
+  plus primary-selection variants (video init needed; dummy works);
+  MIME/callback APIs left out (no C-callback mapping in this style)
+- `tests/test_sysinfo.mod` — power state, locale decode
+  (`fr` here), base/cwd paths; `tests/test_clipboard.mod` —
+  set/has/get round-trip; runner + `Makefile` extended (7/7 green)
+
+## GM2 FFI rules learned (additions)
+
+9. gm2 has no `CAST` — return typed pointers (`POINTER TO ...`)
+   from `FOR "C"` procedures instead of `ADDRESS` + cast.
+10. Imported type names clash with local `TYPE` declarations —
+    declare shared shapes once, in the `.def`.
+11. `SDL_GetPreferredLocales`-style out-arrays: bound the Modula-2
+    array type (256) and range-check `count` before indexing.
+
+## Next
+
+Audio streams (spec records + buffer flow), camera (needs hardware),
+gamepad/joystick, typed `SDL_Event` record, `infinite-monkeys` demo.

+ 39 - 0
src/SDL3Clipboard.def

@@ -0,0 +1,39 @@
+DEFINITION MODULE FOR "C" SDL3Clipboard ;
+
+(*
+   m2SDL3 - clipboard text (<SDL3/SDL_clipboard.h>), plain-text subset.
+   MIME/callback APIs (SetClipboardData and friends) are left out: C
+   function pointers have no Modula-2 mapping in this binding style.
+
+   Needs SDL_INIT_VIDEO (dummy driver works headless). Getters return
+   SDL-allocated strings; release with SDL3Stdinc.SDL_free.
+   Primary-selection calls are X11-only and harmless elsewhere.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   SDL_SetClipboardText, SDL_GetClipboardText, SDL_HasClipboardText,
+   SDL_SetPrimarySelectionText, SDL_GetPrimarySelectionText,
+   SDL_HasPrimarySelectionText;
+
+
+(* bool SDL_SetClipboardText(const char *text) *)
+PROCEDURE SDL_SetClipboardText (text: ARRAY OF CHAR) : [ BOOLEAN ];
+
+(* char *SDL_GetClipboardText(void) *)
+PROCEDURE SDL_GetClipboardText () : ADDRESS;
+
+(* bool SDL_HasClipboardText(void) *)
+PROCEDURE SDL_HasClipboardText () : [ BOOLEAN ];
+
+(* bool SDL_SetPrimarySelectionText(const char *text) *)
+PROCEDURE SDL_SetPrimarySelectionText (text: ARRAY OF CHAR) : [ BOOLEAN ];
+
+(* char *SDL_GetPrimarySelectionText(void) *)
+PROCEDURE SDL_GetPrimarySelectionText () : ADDRESS;
+
+(* bool SDL_HasPrimarySelectionText(void) *)
+PROCEDURE SDL_HasPrimarySelectionText () : [ BOOLEAN ];
+
+END SDL3Clipboard.

+ 67 - 0
src/SDL3Filesystem.def

@@ -0,0 +1,67 @@
+DEFINITION MODULE FOR "C" SDL3Filesystem ;
+
+(*
+   m2SDL3 - filesystem paths (<SDL3/SDL_filesystem.h>), starter subset
+   without callbacks. Callback APIs (EnumerateDirectory) and
+   multi-string out-params (GlobDirectory) are left out: C function
+   pointers have no Modula-2 mapping in this binding style.
+
+   Ownership: GetBasePath/GetPrefPath/GetUserFolder/GetCurrentDirectory
+   return SDL-allocated NUL-terminated strings (possibly NIL); release
+   them with SDL3Stdinc.SDL_free. GetBasePath needs no SDL_Init.
+   GetPrefPath("org", "app") creates the dir if needed.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   FolderHome, FolderDesktop, FolderDocuments, FolderDownloads,
+   FolderMusic, FolderPictures, FolderPublicShare, FolderSavedGames,
+   FolderScreenshots, FolderTemplates, FolderVideos, FolderCount,
+
+   SDL_GetBasePath, SDL_GetPrefPath, SDL_GetUserFolder,
+   SDL_GetCurrentDirectory,
+   SDL_CreateDirectory, SDL_RemovePath,
+   SDL_RenamePath, SDL_CopyFile;
+
+
+CONST
+   FolderHome        = 0;
+   FolderDesktop     = 1;
+   FolderDocuments   = 2;
+   FolderDownloads   = 3;
+   FolderMusic       = 4;
+   FolderPictures    = 5;
+   FolderPublicShare = 6;
+   FolderSavedGames  = 7;
+   FolderScreenshots = 8;
+   FolderTemplates   = 9;
+   FolderVideos      = 10;
+   FolderCount       = 11;
+
+
+(* const char *SDL_GetBasePath(void) *)
+PROCEDURE SDL_GetBasePath () : ADDRESS;
+
+(* char *SDL_GetPrefPath(const char *org, const char *app) *)
+PROCEDURE SDL_GetPrefPath (org, app: ARRAY OF CHAR) : ADDRESS;
+
+(* const char *SDL_GetUserFolder(SDL_Folder folder) *)
+PROCEDURE SDL_GetUserFolder (folder: INTEGER) : ADDRESS;
+
+(* char *SDL_GetCurrentDirectory(void) *)
+PROCEDURE SDL_GetCurrentDirectory () : ADDRESS;
+
+(* bool SDL_CreateDirectory(const char *path) *)
+PROCEDURE SDL_CreateDirectory (path: ARRAY OF CHAR) : [ BOOLEAN ];
+
+(* bool SDL_RemovePath(const char *path) *)
+PROCEDURE SDL_RemovePath (path: ARRAY OF CHAR) : [ BOOLEAN ];
+
+(* bool SDL_RenamePath(const char *oldpath, const char *newpath) *)
+PROCEDURE SDL_RenamePath (oldpath, newpath: ARRAY OF CHAR) : [ BOOLEAN ];
+
+(* bool SDL_CopyFile(const char *oldpath, const char *newpath) *)
+PROCEDURE SDL_CopyFile (oldpath, newpath: ARRAY OF CHAR) : [ BOOLEAN ];
+
+END SDL3Filesystem.

+ 31 - 0
src/SDL3Locale.def

@@ -0,0 +1,31 @@
+DEFINITION MODULE FOR "C" SDL3Locale ;
+
+(*
+   m2SDL3 - user locale (<SDL3/SDL_locale.h>).
+
+   SDL_GetPreferredLocales returns a NIL-terminated array of
+   POINTER TO SDLLocale (countptr receives the length, may be NIL);
+   each entry holds language/country as NUL-terminated C strings
+   (country may be NIL). Release the whole list with a single
+   SDL3Stdinc.SDL_free. No SDL_Init needed.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   SDLLocale, LocalePtr, LocaleVecPtr,
+   SDL_GetPreferredLocales;
+
+
+TYPE
+   SDLLocale = RECORD language, country: ADDRESS END;
+   LocalePtr = POINTER TO SDLLocale;
+   LocaleVecPtr = POINTER TO ARRAY [0..255] OF LocalePtr;
+
+
+(* SDL_Locale **SDL_GetPreferredLocales(int *count) - up to 256
+   entries used here; the C array is longer in theory but locale
+   lists are tiny in practice. *)
+PROCEDURE SDL_GetPreferredLocales (VAR count: INTEGER) : LocaleVecPtr;
+
+END SDL3Locale.

+ 28 - 0
src/SDL3Power.def

@@ -0,0 +1,28 @@
+DEFINITION MODULE FOR "C" SDL3Power ;
+
+(*
+   m2SDL3 - power status (<SDL3/SDL_power.h>). Single call, no init
+   needed. Returns the state; seconds/percent come back through VAR
+   params (-1 when unknown). No battery here is a valid result
+   (PowerNoBattery), not a failure.
+*)
+
+EXPORT UNQUALIFIED
+   PowerError, PowerUnknown, PowerOnBattery,
+   PowerNoBattery, PowerCharging, PowerCharged,
+   SDL_GetPowerInfo;
+
+
+CONST
+   PowerError     = -1;
+   PowerUnknown   = 0;
+   PowerOnBattery = 1;
+   PowerNoBattery = 2;
+   PowerCharging  = 3;
+   PowerCharged   = 4;
+
+
+(* SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent) *)
+PROCEDURE SDL_GetPowerInfo (VAR seconds, percent: INTEGER) : INTEGER;
+
+END SDL3Power.

+ 26 - 0
src/SDL3Stdinc.def

@@ -0,0 +1,26 @@
+DEFINITION MODULE FOR "C" SDL3Stdinc ;
+
+(*
+   m2SDL3 - SDL memory management (<SDL3/SDL_stdinc.h>), starter subset.
+
+   SDL-allocated strings (base/pref paths, clipboard text, locale
+   lists) must be released with SDL_free, not libc free. size_t is
+   64 bits here, hence LONGCARD.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+
+EXPORT UNQUALIFIED
+   SDL_malloc, SDL_calloc, SDL_free;
+
+
+(* void *SDL_malloc(size_t size) *)
+PROCEDURE SDL_malloc (size: LONGCARD) : ADDRESS;
+
+(* void *SDL_calloc(size_t nmemb, size_t size) *)
+PROCEDURE SDL_calloc (nmemb, size: LONGCARD) : ADDRESS;
+
+(* void SDL_free(void *mem) *)
+PROCEDURE SDL_free (mem: ADDRESS);
+
+END SDL3Stdinc.

+ 7 - 3
tests/run_tests.sh

@@ -24,14 +24,18 @@ $GM2 -I"$ROOT/src" -I"$ROOT/lib" $SDL_INC $GM2FLAGS -c "$ROOT/lib/SDL3Utils.mod"
 # shellcheck disable=SC2086
 $GM2 -I"$ROOT/src" -I"$ROOT/lib" $SDL_INC $GM2FLAGS -c "$ROOT/src/SDL3Key.mod" -o "$OBJ/SDL3Key.o"
 pass=0; fail=0
-for t in test_init test_version test_rect test_window test_key; do
+for t in test_init test_version test_rect test_window test_key test_sysinfo test_clipboard; do
   echo "== $t =="
   # shellcheck disable=SC2086
   $GM2 -I"$ROOT/src" -I"$ROOT/lib" $SDL_INC $GM2FLAGS "$ROOT/tests/$t.mod" \
        "$OBJ/SDL3Rect.o" "$OBJ/SDL3Utils.o" "$OBJ/SDL3Key.o" \
        -o "$BIN/$t" $SDL_LIB
-  if [ "$t" = "test_window" ] && [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
-    SDL_VIDEODRIVER=dummy "$BIN/$t"
+  if [ "$t" = "test_window" ] || [ "$t" = "test_clipboard" ]; then
+    if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
+      SDL_VIDEODRIVER=dummy "$BIN/$t"
+    else
+      "$BIN/$t"
+    fi
   else
     "$BIN/$t"
   fi

+ 49 - 0
tests/test_clipboard.mod

@@ -0,0 +1,49 @@
+MODULE test_clipboard ;
+
+(*
+   m2SDL3 smoke test: clipboard text round-trip under the dummy
+   video driver. Set text, check HasClipboardText, read back and
+   compare. SDL-allocated readback is released with SDL_free.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM SDL3 IMPORT SDL_Init, SDL_Quit, SDLInitVideo;
+FROM SDL3Clipboard IMPORT SDL_SetClipboardText, SDL_GetClipboardText,
+   SDL_HasClipboardText;
+FROM SDL3Stdinc IMPORT SDL_free;
+FROM SDL3Utils IMPORT CStrToM2;
+FROM StrLib IMPORT StrEqual;
+FROM libc IMPORT printf;
+
+VAR
+   p: ADDRESS;
+   buf: ARRAY [0..127] OF CHAR;
+
+PROCEDURE Fail (msg: ARRAY OF CHAR);
+BEGIN
+   printf("FAIL test_clipboard: %s\n", msg);
+   HALT(1)
+END Fail;
+
+BEGIN
+   IF NOT SDL_Init(SDLInitVideo) THEN
+      printf("FAIL test_clipboard: SDL_Init video failed\n");
+      HALT(1)
+   END;
+
+   IF NOT SDL_SetClipboardText("m2sdl3-clipboard-probe") THEN
+      Fail("set failed")
+   END;
+   IF NOT SDL_HasClipboardText() THEN Fail("has-clipboard false") END;
+   p := SDL_GetClipboardText();
+   IF p = NIL THEN Fail("get returned NIL") END;
+   CStrToM2(p, buf);
+   SDL_free(p);
+   printf("clipboard: %s\n", buf);
+   IF NOT StrEqual(buf, "m2sdl3-clipboard-probe") THEN
+      Fail("round-trip mismatch")
+   END;
+
+   SDL_Quit;
+   printf("PASS test_clipboard\n")
+END test_clipboard.

+ 74 - 0
tests/test_sysinfo.mod

@@ -0,0 +1,74 @@
+MODULE test_sysinfo ;
+
+(*
+   m2SDL3 smoke tests, no window needed:
+   - power: SDL_GetPowerInfo returns a known state id.
+   - locale: preferred-locales list decodes (first language nonempty).
+   - filesystem: base path + current directory are nonempty strings.
+   SDL-allocated results are released with SDL_free.
+*)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM SDL3Power IMPORT SDL_GetPowerInfo,
+   PowerError, PowerUnknown, PowerOnBattery,
+   PowerNoBattery, PowerCharging, PowerCharged;
+FROM SDL3Locale IMPORT SDLLocale, LocalePtr, LocaleVecPtr,
+   SDL_GetPreferredLocales;
+FROM SDL3Filesystem IMPORT SDL_GetBasePath, SDL_GetCurrentDirectory;
+FROM SDL3Stdinc IMPORT SDL_free;
+FROM SDL3Utils IMPORT CStrToM2;
+FROM libc IMPORT printf;
+
+VAR
+   secs, pct, state, n, i: INTEGER;
+   vec: LocaleVecPtr;
+   entry: LocalePtr;
+   p: ADDRESS;
+   buf: ARRAY [0..255] OF CHAR;
+
+PROCEDURE Fail (msg: ARRAY OF CHAR);
+BEGIN
+   printf("FAIL test_sysinfo: %s\n", msg);
+   HALT(1)
+END Fail;
+
+BEGIN
+   (* power *)
+   secs := 0; pct := 0;
+   state := SDL_GetPowerInfo(secs, pct);
+   printf("power state=%d secs=%d pct=%d\n", state, secs, pct);
+   IF (state < PowerError) OR (state > PowerCharged) THEN
+      Fail("power state out of range")
+   END;
+
+   (* locale *)
+   n := 0;
+   vec := SDL_GetPreferredLocales(n);
+   IF vec = NIL THEN Fail("locale list NIL") END;
+   printf("locales: %d\n", n);
+   IF n <= 0 THEN Fail("locale count not positive") END;
+   IF n > 256 THEN Fail("locale count over 256") END;
+   entry := vec^[0];
+   IF entry = NIL THEN Fail("first locale NIL") END;
+   CStrToM2(entry^.language, buf);
+   printf("first locale: %s\n", buf);
+   IF buf[0] = 0C THEN Fail("first language empty") END;
+   FOR i := 0 TO n - 1 DO
+      IF vec^[i] = NIL THEN Fail("locale entry NIL") END
+   END;
+   SDL_free(vec);
+
+   (* filesystem *)
+   p := SDL_GetBasePath();
+   CStrToM2(p, buf);
+   SDL_free(p);
+   printf("base: %s\n", buf);
+   IF buf[0] = 0C THEN Fail("base path empty") END;
+   p := SDL_GetCurrentDirectory();
+   CStrToM2(p, buf);
+   SDL_free(p);
+   printf("cwd: %s\n", buf);
+   IF buf[0] = 0C THEN Fail("cwd empty") END;
+
+   printf("PASS test_sysinfo\n")
+END test_sysinfo.