diff --git a/.github/workflows/PR-builds.yml b/.github/workflows/PR-builds.yml index bef8435d1..d18dab020 100644 --- a/.github/workflows/PR-builds.yml +++ b/.github/workflows/PR-builds.yml @@ -52,11 +52,10 @@ jobs: # =========================================================================== linux-x64: name: Linux x86_64 - # Pinned to 22.04: the engine's X11 back-end needs GENUINE SDL 1.2 (it links - # the SDL-1.2 X11 driver symbol X11_KeyToUnicode and calls 1.2-only APIs). - # On 24.04, libsdl1.2-dev is the SDL2-based sdl12-compat shim, which does NOT - # export those symbols, so the link fails. - runs-on: ubuntu-22.04 + # Not pinned: libsdl1.2-dev on 24.04 is the SDL2-backed sdl12-compat shim, + # which the back-end is happy with now that the ascii key table is read from + # the X keymap rather than from SDL's private X11_KeyToUnicode. + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest @@ -88,9 +87,11 @@ jobs: linux-x86: name: Linux x86 (32-bit) - # Pinned to 22.04 for genuine SDL 1.2 (see the linux-x64 job). The 32-bit - # build additionally needs NASM: platform/platformCPUInfo.asm (detectX86CPUInfo) - # is compiled only on 32-bit (it is x86-only and is skipped on 64-bit via + # Still pinned to 22.04, but for the :i386 dev packages rather than for SDL: + # newer Debian/Ubuntu drop the 32-bit dev symlinks the configure step needs + # (see the multilib gotchas in cmake/BUILD-PLATFORM-NOTES.md). The 32-bit build + # additionally needs NASM: platform/platformCPUInfo.asm (detectX86CPUInfo) is + # compiled only on 32-bit (it is x86-only and is skipped on 64-bit via # TORQUE_64). runs-on: ubuntu-22.04 steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f98bebc8..68b16916c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,8 @@ and makes CMake the only build system. - The engine reported its version as 1.0. `TORQUE_GAME_ENGINE` had never been raised past `1000`, so `getVersionNumber()` answered `1000` where 4.0 is `4000`, and the server-query compatibility check compared every build as 1.0. `getVersionString()` returned `"Open Source"`, which is not a version at all; it now returns `"4.0 Early Access 4"`, which is also what Linux prints at start-up. - On Linux, per-user files were written next to the executable. `getUserDataDirectory()`, `getUserHomeDirectory()` and the temporary directory all answered `"~/"`, which only a shell expands -- so the engine read it as a relative path and resolved it against the working directory, putting the editor's preferences in a folder literally named `~` inside whatever project was running, and scratch files from `getTemporaryFileName()` beside them. They now follow the XDG Base Directory specification, as the equivalents on Windows and macOS have always followed theirs: `$XDG_DATA_HOME` or `~/.local/share` for user data, `$HOME` for the home directory, and `$TMPDIR` or `/tmp` for temporaries. A project's preferences move to `~/.local/share///`; anything under an old `~` folder can be moved there or discarded. - On Linux, a relative path was resolved against the directory the process was launched from rather than the one the game runs in. The Unix back-end carried a "pref dir" redirect, meant to route created files under the user's home on the assumption that the game's own folder is not writable. It was never switched on -- it was gated on a `USE_FILE_REDIRECT` macro that nothing ever defined -- so the pref dir was only ever the working directory, but it was cached at the first file open, which happens before the engine changes directory to the folder holding `main.cs`. Launching `/path/to/Torque2D /path/to/main.cs` from anywhere else therefore wrote `console.log` into the folder you happened to be standing in, and read a relative name from there in preference to the game's own copy. A relative path is now resolved against the working directory as it stands at the call, which is what Windows and macOS have always done. The `-nohomedir` command-line switch, which turned off a redirect that was already off, is gone. +- On Linux, the engine can be built and run against the SDL 1.2 that distributions actually ship. It used to call `X11_KeyToUnicode`, a private symbol of SDL 1.2's X11 driver that was never in a public header, to find out which character each key types; the ascii table is now read from the X keymap through Xlib instead. That symbol is missing or wrong everywhere except a hand-built genuine SDL 1.2 -- the `sdl12-compat` shim now shipped as SDL 1.2 by Ubuntu 24.04+ and Arch does export one, but it is a US-layout `toupper()` stub that answers `1` for shift-`1` and `;` for shift-`;`, so building against a distribution package used to link and then mistype every shifted punctuation character. The table is unchanged on a US layout; on an international layout the AltGr row now holds the characters AltGr actually produces (on a German keyboard AltGr-q is `@`) rather than repeating the unshifted ones. +- On Linux, the engine no longer stops at "Unable to initialize SDL" on a Wayland desktop. The X11 back-end takes its display, screen and locking from `SDL_GetWMInfo`, and the SDL2/3-backed `sdl12-compat` defaults to the Wayland video driver, where that call has nothing to hand back. The back-end now asks for SDL's X11 video driver, so it runs through Xwayland; setting `SDL_VIDEODRIVER` yourself still overrides it. ### Removed diff --git a/CMakeLists.txt b/CMakeLists.txt index 408507c15..a15837aa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -452,8 +452,10 @@ if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT EMSCRIPTEN) # Linux / X11 desktop build (platformX86UNIX). NOTES for the on-platform # (WSL/Linux) session: # * SDL 1.2 is REQUIRED, not optional: the platform back-end calls 1.2-only - # APIs (SDL_GetVideoSurface, SDL_WM_*, SDL_*GammaRamp, SDL_GL_SwapBuffers) - # and pulls X11_KeyToUnicode out of libSDL. This is NOT SDL2. + # APIs (SDL_GetVideoSurface, SDL_WM_*, SDL_*GammaRamp, SDL_GL_SwapBuffers). + # This is NOT SDL2. The SDL2-backed sdl12-compat shim (Ubuntu 24.04+, Arch) + # is fine: the back-end no longer reaches into libSDL for the private + # X11_KeyToUnicode symbol, it reads the keymap through Xlib. # * `rt` is a no-op on modern glibc but harmless. # * OpenGL/FreeType are resolved via find_package (more robust than the old # hard-coded /usr/include/freetype2 paths). Install dev packages, e.g. on diff --git a/build-linux.sh b/build-linux.sh index 471c1b258..448d6235f 100755 --- a/build-linux.sh +++ b/build-linux.sh @@ -17,8 +17,9 @@ # sudo apt install build-essential cmake \ # libsdl1.2-dev libx11-dev libxft-dev libfreetype6-dev \ # libopenal-dev libgl1-mesa-dev -# Note: GENUINE SDL 1.2 is required (NOT the SDL2-based sdl12-compat shim that -# ships on Ubuntu 24.04+). For a 32-bit build see cmake/BUILD-PLATFORM-NOTES.md. +# Note: that is SDL *1.2*, not SDL2. The SDL2-backed sdl12-compat shim that +# ships on Ubuntu 24.04+ and Arch works too. +# For a 32-bit build see cmake/BUILD-PLATFORM-NOTES.md. # --------------------------------------------------------------------------- set -e diff --git a/cmake/BUILD-PLATFORM-NOTES.md b/cmake/BUILD-PLATFORM-NOTES.md index 11356ee48..5f28d6cb8 100644 --- a/cmake/BUILD-PLATFORM-NOTES.md +++ b/cmake/BUILD-PLATFORM-NOTES.md @@ -370,10 +370,15 @@ codesign — `rm -rf Torque2D_DEBUG.app` when switching platforms. and rebuild whichever bitness you want at the root. 3. Resolved issues (the original scaffold's wrong assumptions): - **SDL 1.2 is REQUIRED, not optional.** The back-end calls 1.2-only APIs - (`SDL_GetVideoSurface`, `SDL_WM_*`, `SDL_*GammaRamp`, `SDL_GL_SwapBuffers`) - and pulls `X11_KeyToUnicode` out of `libSDL`. This is **NOT SDL2** — and NOT - the SDL2-based `sdl12-compat` shim, which lacks `X11_KeyToUnicode` (so CI is - pinned to ubuntu-22.04, which still ships genuine SDL 1.2.15). + (`SDL_GetVideoSurface`, `SDL_WM_*`, `SDL_*GammaRamp`, `SDL_GL_SwapBuffers`). + This is **NOT SDL2**. The SDL2-backed `sdl12-compat` shim (Ubuntu 24.04+, + Arch) *is* supported: the ascii key table used to be built by calling + `X11_KeyToUnicode`, a private symbol of SDL 1.2's X11 driver that was never + in a public header, and it is now read from the X keymap through Xlib. + `sdl12-compat` does export an `X11_KeyToUnicode`, but it is a US-layout + `toupper()` stub — it answers `1` for shift-`1` and `;` for shift-`;` — so + linking against it used to succeed and then mistype every shifted + punctuation character. - **`detectX86CPUInfo`** comes from `platform/platformCPUInfo.asm`, 32-bit-only NASM (does not assemble for elf64). It's referenced only `#ifndef TORQUE_64`, so 64-bit defines `TORQUE_64` (asm unneeded); 32-bit assembles it via NASM. @@ -386,9 +391,8 @@ codesign — `rm -rf Torque2D_DEBUG.app` when switching platforms. up (`DISPLAY=:0`, `WAYLAND_DISPLAY=wayland-0`, `/mnt/wslg/.X11-unix/X0`), `./Torque2D_DEBUG` launches the Project Manager GUI: OpenGL initializes through WSLg's GL stack (`Renderer: D3D12 (...) Mesa`), screen mode sets, editor modules - load, and it exits 0 on close. The `X11_KeyToUnicode()` warning at startup is - expected (the genuine-SDL-1.2 symbol, see above) and harmless. Without WSLg/an X - server the build/link still verifies but the window won't appear. + load, and it exits 0 on close. Without WSLg/an X server the build/link still + verifies but the window won't appear. **32-bit also boots under WSLg**, but falls back to **llvmpipe (software GL)** — `Renderer: llvmpipe (...)` rather than the 64-bit `D3D12 (NVIDIA ...)`, because WSLg's hardware-GL passthrough (the d3d12 Mesa driver) is 64-bit only. It still @@ -748,9 +752,9 @@ keyboard input). Four fixes: (`string/stringTable.cc`) — a latent CROSS-PLATFORM bug; fixes all high-bit/accented input. - **Phantom glyphs from modifier keys.** `EmscriptenInputManager::MapKey` assigned the raw SDL keysym as each key's `ascii`, so modifiers/function/arrow/keypad keys all carried a bogus - non-zero ascii and got inserted as (unrenderable) characters. Desktop x86UNIX avoids this via - `X11_KeyToUnicode()` (returns 0 for non-character keys), but emscripten's SDL1 port has no - working `X11_KeyToUnicode`. Filtered the default assignment: only printable ASCII (0x20-0x7E) + non-zero ascii and got inserted as (unrenderable) characters. Desktop x86UNIX avoids this by + reading the X keymap (which yields nothing for a non-character key), but a browser canvas has + no keymap to read. Filtered the default assignment: only printable ASCII (0x20-0x7E) carries a character ascii; SDL specials (≥0x100), 0x7F-0xFF, and control keys (<0x20) map to 0 and stay handled by keycode (`EmscriptenInputManager.cpp`). - **Event-list re-entrancy OOB.** `ProcessMessages()` cached the size of the shared diff --git a/engine/source/platformEmscripten/EmscriptenInputManager.cpp b/engine/source/platformEmscripten/EmscriptenInputManager.cpp index b15b2c981..b927f7556 100644 --- a/engine/source/platformEmscripten/EmscriptenInputManager.cpp +++ b/engine/source/platformEmscripten/EmscriptenInputManager.cpp @@ -53,9 +53,6 @@ static const U32 JoystickMask = SDL_JOYAXISMOTION | SDL_JOYBUTTONUP;//SDL_JOYEVE static const U32 AllInputEvents = MouseMask | KeyboardMask | JoystickMask; -// defined in SDL -extern "C" Uint16 X11_KeyToUnicode( SDLKey keysym, SDLMod modifiers ); - //============================================================================== // Static helper functions //============================================================================== @@ -68,8 +65,8 @@ static void MapKey(Uint16 SDLkey, U8 tkey) // >= 0x100) and the 0x7F-0xFF range must map to ascii 0; otherwise they're // treated as character input and insert phantom glyphs into text fields (e.g. // pressing Ctrl typed a stray "box" character). The desktop x86UNIX back-end - // gets this for free via X11_KeyToUnicode (which returns 0 for non-character - // keys), but emscripten's SDL1 port has no working X11_KeyToUnicode, hence this + // gets this for free by reading the X keymap (which yields nothing for a + // non-character key), but a browser canvas has no keymap to read, hence this // explicit filter. Control keys (< 0x20: Tab/Enter/Backspace/Esc) also map to 0 // here and are handled by keycode, not by character input. Shifted variants of // the printable keys are still set by the switch below. diff --git a/engine/source/platformX86UNIX/x86UNIXInputManager.cc b/engine/source/platformX86UNIX/x86UNIXInputManager.cc index c7e498e41..f53626e46 100755 --- a/engine/source/platformX86UNIX/x86UNIXInputManager.cc +++ b/engine/source/platformX86UNIX/x86UNIXInputManager.cc @@ -28,6 +28,10 @@ #include "platformX86UNIX/x86UNIXInputManager.h" #include "math/mMathFn.h" +#include +#include +#include + #include // ascii table @@ -54,30 +58,157 @@ static const U32 JoystickMask = SDL_JOYEVENTMASK; static const U32 AllInputEvents = MouseMask | KeyboardMask | JoystickMask; -// defined in SDL -extern "C" Uint16 X11_KeyToUnicode( SDLKey keysym, SDLMod modifiers ); +// The display InitKeyMaps() builds the ascii table against. Only valid for the +// duration of that call; MapKey() reads it rather than taking it as an argument +// so that the (long) list of MapKey() calls below stays readable. +static Display* sgKeymapDisplay = NULL; //============================================================================== // Static helper functions //============================================================================== +// Translate an SDL 1.2 keysym to the X keysym naming the same key. +// +// SDL numbered its keysyms after the X ones it was built on, so the printable +// range needs no table: SDLK_SPACE..SDLK_z and the Latin-1 SDLK_WORLD_* range +// already are the X keysym of the same character. Everything else -- the +// control keys (whose SDL value is the ASCII code, not the keysym), the keypad, +// navigation, function and modifier keys -- has to be named explicitly. +static KeySym SDLKeyToXKeySym(SDLKey key) +{ + switch (key) + { + // Control keys. SDL numbered these after their ASCII code. + case SDLK_BACKSPACE: return XK_BackSpace; + case SDLK_TAB: return XK_Tab; + case SDLK_CLEAR: return XK_Clear; + case SDLK_RETURN: return XK_Return; + case SDLK_PAUSE: return XK_Pause; + case SDLK_ESCAPE: return XK_Escape; + case SDLK_DELETE: return XK_Delete; + + // Keypad. These are deliberately the numlock-OFF spellings: a keyboard + // binds both spellings of each numpad key and XKeysymToKeycode can resolve + // them to *different* keycodes -- XK_KP_Decimal commonly lands on a + // separate layout-defined separator key rather than the physical numpad + // period. The numlock-off spelling is the one SDL 1.2 resolved to. + case SDLK_KP0: return XK_KP_Insert; + case SDLK_KP1: return XK_KP_End; + case SDLK_KP2: return XK_KP_Down; + case SDLK_KP3: return XK_KP_Page_Down; + case SDLK_KP4: return XK_KP_Left; + case SDLK_KP5: return XK_KP_Begin; + case SDLK_KP6: return XK_KP_Right; + case SDLK_KP7: return XK_KP_Home; + case SDLK_KP8: return XK_KP_Up; + case SDLK_KP9: return XK_KP_Page_Up; + case SDLK_KP_PERIOD: return XK_KP_Delete; + case SDLK_KP_DIVIDE: return XK_KP_Divide; + case SDLK_KP_MULTIPLY: return XK_KP_Multiply; + case SDLK_KP_MINUS: return XK_KP_Subtract; + case SDLK_KP_PLUS: return XK_KP_Add; + case SDLK_KP_ENTER: return XK_KP_Enter; + case SDLK_KP_EQUALS: return XK_KP_Equal; + + // Navigation. + case SDLK_UP: return XK_Up; + case SDLK_DOWN: return XK_Down; + case SDLK_RIGHT: return XK_Right; + case SDLK_LEFT: return XK_Left; + case SDLK_INSERT: return XK_Insert; + case SDLK_HOME: return XK_Home; + case SDLK_END: return XK_End; + case SDLK_PAGEUP: return XK_Page_Up; + case SDLK_PAGEDOWN: return XK_Page_Down; + + // Function keys. + case SDLK_F1: return XK_F1; + case SDLK_F2: return XK_F2; + case SDLK_F3: return XK_F3; + case SDLK_F4: return XK_F4; + case SDLK_F5: return XK_F5; + case SDLK_F6: return XK_F6; + case SDLK_F7: return XK_F7; + case SDLK_F8: return XK_F8; + case SDLK_F9: return XK_F9; + case SDLK_F10: return XK_F10; + case SDLK_F11: return XK_F11; + case SDLK_F12: return XK_F12; + case SDLK_F13: return XK_F13; + case SDLK_F14: return XK_F14; + case SDLK_F15: return XK_F15; + + // Locks and modifiers. + case SDLK_NUMLOCK: return XK_Num_Lock; + case SDLK_CAPSLOCK: return XK_Caps_Lock; + case SDLK_SCROLLOCK: return XK_Scroll_Lock; + case SDLK_RSHIFT: return XK_Shift_R; + case SDLK_LSHIFT: return XK_Shift_L; + case SDLK_RCTRL: return XK_Control_R; + case SDLK_LCTRL: return XK_Control_L; + case SDLK_RALT: return XK_Alt_R; + case SDLK_LALT: return XK_Alt_L; + case SDLK_RMETA: return XK_Meta_R; + case SDLK_LMETA: return XK_Meta_L; + case SDLK_LSUPER: return XK_Super_L; + case SDLK_RSUPER: return XK_Super_R; + case SDLK_MODE: return XK_Mode_switch; + case SDLK_COMPOSE: return XK_Multi_key; + + // Misc. + case SDLK_HELP: return XK_Help; + case SDLK_PRINT: return XK_Print; + case SDLK_SYSREQ: return XK_Sys_Req; + case SDLK_BREAK: return XK_Break; + case SDLK_MENU: return XK_Menu; + + default: + if ((key >= 0x20 && key < 0x7F) || (key >= 0xA0 && key <= 0xFF)) + return static_cast(key); + return NoSymbol; + } +} + +// The character the given key produces with the given modifiers held, according +// to the keymap the X server is currently using. Asking X rather than assuming +// a layout is the whole point: on a German keyboard shift-7 is '/' and AltGr-q +// is '@', and a hardcoded US table would get both wrong. +static U16 KeySymToAscii(KeySym sym, unsigned int state) +{ + XKeyEvent xkey; + char keybuf[32]; + + if (sgKeymapDisplay == NULL || sym == NoSymbol) + return 0; + + dMemset(&xkey, 0, sizeof(xkey)); + xkey.type = KeyPress; + xkey.display = sgKeymapDisplay; + xkey.keycode = XKeysymToKeycode(sgKeymapDisplay, sym); + xkey.state = state; + + // The key isn't on this keymap at all. + if (xkey.keycode == 0) + return 0; + + if (XLookupString(&xkey, keybuf, sizeof(keybuf), NULL, NULL) > 0) + return static_cast(static_cast(keybuf[0])); + + return 0; +} + +//------------------------------------------------------------------------------ static void MapKey(Uint16 SDLkey, U8 tkey) { SDLtoTKeyMap[SDLkey] = tkey; - Uint16 key = 0; - SDLKey skey = (SDLKey)SDLkey; - SDLMod mod = KMOD_NONE; + KeySym sym = SDLKeyToXKeySym(static_cast(SDLkey)); + // lower case - key = X11_KeyToUnicode( skey, mod ); - AsciiTable[tkey].lower.ascii = key; + AsciiTable[tkey].lower.ascii = KeySymToAscii(sym, 0); // upper case - mod = KMOD_LSHIFT; - key = X11_KeyToUnicode( skey, mod ); - AsciiTable[tkey].upper.ascii = key; - // goofy (i18n) case - mod = KMOD_MODE; - key = X11_KeyToUnicode( skey, mod ); - AsciiTable[tkey].goofy.ascii = key; + AsciiTable[tkey].upper.ascii = KeySymToAscii(sym, ShiftMask); + // goofy (i18n) case -- AltGr, which X keymaps reach through Mod5. + AsciiTable[tkey].goofy.ascii = KeySymToAscii(sym, Mod5Mask); } //------------------------------------------------------------------------------ @@ -85,7 +216,16 @@ void InitKeyMaps() { dMemset( &AsciiTable, 0, sizeof( AsciiTable ) ); dMemset(SDLtoTKeyMap, KEY_NULL, SDLtoTKeyMapSize); - + + // The ascii half of the table is read off the X keymap, so it is only as + // good as the layout in effect right now. If there is no display to ask, + // every key still maps to its Torque keycode; only the ascii stays zero. + DisplayPtrManager xdisplay; + sgKeymapDisplay = xdisplay.getDisplayPointer(); + if (sgKeymapDisplay == NULL) + Con::warnf("Input: no X display to read the keymap from; " + "keys will report no ascii character."); + // set up the X to Torque key map // stuff MapKey(SDLK_BACKSPACE, KEY_BACKSPACE); @@ -167,6 +307,7 @@ void InitKeyMaps() MapKey(SDLK_MENU, KEY_WIN_APPS); MapKey(SDLK_MODE, KEY_OEM_102); + sgKeymapDisplay = NULL; keyMapsInitialized = true; }; diff --git a/engine/source/platformX86UNIX/x86UNIXWindow.cc b/engine/source/platformX86UNIX/x86UNIXWindow.cc index 6440ee4e8..9b2d733d1 100755 --- a/engine/source/platformX86UNIX/x86UNIXWindow.cc +++ b/engine/source/platformX86UNIX/x86UNIXWindow.cc @@ -152,6 +152,16 @@ static void InitWindow(const Point2I &initialSize, const char *name) //------------------------------------------------------------------------------ static bool InitSDL() { + // Ask for the X11 video driver unless the user named one. This back-end is + // an X11 back-end: it takes its Display, screen and display lock functions + // straight out of SDL_GetWMInfo's x11 union below, and everything from the + // keymap to the clipboard is built on them. Genuine SDL 1.2 only ever had + // X11 to offer, but the SDL2/3-backed sdl12-compat shim that distributions + // now ship as SDL 1.2 defaults to Wayland on a Wayland desktop, where + // SDL_GetWMInfo answers "No SysWM support available" and the engine used to + // stop at "Unable to initialize SDL". Xwayland serves us fine. + setenv("SDL_VIDEODRIVER", "x11", 0); + if (SDL_Init(SDL_INIT_VIDEO) != 0) return false;