From cc04d4e1211087eb300a97309477caa81d0f2797 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 18 Oct 2026 02:06:03 +0200 Subject: [PATCH] fix(wayland): constant-initialised statics in Device, for clang 23.1 on aarch64-musl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the touch positions in an inline static std::unordered_map and the text input's strings in inline static std::strings, clang 23.1 crashed compiling Device.cpp for aarch64-alpine-linux-musl against that sysroot's libc++ 22 — in LazyCallGraph at -O3, in the ThinLTO bitcode writer at -O0 (a dangling global); the native build was fine. The fingers now live in a constant-initialised array of slots and the strings in function-local statics of the implementation unit; nothing else changes. --- implementations/Crafter.Graphics-Device.cpp | 85 ++++++++++++++++----- implementations/Crafter.Graphics-Window.cpp | 2 +- interfaces/Crafter.Graphics-Device.cppm | 16 ++-- 3 files changed, 80 insertions(+), 23 deletions(-) diff --git a/implementations/Crafter.Graphics-Device.cpp b/implementations/Crafter.Graphics-Device.cpp index b8d0f15..8258218 100644 --- a/implementations/Crafter.Graphics-Device.cpp +++ b/implementations/Crafter.Graphics-Device.cpp @@ -338,6 +338,44 @@ void Device::handle_global(void *data, wl_registry *registry, std::uint32_t name } } +namespace { + // A finger's slot by id, or a free one for a new finger; nullptr with more + // than ten fingers down. + Crafter::Device::TouchSlot* SlotFor(std::int32_t id) { + Crafter::Device::TouchSlot* free = nullptr; + for (Crafter::Device::TouchSlot& slot : Crafter::Device::touchSlots) { + if (slot.used && slot.id == id) return &slot; + if (!slot.used && free == nullptr) free = &slot; + } + if (free != nullptr) { + free->used = true; + free->id = id; + } + return free; + } + + bool AnyTouch() { + for (const Crafter::Device::TouchSlot& slot : Crafter::Device::touchSlots) { + if (slot.used) return true; + } + return false; + } + + // The text input's pending strings live here, as function-local statics: + // an inline static std::string in the interface made clang 23.1 crash + // in ThinLTO for aarch64-alpine-linux-musl. + struct TextInputStrings { + std::string preedit; + std::string shown; // what the window was last told + std::string commit; + }; + + TextInputStrings& TextStrings() { + static TextInputStrings strings; + return strings; + } +} + void Device::text_input_enter(void*, zwp_text_input_v3*, wl_surface* surface) { textInputWindow = nullptr; for (Window* window : windows) { @@ -349,17 +387,17 @@ void Device::text_input_enter(void*, zwp_text_input_v3*, wl_surface* surface) { void Device::text_input_leave(void*, zwp_text_input_v3*, wl_surface*) { textInputWindow = nullptr; textInputHasPreedit = textInputHasCommit = textInputHasDelete = false; - textInputPreedit.clear(); - textInputShownPreedit.clear(); + TextStrings().preedit.clear(); + TextStrings().shown.clear(); } void Device::text_input_preedit(void*, zwp_text_input_v3*, const char* text, std::int32_t, std::int32_t) { - textInputPreedit = text == nullptr ? "" : text; + TextStrings().preedit = text == nullptr ? "" : text; textInputHasPreedit = true; } void Device::text_input_commit(void*, zwp_text_input_v3*, const char* text) { - textInputCommit = text == nullptr ? "" : text; + TextStrings().commit = text == nullptr ? "" : text; textInputHasCommit = true; } @@ -375,17 +413,18 @@ void Device::text_input_delete(void*, zwp_text_input_v3*, std::uint32_t before, // the window hears about it only when that differs from what it shows. void Device::text_input_done(void*, zwp_text_input_v3*, std::uint32_t) { Window* window = textInputWindow; - if (!textInputHasPreedit) textInputPreedit.clear(); + TextInputStrings& strings = TextStrings(); + if (!textInputHasPreedit) strings.preedit.clear(); if (window != nullptr) { if (textInputHasDelete && (textInputDeleteBefore > 0 || textInputDeleteAfter > 0)) window->onTextInputDelete.Invoke({ textInputDeleteBefore, textInputDeleteAfter }); - if (textInputHasCommit && !textInputCommit.empty()) window->onTextInput.Invoke(textInputCommit); - if (textInputPreedit != textInputShownPreedit || (textInputHasCommit && !textInputCommit.empty())) { - textInputShownPreedit = textInputPreedit; - window->onPreedit.Invoke(textInputPreedit); + if (textInputHasCommit && !strings.commit.empty()) window->onTextInput.Invoke(strings.commit); + if (strings.preedit != strings.shown || (textInputHasCommit && !strings.commit.empty())) { + strings.shown = strings.preedit; + window->onPreedit.Invoke(strings.preedit); } } textInputHasPreedit = textInputHasCommit = textInputHasDelete = false; - textInputCommit.clear(); + strings.commit.clear(); } void Device::ApplyTextInput(Window* window) { @@ -626,6 +665,10 @@ void Device::TickKeyRepeats() { } } +void Device::ForgetTouches() { + for (TouchSlot& slot : touchSlots) slot = TouchSlot{}; +} + // The first finger stands in for the pointer: it enters the window (so the // clipboard has a serial and Device::focusedWindow a window), presses the // left button, moves it and releases it. Every finger is also reported as @@ -639,7 +682,10 @@ void Device::touch_down(void*, wl_touch*, std::uint32_t serial, std::uint32_t, w touchWindow = window; window->lastPointerSerial_ = serial; const Vector pos = Vector(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale; - touchPositions[id] = { pos.x, pos.y }; + if (TouchSlot* slot = SlotFor(id)) { + slot->x = pos.x; + slot->y = pos.y; + } window->onTouchDown.Invoke({ id, pos }); if (window->primaryTouchId < 0) { window->primaryTouchId = id; @@ -653,23 +699,28 @@ void Device::touch_down(void*, wl_touch*, std::uint32_t serial, std::uint32_t, w void Device::touch_up(void*, wl_touch*, std::uint32_t, std::uint32_t, std::int32_t id) { if (touchWindow == nullptr) return; Window* window = touchWindow; - const auto at = touchPositions.find(id); - const Vector pos = at == touchPositions.end() ? window->currentMousePos : Vector(at->second.first, at->second.second); - touchPositions.erase(id); + Vector pos = window->currentMousePos; + if (TouchSlot* slot = SlotFor(id)) { + pos = Vector(slot->x, slot->y); + *slot = TouchSlot{}; + } window->onTouchUp.Invoke({ id, pos }); if (id == window->primaryTouchId) { window->primaryTouchId = -1; window->mouseLeftHeld = false; window->onMouseLeftRelease.Invoke(); } - if (touchPositions.empty()) touchWindow = nullptr; + if (!AnyTouch()) touchWindow = nullptr; } void Device::touch_motion(void*, wl_touch*, std::uint32_t, std::int32_t id, wl_fixed_t x, wl_fixed_t y) { if (touchWindow == nullptr) return; Window* window = touchWindow; const Vector pos = Vector(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale; - touchPositions[id] = { pos.x, pos.y }; + if (TouchSlot* slot = SlotFor(id)) { + slot->x = pos.x; + slot->y = pos.y; + } window->onTouchMove.Invoke({ id, pos }); if (id == window->primaryTouchId) { window->currentMousePos = pos; @@ -682,7 +733,7 @@ void Device::touch_frame(void*, wl_touch*) {} void Device::touch_cancel(void*, wl_touch*) { if (touchWindow == nullptr) return; Window* window = touchWindow; - touchPositions.clear(); + ForgetTouches(); touchWindow = nullptr; if (window->primaryTouchId >= 0) { window->primaryTouchId = -1; diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index 0e3fdf5..483d9ee 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -560,7 +560,7 @@ Window::~Window() { if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr; if (Device::touchWindow == this) { Device::touchWindow = nullptr; - Device::touchPositions.clear(); + Device::ForgetTouches(); } if (Device::textInputWindow == this) Device::textInputWindow = nullptr; #endif diff --git a/interfaces/Crafter.Graphics-Device.cppm b/interfaces/Crafter.Graphics-Device.cppm index cc4ea3c..7fbad00 100644 --- a/interfaces/Crafter.Graphics-Device.cppm +++ b/interfaces/Crafter.Graphics-Device.cppm @@ -62,8 +62,17 @@ export namespace Crafter { inline static std::vector windows; inline static wl_pointer* wlPointer; inline static wl_touch* wlTouch = nullptr; - inline static Window* touchWindow = nullptr; // the window under the fingers - inline static std::unordered_map> touchPositions; // per finger, physical px + inline static Window* touchWindow = nullptr; // the window under the fingers + // The fingers down, by the compositor's id, in physical pixels. Plain + // data: the statics here are constant-initialised on purpose. + struct TouchSlot { + std::int32_t id; + float x; + float y; + bool used; + }; + inline static std::array touchSlots{}; + static void ForgetTouches(); // wl_data_device_manager + wl_data_device drive copy/paste. Bound // lazily in handle_global; the data device is created once both // the manager and the seat are present (registry binding order @@ -83,9 +92,6 @@ export namespace Crafter { inline static zwp_text_input_manager_v3* textInputManager = nullptr; inline static zwp_text_input_v3* textInput = nullptr; inline static Window* textInputWindow = nullptr; - inline static std::string textInputPreedit; - inline static std::string textInputShownPreedit; // what the window was last told - inline static std::string textInputCommit; inline static std::uint32_t textInputDeleteBefore = 0; inline static std::uint32_t textInputDeleteAfter = 0; inline static bool textInputHasPreedit = false;