diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 841a221..c3c2cc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,11 @@ # LLVM apt repo is added so clang-format-20 is available on ubuntu-24.04 # (which ships clang-format-18 by default). # -# All eight top-level jobs (lint, build, debug-build, sanitize, motif, -# osiris, xfig, gimp) run in parallel. Osiris and Xfig each have no Motif +# All nine top-level jobs (lint, build, debug-build, sanitize, motif, +# osiris, xfig, gimp, sdl3) run in parallel. The sdl3 job builds the +# compat stack against the native SDL3 backend (SDL_BACKEND=sdl3); every +# other job uses the default SDL2 backend, so the SDL3 translation shim in +# src/sdl-compat.h is only exercised there. Osiris and Xfig each have no Motif # dependency, so they live in their own jobs rather than queueing behind # the Motif + ViolaWWW + Mosaic chain. GIMP 0.54 is a Motif client but # gets its own job too: its plug-in build pulls in libpng/jpeg/tiff that @@ -970,3 +973,137 @@ jobs: - name: ccache stats if: ${{ !cancelled() }} run: ccache --show-stats + + # ---- Native SDL3 backend build + regression tests ---- + # ubuntu-24.04 ships no SDL3 in its repos (SDL3 GA'd after noble), and no + # GitHub runner image or readily-available PPA carries libsdl3-dev for + # noble (the savoury1 multimedia PPA, for instance, packages only SDL2), + # so build SDL3 + SDL3_ttf from their latest stable release-3.2.* tags + # into a cached prefix, then build the compat stack with SDL_BACKEND=sdl3 + # and run the sanitizer-friendly regression subset. This is the only job + # that exercises the native-SDL3 translation shim (src/sdl-compat.h). + sdl3: + runs-on: ubuntu-24.04 + env: + SDL3_PREFIX: ${{ github.workspace }}/.sdl3-prefix + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install build dependencies + # cmake/ninja drive the SDL3 source build; freetype/harfbuzz back + # SDL3_ttf (built with SDLTTF_VENDORED=OFF). No libsdl2-dev: the SDL3 + # backend links the libSDL3 / libSDL3_ttf built below directly. + # + # libx11-dev is still needed: the staged upstream libXt/libX11 sources + # fall through to a few system X11 protocol/private headers (e.g. + # X11/Xpoll.h) that the compat header tree does not stage. The other + # jobs pull these in transitively via libsdl2-dev -> libx11-dev; this + # job has no SDL2, so install libx11-dev explicitly. + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + clang ccache make pkg-config python3 libpixman-1-dev \ + cmake ninja-build git libfreetype-dev libharfbuzz-dev libx11-dev + + - name: Cache SDL3 + SDL3_ttf prefix + id: sdl3-cache + uses: actions/cache@v5 + with: + path: ${{ env.SDL3_PREFIX }} + key: sdl3-src-prefix-v1-${{ runner.os }} + + - name: Build SDL3 + SDL3_ttf from source + if: steps.sdl3-cache.outputs.cache-hit != 'true' + # --filter=blob:none keeps history + tags cheap so the latest stable + # release-3.2.* tag can be selected without a full checkout. + # SDL_UNIX_CONSOLE_BUILD=ON builds the dummy/offscreen video drivers + # without requiring X11 or Wayland development libraries; the compat + # regression tests run under SDL_VIDEODRIVER=dummy with the software + # renderer, so no real windowing backend is needed in CI. + run: | + set -eux + tmp="$(mktemp -d)" + git clone --filter=blob:none --no-checkout \ + https://github.com/libsdl-org/SDL.git "$tmp/SDL" + git -C "$tmp/SDL" checkout \ + "$(git -C "$tmp/SDL" tag -l 'release-3.2.*' | sort -V | tail -1)" + cmake -S "$tmp/SDL" -B "$tmp/SDL/b" -G Ninja \ + -DCMAKE_INSTALL_PREFIX="$SDL3_PREFIX" -DCMAKE_BUILD_TYPE=Release \ + -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TEST_LIBRARY=OFF \ + -DSDL_UNIX_CONSOLE_BUILD=ON + cmake --build "$tmp/SDL/b" + cmake --install "$tmp/SDL/b" + git clone --filter=blob:none --no-checkout \ + https://github.com/libsdl-org/SDL_ttf.git "$tmp/SDL_ttf" + git -C "$tmp/SDL_ttf" checkout \ + "$(git -C "$tmp/SDL_ttf" tag -l 'release-3.2.*' | sort -V | tail -1)" + cmake -S "$tmp/SDL_ttf" -B "$tmp/SDL_ttf/b" -G Ninja \ + -DCMAKE_INSTALL_PREFIX="$SDL3_PREFIX" -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$SDL3_PREFIX" -DBUILD_SHARED_LIBS=ON \ + -DSDLTTF_VENDORED=OFF -DSDLTTF_SAMPLES=OFF + cmake --build "$tmp/SDL_ttf/b" + cmake --install "$tmp/SDL_ttf/b" + rm -rf "$tmp" + + - name: Cache upstream tarballs and extracted source/headers + uses: actions/cache@v5 + with: + path: | + build/upstream + !build/upstream/**/*.o + !build/upstream/**/*.d + !build/upstream/motif + !build/upstream/mosaic + !build/upstream/osiris + !build/upstream/xfig-* + !build/upstream/.cache/xfig-* + !build/upstream/gimp-* + !build/upstream/.cache/gimp-* + key: upstream-src-v2-${{ runner.os }}-${{ hashFiles('scripts/sync-upstream-headers.py') }} + restore-keys: | + upstream-src-v2-${{ runner.os }}- + + - name: Cache ccache + uses: actions/cache@v5 + with: + path: ~/.cache/ccache + key: ccache-sdl3-v1-${{ runner.os }}-${{ github.sha }} + restore-keys: | + ccache-sdl3-v1-${{ runner.os }}- + + - name: Configure ccache and SDL3 environment + # PKG_CONFIG_PATH lets the build auto-detect sdl3/sdl3-ttf from the + # prefix; LD_LIBRARY_PATH lets the test binaries find libSDL3 at + # runtime. Both lib and lib64 are covered regardless of the prefix's + # GNUInstallDirs layout. + run: | + ccache --max-size=400M + ccache --zero-stats + { + echo "CC=ccache clang" + echo "PKG_CONFIG_PATH=$SDL3_PREFIX/lib/pkgconfig:$SDL3_PREFIX/lib64/pkgconfig" + echo "LD_LIBRARY_PATH=$SDL3_PREFIX/lib:$SDL3_PREFIX/lib64" + } >>"$GITHUB_ENV" + + - name: Confirm SDL3 is detected + run: | + pkg-config --exists sdl3 sdl3-ttf + echo "SDL3 $(pkg-config --modversion sdl3), SDL3_ttf $(pkg-config --modversion sdl3-ttf)" + + - name: Build libX11-compat.so (SDL_BACKEND=sdl3) + run: make SDL_BACKEND=sdl3 -j"$(nproc)" + + - name: Run regression tests (SDL_BACKEND=sdl3 make check-unit) + # check-unit runs the in-tree binaries headless (SDL_VIDEODRIVER= + # dummy, software renderer) plus symbol coverage; it does not need + # Xvfb. The make test runner also folds SDL_RUNTIME_LIBDIR (the sdl3 + # pkg-config libdir) onto LD_LIBRARY_PATH so libSDL3 resolves. + run: make SDL_BACKEND=sdl3 check-unit + + - name: Build bundled examples (SDL_BACKEND=sdl3) + run: make SDL_BACKEND=sdl3 examples -j"$(nproc)" + + - name: ccache stats + if: ${{ !cancelled() }} + run: ccache --show-stats diff --git a/README.md b/README.md index e7f2689..a71eb5c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # libx11-compat -`libx11-compat` is an in-process implementation of the [X Window System](https://en.wikipedia.org/wiki/X_Window_System) client library (Xlib) layered on top of [SDL2](https://www.libsdl.org/), SDL2_ttf, and pixman. +`libx11-compat` is an in-process implementation of the [X Window System](https://en.wikipedia.org/wiki/X_Window_System) client library (Xlib) layered on top of [SDL](https://www.libsdl.org/) (SDL2 or SDL3), SDL_ttf, and pixman. It lets existing Xlib clients keep their source unchanged while running on platforms where a conventional X server is unavailable or inconvenient: -macOS without XQuartz, Wayland-only sessions, headless CI, Android apps with their own SDL2 integration, and similar environments. +macOS without XQuartz, Wayland-only sessions, headless CI, Android apps with their own SDL integration, and similar environments. + +Both SDL major versions are supported from a single source tree: the build +auto-detects SDL3 (when both SDL3 and SDL3_ttf are visible to `pkg-config`) and +otherwise uses SDL2, and the backend can be selected explicitly. See +[SDL backend](#sdl-backend) below. The library is not a re-implementation of the X11 wire protocol and does not replace a real X server. Its goal is to keep legacy Xlib code building and running while it is being migrated to a different toolkit or display stack. @@ -10,7 +15,8 @@ Its goal is to keep legacy Xlib code building and running while it is being migr ## Building The build is Makefile-based and organized as small `mk/` fragments. -The required dependencies are SDL2, SDL2_ttf, and pixman. +The required dependencies are pixman plus one SDL stack: either SDL2 + SDL2_ttf +or SDL3 + SDL3_ttf (see [SDL backend](#sdl-backend)). Optional validation workloads may need their upstream build tools; Osiris uses Meson and Ninja at build time and links against libjpeg, libpng, and freetype. @@ -19,6 +25,30 @@ make make check ``` +### SDL backend + +The same source builds against either SDL major version, selected by the +`SDL_BACKEND` make variable: + +```sh +make # auto-detect: SDL3 if present, otherwise SDL2 +make SDL_BACKEND=sdl2 # force the SDL2 backend +make SDL_BACKEND=sdl3 # force the native SDL3 backend +``` + +When `SDL_BACKEND` is unset, the build prefers SDL3 when both `sdl3` and +`sdl3-ttf` are visible to `pkg-config`, and falls back to SDL2 otherwise; an +explicit `SDL_BACKEND=...` always wins. + +Under the SDL2 backend the compat stack links small in-tree wrapper shims +(`libSDL2-x11compat.so`, `libSDL2_ttf-x11compat.so`) that `dlopen` the host SDL2 +at runtime, so a system SDL2 that is itself +[sdl2-compat](https://github.com/libsdl-org/sdl2-compat) over SDL3 also works. +Under the SDL3 backend the stack links `libSDL3` and `libSDL3_ttf` directly; a +single chokepoint header (`src/sdl-compat.h`) translates the SDL2-spelled API +the sources are written against to SDL3, so no source changes are needed to +switch backends. + `make check` runs the in-tree C tests, exported-symbol coverage, Motif link and demo checks, replay-driven UI smoke tests, and system-X11 differential checks. For faster local loops, use `make check-unit`, `make check-smoke`, or `make check-differential` depending on the subsystem being changed. @@ -152,7 +182,7 @@ See [`docs/COVERAGE.md`](docs/COVERAGE.md) for the per-subsystem status table, s Most Xlib sources need no edits (porting is a build-system and runtime-environment exercise). The general shape: -1. Link the application against `build/libX11-compat.so` plus SDL2, SDL2_ttf, and pixman, replacing the system `-lX11`. +1. Link the application against `build/libX11-compat.so` plus the SDL stack it was built with (SDL2 + SDL2_ttf, or SDL3 + SDL3_ttf) and pixman, replacing the system `-lX11`. 2. Keep the existing Xlib source unchanged. 3. Drive the event loop with `XPending` / `XNextEvent` and `ConnectionNumber` + `select()`; the library pumps SDL internally. diff --git a/examples/clipboard.c b/examples/clipboard.c index 9036005..3dbd85d 100644 --- a/examples/clipboard.c +++ b/examples/clipboard.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include #include #include diff --git a/mk/common.mk b/mk/common.mk index 271ddab..33bbe03 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -35,7 +35,7 @@ endef $(OUT): @mkdir -p $@ -$(OUT)/%.o: %.c | $(OUT) +$(OUT)/%.o: %.c $(SDL_BACKEND_STAMP) | $(OUT) @mkdir -p $(dir $@) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ @@ -51,12 +51,11 @@ $(OUT)/%.o: %.c | $(OUT) # compiling these translation units and keeps the WIN32 macro rewrites in # Xlibint.h disabled so the function-pointer storage in src/xlibint-stubs.c # stays consistent across platforms. -$(OUT)/upstream/src/%.o: $(OUT)/upstream/src/%.c | $(OUT) +$(OUT)/upstream/src/%.o: $(OUT)/upstream/src/%.c $(SDL_BACKEND_STAMP) | $(OUT) @mkdir -p $(dir $@) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_EXTRA) -Wno-sign-compare -D_XLIBINT_ \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ - .PHONY: clean distclean ## Remove build artifacts diff --git a/mk/libxaw.mk b/mk/libxaw.mk index 9e3fa9c..e08dd61 100644 --- a/mk/libxaw.mk +++ b/mk/libxaw.mk @@ -46,7 +46,8 @@ $(LIBXAW_OBJ_DIR): $(LIBXAW_SRCS): $(UPSTREAM_HEADERS_STAMP) -$(LIBXAW_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_STAGED_H) | $(LIBXAW_OBJ_DIR) +$(LIBXAW_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_STAGED_H) \ + $(SDL_BACKEND_STAMP) | $(LIBXAW_OBJ_DIR) @echo " CC $(LIBXAW_SRC_DIR)/$*.c" $(Q)$(CC) $(LIBXAW_CPPFLAGS) $(CFLAGS) $(LIBXAW_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) \ diff --git a/mk/libxpm.mk b/mk/libxpm.mk index 6a771a5..846b6bd 100644 --- a/mk/libxpm.mk +++ b/mk/libxpm.mk @@ -31,7 +31,8 @@ $(LIBXPM_OBJ_DIR): $(LIBXPM_SRCS): $(UPSTREAM_HEADERS_STAMP) -$(LIBXPM_OBJ_DIR)/%.o: $(LIBXPM_SRC_DIR)/%.c $(UPSTREAM_HEADERS_STAMP) | $(LIBXPM_OBJ_DIR) +$(LIBXPM_OBJ_DIR)/%.o: $(LIBXPM_SRC_DIR)/%.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(LIBXPM_OBJ_DIR) @echo " CC $<" $(Q)$(CC) $(LIBXPM_CPPFLAGS) $(CFLAGS) $(LIBXPM_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ diff --git a/mk/libxt.mk b/mk/libxt.mk index 1a2c5b6..eba8610 100644 --- a/mk/libxt.mk +++ b/mk/libxt.mk @@ -142,16 +142,17 @@ $(OUT)/upstream/include/X11/Shell.h: $(LIBXT_GEN_DIR)/Shell.h # files have been staged before the recipe reads them; the explicit # dependency on LIBXT_GEN_HEADERS ensures StringDefs.h is available before # any unit that quotes it compiles. -$(LIBXT_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_GEN_HEADERS) $(LIBXT_STAGED_H) | \ - $(LIBXT_OBJ_DIR) +$(LIBXT_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_GEN_HEADERS) \ + $(LIBXT_STAGED_H) $(SDL_BACKEND_STAMP) | $(LIBXT_OBJ_DIR) @echo " CC $(LIBXT_SRC_DIR)/$*.c" $(Q)$(CC) $(LIBXT_CPPFLAGS) $(CFLAGS) $(LIBXT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) \ -c $(LIBXT_SRC_DIR)/$*.c -o $@ # StringDefs.c lives in $(LIBXT_GEN_DIR), not LIBXT_SRC_DIR. -$(LIBXT_OBJ_DIR)/StringDefs.o: $(LIBXT_GEN_C) $(LIBXT_GEN_HEADERS) $(LIBXT_STAGED_H) | \ - $(LIBXT_OBJ_DIR) $(UPSTREAM_HEADERS_STAMP) +$(LIBXT_OBJ_DIR)/StringDefs.o: $(LIBXT_GEN_C) $(LIBXT_GEN_HEADERS) \ + $(LIBXT_STAGED_H) $(SDL_BACKEND_STAMP) | $(LIBXT_OBJ_DIR) \ + $(UPSTREAM_HEADERS_STAMP) @echo " CC $<" $(Q)$(CC) $(LIBXT_CPPFLAGS) $(CFLAGS) $(LIBXT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ diff --git a/mk/sdl-wrapper.mk b/mk/sdl-wrapper.mk index 60abeea..6059c3c 100644 --- a/mk/sdl-wrapper.mk +++ b/mk/sdl-wrapper.mk @@ -1,3 +1,10 @@ +# The dlopen wrapper shims exist only for the SDL2 backend. Under +# SDL_BACKEND=sdl3 the compat stack links libSDL3 directly (see mk/sdl.mk), so +# this whole fragment collapses to an empty target list. +ifneq ($(SDL_USE_WRAPPER),1) +SDL_WRAPPER_TARGETS := +else + SDL_WRAPPER_TARGET := $(OUT)/libSDL2-x11compat.so SDL_TTF_WRAPPER_TARGET := $(OUT)/libSDL2_ttf-x11compat.so SDL_WRAPPER_TARGETS := $(SDL_WRAPPER_TARGET) $(SDL_TTF_WRAPPER_TARGET) @@ -36,3 +43,5 @@ $(SDL_TTF_WRAPPER_TARGET): $(SDL_TTF_WRAPPER_OBJS) | $(OUT) @echo " LD $@" $(Q)$(CC) $(LDFLAGS) $(call shared_lib_rpath_ldflags,$(notdir $@)) \ -shared -o $@ $(SDL_TTF_WRAPPER_OBJS) $(SDL_WRAPPER_LDLIBS) + +endif diff --git a/mk/sdl.mk b/mk/sdl.mk index 7b0354d..5e29bc1 100644 --- a/mk/sdl.mk +++ b/mk/sdl.mk @@ -1,14 +1,37 @@ # SDL detection and derived flags, isolated from config.mk so the rest of the -# build consumes one stable interface and a future SDL3 path can live here -# alongside SDL2 rather than being scattered across config / toolchain / tests. +# build consumes one stable interface across both SDL major versions rather +# than scattering the choice across config / toolchain / tests. # # Consumers use: # SDL_CPPFLAGS include flags to fold into CPPFLAGS -# SDL_COMPAT_LIBS link flags for the in-tree SDL wrapper shims +# SDL_COMPAT_LIBS link flags (wrapper shims for sdl2, real libSDL3 for sdl3) # SDL_RUNTIME_LIBDIR loader path dir tests need at runtime (may be empty) +# SDL_USE_WRAPPER 1 when the in-tree dlopen wrapper shims are built # SDL2_PREFIX install prefix, also read by mk/sdl-wrapper.mk and # mk/libxt.mk for the dlopen override and include path # +# SDL_BACKEND selects the SDL major version. +# sdl2: source compiles against the SDL2 API and links the in-tree dlopen +# wrapper shims (the runtime SDL2 may itself be sdl2-compat over SDL3). +# sdl3: source compiles against the native SDL3 API through src/sdl-compat.h +# and links libSDL3 / libSDL3_ttf directly, skipping the wrapper +# (extending the ~300-symbol wrapper to SDL3's renamed surface buys +# nothing). +# When SDL_BACKEND is not set on the command line or environment, auto-detect: +# prefer SDL3 (both sdl3 and sdl3-ttf present via pkg-config), else fall back to +# SDL2. An explicit SDL_BACKEND=... always wins. +ifeq ($(origin SDL_BACKEND),undefined) +SDL_BACKEND := $(shell $(PKG_CONFIG) --exists sdl3 sdl3-ttf 2>/dev/null && echo sdl3 || echo sdl2) +endif + +# Reject anything but the two known backends so a typo (e.g. SDL_BACKEND=SDL3) +# fails loudly instead of silently selecting the sdl2 branch below. +ifeq ($(filter $(SDL_BACKEND),sdl2 sdl3),) +$(error Invalid SDL_BACKEND '$(SDL_BACKEND)'; expected 'sdl2' or 'sdl3') +endif + +SDL_BACKEND_STAMP := $(OUT)/.sdl-backend + # Detection prefers pkg-config (the interface sdl2-compat standardizes on) and # falls back to sdl2-config for a classic SDL2 install. sdl2-compat ships both # today, but a distro that packages it with only the .pc file must still @@ -24,6 +47,24 @@ SDL2_TTF_PREFIX := $(shell $(PKG_CONFIG) --variable=prefix SDL2_ttf 2>/dev/null SDL2_TTF_CFLAGS := $(shell $(PKG_CONFIG) --cflags SDL2_ttf 2>/dev/null) SDL2_TTF_LIBS := $(shell $(PKG_CONFIG) --libs SDL2_ttf 2>/dev/null) +ifeq ($(SDL_BACKEND),sdl3) + +# Native SDL3 leg. The pkg-config modules are sdl3 and sdl3-ttf. +SDL3_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl3 2>/dev/null) +SDL3_LIBS := $(shell $(PKG_CONFIG) --libs sdl3 2>/dev/null) +SDL3_TTF_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl3-ttf 2>/dev/null) +SDL3_TTF_LIBS := $(shell $(PKG_CONFIG) --libs sdl3-ttf 2>/dev/null) +SDL3_LIBDIR := $(shell $(PKG_CONFIG) --variable=libdir sdl3 2>/dev/null) + +SDL_CPPFLAGS := $(SDL3_CFLAGS) $(SDL3_TTF_CFLAGS) -DLIBX11_COMPAT_SDL3 +SDL_COMPAT_LIBS := $(SDL3_LIBS) $(SDL3_TTF_LIBS) +SDL_RUNTIME_LIBDIR := $(SDL3_LIBDIR) +SDL_USE_WRAPPER := 0 + +else + +SDL_USE_WRAPPER := 1 + SDL_CPPFLAGS := $(if $(SDL2_PREFIX),-I$(SDL2_PREFIX)/include) \ $(if $(SDL2_TTF_PREFIX),-I$(SDL2_TTF_PREFIX)/include) \ $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) @@ -38,3 +79,22 @@ SDL_COMPAT_LIBS := -L$(abspath $(OUT)) -lSDL2-x11compat -lSDL2_ttf-x11compat # installs); fall back to prefix/lib for the sdl2-config-only case. Empty when # SDL is undetected. SDL_RUNTIME_LIBDIR := $(if $(SDL2_LIBDIR),$(SDL2_LIBDIR),$(if $(SDL2_PREFIX),$(SDL2_PREFIX)/lib)) + +endif + +.PHONY: sdl-backend-force + +$(SDL_BACKEND_STAMP): sdl-backend-force | $(OUT) + $(Q){ \ + printf '%s\n' 'SDL_BACKEND=$(SDL_BACKEND)'; \ + printf '%s\n' 'SDL_CPPFLAGS=$(SDL_CPPFLAGS)'; \ + printf '%s\n' 'SDL_COMPAT_LIBS=$(SDL_COMPAT_LIBS)'; \ + } > $@.tmp + $(Q)if test -r $@ && cmp -s $@.tmp $@; then \ + rm -f $@.tmp; \ + else \ + echo " SDL $(SDL_BACKEND)"; \ + mv $@.tmp $@; \ + sleep 1; \ + touch $@; \ + fi diff --git a/mk/xcompat-libs.mk b/mk/xcompat-libs.mk index 0b68321..7f9c781 100644 --- a/mk/xcompat-libs.mk +++ b/mk/xcompat-libs.mk @@ -19,13 +19,14 @@ XMU_UPSTREAM_SRC_BASES := \ XMU_UPSTREAM_SRCS := $(addprefix $(XMU_SRC_DIR)/,$(XMU_UPSTREAM_SRC_BASES)) XMU_UPSTREAM_OBJS := $(patsubst $(XMU_SRC_DIR)/%.c,$(XMU_OBJ_DIR)/%.o,$(XMU_UPSTREAM_SRCS)) -$(OUT)/xext-compat.o: compat/xext-compat.c $(UPSTREAM_HEADERS_STAMP) | $(OUT) +$(OUT)/xext-compat.o: compat/xext-compat.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ $(OUT)/xmu-compat.o: compat/xmu-compat.c $(UPSTREAM_HEADERS_STAMP) \ - $(LIBXT_STAGED_H) | $(OUT) + $(LIBXT_STAGED_H) $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ @@ -35,29 +36,34 @@ $(XMU_OBJ_DIR): $(XMU_UPSTREAM_SRCS): $(UPSTREAM_HEADERS_STAMP) -$(XMU_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_STAGED_H) | $(XMU_OBJ_DIR) +$(XMU_OBJ_DIR)/%.o: $(UPSTREAM_HEADERS_STAMP) $(LIBXT_STAGED_H) \ + $(SDL_BACKEND_STAMP) | $(XMU_OBJ_DIR) @echo " CC $(XMU_SRC_DIR)/$*.c" $(Q)$(CC) $(LIBXT_CPPFLAGS) -iquote $(OUT)/upstream/include/X11/Xmu \ $(CFLAGS) $(LIBXT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) \ -c $(XMU_SRC_DIR)/$*.c -o $@ -$(OUT)/xinerama-compat.o: compat/xinerama-compat.c $(UPSTREAM_HEADERS_STAMP) | $(OUT) +$(OUT)/xinerama-compat.o: compat/xinerama-compat.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ -$(OUT)/ice-compat.o: compat/ice-compat.c $(UPSTREAM_HEADERS_STAMP) | $(OUT) +$(OUT)/ice-compat.o: compat/ice-compat.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ -$(OUT)/sm-compat.o: compat/sm-compat.c $(UPSTREAM_HEADERS_STAMP) | $(OUT) +$(OUT)/sm-compat.o: compat/sm-compat.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ -$(OUT)/xft-compat.o: src/xft.c $(UPSTREAM_HEADERS_STAMP) | $(OUT) +$(OUT)/xft-compat.o: src/xft.c $(UPSTREAM_HEADERS_STAMP) \ + $(SDL_BACKEND_STAMP) | $(OUT) @echo " CC $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(STRICT_CFLAGS) $(CFLAGS_EXTRA) \ -MMD -MP -MF $(@:.o=.d) -MT $@ -MT $(@:.o=.d) -c $< -o $@ diff --git a/src/colors.c b/src/colors.c index 8d2ef90..52b2bc3 100644 --- a/src/colors.c +++ b/src/colors.c @@ -63,7 +63,7 @@ void freeColorStorage(void) } } -SDL_Color uLongToColor(SDL_PixelFormat *pixelFormat, unsigned long color) +SDL_Color uLongToColor(XcPixelFormat pixelFormat, unsigned long color) { SDL_Color res; SDL_GetRGBA(color, pixelFormat, &res.r, &res.g, &res.b, &res.a); diff --git a/src/colors.h b/src/colors.h index c38f7b4..10d595e 100644 --- a/src/colors.h +++ b/src/colors.h @@ -1,7 +1,7 @@ #ifndef _COLORS_H_ #define _COLORS_H_ -#include +#include "sdl-compat.h" #include // #if SDL_BYTEORDER != SDL_BIG_ENDIAN @@ -51,7 +51,7 @@ static inline unsigned long colorWithOpaqueDefault(unsigned long color) extern Colormap GREY_SCALE_COLORMAP; extern Colormap REAL_COLOR_COLORMAP; -SDL_Color uLongToColor(SDL_PixelFormat *pixelFormat, unsigned long color); +SDL_Color uLongToColor(XcPixelFormat pixelFormat, unsigned long color); Bool initColorStorage(void); void freeColorStorage(void); diff --git a/src/cursor.c b/src/cursor.c index 143cbd6..627bcca 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -1,6 +1,6 @@ #include "X11/Xlib.h" #include "X11/cursorfont.h" -#include +#include "sdl-ttf-compat.h" #include #include #include "errors.h" @@ -26,7 +26,7 @@ typedef struct { int height; } Cursor_; -static Uint32 mapXColorOrDefault(SDL_PixelFormat *format, +static Uint32 mapXColorOrDefault(XcPixelFormat format, const XColor *color, Uint8 fallback) { @@ -51,9 +51,11 @@ static SDL_Cursor *buildColorCursorFromBits(const unsigned char *srcBits, 0, width, height, 32, SDL_PIXELFORMAT_ARGB8888); if (!surface) return NULL; - Uint32 fg = mapXColorOrDefault(surface->format, foreground_color, 0x00); - Uint32 bg = mapXColorOrDefault(surface->format, background_color, 0xFF); - Uint32 transparent = SDL_MapRGBA(surface->format, 0, 0, 0, 0); + Uint32 fg = + mapXColorOrDefault(XC_SURFACE_FORMAT(surface), foreground_color, 0x00); + Uint32 bg = + mapXColorOrDefault(XC_SURFACE_FORMAT(surface), background_color, 0xFF); + Uint32 transparent = SDL_MapRGBA(XC_SURFACE_FORMAT(surface), 0, 0, 0, 0); int stride = (width + 7) / 8; Uint32 *pixels = (Uint32 *) surface->pixels; int pitchInPixels = surface->pitch / 4; diff --git a/src/display.c b/src/display.c index c82c85b..f520481 100644 --- a/src/display.c +++ b/src/display.c @@ -1,8 +1,8 @@ #include #include #include "X11/Xutil.h" -#include -#include +#include "sdl-compat.h" +#include "sdl-ttf-compat.h" #include "window.h" #include "errors.h" #include "events.h" @@ -146,7 +146,12 @@ Display *XOpenDisplay(_Xconst char *display_name) Bool sdlOwned = False; Bool ttfOwned = False; if (!SDL_WasInit(SDL_INIT_VIDEO)) { +#ifndef LIBX11_COMPAT_SDL3 + /* SDL3's SDL_SetMainReady lives in the separate SDL3_main helper, not + * libSDL3; this library drives SDL_Init itself and does not need it. + */ SDL_SetMainReady(); +#endif SDL_SetHint(SDL_HINT_VIDEO_X11_XKB, "0"); /* On macOS, the click that activates a background window is consumed by diff --git a/src/drawing.c b/src/drawing.c index 5e3bf83..0817b07 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -156,7 +156,7 @@ static FILE *renderStatsFile(void) return file; } -static Uint32 presentWakeTimerCallback(Uint32 interval, void *param) +static Uint32 presentWakeTimerCallback(XC_TIMER_CALLBACK_PARAMS) { (void) interval; (void) param; @@ -394,7 +394,7 @@ void drawWindowDataToScreen() continue; } screenTargetMutated = True; - Uint32 winFmt = winSurface->format->format; + Uint32 winFmt = XC_SURFACE_FMT_ENUM(winSurface); Uint32 readFmt = SDL_PIXELFORMAT_RGBA8888; int readRc = 0; SDL_Rect surfaceRects[DIRTY_RECT_BUDGET]; @@ -412,7 +412,7 @@ void drawWindowDataToScreen() (Uint8 *) winSurface->pixels + (size_t) rects[r].y * (size_t) winSurface->pitch + (size_t) rects[r].x * - (size_t) winSurface->format->BytesPerPixel; + (size_t) XC_SURFACE_BYTESPERPIXEL(winSurface); int rc = SDL_RenderReadPixels(screen, &rects[r], readFmt, pixels, winSurface->pitch); if (rc != 0) { @@ -1609,7 +1609,7 @@ static Bool pixelInsideShape(const ShapeMaskView *view, int64_t wx, int64_t wy) return False; Uint32 mp = getPixel(mask, (unsigned int) mx, (unsigned int) my); Uint8 r = 0, g = 0, b = 0; - SDL_GetRGB(mp, mask->format, &r, &g, &b); + SDL_GetRGB(mp, XC_SURFACE_FORMAT(mask), &r, &g, &b); if (!(r || g || b)) return False; } @@ -1621,7 +1621,7 @@ void putPixel(SDL_Surface *surface, unsigned int y, Uint32 pixel) { - int bytesPerPixel = surface->format->BytesPerPixel; + int bytesPerPixel = XC_SURFACE_BYTESPERPIXEL(surface); Uint8 *p = (Uint8 *) surface->pixels + y * surface->pitch + x * bytesPerPixel; switch (bytesPerPixel) { @@ -1650,7 +1650,7 @@ void putPixel(SDL_Surface *surface, Uint32 getPixel(SDL_Surface *surface, unsigned int x, unsigned int y) { - int bytesPerPixel = surface->format->BytesPerPixel; + int bytesPerPixel = XC_SURFACE_BYTESPERPIXEL(surface); Uint8 *pointer = (Uint8 *) surface->pixels + y * surface->pitch + x * bytesPerPixel; switch (bytesPerPixel) { @@ -2723,7 +2723,7 @@ int XCopyPlane(Display *display, opaqueColorIfAlphaUnset(gContext->foreground); unsigned long backgroundColor = opaqueColorIfAlphaUnset(gContext->background); - SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); + XcPixelFormat format = xcAllocFormat(SDL_PIXELFORMAT_RGBA8888); if (!format) { SDL_FreeSurface(srcSurface); handleOutOfMemory(0, display, 0, 0); @@ -2755,7 +2755,7 @@ int XCopyPlane(Display *display, */ destSurface = getRenderSurfaceRect(destRenderer, &destRect); if (!destSurface) { - SDL_FreeFormat(format); + xcFreeFormat(format); SDL_FreeSurface(srcSurface); handleError(0, display, dest, 0, BadMatch, 0); return 0; @@ -2764,7 +2764,7 @@ int XCopyPlane(Display *display, Uint32 *pixels = malloc((size_t) width * (size_t) height * sizeof(Uint32)); if (!pixels) { SDL_FreeSurface(destSurface); - SDL_FreeFormat(format); + xcFreeFormat(format); SDL_FreeSurface(srcSurface); handleOutOfMemory(0, display, 0, 0); return 0; @@ -2785,14 +2785,15 @@ int XCopyPlane(Display *display, } Uint32 srcPixel = getPixel(srcSurface, x, y); Uint8 red = 0, green = 0, blue = 0; - SDL_GetRGB(srcPixel, srcSurface->format, &red, &green, &blue); + SDL_GetRGB(srcPixel, XC_SURFACE_FORMAT(srcSurface), &red, &green, + &blue); Bool bitSet = plane == 1 ? (red || green || blue) : ((srcPixel & plane) != 0); pixels[y * width + x] = bitSet ? foreground : background; } } SDL_FreeSurface(destSurface); - SDL_FreeFormat(format); + xcFreeFormat(format); SDL_FreeSurface(srcSurface); SDL_Texture *texture = diff --git a/src/drawing.h b/src/drawing.h index 47d3d0f..5f8195f 100644 --- a/src/drawing.h +++ b/src/drawing.h @@ -1,7 +1,7 @@ #ifndef _DRAWING_H_ #define _DRAWING_H_ -#include +#include "sdl-compat.h" #include #include #include "resource-types.h" diff --git a/src/error.c b/src/error.c index 9e855a3..770a876 100644 --- a/src/error.c +++ b/src/error.c @@ -1,5 +1,5 @@ #include "errors.h" -#include +#include "sdl-compat.h" #include #include #include "display.h" diff --git a/src/events-expose.c b/src/events-expose.c index 0348ef2..4229328 100644 --- a/src/events-expose.c +++ b/src/events-expose.c @@ -11,7 +11,7 @@ */ #include -#include +#include "sdl-compat.h" #include #include "events.h" diff --git a/src/events.c b/src/events.c index 2cb59c5..f0e7a9b 100644 --- a/src/events.c +++ b/src/events.c @@ -5,7 +5,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "events.h" #include "events-ewmh.h" #include "events-expose.h" @@ -124,7 +124,7 @@ static void pumpEventsSafe(void) SDL_PumpEvents(); } -static Uint32 xtWakeTimerCallback(Uint32 interval, void *param) +static Uint32 xtWakeTimerCallback(XC_TIMER_CALLBACK_PARAMS) { (void) param; if (xtWakeEventType == (Uint32) -1) @@ -169,7 +169,7 @@ typedef struct PutBackEvent { static PutBackEvent *putBackEvents = NULL; static void updateWindowRenderTargets(Display *display); -static int onSdlEvent(void *userdata, SDL_Event *event); +static XC_EVENTFILTER_RET onSdlEvent(void *userdata, SDL_Event *event); static Bool getEventQueueLength(int *qlen); static int countPutBackEvents(Display *display); int convertEvent(Display *display, @@ -347,14 +347,14 @@ void wakeEventPipeForExternalEvent(Display *display) * src/events-expose.c. */ -static int onSdlEvent(void *userdata, SDL_Event *event) +static XC_EVENTFILTER_RET onSdlEvent(void *userdata, SDL_Event *event) { if (SCREEN_WINDOW == None || !IS_TYPE(SCREEN_WINDOW, WINDOW)) return 0; switch (event->type) { // case SDL_QUIT: - case SDL_WINDOWEVENT: + XC_CASE_WINDOWEVENT: if (!GET_WINDOW_STRUCT(SCREEN_WINDOW)->sdlWindow || event->window.windowID == SDL_GetWindowID(GET_WINDOW_STRUCT(SCREEN_WINDOW)->sdlWindow)) { @@ -1565,8 +1565,8 @@ int convertEvent(Display *display, FILL_STANDARD_VALUES(xkey); Window sdlKeyWindow = getWindowFromId(sdlEvent->key.windowID); xEvent->xkey.root = SCREEN_WINDOW; - xEvent->xkey.state = convertModifierState(sdlEvent->key.keysym.mod); - xEvent->xkey.keycode = (unsigned int) sdlEvent->key.keysym.sym & 0xFF; + xEvent->xkey.state = convertModifierState(XC_EVENT_KEYMOD(sdlEvent)); + xEvent->xkey.keycode = (unsigned int) XC_EVENT_KEYSYM(sdlEvent) & 0xFF; /* Route priority for key events: * 1. Active XGrabKeyboard (modal dialogs like Motif's Help popup) * 2. Passive XGrabKey match (Motif accelerators) @@ -1587,7 +1587,7 @@ int convertEvent(Display *display, } xEvent->xkey.window = eventWindow; xEvent->xkey.subwindow = None; - xEvent->xkey.time = sdlEvent->key.timestamp; + xEvent->xkey.time = XC_EVENT_TIME_MS(sdlEvent->key.timestamp); int pointerX = 0, pointerY = 0; if (!replayTargetReadPointer(&pointerX, &pointerY)) SDL_GetMouseState(&pointerX, &pointerY); @@ -1618,7 +1618,7 @@ int convertEvent(Display *display, FILL_STANDARD_VALUES(xbutton); Window sdlButtonWindow = getWindowFromId(sdlEvent->button.windowID); xEvent->xbutton.root = SCREEN_WINDOW; - xEvent->xbutton.time = sdlEvent->button.timestamp; + xEvent->xbutton.time = XC_EVENT_TIME_MS(sdlEvent->button.timestamp); translateSdlPointToRoot(display, sdlButtonWindow, sdlEvent->button.x, sdlEvent->button.y, &xEvent->xbutton.x_root, &xEvent->xbutton.y_root); @@ -1707,7 +1707,7 @@ int convertEvent(Display *display, FILL_STANDARD_VALUES(xmotion); Window sdlMotionWindow = getWindowFromId(sdlEvent->motion.windowID); xEvent->xmotion.root = SCREEN_WINDOW; - xEvent->xmotion.time = sdlEvent->motion.timestamp; + xEvent->xmotion.time = XC_EVENT_TIME_MS(sdlEvent->motion.timestamp); translateSdlPointToRoot(display, sdlMotionWindow, sdlEvent->motion.x, sdlEvent->motion.y, &xEvent->xmotion.x_root, &xEvent->xmotion.y_root); @@ -1716,7 +1716,7 @@ int convertEvent(Display *display, convertModifierState(SDL_GetModState()) | motionButtonState; Bool crossingQueued = postPointerCrossingEvents( display, xEvent->xmotion.x_root, xEvent->xmotion.y_root, - motionState, sdlEvent->motion.timestamp); + motionState, XC_EVENT_TIME_MS(sdlEvent->motion.timestamp)); long motionMask = motionMaskForButtonState(motionButtonState); /* Explicit XGrabPointer routing owns the event when * routePointerGrabEvent returns True; otherwise the implicit @@ -1760,9 +1760,9 @@ int convertEvent(Display *display, return -1; } break; - case SDL_WINDOWEVENT: + XC_CASE_WINDOWEVENT: eventWindow = getWindowFromId(sdlEvent->window.windowID); - switch (sdlEvent->window.event) { + switch (XC_WINDOW_SUBEVENT(sdlEvent)) { case SDL_WINDOWEVENT_SHOWN: LOG("Window %d shown\n", sdlEvent->window.windowID); if (eventWindow != None) { @@ -1807,16 +1807,16 @@ int convertEvent(Display *display, } /* fall through: MOVED, RESIZED and SIZE_CHANGED share a single * ConfigureNotify dispatch below; the unified handler keys off - * sdlEvent->window.event to decide which fields to query. + * XC_WINDOW_SUBEVENT(sdlEvent) to decide which fields to query. */ case SDL_WINDOWEVENT_RESIZED: - if (sdlEvent->window.event == SDL_WINDOWEVENT_RESIZED) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_RESIZED) { LOG("Window %d resized to %dx%d\n", sdlEvent->window.windowID, sdlEvent->window.data1, sdlEvent->window.data2); } /* fall through */ case SDL_WINDOWEVENT_SIZE_CHANGED: - if (sdlEvent->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_SIZE_CHANGED) { LOG("Window %d size changed to %dx%d\n", sdlEvent->window.windowID, sdlEvent->window.data1, sdlEvent->window.data2); @@ -1825,7 +1825,7 @@ int convertEvent(Display *display, FILL_STANDARD_VALUES(xconfigure); xEvent->xconfigure.event = eventWindow; xEvent->xconfigure.window = xEvent->xconfigure.event; - if (sdlEvent->window.event == SDL_WINDOWEVENT_MOVED) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_MOVED) { xEvent->xconfigure.x = sdlEvent->window.data1; xEvent->xconfigure.y = sdlEvent->window.data2; if (eventWindow != None) { @@ -1843,8 +1843,8 @@ int convertEvent(Display *display, &xEvent->xconfigure.x, &xEvent->xconfigure.y); } } - if (sdlEvent->window.event == SDL_WINDOWEVENT_RESIZED || - sdlEvent->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_RESIZED || + XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_SIZE_CHANGED) { xEvent->xconfigure.width = sdlEvent->window.data1; xEvent->xconfigure.height = sdlEvent->window.data2; /* After unification, every mapped top-level window draws into a @@ -1903,19 +1903,20 @@ int convertEvent(Display *display, LOG("Mouse entered window %d\n", sdlEvent->window.windowID); type = EnterNotify; case SDL_WINDOWEVENT_LEAVE: - if (sdlEvent->window.event == SDL_WINDOWEVENT_LEAVE) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_LEAVE) { LOG("Mouse left window %d\n", sdlEvent->window.windowID); type = LeaveNotify; } fillCrossingEvent(display, &xEvent->xcrossing, eventWindow, type, NotifyNormal, NotifyAncestor, convertModifierState(SDL_GetModState())); - xEvent->xcrossing.time = sdlEvent->window.timestamp; + xEvent->xcrossing.time = + XC_EVENT_TIME_MS(sdlEvent->window.timestamp); Bool queuedNestedLeaves = False; if (type == LeaveNotify) { queuedNestedLeaves = queueNestedPointerLeaves( display, eventWindow, xEvent->xcrossing.state, - sdlEvent->window.timestamp); + XC_EVENT_TIME_MS(sdlEvent->window.timestamp)); } /* Keep pointerHoverWindow in sync with the SDL-level crossing that * was just emitted; otherwise the next motion event's @@ -1936,7 +1937,7 @@ int convertEvent(Display *display, LOG("Window %d gained keyboard focus\n", sdlEvent->window.windowID); type = FocusIn; case SDL_WINDOWEVENT_FOCUS_LOST: - if (sdlEvent->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { + if (XC_WINDOW_SUBEVENT(sdlEvent) == SDL_WINDOWEVENT_FOCUS_LOST) { LOG("Window %d lost keyboard focus\n", sdlEvent->window.windowID); type = FocusOut; @@ -1965,7 +1966,8 @@ int convertEvent(Display *display, wmDeleteWindowAtom) { postEvent(display, eventWindow, ClientMessage, 32, wmProtocolsAtom, wmDeleteWindowAtom, - (Time) sdlEvent->window.timestamp); + (Time) XC_EVENT_TIME_MS( + sdlEvent->window.timestamp)); clientHandlesDelete = True; break; } @@ -1981,7 +1983,7 @@ int convertEvent(Display *display, break; default: LOG("Window %d got unknown event %d\n", sdlEvent->window.windowID, - sdlEvent->window.event); + XC_WINDOW_SUBEVENT(sdlEvent)); return -1; } break; @@ -2033,9 +2035,11 @@ int convertEvent(Display *display, */ LOG("SDL_APP_DIDENTERFOREGROUND\n"); return -1; +#ifndef LIBX11_COMPAT_SDL3 case SDL_SYSWMEVENT: /**< System specific event */ LOG("SDL_SYSWMEVENT\n"); return -1; +#endif case SDL_TEXTEDITING: /**< Keyboard text editing (composition) */ LOG("SDL_TEXTEDITING\n"); return -1; @@ -2045,6 +2049,14 @@ int convertEvent(Display *display, case SDL_MOUSEWHEEL: /**< Mouse wheel motion */ LOG("SDL_MOUSEWHEEL\n"); { +#ifdef LIBX11_COMPAT_SDL3 + /* SDL3 wheel deltas are floats carrying any sub-notch fraction + * directly in x/y, so accumulate them through the same notch filter + * the SDL2 precise path used. + */ + int wy = accumulateWheelNotch(0, sdlEvent->wheel.y, &wheelPreciseY); + int wx = accumulateWheelNotch(0, sdlEvent->wheel.x, &wheelPreciseX); +#else int wy = sdlEvent->wheel.y, wx = sdlEvent->wheel.x; #if SDL_VERSION_ATLEAST(2, 0, 18) /* sdl2-compat can report sub-notch wheel deltas in preciseX/Y while @@ -2055,6 +2067,7 @@ int convertEvent(Display *display, &wheelPreciseY); wx = accumulateWheelNotch(wx, sdlEvent->wheel.preciseX, &wheelPreciseX); +#endif #endif if (sdlEvent->wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { wy = -wy; @@ -2091,7 +2104,7 @@ int convertEvent(Display *display, } else { SDL_GetMouseState(&mx, &my); } - xEvent->xbutton.time = sdlEvent->wheel.timestamp; + xEvent->xbutton.time = XC_EVENT_TIME_MS(sdlEvent->wheel.timestamp); translateSdlPointToRoot(display, sdlWheelWindow, mx, my, &xEvent->xbutton.x_root, &xEvent->xbutton.y_root); @@ -2201,7 +2214,7 @@ int convertEvent(Display *display, xEvent->xbutton.window = xEvent->xbutton.root; // The event window is always the SDL Window. xEvent->xbutton.subwindow = None; - xEvent->xbutton.time = sdlEvent->tfinger.timestamp; + xEvent->xbutton.time = XC_EVENT_TIME_MS(sdlEvent->tfinger.timestamp); xEvent->xbutton.x = sdlEvent->tfinger.x; xEvent->xbutton.y = sdlEvent->tfinger.y; xEvent->xbutton.x_root = @@ -2214,6 +2227,7 @@ int convertEvent(Display *display, case SDL_FINGERMOTION: // Should not happen LOG("SDL_FINGERMOTION\n"); return -1; +#ifndef LIBX11_COMPAT_SDL3 case SDL_DOLLARGESTURE: LOG("SDL_DOLLARGESTURE\n"); return -1; @@ -2223,6 +2237,7 @@ int convertEvent(Display *display, case SDL_MULTIGESTURE: LOG("SDL_MULTIGESTURE\n"); return -1; +#endif case SDL_CLIPBOARDUPDATE: /**< The clipboard changed */ LOG("SDL_CLIPBOARDUPDATE\n"); return -1; @@ -2522,7 +2537,7 @@ static Bool isInteractiveSdlEvent(const SDL_Event *event) case SDL_MOUSEWHEEL: case SDL_KEYDOWN: case SDL_KEYUP: - case SDL_WINDOWEVENT: + XC_CASE_WINDOWEVENT: return True; default: return False; diff --git a/src/events.h b/src/events.h index 33a12bf..3c1eb22 100644 --- a/src/events.h +++ b/src/events.h @@ -2,7 +2,7 @@ #define _EVENTS_H_ #include -#include +#include "sdl-compat.h" #define SEND_EVENT_CODE 1 #define INTERNAL_EVENT_CODE 2 diff --git a/src/font.c b/src/font.c index 407d4c1..94a5e13 100644 --- a/src/font.c +++ b/src/font.c @@ -10,8 +10,8 @@ #include #include #include -#include -#include +#include "sdl-compat.h" +#include "sdl-ttf-compat.h" #include "errors.h" #include "colors.h" #include "resource-types.h" @@ -2378,6 +2378,13 @@ Bool renderText(Display *display, return False; } SDL_SetTextureBlendMode(fontTexture, SDL_BLENDMODE_BLEND); + /* Match SDL2's default nearest scaling: when the glyph texture is + * scaled to the core-metric cell, linear filtering (SDL3's default) + * would smear opaque strokes into partial coverage. + */ +#if defined(LIBX11_COMPAT_SDL3) || SDL_VERSION_ATLEAST(2, 0, 12) + SDL_SetTextureScaleMode(fontTexture, XC_SCALEMODE_NEAREST); +#endif textureAscent = TTF_FontAscent(GET_FONT(gContext->font)); if (stringHasEmbeddedNul || !textCacheInsert(gContext->font, (Uint32) foreground, renderer, diff --git a/src/font.h b/src/font.h index 99e28f8..e92917b 100644 --- a/src/font.h +++ b/src/font.h @@ -2,7 +2,7 @@ #define FONT_H #include -#include +#include "sdl-compat.h" struct TTF_Font; diff --git a/src/image.h b/src/image.h index f2aafc4..762a54e 100644 --- a/src/image.h +++ b/src/image.h @@ -1,7 +1,7 @@ #ifndef IMAGE_H #define IMAGE_H -#include +#include "sdl-compat.h" void freeImageStorage(void); void invalidatePutImageStagingTexture(SDL_Renderer *renderer); diff --git a/src/input.c b/src/input.c index 974777d..30e91fc 100644 --- a/src/input.c +++ b/src/input.c @@ -170,11 +170,11 @@ KeySym *XGetKeyboardMapping(Display *display, } int firstAfterRange = first_keycode + count; - for (int kc = SDLK_0; kc <= SDLK_9; kc++) { + for (int kc = SDLK_0; kc <= (int) SDLK_9; kc++) { if (first_keycode <= kc && kc < firstAfterRange) mapping[kc - first_keycode] = XkbKeycodeToKeysym(display, kc, 0, 0); } - for (int kc = SDLK_a; kc <= SDLK_z; kc++) { + for (int kc = SDLK_a; kc <= (int) SDLK_z; kc++) { if (first_keycode <= kc && kc < firstAfterRange) mapping[kc - first_keycode] = XkbKeycodeToKeysym(display, kc, 0, 0); } diff --git a/src/path/compose.h b/src/path/compose.h index 8b01803..4f6226f 100644 --- a/src/path/compose.h +++ b/src/path/compose.h @@ -6,7 +6,7 @@ #ifndef PATH_COMPOSE_H #define PATH_COMPOSE_H -#include +#include "sdl-compat.h" #include #include "rasterize.h" diff --git a/src/path/raster.h b/src/path/raster.h index 7a084aa..6dae1ee 100644 --- a/src/path/raster.h +++ b/src/path/raster.h @@ -6,7 +6,7 @@ #ifndef PATH_RASTER_H #define PATH_RASTER_H -#include +#include "sdl-compat.h" #include #include "path.h" diff --git a/src/pixmap.c b/src/pixmap.c index 88a2ed3..2479525 100644 --- a/src/pixmap.c +++ b/src/pixmap.c @@ -22,7 +22,7 @@ static Bool isSupportedPixmapDepth(unsigned int depth) return depth == 1 || depth == 16 || depth == 24 || depth == 32; } -static Uint32 mapPixel(SDL_PixelFormat *format, unsigned long pixel) +static Uint32 mapPixel(XcPixelFormat format, unsigned long pixel) { pixel = colorWithOpaqueDefault(pixel); return SDL_MapRGBA(format, GET_RED_FROM_COLOR(pixel), @@ -58,12 +58,50 @@ static Pixmap createPixmapFromPixels(Display *display, XFreePixmap(display, pixmap); return None; } +#ifdef LIBX11_COMPAT_SDL3 + /* SDL3's software renderer does not reliably reflect an SDL_UpdateTexture + * into a render-target texture's readable surface once the renderer has + * been used for other targets. Populate the target the canonical way: + * upload to a staging texture and render it onto the pixmap target with + * blending disabled so the source pixels overwrite verbatim. + */ + SDL_Renderer *renderer = GET_WINDOW_STRUCT(SCREEN_WINDOW)->sdlRenderer; + SDL_Texture *staging = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STATIC, (int) width, (int) height); + if (!staging) { + LOG("SDL_CreateTexture (staging) failed in %s: %s\n", __func__, + SDL_GetError()); + XFreePixmap(display, pixmap); + return None; + } + SDL_SetTextureBlendMode(staging, SDL_BLENDMODE_NONE); + if (SDL_UpdateTexture(staging, NULL, pixels, + (int) (width * sizeof(Uint32))) != 0) { + LOG("SDL_UpdateTexture failed in %s: %s\n", __func__, SDL_GetError()); + SDL_DestroyTexture(staging); + XFreePixmap(display, pixmap); + return None; + } + SDL_Texture *prevTarget = SDL_GetRenderTarget(renderer); + int copyResult = SDL_SetRenderTarget(renderer, texture); + if (copyResult == 0) + copyResult = SDL_RenderCopy(renderer, staging, NULL, NULL); + SDL_SetRenderTarget(renderer, prevTarget); + SDL_DestroyTexture(staging); + if (copyResult != 0) { + LOG("staging blit failed in %s: %s\n", __func__, SDL_GetError()); + XFreePixmap(display, pixmap); + return None; + } +#else if (SDL_UpdateTexture(texture, NULL, pixels, (int) (width * sizeof(Uint32))) != 0) { LOG("SDL_UpdateTexture failed in %s: %s\n", __func__, SDL_GetError()); XFreePixmap(display, pixmap); return None; } +#endif return pixmap; } @@ -176,7 +214,7 @@ Pixmap XCreatePixmapFromBitmapData(Display *display, handleError(0, display, None, 0, BadValue, 0); return None; } - SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); + XcPixelFormat format = xcAllocFormat(SDL_PIXELFORMAT_RGBA8888); if (!format) { handleOutOfMemory(0, display, 0, 0); return None; @@ -185,7 +223,7 @@ Pixmap XCreatePixmapFromBitmapData(Display *display, Uint32 background = mapPixel(format, bg); Uint32 *pixels = malloc(sizeof(Uint32) * (size_t) width * (size_t) height); if (!pixels) { - SDL_FreeFormat(format); + xcFreeFormat(format); handleOutOfMemory(0, display, 0, 0); return None; } @@ -199,7 +237,7 @@ Pixmap XCreatePixmapFromBitmapData(Display *display, Pixmap pixmap = createPixmapFromPixels(display, width, height, pixels, depth); free(pixels); - SDL_FreeFormat(format); + xcFreeFormat(format); return pixmap; } diff --git a/src/pointer.c b/src/pointer.c index d784fe4..b79fe4d 100644 --- a/src/pointer.c +++ b/src/pointer.c @@ -1,5 +1,5 @@ #include "X11/Xlib.h" -#include +#include "sdl-compat.h" #include #include #include diff --git a/src/replay-target.c b/src/replay-target.c index 5713c75..cf8a7bc 100644 --- a/src/replay-target.c +++ b/src/replay-target.c @@ -1,5 +1,5 @@ #include "replay-target.h" -#include +#include "sdl-compat.h" #include #include #include "util.h" diff --git a/src/replay-target.h b/src/replay-target.h index ae3e2fe..e9e06fb 100644 --- a/src/replay-target.h +++ b/src/replay-target.h @@ -2,7 +2,7 @@ #define LIBX11_COMPAT_REPLAY_TARGET_H #include -#include +#include "sdl-compat.h" /* Shared target state for in-process replay, XTest injection, snapshots, * and replay-driven resize. The target is captured on the main thread when diff --git a/src/replay.c b/src/replay.c index 2e40cf4..d26e448 100644 --- a/src/replay.c +++ b/src/replay.c @@ -36,7 +36,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "replay.h" #include "replay-target.h" #include "snapshot.h" @@ -316,9 +316,9 @@ static void runScript(const char *path) * before any numeric field. Without this, forms like * wait-converge failure-marker=foo * wait-converge 200 failure-marker=foo - * are rejected by strtoull as "bad arg N" because the marker - * is not a digit. Break out here so remaining fields keep - * their defaults and the post-loop check handles the marker. + * are rejected by strtoull as "bad arg N" because the marker is + * not a digit. Break out here so remaining fields keep their + * defaults and the post-loop check handles the marker. */ if (!strncmp(cursor, "failure-marker=", 15)) break; @@ -403,12 +403,12 @@ static void runScript(const char *path) } /* Do not break the script on timeout/divergence (rc <= 0). The * runner's expanded replay puts a state-snapshot line immediately - * after every wait-converge and blocks waiting for the JSON - * marker. Breaking here leaves the marker unwritten, and the runner - * spends the full timeout plus slack polling before raising. - * Letting the next line run writes the JSON (with the current, - * possibly unsettled state) so the runner can observe the failure - * marker above instead of hanging on missing synchronization JSON. + * after every wait-converge and blocks waiting for the JSON marker. + * Breaking here leaves the marker unwritten, and the runner spends + * the full timeout plus slack polling before raising. Letting the + * next line run writes the JSON (with the current, possibly + * unsettled state) so the runner can observe the failure marker + * above instead of hanging on missing synchronization JSON. */ } else if (!strcmp(cmd, "state-snapshot")) { /* Marshal the in-process focus / grab / window / property state to diff --git a/src/screensaver.c b/src/screensaver.c index 3a5ad03..4aa9666 100644 --- a/src/screensaver.c +++ b/src/screensaver.c @@ -1,5 +1,5 @@ #include "X11/Xlib.h" -#include +#include "sdl-compat.h" #include "errors.h" #include "display.h" diff --git a/src/sdl-compat.h b/src/sdl-compat.h new file mode 100644 index 0000000..29828e9 --- /dev/null +++ b/src/sdl-compat.h @@ -0,0 +1,765 @@ +#ifndef LIBX11_COMPAT_SDL_COMPAT_H +#define LIBX11_COMPAT_SDL_COMPAT_H + +/* Single include chokepoint for the SDL core API. Every source file includes + * this instead of so the SDL2 / SDL3 divergence lives in one + * place. The default build (no LIBX11_COMPAT_SDL3) is a pass-through to SDL2 + * and must stay byte-for-byte equivalent to including directly. + * + * Under LIBX11_COMPAT_SDL3 this header reshapes the SDL3 API back into the + * SDL2 spelling the rest of the tree was written against. It covers only the + * symbols this codebase actually uses; it is not a general SDL2-on-SDL3 shim. + * Two rules keep it honest: + * 1. inline wrappers that reuse an SDL3 function whose name collides with the + * SDL2 name are defined BEFORE the #define that renames the call sites, so + * the wrapper body still binds the real SDL3 symbol; + * 2. only divergent symbols appear here; anything source-identical across + * SDL2 and SDL3 is left untouched. + */ + +#ifdef LIBX11_COMPAT_SDL3 + +/* SDL3 ships an "old names" compatibility layer (SDL_oldnames.h) that aliases + * the SDL2 spelling to the SDL3 name for everything that was a pure rename: + * the SDLK_* / KMOD_* constants, the SDL_EVENT_* enum, atomics, cursor enums, + * SDL_mutex, and so on. Enabling it carries the bulk of the migration so this + * header only has to deal with the symbols whose SIGNATURE or semantics + * changed. Where an old-names alias is actively wrong (it renames but does not + * convert, e.g. SDL_RenderCopy's SDL_Rect -> SDL_FRect), we #undef it and + * install a converting wrapper below. + */ +#define SDL_ENABLE_OLD_NAMES +#include +#include + +/* SDL2's headers transitively dragged in the libc declarations (stdlib, + * string) that much of the tree relies on without including them directly. + * SDL3 deliberately stopped leaking libc, so re-provide the same surface here + * to keep those source files compiling unchanged. + */ +#include +#include + +/* Pixel-format handle. SDL2 surfaces carry a SDL_PixelFormat* details struct; + * SDL3 surfaces carry a SDL_PixelFormat enum and expose the details through + * SDL_GetPixelFormatDetails. XcPixelFormat is the neutral handle the colour + * helpers below consume, so call sites pass XC_SURFACE_FORMAT() or + * xcAllocFormat rather than touching surface->format directly. + */ +typedef const SDL_PixelFormatDetails *XcPixelFormat; +#define XC_SURFACE_FORMAT(s) SDL_GetPixelFormatDetails((s)->format) +#define XC_SURFACE_FMT_ENUM(s) ((s)->format) +#define XC_SURFACE_BYTESPERPIXEL(s) \ + (SDL_GetPixelFormatDetails((s)->format)->bytes_per_pixel) +#define xcFreeFormat(f) ((void) (f)) + +static inline XcPixelFormat xcAllocFormat(SDL_PixelFormat format) +{ + return SDL_GetPixelFormatDetails(format); +} + +/* Colour map/unmap: SDL3 added a palette argument and takes the details + * struct. The neutral handle is already the details struct, so pass NULL for + * the (unused, non-indexed) palette. + */ +static inline Uint32 xc_MapRGBA(XcPixelFormat f, + Uint8 r, + Uint8 g, + Uint8 b, + Uint8 a) +{ + return SDL_MapRGBA(f, NULL, r, g, b, a); +} + +static inline Uint32 xc_MapRGB(XcPixelFormat f, Uint8 r, Uint8 g, Uint8 b) +{ + return SDL_MapRGB(f, NULL, r, g, b); +} + +static inline void xc_GetRGB(Uint32 pixel, + XcPixelFormat f, + Uint8 *r, + Uint8 *g, + Uint8 *b) +{ + SDL_GetRGB(pixel, f, NULL, r, g, b); +} + +static inline void xc_GetRGBA(Uint32 pixel, + XcPixelFormat f, + Uint8 *r, + Uint8 *g, + Uint8 *b, + Uint8 *a) +{ + SDL_GetRGBA(pixel, f, NULL, r, g, b, a); +} + +/* Surface creation: SDL3 folds masks/depth into a single format enum. */ +static inline SDL_Surface *xc_CreateRGBSurface(Uint32 flags, + int width, + int height, + int depth, + Uint32 rmask, + Uint32 gmask, + Uint32 bmask, + Uint32 amask) +{ + (void) flags; + return SDL_CreateSurface( + width, height, + SDL_GetPixelFormatForMasks(depth, rmask, gmask, bmask, amask)); +} + +static inline SDL_Surface *xc_CreateRGBSurfaceWithFormat(Uint32 flags, + int width, + int height, + int depth, + Uint32 format) +{ + (void) flags; + (void) depth; + return SDL_CreateSurface(width, height, (SDL_PixelFormat) format); +} + +static inline SDL_Surface *xc_CreateRGBSurfaceFrom(void *pixels, + int width, + int height, + int depth, + int pitch, + Uint32 rmask, + Uint32 gmask, + Uint32 bmask, + Uint32 amask) +{ + return SDL_CreateSurfaceFrom( + width, height, + SDL_GetPixelFormatForMasks(depth, rmask, gmask, bmask, amask), pixels, + pitch); +} + +static inline SDL_Surface *xc_CreateRGBSurfaceWithFormatFrom(void *pixels, + int width, + int height, + int depth, + int pitch, + Uint32 format) +{ + (void) depth; + return SDL_CreateSurfaceFrom(width, height, (SDL_PixelFormat) format, + pixels, pitch); +} + +/* Renderer: SDL3 renamed the copy/draw calls and moved geometry to floats. + * These wrappers keep the SDL2 int return convention (0 success / -1 error) + * the call sites test with `< 0` / `!= 0`. + */ +static inline int xc_RenderCopy(SDL_Renderer *renderer, + SDL_Texture *texture, + const SDL_Rect *src, + const SDL_Rect *dst) +{ + SDL_FRect fsrc, fdst; + if (src) { + fsrc.x = (float) src->x; + fsrc.y = (float) src->y; + fsrc.w = (float) src->w; + fsrc.h = (float) src->h; + } + if (dst) { + fdst.x = (float) dst->x; + fdst.y = (float) dst->y; + fdst.w = (float) dst->w; + fdst.h = (float) dst->h; + } + return SDL_RenderTexture(renderer, texture, src ? &fsrc : NULL, + dst ? &fdst : NULL) + ? 0 + : -1; +} + +static inline int xc_RenderFillRect(SDL_Renderer *renderer, + const SDL_Rect *rect) +{ + if (!rect) + return SDL_RenderFillRect(renderer, NULL) ? 0 : -1; + SDL_FRect f = {(float) rect->x, (float) rect->y, (float) rect->w, + (float) rect->h}; + return SDL_RenderFillRect(renderer, &f) ? 0 : -1; +} + +static inline int xc_RenderFillRects(SDL_Renderer *renderer, + const SDL_Rect *rects, + int count) +{ + if (count <= 0) + return 0; + if (!rects || (size_t) count > SIZE_MAX / sizeof(SDL_FRect)) + return -1; + SDL_FRect stackRects[64]; + SDL_FRect *frects = + count <= 64 ? stackRects + : (SDL_FRect *) SDL_malloc(sizeof(SDL_FRect) * count); + if (!frects) + return -1; + for (int i = 0; i < count; i++) { + frects[i].x = (float) rects[i].x; + frects[i].y = (float) rects[i].y; + frects[i].w = (float) rects[i].w; + frects[i].h = (float) rects[i].h; + } + bool ok = SDL_RenderFillRects(renderer, frects, count); + if (frects != stackRects) + SDL_free(frects); + return ok ? 0 : -1; +} + +static inline int xc_RenderDrawRect(SDL_Renderer *renderer, + const SDL_Rect *rect) +{ + if (!rect) + return SDL_RenderRect(renderer, NULL) ? 0 : -1; + SDL_FRect f = {(float) rect->x, (float) rect->y, (float) rect->w, + (float) rect->h}; + return SDL_RenderRect(renderer, &f) ? 0 : -1; +} + +/* SDL3 SDL_RenderReadPixels returns a fresh surface instead of filling caller + * memory. Convert it back into the SDL2-style (format, pixels, pitch) buffer. + */ +static inline int xc_RenderReadPixels(SDL_Renderer *renderer, + const SDL_Rect *rect, + Uint32 format, + void *pixels, + int pitch) +{ + SDL_Surface *s = SDL_RenderReadPixels(renderer, rect); + if (!s) + return -1; + int rc = -1; + if (s->pixels && pixels) { + /* The caller's buffer is sized for the requested rect at `pitch`; SDL3 + * may hand back a surface that differs (clip/scale). Clamp the convert + * extent to what the destination can hold so a larger source cannot + * overflow the caller's buffer. + */ + int w = s->w; + int h = s->h; + if (rect) { + if (rect->w < w) + w = rect->w; + if (rect->h < h) + h = rect->h; + } + int bpp = SDL_BYTESPERPIXEL((SDL_PixelFormat) format); + if (bpp > 0) { + int dstMaxW = pitch / bpp; + if (dstMaxW > 0 && dstMaxW < w) + w = dstMaxW; + } + if (w > 0 && h > 0) + rc = SDL_ConvertPixels(w, h, s->format, s->pixels, s->pitch, + (SDL_PixelFormat) format, pixels, pitch) + ? 0 + : -1; + } + SDL_DestroySurface(s); + return rc; +} + +/* SDL3 flipped the success convention (int 0 -> bool true) for these calls, + * but the call sites still test the SDL2 way (`< 0` / `!= 0` / `== 0`). Each + * wrapper restores the int 0/-1 result. The four render entry points that + * old-names already aliased are #undef'd below before being redefined. + */ +static inline int xc_SetRenderTarget(SDL_Renderer *renderer, + SDL_Texture *texture) +{ + return SDL_SetRenderTarget(renderer, texture) ? 0 : -1; +} + +static inline int xc_RenderDrawLine(SDL_Renderer *renderer, + int x1, + int y1, + int x2, + int y2) +{ + return SDL_RenderLine(renderer, (float) x1, (float) y1, (float) x2, + (float) y2) + ? 0 + : -1; +} + +static inline int xc_RenderDrawPoint(SDL_Renderer *renderer, int x, int y) +{ + return SDL_RenderPoint(renderer, (float) x, (float) y) ? 0 : -1; +} + +static inline int xc_RenderSetClipRect(SDL_Renderer *renderer, + const SDL_Rect *rect) +{ + return SDL_SetRenderClipRect(renderer, rect) ? 0 : -1; +} + +static inline int xc_RenderSetViewport(SDL_Renderer *renderer, + const SDL_Rect *rect) +{ + return SDL_SetRenderViewport(renderer, rect) ? 0 : -1; +} + +static inline int xc_LockSurface(SDL_Surface *surface) +{ + return SDL_LockSurface(surface) ? 0 : -1; +} + +static inline int xc_UpdateTexture(SDL_Texture *texture, + const SDL_Rect *rect, + const void *pixels, + int pitch) +{ + return SDL_UpdateTexture(texture, rect, pixels, pitch) ? 0 : -1; +} + +static inline int xc_SaveBMP(SDL_Surface *surface, const char *file) +{ + return SDL_SaveBMP(surface, file) ? 0 : -1; +} + +/* SDL3 dropped the x,y window-creation arguments; place via properties. */ +static inline SDL_Window *xc_CreateWindow(const char *title, + int x, + int y, + int w, + int h, + Uint64 flags) +{ + SDL_PropertiesID props = SDL_CreateProperties(); + if (!props) + return NULL; + if (title) + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, + title); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, + (Sint64) flags); + SDL_Window *win = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + return win; +} + +/* SDL3 SDL_Init returns bool; map to the SDL2 int 0/-1 the call sites test. */ +static inline int xc_Init(Uint32 flags) +{ + return SDL_Init(flags) ? 0 : -1; +} + +/* SDL3 grew a modifier-state out-parameter on this query. */ +static inline SDL_Scancode xc_GetScancodeFromKey(SDL_Keycode key) +{ + return SDL_GetScancodeFromKey(key, NULL); +} + +/* SDL2 SDL_RenderDrawLines took SDL_Point; SDL3 SDL_RenderLines takes + * SDL_FPoint, so the old-names alias is a type mismatch, not a rename. + */ +static inline int xc_RenderDrawLines(SDL_Renderer *renderer, + const SDL_Point *points, + int count) +{ + if (count <= 0) + return 0; + if (!points || (size_t) count > SIZE_MAX / sizeof(SDL_FPoint)) + return -1; + SDL_FPoint stackPts[64]; + SDL_FPoint *fpts = + count <= 64 ? stackPts + : (SDL_FPoint *) SDL_malloc(sizeof(SDL_FPoint) * count); + if (!fpts) + return -1; + for (int i = 0; i < count; i++) { + fpts[i].x = (float) points[i].x; + fpts[i].y = (float) points[i].y; + } + bool ok = SDL_RenderLines(renderer, fpts, count); + if (fpts != stackPts) + SDL_free(fpts); + return ok ? 0 : -1; +} + +/* SDL3 SDL_BlitSurfaceScaled grew a scale-mode argument. */ +static inline int xc_BlitScaled(SDL_Surface *src, + const SDL_Rect *srcrect, + SDL_Surface *dst, + SDL_Rect *dstrect) +{ + return SDL_BlitSurfaceScaled(src, srcrect, dst, dstrect, + SDL_SCALEMODE_NEAREST) + ? 0 + : -1; +} + +/* SDL3 removed SDL_QueryTexture; size comes from SDL_GetTextureSize and the + * format/access live on the texture properties. + */ +static inline int xc_QueryTexture(SDL_Texture *texture, + Uint32 *format, + int *access, + int *w, + int *h) +{ + /* Resolve everything that can fail before touching any out-param, so a + * failed query leaves the caller's variables untouched. + */ + float fw = 0.0f, fh = 0.0f; + if (!SDL_GetTextureSize(texture, &fw, &fh)) + return -1; + SDL_PropertiesID props = SDL_GetTextureProperties(texture); + if (format) + *format = (Uint32) SDL_GetNumberProperty( + props, SDL_PROP_TEXTURE_FORMAT_NUMBER, SDL_PIXELFORMAT_UNKNOWN); + if (access) + *access = (int) SDL_GetNumberProperty( + props, SDL_PROP_TEXTURE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); + if (w) + *w = (int) fw; + if (h) + *h = (int) fh; + return 0; +} + +/* --- Renames applied after the wrappers above are defined. Only symbols whose + * signature or semantics changed appear here; the SDL_oldnames.h layer carries + * every pure rename (SDLK_*, KMOD_*, SDL_EVENT_*, atomics, cursor enums, ...). + * The four #undef'd entries are old-names aliases that rename without the + * required type conversion. + */ +#undef SDL_RenderCopy +#define SDL_RenderCopy xc_RenderCopy +#undef SDL_RenderDrawRect +#define SDL_RenderDrawRect xc_RenderDrawRect +#undef SDL_RenderDrawLines +#define SDL_RenderDrawLines xc_RenderDrawLines +#undef SDL_BlitScaled +#define SDL_BlitScaled xc_BlitScaled +#undef SDL_RenderDrawLine +#define SDL_RenderDrawLine xc_RenderDrawLine +#undef SDL_RenderDrawPoint +#define SDL_RenderDrawPoint xc_RenderDrawPoint +#undef SDL_RenderSetClipRect +#define SDL_RenderSetClipRect xc_RenderSetClipRect +#undef SDL_RenderSetViewport +#define SDL_RenderSetViewport xc_RenderSetViewport + +#define SDL_SetRenderTarget xc_SetRenderTarget +#define SDL_LockSurface xc_LockSurface +#define SDL_UpdateTexture xc_UpdateTexture +#define SDL_SaveBMP xc_SaveBMP + +#define SDL_MapRGBA xc_MapRGBA +#define SDL_MapRGB xc_MapRGB +#define SDL_GetRGB xc_GetRGB +#define SDL_GetRGBA xc_GetRGBA +#define SDL_CreateRGBSurface xc_CreateRGBSurface +#define SDL_CreateRGBSurfaceWithFormat xc_CreateRGBSurfaceWithFormat +#define SDL_CreateRGBSurfaceFrom xc_CreateRGBSurfaceFrom +#define SDL_CreateRGBSurfaceWithFormatFrom xc_CreateRGBSurfaceWithFormatFrom +#define SDL_RenderFillRect xc_RenderFillRect +#define SDL_RenderFillRects xc_RenderFillRects +#define SDL_RenderReadPixels xc_RenderReadPixels +#define SDL_QueryTexture xc_QueryTexture +#define SDL_CreateWindow xc_CreateWindow +#define SDL_Init xc_Init +#define SDL_GetScancodeFromKey(k) xc_GetScancodeFromKey(k) +#define SDL_SetWindowGrab SDL_SetWindowMouseGrab + +/* SDL3 reports mouse coordinates as floats; the tree works in integer pixels. + */ +static inline SDL_MouseButtonFlags xc_GetMouseState(int *x, int *y) +{ + float fx = 0.0f, fy = 0.0f; + SDL_MouseButtonFlags buttons = SDL_GetMouseState(&fx, &fy); + if (x) + *x = (int) fx; + if (y) + *y = (int) fy; + return buttons; +} + +static inline SDL_MouseButtonFlags xc_GetGlobalMouseState(int *x, int *y) +{ + float fx = 0.0f, fy = 0.0f; + SDL_MouseButtonFlags buttons = SDL_GetGlobalMouseState(&fx, &fy); + if (x) + *x = (int) fx; + if (y) + *y = (int) fy; + return buttons; +} + +/* SDL3 SDL_WarpMouseGlobal returns bool; preserve the SDL2 int 0/-1 result. */ +static inline int xc_WarpMouseGlobal(int x, int y) +{ + return SDL_WarpMouseGlobal((float) x, (float) y) ? 0 : -1; +} + +/* libX11-compat tracks a per-display X event count (`qlen`) that the SDL event + * filter bumps on push and the consume path drains. SDL_FlushEvent removes + * events without consuming them; the SDL2 build reconciled this inside the + * dlopen wrapper by calling the exported hook below for each removed event. + * The SDL3 build links libSDL3 directly, so reconcile here instead. The lib + * never calls SDL_FlushEvent itself, so this only fires for client flushes. + */ +extern void libx11CompatSideQueueEventRemoved(SDL_EventFilter filter, + void *userdata); +extern int libx11CompatSdlPeepEventsIsXlibDrain(void); + +/* Decrement the lib's per-display qlen (and drain a pipe byte) once per event + * that left SDL's queue without going through the lib's own consume path. + */ +static inline void xc_DrainQlen(int count) +{ + if (count <= 0) + return; + SDL_EventFilter filter = NULL; + void *userdata = NULL; + if (!SDL_GetEventFilter(&filter, &userdata) || !filter) + return; + for (int i = 0; i < count; i++) + libx11CompatSideQueueEventRemoved(filter, userdata); +} + +/* SDL_PeepEvents with a NULL array is SDL's count form: it returns the exact + * number of matching events without a stack buffer or truncation. + */ +static inline void xc_FlushEvent(Uint32 type) +{ + xc_DrainQlen(SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, type, type)); + SDL_FlushEvent(type); +} + +static inline void xc_FlushEvents(Uint32 minType, Uint32 maxType) +{ + xc_DrainQlen(SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, minType, maxType)); + SDL_FlushEvents(minType, maxType); +} + +/* Client retrieval via SDL_PeepEvents(GETEVENT) removes counted events; the + * lib's own drain bumps a thread-local guard so only client calls reconcile. + */ +static inline int xc_PeepEvents(SDL_Event *events, + int numevents, + SDL_EventAction action, + Uint32 minType, + Uint32 maxType) +{ + int n = SDL_PeepEvents(events, numevents, action, minType, maxType); + if (action == SDL_GETEVENT && n > 0 && + !libx11CompatSdlPeepEventsIsXlibDrain()) + xc_DrainQlen(n); + return n; +} + +static inline bool xc_WaitEvent(SDL_Event *event) +{ + bool r = SDL_WaitEvent(event); + if (r && !libx11CompatSdlPeepEventsIsXlibDrain()) + xc_DrainQlen(1); + return r; +} + +static inline bool xc_PollEvent(SDL_Event *event) +{ + bool r = SDL_PollEvent(event); + if (r && !libx11CompatSdlPeepEventsIsXlibDrain()) + xc_DrainQlen(1); + return r; +} + +/* SDL3 made text input per-window; target the focused window. */ +static inline void xc_StopTextInput(void) +{ + SDL_Window *w = SDL_GetKeyboardFocus(); + if (w) + SDL_StopTextInput(w); +} + +static inline void xc_SetTextInputRect(const SDL_Rect *rect) +{ + SDL_Window *w = SDL_GetKeyboardFocus(); + if (w) + SDL_SetTextInputArea(w, rect, 0); +} + +/* SDL3 split the modal relationship into parent + modal flag. */ +static inline int xc_SetWindowModalFor(SDL_Window *modal, SDL_Window *parent) +{ + if (parent) { + SDL_SetWindowParent(modal, parent); + SDL_SetWindowModal(modal, true); + } else { + SDL_SetWindowModal(modal, false); + SDL_SetWindowParent(modal, NULL); + } + return 0; +} + +/* SDL3 display-mode and display-count queries key off display IDs. */ +static inline int xc_GetCurrentDisplayMode(int displayIndex, + SDL_DisplayMode *mode) +{ + (void) displayIndex; + const SDL_DisplayMode *m = + SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay()); + if (!m) + return -1; + *mode = *m; + return 0; +} + +static inline int xc_GetNumVideoDisplays(void) +{ + int count = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&count); + if (displays) + SDL_free(displays); + return count; +} + +/* SDL2 addressed displays by a contiguous index; SDL3 uses opaque display IDs, + * so map the index through the live display list. + */ +static inline int xc_GetDesktopDisplayMode(int displayIndex, + SDL_DisplayMode *mode) +{ + int count = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&count); + if (!displays || displayIndex < 0 || displayIndex >= count) { + SDL_free(displays); + return -1; + } + const SDL_DisplayMode *m = + SDL_GetDesktopDisplayMode(displays[displayIndex]); + SDL_free(displays); + if (!m) + return -1; + *mode = *m; + return 0; +} + +#define SDL_GetMouseState xc_GetMouseState +#define SDL_GetGlobalMouseState xc_GetGlobalMouseState +#define SDL_WarpMouseGlobal xc_WarpMouseGlobal +#define SDL_FlushEvent xc_FlushEvent +#define SDL_FlushEvents xc_FlushEvents +#define SDL_PeepEvents xc_PeepEvents +#define SDL_WaitEvent xc_WaitEvent +#define SDL_PollEvent xc_PollEvent +#define SDL_StopTextInput() xc_StopTextInput() +#define SDL_SetTextInputRect(rect) xc_SetTextInputRect(rect) +#define SDL_SetWindowModalFor xc_SetWindowModalFor +#define SDL_GetCurrentDisplayMode xc_GetCurrentDisplayMode +#define SDL_GetDesktopDisplayMode xc_GetDesktopDisplayMode +#define SDL_GetNumVideoDisplays() xc_GetNumVideoDisplays() + +/* SDL3 made SDL_ThreadID a type; the current-thread query is now a renamed + * function. Map the call form without disturbing the (old-names) type alias. + */ +#define SDL_ThreadID() SDL_GetCurrentThreadID() + +/* SDL3 removed SDL_SetWindowInputFocus; raising is the closest analogue. */ +#undef SDL_SetWindowInputFocus +#define SDL_SetWindowInputFocus(win) SDL_RaiseWindow(win) + +/* SDL3 SDL_SetWindowFullscreen takes a bool, not a flag word. */ +#define XC_SetWindowFullscreen(win, on) SDL_SetWindowFullscreen((win), (on)) + +/* SDL3 event filters return bool; SDL3 timer callbacks gained a timer-id + * argument and reordered their parameters. These let the callback definitions + * stay backend-neutral. + */ +#define XC_EVENTFILTER_RET bool +#define XC_TIMER_CALLBACK_PARAMS \ + void *param, SDL_TimerID xcTimerID, Uint32 interval + +/* Window/key event field access differs structurally; the helpers below let + * the call sites stay backend-neutral. + */ +#define XC_WINDOW_SUBEVENT(ev) ((ev)->type) +#define XC_CASE_WINDOWEVENT \ + case SDL_EVENT_WINDOW_FIRST ... SDL_EVENT_WINDOW_LAST +#define XC_EVENT_KEYSYM(ev) ((ev)->key.key) +#define XC_EVENT_KEYMOD(ev) ((ev)->key.mod) +#define XC_EVENT_SET_KEYSYM(ev, v) ((ev)->key.key = (v)) +#define XC_EVENT_SET_SCANCODE(ev, v) ((ev)->key.scancode = (v)) +#define XC_EVENT_SET_KEY_PRESSED(ev, p) ((ev)->key.down = (p)) +#define XC_EVENT_SET_BUTTON_PRESSED(ev, p) ((ev)->button.down = (p)) +#define XC_EVENT_TIME_MS(ts) ((Uint32) SDL_NS_TO_MS(ts)) +#define XC_NOW_EVENT_TS() SDL_GetTicksNS() + +/* Construct an SDL window event (used by the test harness). In SDL3 the + * sub-event IS the top-level type, so the placeholder init is a no-op and the + * sub-event assignment writes the type. + */ +#define XC_INIT_WINDOW_EVENT(evptr) ((void) 0) +#define XC_SET_WINDOW_SUBEVENT(evptr, sub) ((evptr)->type = (sub)) +#define XC_SET_WHEEL_Y(evptr, v) ((evptr)->wheel.y = (v)) +#define XC_SET_WHEEL_X(evptr, v) ((evptr)->wheel.x = (v)) + +/* SDL3 has no global relative-mouse query; this library never enables relative + * mode, so it is always off. + */ +#define SDL_GetRelativeMouseMode() false + +/* SDL3 SDL_TextInputEvent.text is a const char* rather than an inline char[], + * so a synthetic text event points at the caller-owned string (which must + * outlive the push, e.g. a literal) instead of copying into the struct. + */ +#define XC_SET_TEXT_EVENT(ev, s) ((ev).text.text = (s)) + +/* SDL3 defaults texture scaling to linear; SDL2 defaulted to nearest. Pin + * glyph/blit textures to nearest so scaled copies stay crisp (and opaque + * pixels survive) exactly as on SDL2. + */ +#define XC_SCALEMODE_NEAREST SDL_SCALEMODE_NEAREST + +#else /* SDL2 */ + +#include + +typedef SDL_PixelFormat *XcPixelFormat; +#define XC_SURFACE_FORMAT(s) ((s)->format) +#define XC_SURFACE_FMT_ENUM(s) ((s)->format->format) +#define XC_SURFACE_BYTESPERPIXEL(s) ((s)->format->BytesPerPixel) +#define xcAllocFormat(f) SDL_AllocFormat(f) +#define xcFreeFormat(f) SDL_FreeFormat(f) + +#define XC_WINDOW_SUBEVENT(ev) ((ev)->window.event) +#define XC_CASE_WINDOWEVENT case SDL_WINDOWEVENT +#define XC_EVENT_KEYSYM(ev) ((ev)->key.keysym.sym) +#define XC_EVENT_KEYMOD(ev) ((ev)->key.keysym.mod) +#define XC_EVENT_SET_KEYSYM(ev, v) ((ev)->key.keysym.sym = (v)) +#define XC_EVENT_SET_SCANCODE(ev, v) ((ev)->key.keysym.scancode = (v)) +#define XC_EVENT_SET_KEY_PRESSED(ev, p) \ + ((ev)->key.state = (p) ? SDL_PRESSED : SDL_RELEASED) +#define XC_EVENT_SET_BUTTON_PRESSED(ev, p) \ + ((ev)->button.state = (p) ? SDL_PRESSED : SDL_RELEASED) +#define XC_EVENT_TIME_MS(ts) ((Uint32) (ts)) +#define XC_NOW_EVENT_TS() SDL_GetTicks() +#define XC_SetWindowFullscreen(win, on) \ + SDL_SetWindowFullscreen((win), (on) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) +#define XC_EVENTFILTER_RET int +#define XC_TIMER_CALLBACK_PARAMS Uint32 interval, void *param +#define XC_INIT_WINDOW_EVENT(evptr) ((evptr)->type = SDL_WINDOWEVENT) +#define XC_SET_WINDOW_SUBEVENT(evptr, sub) ((evptr)->window.event = (sub)) +#define XC_SET_WHEEL_Y(evptr, v) ((evptr)->wheel.preciseY = (v)) +#define XC_SET_WHEEL_X(evptr, v) ((evptr)->wheel.preciseX = (v)) +#define XC_SET_TEXT_EVENT(ev, s) \ + ((void) snprintf((ev).text.text, sizeof((ev).text.text), "%s", (s))) +#define XC_SCALEMODE_NEAREST SDL_ScaleModeNearest + +#endif /* LIBX11_COMPAT_SDL3 */ + +#endif diff --git a/src/sdl-ttf-compat.h b/src/sdl-ttf-compat.h new file mode 100644 index 0000000..83c0ef2 --- /dev/null +++ b/src/sdl-ttf-compat.h @@ -0,0 +1,76 @@ +#ifndef LIBX11_COMPAT_SDL_TTF_COMPAT_H +#define LIBX11_COMPAT_SDL_TTF_COMPAT_H + +/* Include chokepoint for SDL_ttf, mirroring sdl-compat.h. The default build is + * a pass-through to SDL2_ttf; under LIBX11_COMPAT_SDL3 it reshapes the + * SDL_ttf 3.x API into the 2.x spelling the tree was written against. The + * inline wrappers preserve the SDL2_ttf return conventions (0 success / -1 + * error) that the call sites test against, and are defined before the renaming + * #defines so a wrapper that reuses a surviving name still binds the real + * function. + */ + +#include "sdl-compat.h" + +#ifdef LIBX11_COMPAT_SDL3 + +#include + +static inline int xc_TTF_Init(void) +{ + return TTF_Init() ? 0 : -1; +} + +static inline int xc_TTF_GlyphMetrics(TTF_Font *font, + Uint32 ch, + int *minx, + int *maxx, + int *miny, + int *maxy, + int *advance) +{ + return TTF_GetGlyphMetrics(font, ch, minx, maxx, miny, maxy, advance) ? 0 + : -1; +} + +static inline int xc_TTF_SizeUTF8(TTF_Font *font, + const char *text, + int *w, + int *h) +{ + return TTF_GetStringSize(font, text, 0, w, h) ? 0 : -1; +} + +static inline SDL_Surface *xc_TTF_RenderUTF8_Solid(TTF_Font *font, + const char *text, + SDL_Color fg) +{ + return TTF_RenderText_Solid(font, text, 0, fg); +} + +static inline SDL_Surface *xc_TTF_RenderUTF8_Blended(TTF_Font *font, + const char *text, + SDL_Color fg) +{ + return TTF_RenderText_Blended(font, text, 0, fg); +} + +#define TTF_Init xc_TTF_Init +#define TTF_GlyphMetrics xc_TTF_GlyphMetrics +#define TTF_SizeUTF8 xc_TTF_SizeUTF8 +#define TTF_RenderUTF8_Solid xc_TTF_RenderUTF8_Solid +#define TTF_RenderUTF8_Blended xc_TTF_RenderUTF8_Blended +#define TTF_FontAscent TTF_GetFontAscent +#define TTF_FontDescent TTF_GetFontDescent +#define TTF_FontFaceFamilyName TTF_GetFontFamilyName +#define TTF_FontFaceIsFixedWidth TTF_FontIsFixedWidth +#define TTF_GlyphIsProvided TTF_FontHasGlyph +#define TTF_GetError SDL_GetError + +#else /* SDL2 */ + +#include + +#endif /* LIBX11_COMPAT_SDL3 */ + +#endif diff --git a/src/selection.c b/src/selection.c index 37e4a07..b9d2c40 100644 --- a/src/selection.c +++ b/src/selection.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include #include #include diff --git a/src/snapshot.c b/src/snapshot.c index bee8d6f..c8d2fd6 100644 --- a/src/snapshot.c +++ b/src/snapshot.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "drawing.h" #include "events.h" diff --git a/src/snapshot.h b/src/snapshot.h index 09867e2..1a8b83e 100644 --- a/src/snapshot.h +++ b/src/snapshot.h @@ -2,7 +2,7 @@ #define _LIBX11_COMPAT_SNAPSHOT_INTERNAL_H_ #include -#include +#include "sdl-compat.h" /* Request a snapshot of the replay target SDL window's backing surface to * path (BMP, format chosen by SDL_SaveBMP). Blocks the calling thread diff --git a/src/state-snapshot.c b/src/state-snapshot.c index 1476606..9a2c2d8 100644 --- a/src/state-snapshot.c +++ b/src/state-snapshot.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include #include "display.h" #include "events.h" diff --git a/src/state-snapshot.h b/src/state-snapshot.h index a136521..2014904 100644 --- a/src/state-snapshot.h +++ b/src/state-snapshot.h @@ -3,7 +3,7 @@ #include #include -#include +#include "sdl-compat.h" /* Replay-time introspection. Runs on the main / X-client event thread (same * constraint src/snapshot.c enforces) so callbacks can safely read diff --git a/src/timeline.c b/src/timeline.c index fe5a0c9..8ca23e0 100644 --- a/src/timeline.c +++ b/src/timeline.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "util.h" static SDL_atomic_t timelineCounters[TIMELINE_KIND_COUNT]; diff --git a/src/timeline.h b/src/timeline.h index 34ed310..bca2394 100644 --- a/src/timeline.h +++ b/src/timeline.h @@ -3,7 +3,7 @@ #include #include -#include +#include "sdl-compat.h" /* Stable JSONL trace of X events, window lifecycle, present activity, focus * and grab transitions. Gated on LIBX11_COMPAT_TIMELINE=1; off in release diff --git a/src/visual.c b/src/visual.c index 3bb424c..81fa848 100644 --- a/src/visual.c +++ b/src/visual.c @@ -3,7 +3,7 @@ #include #include "visual.h" #include "util.h" -#include +#include "sdl-compat.h" #include "errors.h" Visual *VISUAL_LIST = NULL; diff --git a/src/window-internal.c b/src/window-internal.c index f07541d..e4201e3 100644 --- a/src/window-internal.c +++ b/src/window-internal.c @@ -830,7 +830,7 @@ static Bool shapeMaskPixelActive(SDL_Surface *mask, int x, int y) return False; Uint32 pixel = getPixel(mask, (unsigned int) x, (unsigned int) y); Uint8 r = 0, g = 0, b = 0; - SDL_GetRGB(pixel, mask->format, &r, &g, &b); + SDL_GetRGB(pixel, XC_SURFACE_FORMAT(mask), &r, &g, &b); return r || g || b; } diff --git a/src/window-wm.c b/src/window-wm.c index 34d3e0b..2e82f04 100644 --- a/src/window-wm.c +++ b/src/window-wm.c @@ -239,8 +239,7 @@ void applyNetWmStateFromProperty(Window window) SDL_Window *sdlWindow = windowStruct->sdlWindow; if (!state.hidden) SDL_RestoreWindow(sdlWindow); - SDL_SetWindowFullscreen( - sdlWindow, state.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + XC_SetWindowFullscreen(sdlWindow, state.fullscreen); if (state.maximized) SDL_MaximizeWindow(sdlWindow); if (state.hidden) diff --git a/src/window.h b/src/window.h index 7128d6e..c70d788 100644 --- a/src/window.h +++ b/src/window.h @@ -1,7 +1,7 @@ #ifndef _WINDOW_H_ #define _WINDOW_H_ -#include +#include "sdl-compat.h" #include #include "window-debug.h" #include "resource-types.h" diff --git a/src/wrapper/dlwrap.h b/src/wrapper/dlwrap.h new file mode 100644 index 0000000..3cb0061 --- /dev/null +++ b/src/wrapper/dlwrap.h @@ -0,0 +1,131 @@ +#ifndef LIBX11_COMPAT_WRAPPER_DLWRAP_H +#define LIBX11_COMPAT_WRAPPER_DLWRAP_H + +/* Shared dlopen/dlsym plumbing for the SDL and SDL_ttf wrapper shims. Both + * shims forward their exported symbols to the real host library resolved at + * runtime; only the candidate library names and the human-readable label + * differ, so the loader, the lazy symbol resolver, and the per-symbol thunk + * macros live here once. + */ + +#include +#include +#include +#include + +#ifndef RTLD_DEEPBIND +#define RTLD_DEEPBIND 0 +#endif + +/* ASan (and other interceptor-based sanitizers) aborts on dlopen with + * RTLD_DEEPBIND because deep binding bypasses their symbol interception. Drop + * the flag in sanitizer builds. The escape hatch LIBX11_COMPAT_NO_RTLD_DEEPBIND + * lets the build system force the same downgrade when a sanitizer variant the + * wrapper does not auto-detect is in play. + * + * Caveat: RTLD_DEEPBIND was protecting against the real SDL library looking up + * SDL_* symbols via RTLD_DEFAULT and finding a wrapper's exports instead + * (which would recurse). RTLD_LOCAL on the dlopen sites doesn't fully replace + * that; it only hides the loaded library's symbols from later lookups, it + * doesn't stop the library itself from peeking back into the global scope where + * the wrapper lives. In practice SDL calls its own internal symbols directly + * (resolved against its own .so at its link time) rather than via RTLD_DEFAULT, + * so the recursion has not been observed. If a future SDL release reintroduces + * RTLD_DEFAULT lookups for its own symbols, sanitizer runs will need to link + * the real library directly and skip the wrapper. + */ +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if defined(LIBX11_COMPAT_NO_RTLD_DEEPBIND) || \ + defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) || \ + defined(__SANITIZE_HWADDRESS__) || __has_feature(address_sanitizer) || \ + __has_feature(hwaddress_sanitizer) || __has_feature(memory_sanitizer) || \ + __has_feature(thread_sanitizer) +#undef RTLD_DEEPBIND +#define RTLD_DEEPBIND 0 +#endif + +#define DLWRAP_FLAGS (RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND) + +/* Resolve and cache the real library handle into the caller-owned slot. The + * slot is accessed with atomic ACQUIRE/RELEASE so concurrent first callers have + * a well-defined outcome under the C memory model. The race is benign because + * dlopen reference-counts the underlying library, but the CAS still matters: + * racing first callers each take a dlopen refcount, so the losers dlclose to + * balance their open and adopt the winner's handle rather than leaking it. + */ +static inline void *dlwrapOpen(void **handleSlot, + const char *overrideEnv, + const char *const *candidates, + const char *libDesc) +{ + void *cached = __atomic_load_n(handleSlot, __ATOMIC_ACQUIRE); + if (cached) + return cached; + + const char *override = getenv(overrideEnv); + void *opened = NULL; + if (override && override[0]) + opened = dlopen(override, DLWRAP_FLAGS); + for (size_t i = 0; !opened && candidates[i]; i++) + opened = dlopen(candidates[i], DLWRAP_FLAGS); + if (!opened) { + fprintf(stderr, "libX11-compat: failed to load %s: %s\n", libDesc, + dlerror()); + abort(); + } + + void *expected = NULL; + if (__atomic_compare_exchange_n(handleSlot, &expected, opened, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) + return opened; + dlclose(opened); + return expected; +} + +static inline void *dlwrapSym(void *handle, + const char *name, + const char *libDesc) +{ + void *symbol = dlsym(handle, name); + if (!symbol) { + fprintf(stderr, "libX11-compat: failed to resolve %s symbol %s: %s\n", + libDesc, name, dlerror()); + abort(); + } + return symbol; +} + +/* Define an exported thunk that lazily resolves its real implementation through + * resolver(#name) and caches it in a function-local slot using the same atomic + * ACQUIRE/RELEASE dance as dlwrapOpen. resolver is a symbol-lookup function + * taking the symbol name and returning its address. + */ +#define DLWRAP_THUNK(resolver, ret, name, args, callargs) \ + ret SDLCALL name args \ + { \ + typedef ret(SDLCALL * RealFunc) args; \ + static RealFunc realFunc; \ + RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ + if (!cached) { \ + cached = (RealFunc) resolver(#name); \ + __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ + } \ + return cached callargs; \ + } + +#define DLWRAP_THUNK_VOID(resolver, name, args, callargs) \ + void SDLCALL name args \ + { \ + typedef void(SDLCALL * RealFunc) args; \ + static RealFunc realFunc; \ + RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ + if (!cached) { \ + cached = (RealFunc) resolver(#name); \ + __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ + } \ + cached callargs; \ + } + +#endif diff --git a/src/wrapper/sdl-ttf-wrapper.c b/src/wrapper/sdl-ttf-wrapper.c index f00917c..46c605d 100644 --- a/src/wrapper/sdl-ttf-wrapper.c +++ b/src/wrapper/sdl-ttf-wrapper.c @@ -1,26 +1,6 @@ #include -#include -#include -#include -#ifndef RTLD_DEEPBIND -#define RTLD_DEEPBIND 0 -#endif - -/* See sdl-wrapper.c for the rationale and caveat. Same trigger set so - * both shims downgrade together. - */ -#ifndef __has_feature -#define __has_feature(x) 0 -#endif -#if defined(LIBX11_COMPAT_NO_RTLD_DEEPBIND) || \ - defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) || \ - defined(__SANITIZE_HWADDRESS__) || __has_feature(address_sanitizer) || \ - __has_feature(hwaddress_sanitizer) || __has_feature(memory_sanitizer) || \ - __has_feature(thread_sanitizer) -#undef RTLD_DEEPBIND -#define RTLD_DEEPBIND 0 -#endif +#include "dlwrap.h" #ifdef TTF_GetError #undef TTF_GetError @@ -34,20 +14,10 @@ SDL_TTF_PATCHLEVEL >= (z))) #endif -/* See sdl-wrapper.c for the rationale: atomic load/store with - * ACQUIRE/RELEASE ordering on the shared handle plus the per-wrapper - * realFunc caches so racing first-callers are well-defined under the C - * memory model. - */ static void *realTtfHandle(void) { static void *handle; - void *cached = __atomic_load_n(&handle, __ATOMIC_ACQUIRE); - if (cached) - return cached; - - const char *override = getenv("LIBX11_COMPAT_REAL_SDL2_TTF"); - const char *candidates[] = { + static const char *const candidates[] = { #ifdef LIBX11_COMPAT_SDL2_TTF_DYLIB LIBX11_COMPAT_SDL2_TTF_DYLIB, #endif @@ -61,66 +31,20 @@ static void *realTtfHandle(void) #endif NULL, }; - - void *opened = NULL; - if (override && override[0]) - opened = dlopen(override, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); - for (size_t i = 0; !opened && candidates[i]; i++) - opened = dlopen(candidates[i], RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); - if (!opened) { - fprintf(stderr, "libX11-compat: failed to load real SDL2_ttf: %s\n", - dlerror()); - abort(); - } - /* CAS so racing first-callers don't each pin an extra dlopen - * refcount; loser dlcloses and uses the winner's handle. See - * sdl-wrapper.c for the full rationale. - */ - void *expected = NULL; - if (__atomic_compare_exchange_n(&handle, &expected, opened, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) - return opened; - dlclose(opened); - return expected; + return dlwrapOpen(&handle, "LIBX11_COMPAT_REAL_SDL2_TTF", candidates, + "real SDL2_ttf"); } static void *realTtfSymbol(const char *name) { - void *symbol = dlsym(realTtfHandle(), name); - if (!symbol) { - fprintf(stderr, - "libX11-compat: failed to resolve SDL2_ttf symbol %s: %s\n", - name, dlerror()); - abort(); - } - return symbol; + return dlwrapSym(realTtfHandle(), name, "SDL2_ttf"); } -#define TTF_WRAP(ret, name, args, callargs) \ - ret SDLCALL name args \ - { \ - typedef ret(SDLCALL * RealFunc) args; \ - static RealFunc realFunc; \ - RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ - if (!cached) { \ - cached = (RealFunc) realTtfSymbol(#name); \ - __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ - } \ - return cached callargs; \ - } +#define TTF_WRAP(ret, name, args, callargs) \ + DLWRAP_THUNK(realTtfSymbol, ret, name, args, callargs) -#define TTF_WRAP_VOID(name, args, callargs) \ - void SDLCALL name args \ - { \ - typedef void(SDLCALL * RealFunc) args; \ - static RealFunc realFunc; \ - RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ - if (!cached) { \ - cached = (RealFunc) realTtfSymbol(#name); \ - __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ - } \ - cached callargs; \ - } +#define TTF_WRAP_VOID(name, args, callargs) \ + DLWRAP_THUNK_VOID(realTtfSymbol, name, args, callargs) TTF_WRAP_VOID(TTF_CloseFont, (TTF_Font * font), (font)) TTF_WRAP(int, TTF_FontAscent, (const TTF_Font *font), (font)) diff --git a/src/wrapper/sdl-wrapper.c b/src/wrapper/sdl-wrapper.c index 8426ea9..92f19fa 100644 --- a/src/wrapper/sdl-wrapper.c +++ b/src/wrapper/sdl-wrapper.c @@ -1,62 +1,14 @@ #include -#include #include #include #include -#include -#include -#ifndef RTLD_DEEPBIND -#define RTLD_DEEPBIND 0 -#endif +#include "dlwrap.h" -/* ASan (and other interceptor-based sanitizers) aborts on dlopen with - * RTLD_DEEPBIND because deep binding bypasses their symbol interception. Drop - * the flag in sanitizer builds. The escape hatch LIBX11_COMPAT_NO_RTLD_DEEPBIND - * lets the build system force the same downgrade when a sanitizer variant the - * wrapper does not auto-detect is in play. - * - * Caveat: RTLD_DEEPBIND was protecting against the real libSDL2 looking up - * SDL_* symbols via RTLD_DEFAULT and finding this wrapper's exports instead - * (which would recurse). RTLD_LOCAL on the dlopen sites doesn't fully replace - * that; it only hides the loaded SDL's symbols from later lookups, it doesn't - * stop libSDL2 itself from peeking back into the global scope where the wrapper - * lives. In practice libSDL2 calls its own internal symbols directly (resolved - * against its own .so at its link time) rather than via RTLD_DEFAULT, so the - * recursion has not been observed. If a future SDL release reintroduces - * RTLD_DEFAULT lookups for its own symbols, sanitizer runs will need to link - * the real libSDL2 directly and skip the wrapper. - */ -#ifndef __has_feature -#define __has_feature(x) 0 -#endif -#if defined(LIBX11_COMPAT_NO_RTLD_DEEPBIND) || \ - defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) || \ - defined(__SANITIZE_HWADDRESS__) || __has_feature(address_sanitizer) || \ - __has_feature(hwaddress_sanitizer) || __has_feature(memory_sanitizer) || \ - __has_feature(thread_sanitizer) -#undef RTLD_DEEPBIND -#define RTLD_DEEPBIND 0 -#endif - -/* The lazy caches below (the shared handle plus the per-wrapper realFunc - * statics expanded by the SDL_WRAP macros) are accessed via __atomic_load_n / - * __atomic_store_n with ACQUIRE/RELEASE ordering so concurrent first calls have - * a well-defined outcome under the C memory model rather than UB. The race is - * benign in practice because dlopen reference-counts the underlying library and - * dlsym is idempotent, so both racers see the same handle and pointer; the - * atomics give the compiler permission to reason about it instead of folding - * the load into a single read. - */ static void *realSdlHandle(void) { static void *handle; - void *cached = __atomic_load_n(&handle, __ATOMIC_ACQUIRE); - if (cached) - return cached; - - const char *override = getenv("LIBX11_COMPAT_REAL_SDL2"); - const char *candidates[] = { + static const char *const candidates[] = { #ifdef LIBX11_COMPAT_SDL2_DYLIB LIBX11_COMPAT_SDL2_DYLIB, #endif @@ -70,73 +22,26 @@ static void *realSdlHandle(void) #endif NULL, }; - - void *opened = NULL; - if (override && override[0]) - opened = dlopen(override, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); - for (size_t i = 0; !opened && candidates[i]; i++) - opened = dlopen(candidates[i], RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); - if (!opened) { - fprintf(stderr, "libX11-compat: failed to load real SDL2: %s\n", - dlerror()); - abort(); - } - /* Publish via CAS so racing first-callers don't each keep their own dlopen - * refcount alive. The loser dlcloses to balance its dlopen and uses the - * winner's handle. POSIX dlopen reference-counts the underlying library, so - * the loser's dlclose just decrements that count back to where the winner - * left it. - */ - void *expected = NULL; - if (__atomic_compare_exchange_n(&handle, &expected, opened, 0, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) - return opened; - dlclose(opened); - return expected; + return dlwrapOpen(&handle, "LIBX11_COMPAT_REAL_SDL2", candidates, + "real SDL2"); } static void *realSdlSymbol(const char *name) { - void *symbol = dlsym(realSdlHandle(), name); - if (!symbol) { - fprintf(stderr, "libX11-compat: failed to resolve SDL2 symbol %s: %s\n", - name, dlerror()); - abort(); - } - return symbol; + return dlwrapSym(realSdlHandle(), name, "SDL2"); } -#define SDL_WRAP(ret, name, args, callargs) \ - ret SDLCALL name args \ - { \ - typedef ret(SDLCALL * RealFunc) args; \ - static RealFunc realFunc; \ - RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ - if (!cached) { \ - cached = (RealFunc) realSdlSymbol(#name); \ - __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ - } \ - return cached callargs; \ - } +#define SDL_WRAP(ret, name, args, callargs) \ + DLWRAP_THUNK(realSdlSymbol, ret, name, args, callargs) -#define SDL_WRAP_VOID(name, args, callargs) \ - void SDLCALL name args \ - { \ - typedef void(SDLCALL * RealFunc) args; \ - static RealFunc realFunc; \ - RealFunc cached = __atomic_load_n(&realFunc, __ATOMIC_ACQUIRE); \ - if (!cached) { \ - cached = (RealFunc) realSdlSymbol(#name); \ - __atomic_store_n(&realFunc, cached, __ATOMIC_RELEASE); \ - } \ - cached callargs; \ - } +#define SDL_WRAP_VOID(name, args, callargs) \ + DLWRAP_THUNK_VOID(realSdlSymbol, name, args, callargs) /* Lazily resolve a symbol into a function-local cache slot using the same * atomic ACQUIRE/RELEASE dance as the SDL_WRAP macros, then bind it to a - * variable named "cached". Type is a typedef'd function-pointer type, slot is - * a static variable of that type, and resolve is the expression that produces - * the symbol (realSdlSymbol(...) or dlsym(...)). + * variable named "cached". Type is a typedef'd function-pointer type, slot is a + * static variable of that type, and resolve is the expression that produces the + * symbol (realSdlSymbol(...) or dlsym(...)). */ #define CACHED_SYMBOL(Type, slot, resolve) \ Type cached = __atomic_load_n(&(slot), __ATOMIC_ACQUIRE); \ @@ -317,20 +222,20 @@ SDL_WRAP(Uint32, (format, r, g, b, a)) SDL_WRAP_VOID(SDL_MaximizeWindow, (SDL_Window * window), (window)) SDL_WRAP_VOID(SDL_MinimizeWindow, (SDL_Window * window), (window)) -/* sdl2-compat (the SDL2 API implemented over SDL3) crashes inside - * SDL_PushEvent / SDL_PeepEvents when an SDL_TEXTINPUT or SDL_TEXTEDITING event - * is *pushed* into its queue: SDL2 carries the text inline in a fixed char[] - * field, SDL3 carries it as a heap pointer, and that translation does not - * survive the push direction (it hands SDL3 a NULL event). Real input flows the - * other way, SDL3 -> SDL2, and is unaffected; only synthetic injection of a - * text event hits this. To keep that injection working without crashing, the - * wrapper diverts these events into a small side queue and merges them back in - * through the wrapped SDL queue APIs below. The registered event filter is - * invoked on the way in, exactly as SDL would; when a side-queued filtered - * event is later removed through SDL APIs, a narrow libX11-compat hook undoes - * the X event wakeup accounting for that removed entry. On a classic SDL2 - * runtime no text push ever lands here in practice (only synthetic injection - * does), so the workaround is inert for real workloads. +/* sdl2-compat (the SDL2 API implemented over SDL3) crashes inside SDL_PushEvent + * / SDL_PeepEvents when an SDL_TEXTINPUT or SDL_TEXTEDITING event is *pushed* + * into its queue: SDL2 carries the text inline in a fixed char[] field, SDL3 + * carries it as a heap pointer, and that translation does not survive the push + * direction (it hands SDL3 a NULL event). Real input flows the other way, SDL3 + * -> SDL2, and is unaffected; only synthetic injection of a text event hits + * this. To keep that injection working without crashing, the wrapper diverts + * these events into a small side queue and merges them back in through the + * wrapped SDL queue APIs below. The registered event filter is invoked on the + * way in, exactly as SDL would; when a side-queued filtered event is later + * removed through SDL APIs, a narrow libX11-compat hook undoes the X event + * wakeup accounting for that removed entry. On a classic SDL2 runtime no text + * push ever lands here in practice (only synthetic injection does), so the + * workaround is inert for real workloads. * * Limitations, acceptable because real code never injects text events: the side * queue drains before real SDL events, so a text event injected after another @@ -446,8 +351,8 @@ static int sideQueueTextPush(SDL_Event *event) } /* Drain (GET) or copy (PEEK) side-queued text events whose type falls in - * [minType, maxType] into the caller's buffer, FIFO order, up to numevents. - * A non-removing PEEK is forced when events is NULL (the SDL count form). + * [minType, maxType] into the caller's buffer, FIFO order, up to numevents. A + * non-removing PEEK is forced when events is NULL (the SDL count form). * Returns how many were placed (or would be placed, for the count form). */ /* When reclaim is true the caller consumes the drained events itself and @@ -568,7 +473,8 @@ SDL_bool SDLCALL SDL_HasEvent(Uint32 type) } /* Range forms must consult the side queue too, so a flush/has spanning the - * text-event types stays correct. */ + * text-event types stays correct. + */ void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType) { sideQueueRemoveRange(minType, maxType); @@ -624,8 +530,8 @@ int SDLCALL SDL_PeepEvents(SDL_Event *events, /* events == NULL is SDL's count form: count matching side entries without * removing them (sideQueueDrain forces PEEK when events is NULL), then add - * the real queue's count. numevents is per-SDL ignored for counting, so - * cap the side scan at the ring size. + * the real queue's count. numevents is per-SDL ignored for counting, so cap + * the side scan at the ring size. */ int sideMax = events ? numevents : TEXT_SIDEQ_CAP; bool reclaim = action == SDL_GETEVENT && sideQueueDrainShouldReclaim(); @@ -758,6 +664,10 @@ SDL_WRAP(int, SDL_SetTextureBlendMode, (SDL_Texture * texture, SDL_BlendMode blendMode), (texture, blendMode)) +SDL_WRAP(int, + SDL_SetTextureScaleMode, + (SDL_Texture * texture, SDL_ScaleMode scaleMode), + (texture, scaleMode)) #if SDL_VERSION_ATLEAST(2, 0, 16) SDL_WRAP_VOID(SDL_SetWindowAlwaysOnTop, (SDL_Window * window, SDL_bool on_top), diff --git a/src/xft.c b/src/xft.c index f802bf4..dc7fe9e 100644 --- a/src/xft.c +++ b/src/xft.c @@ -1,6 +1,6 @@ #include #include -#include +#include "sdl-ttf-compat.h" #include #include #include @@ -882,7 +882,7 @@ static SDL_Color sdlColorFromXft(const XftColor *color) } static unsigned long blendPixel(unsigned long dst, - SDL_PixelFormat *format, + XcPixelFormat format, Uint32 srcPixel, Uint8 colorAlpha) { @@ -990,8 +990,8 @@ static void drawUtf8String(XftDraw *draw, Uint32 *row = (Uint32 *) ((char *) glyphs->pixels + (yy + srcY) * glyphs->pitch); unsigned long dst = XGetPixel(image, xx, yy); - unsigned long blended = - blendPixel(dst, glyphs->format, row[xx + srcX], sdlColor.a); + unsigned long blended = blendPixel(dst, XC_SURFACE_FORMAT(glyphs), + row[xx + srcX], sdlColor.a); XPutPixel(image, xx, yy, blended); } } diff --git a/src/xshape.c b/src/xshape.c index 1f59cc0..8bfb879 100644 --- a/src/xshape.c +++ b/src/xshape.c @@ -61,7 +61,7 @@ static SDL_Surface *createShapeSurface(int w, int h) DEFAULT_BLUE_MASK, DEFAULT_ALPHA_MASK); if (!surface) return NULL; - Uint32 black = SDL_MapRGBA(surface->format, 0, 0, 0, 255); + Uint32 black = SDL_MapRGBA(XC_SURFACE_FORMAT(surface), 0, 0, 0, 255); SDL_FillRect(surface, NULL, black); return surface; } @@ -72,7 +72,7 @@ static Bool maskPixelActive(SDL_Surface *mask, int x, int y) return False; Uint32 pixel = getPixel(mask, (unsigned int) x, (unsigned int) y); Uint8 r = 0, g = 0, b = 0; - SDL_GetRGB(pixel, mask->format, &r, &g, &b); + SDL_GetRGB(pixel, XC_SURFACE_FORMAT(mask), &r, &g, &b); return r || g || b; } @@ -122,7 +122,7 @@ static SDL_Surface *copyShapeSurface(SDL_Surface *src) SDL_Surface *copy = createShapeSurface(src->w, src->h); if (!copy) return NULL; - Uint32 white = SDL_MapRGBA(copy->format, 255, 255, 255, 255); + Uint32 white = SDL_MapRGBA(XC_SURFACE_FORMAT(copy), 255, 255, 255, 255); for (int y = 0; y < src->h; y++) { for (int x = 0; x < src->w; x++) { if (maskPixelActive(src, x, y)) @@ -225,7 +225,7 @@ static SDL_Surface *combineShapeSurfaces(WindowStruct *window, if (!out) return NULL; - Uint32 white = SDL_MapRGBA(out->format, 255, 255, 255, 255); + Uint32 white = SDL_MapRGBA(XC_SURFACE_FORMAT(out), 255, 255, 255, 255); /* Track whether the produced mask actually excludes any window pixel. A * combine that ends up admitting every window pixel within the mask bbox * AND whose bbox covers the whole window is a no-op. The mask in that case diff --git a/src/xtest.c b/src/xtest.c index a760137..6773522 100644 --- a/src/xtest.c +++ b/src/xtest.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "extension.h" #include "window.h" #include "events.h" @@ -94,7 +94,7 @@ int XTestFakeMotionEvent(Display *display, SDL_Event ev; SDL_zero(ev); ev.type = SDL_MOUSEMOTION; - ev.motion.timestamp = SDL_GetTicks(); + ev.motion.timestamp = XC_NOW_EVENT_TS(); ev.motion.windowID = winId; ev.motion.x = localX; ev.motion.y = localY; @@ -119,7 +119,7 @@ int XTestFakeRelativeMotionEvent(Display *display, SDL_Event ev; SDL_zero(ev); ev.type = SDL_MOUSEMOTION; - ev.motion.timestamp = SDL_GetTicks(); + ev.motion.timestamp = XC_NOW_EVENT_TS(); ev.motion.windowID = winId; ev.motion.x = newX; ev.motion.y = newY; @@ -160,7 +160,7 @@ int XTestFakeButtonEvent(Display *display, SDL_Event ev; SDL_zero(ev); ev.type = SDL_MOUSEWHEEL; - ev.wheel.timestamp = SDL_GetTicks(); + ev.wheel.timestamp = XC_NOW_EVENT_TS(); ev.wheel.windowID = winId; ev.wheel.y = (button == 4) ? 1 : -1; ev.wheel.direction = SDL_MOUSEWHEEL_NORMAL; @@ -185,10 +185,10 @@ int XTestFakeButtonEvent(Display *display, SDL_Event ev; SDL_zero(ev); ev.type = is_press ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; - ev.button.timestamp = SDL_GetTicks(); + ev.button.timestamp = XC_NOW_EVENT_TS(); ev.button.windowID = winId; ev.button.button = (Uint8) sdlButton; - ev.button.state = is_press ? SDL_PRESSED : SDL_RELEASED; + XC_EVENT_SET_BUTTON_PRESSED(&ev, is_press); ev.button.clicks = 1; ev.button.x = curX; ev.button.y = curY; @@ -207,9 +207,9 @@ int XTestFakeKeyEvent(Display *display, SDL_Event ev; SDL_zero(ev); ev.type = is_press ? SDL_KEYDOWN : SDL_KEYUP; - ev.key.timestamp = SDL_GetTicks(); + ev.key.timestamp = XC_NOW_EVENT_TS(); ev.key.windowID = winId; - ev.key.state = is_press ? SDL_PRESSED : SDL_RELEASED; + XC_EVENT_SET_KEY_PRESSED(&ev, is_press); /* X keycodes are server-defined; SDL scancodes are SDL's own enum and the * convertEvent path derives the X keycode back from keysym.sym (low byte). * Pass the requested code through as the SDL_Keycode so the round-trip @@ -217,8 +217,8 @@ int XTestFakeKeyEvent(Display *display, * SDL_GetScancodeFromKey fill in the scancode for callers that consume it. * Callers wanting a specific keysym should XStringToKeysym first. */ - ev.key.keysym.sym = (SDL_Keycode) keycode; - ev.key.keysym.scancode = SDL_GetScancodeFromKey((SDL_Keycode) keycode); + XC_EVENT_SET_KEYSYM(&ev, (SDL_Keycode) keycode); + XC_EVENT_SET_SCANCODE(&ev, SDL_GetScancodeFromKey((SDL_Keycode) keycode)); return pushFakeEvent(display, &ev); } diff --git a/tests/bench-paths.c b/tests/bench-paths.c index 2fe9ac1..0142cd3 100644 --- a/tests/bench-paths.c +++ b/tests/bench-paths.c @@ -1,5 +1,5 @@ #include -#include +#include "sdl-compat.h" #include #include #include "window.h" diff --git a/tests/check.c b/tests/check.c index 487ff9a..fb7ebd5 100644 --- a/tests/check.c +++ b/tests/check.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include #include @@ -1038,7 +1038,8 @@ static int pixel_is_rgba(SDL_Surface *surface, Uint8 gotBlue = 0; Uint8 gotAlpha = 0; SDL_GetRGBA(getPixel(surface, (unsigned int) x, (unsigned int) y), - surface->format, &gotRed, &gotGreen, &gotBlue, &gotAlpha); + XC_SURFACE_FORMAT(surface), &gotRed, &gotGreen, &gotBlue, + &gotAlpha); return gotRed == red && gotGreen == green && gotBlue == blue && gotAlpha == alpha; } @@ -1050,7 +1051,7 @@ static int pixel_is_between_black_and_white(SDL_Surface *surface, int x, int y) Uint8 blue = 0; Uint8 alpha = 0; SDL_GetRGBA(getPixel(surface, (unsigned int) x, (unsigned int) y), - surface->format, &red, &green, &blue, &alpha); + XC_SURFACE_FORMAT(surface), &red, &green, &blue, &alpha); return alpha == 255 && red == green && green == blue && red > 0 && red < 255; } @@ -3056,7 +3057,7 @@ static int test_events(Display *display) SDL_Event textEvent; SDL_zero(textEvent); textEvent.type = SDL_TEXTINPUT; - strcpy(textEvent.text.text, "a"); + XC_SET_TEXT_EVENT(textEvent, "a"); int queuedBeforeText = XEventsQueued(display, QueuedAlready); SDL_PushEvent(&textEvent); CHECK(XEventsQueued(display, QueuedAlready) == queuedBeforeText + 1, @@ -3138,7 +3139,7 @@ static int test_events(Display *display) wheelEvent.type = SDL_MOUSEWHEEL; wheelEvent.wheel.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(window)->sdlWindow); - wheelEvent.wheel.preciseY = 0.25f; + XC_SET_WHEEL_Y(&wheelEvent, 0.25f); wheelEvent.wheel.direction = SDL_MOUSEWHEEL_NORMAL; CHECK(convertEvent(display, &wheelEvent, &out, True) == -1, "fractional precise wheel delta converted directly"); @@ -3149,7 +3150,7 @@ static int test_events(Display *display) wheelEvent.type = SDL_MOUSEWHEEL; wheelEvent.wheel.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(window)->sdlWindow); - wheelEvent.wheel.preciseY = 0.75f; + XC_SET_WHEEL_Y(&wheelEvent, 0.75f); wheelEvent.wheel.direction = SDL_MOUSEWHEEL_NORMAL; CHECK(convertEvent(display, &wheelEvent, &out, True) == -1, "accumulated precise wheel delta converted directly"); @@ -3261,10 +3262,10 @@ static int test_events(Display *display) SDL_Event topLeave; SDL_zero(topLeave); - topLeave.type = SDL_WINDOWEVENT; + XC_INIT_WINDOW_EVENT(&topLeave); topLeave.window.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(pointerParent)->sdlWindow); - topLeave.window.event = SDL_WINDOWEVENT_LEAVE; + XC_SET_WINDOW_SUBEVENT(&topLeave, SDL_WINDOWEVENT_LEAVE); CHECK(convertEvent(display, &topLeave, &out, True) != 0, "top-level leave should queue child leave first"); XNextEvent(display, &out); @@ -3582,15 +3583,15 @@ static int test_events(Display *display) XSelectInput(display, window, EnterWindowMask | LeaveWindowMask); SDL_Event crossingEvent; SDL_zero(crossingEvent); - crossingEvent.type = SDL_WINDOWEVENT; + XC_INIT_WINDOW_EVENT(&crossingEvent); crossingEvent.window.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(window)->sdlWindow); - crossingEvent.window.event = SDL_WINDOWEVENT_ENTER; + XC_SET_WINDOW_SUBEVENT(&crossingEvent, SDL_WINDOWEVENT_ENTER); CHECK(convertEvent(display, &crossingEvent, &out, True) == 0, "SDL window enter did not convert"); CHECK(out.type == EnterNotify && out.xcrossing.mode == NotifyNormal, "SDL window enter crossing fields were incorrect"); - crossingEvent.window.event = SDL_WINDOWEVENT_LEAVE; + XC_SET_WINDOW_SUBEVENT(&crossingEvent, SDL_WINDOWEVENT_LEAVE); CHECK(convertEvent(display, &crossingEvent, &out, True) == 0, "SDL window leave did not convert"); CHECK(out.type == LeaveNotify && out.xcrossing.mode == NotifyNormal, @@ -3775,8 +3776,8 @@ static int test_events(Display *display) SDL_Event resizeEvent; SDL_zero(resizeEvent); - resizeEvent.type = SDL_WINDOWEVENT; - resizeEvent.window.event = SDL_WINDOWEVENT_RESIZED; + XC_INIT_WINDOW_EVENT(&resizeEvent); + XC_SET_WINDOW_SUBEVENT(&resizeEvent, SDL_WINDOWEVENT_RESIZED); resizeEvent.window.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(window)->sdlWindow); resizeEvent.window.data1 = 48; @@ -3817,10 +3818,10 @@ static int test_events(Display *display) XFreeGC(display, resizeGc); SDL_Event windowEvent; SDL_zero(windowEvent); - windowEvent.type = SDL_WINDOWEVENT; + XC_INIT_WINDOW_EVENT(&windowEvent); windowEvent.window.windowID = SDL_GetWindowID(GET_WINDOW_STRUCT(window)->sdlWindow); - windowEvent.window.event = SDL_WINDOWEVENT_MOVED; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_MOVED); windowEvent.window.data1 = 11; windowEvent.window.data2 = 12; CHECK(convertEvent(display, &windowEvent, &out, True) == 0, @@ -3832,12 +3833,12 @@ static int test_events(Display *display) movedWindowAttrs.y == out.xconfigure.y, "SDL move did not update window attributes"); - windowEvent.window.event = SDL_WINDOWEVENT_HIDDEN; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_HIDDEN); CHECK(convertEvent(display, &windowEvent, &out, True) == 0, "SDL hidden did not convert to UnmapNotify"); CHECK(expect_map_state(display, window, IsUnmapped), "SDL hidden did not update map state"); - windowEvent.window.event = SDL_WINDOWEVENT_SHOWN; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_SHOWN); GET_WINDOW_STRUCT(window)->needsPresent = False; CHECK(convertEvent(display, &windowEvent, &out, True) == 0, "SDL shown did not convert to MapNotify"); @@ -3846,18 +3847,18 @@ static int test_events(Display *display) CHECK(GET_WINDOW_STRUCT(window)->needsPresent, "SDL shown did not request repaint of the mapped window"); GET_WINDOW_STRUCT(window)->needsPresent = False; - windowEvent.window.event = SDL_WINDOWEVENT_EXPOSED; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_EXPOSED); CHECK(convertEvent(display, &windowEvent, &out, True) < 0, "SDL exposed should be consumed internally"); CHECK(GET_WINDOW_STRUCT(window)->needsPresent, "SDL exposed did not request repaint of the existing backing store"); GET_WINDOW_STRUCT(window)->needsPresent = False; - windowEvent.window.event = SDL_WINDOWEVENT_MINIMIZED; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_MINIMIZED); CHECK(convertEvent(display, &windowEvent, &out, True) < 0, "SDL minimized should be consumed internally"); CHECK(expect_map_state(display, window, IsUnmapped), "SDL minimized did not update map state"); - windowEvent.window.event = SDL_WINDOWEVENT_RESTORED; + XC_SET_WINDOW_SUBEVENT(&windowEvent, SDL_WINDOWEVENT_RESTORED); CHECK(convertEvent(display, &windowEvent, &out, True) < 0, "SDL restored should be consumed internally"); CHECK(expect_map_state(display, window, IsViewable), @@ -4962,6 +4963,11 @@ static int test_icccm_transient_for_modal(Display *display) #if SDL_VERSION_ATLEAST(2, 0, 5) CHECK(GET_WINDOW_STRUCT(child)->deferredTransientApplied, "transient: modal hint did not trigger SDL_SetWindowModalFor"); +#ifdef LIBX11_COMPAT_SDL3 + bool driverTracksParent = + SDL_GetWindowParent(GET_WINDOW_STRUCT(child)->sdlWindow) == + GET_WINDOW_STRUCT(parent)->sdlWindow; +#endif GET_WINDOW_STRUCT(child)->deferredTransientApplied = False; XEvent event = {.xclient = { @@ -4983,6 +4989,11 @@ static int test_icccm_transient_for_modal(Display *display) "transient: XDeleteProperty(_NET_WM_STATE) failed"); CHECK(!GET_WINDOW_STRUCT(child)->deferredTransientApplied, "transient: clearing modal did not drop applied flag"); +#ifdef LIBX11_COMPAT_SDL3 + CHECK(!driverTracksParent || + SDL_GetWindowParent(GET_WINDOW_STRUCT(child)->sdlWindow) == NULL, + "transient: clearing modal did not drop SDL3 native parent"); +#endif /* XDeleteProperty(WM_TRANSIENT_FOR) clears the deferred parent. */ CHECK(XChangeProperty(display, child, netWmState, XA_ATOM, 32, diff --git a/tests/test-motif-link.c b/tests/test-motif-link.c index 7d34afd..5bc5f2b 100644 --- a/tests/test-motif-link.c +++ b/tests/test-motif-link.c @@ -39,7 +39,7 @@ static int image_has_visible_pixels(XImage *image) static int surface_has_visible_pixels(SDL_Surface *surface) { - if (!surface || surface->format->BytesPerPixel != 4) + if (!surface || XC_SURFACE_BYTESPERPIXEL(surface) != 4) return 0; if (SDL_LockSurface(surface) != 0) @@ -51,7 +51,7 @@ static int surface_has_visible_pixels(SDL_Surface *surface) y * surface->pitch); for (int x = 0; x < surface->w; x++) { Uint8 r, g, b, a; - SDL_GetRGBA(row[x], surface->format, &r, &g, &b, &a); + SDL_GetRGBA(row[x], XC_SURFACE_FORMAT(surface), &r, &g, &b, &a); if (r != 0 || g != 0 || b != 0 || a != 0) { visible = 1; break; diff --git a/tests/test-xtest.c b/tests/test-xtest.c index e7c1c93..fb3277a 100644 --- a/tests/test-xtest.c +++ b/tests/test-xtest.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include "sdl-compat.h" #include "replay-target.h" #define CHECK(cond, msg) \