Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .github/workflows/PR-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<Company>/<Product>/`; 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

Expand Down
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions build-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 14 additions & 10 deletions cmake/BUILD-PLATFORM-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions engine/source/platformEmscripten/EmscriptenInputManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
//==============================================================================
Expand All @@ -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.
Expand Down
171 changes: 156 additions & 15 deletions engine/source/platformX86UNIX/x86UNIXInputManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
#include "platformX86UNIX/x86UNIXInputManager.h"
#include "math/mMathFn.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <SDL/SDL.h>

// ascii table
Expand All @@ -54,38 +58,174 @@ 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<KeySym>(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<U16>(static_cast<unsigned char>(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>(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);
}

//------------------------------------------------------------------------------
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);
Expand Down Expand Up @@ -167,6 +307,7 @@ void InitKeyMaps()
MapKey(SDLK_MENU, KEY_WIN_APPS);
MapKey(SDLK_MODE, KEY_OEM_102);

sgKeymapDisplay = NULL;
keyMapsInitialized = true;
};

Expand Down
Loading
Loading