fix(wayland): constant-initialised statics in Device, for clang 23.1 on aarch64-musl

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.
This commit is contained in:
Jorijn van der Graaf 2026-10-18 02:06:03 +02:00
commit cc04d4e121
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 80 additions and 23 deletions

View file

@ -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) { void Device::text_input_enter(void*, zwp_text_input_v3*, wl_surface* surface) {
textInputWindow = nullptr; textInputWindow = nullptr;
for (Window* window : windows) { 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*) { void Device::text_input_leave(void*, zwp_text_input_v3*, wl_surface*) {
textInputWindow = nullptr; textInputWindow = nullptr;
textInputHasPreedit = textInputHasCommit = textInputHasDelete = false; textInputHasPreedit = textInputHasCommit = textInputHasDelete = false;
textInputPreedit.clear(); TextStrings().preedit.clear();
textInputShownPreedit.clear(); TextStrings().shown.clear();
} }
void Device::text_input_preedit(void*, zwp_text_input_v3*, const char* text, std::int32_t, std::int32_t) { 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; textInputHasPreedit = true;
} }
void Device::text_input_commit(void*, zwp_text_input_v3*, const char* text) { void Device::text_input_commit(void*, zwp_text_input_v3*, const char* text) {
textInputCommit = text == nullptr ? "" : text; TextStrings().commit = text == nullptr ? "" : text;
textInputHasCommit = true; 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. // 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) { void Device::text_input_done(void*, zwp_text_input_v3*, std::uint32_t) {
Window* window = textInputWindow; Window* window = textInputWindow;
if (!textInputHasPreedit) textInputPreedit.clear(); TextInputStrings& strings = TextStrings();
if (!textInputHasPreedit) strings.preedit.clear();
if (window != nullptr) { if (window != nullptr) {
if (textInputHasDelete && (textInputDeleteBefore > 0 || textInputDeleteAfter > 0)) window->onTextInputDelete.Invoke({ textInputDeleteBefore, textInputDeleteAfter }); if (textInputHasDelete && (textInputDeleteBefore > 0 || textInputDeleteAfter > 0)) window->onTextInputDelete.Invoke({ textInputDeleteBefore, textInputDeleteAfter });
if (textInputHasCommit && !textInputCommit.empty()) window->onTextInput.Invoke(textInputCommit); if (textInputHasCommit && !strings.commit.empty()) window->onTextInput.Invoke(strings.commit);
if (textInputPreedit != textInputShownPreedit || (textInputHasCommit && !textInputCommit.empty())) { if (strings.preedit != strings.shown || (textInputHasCommit && !strings.commit.empty())) {
textInputShownPreedit = textInputPreedit; strings.shown = strings.preedit;
window->onPreedit.Invoke(textInputPreedit); window->onPreedit.Invoke(strings.preedit);
} }
} }
textInputHasPreedit = textInputHasCommit = textInputHasDelete = false; textInputHasPreedit = textInputHasCommit = textInputHasDelete = false;
textInputCommit.clear(); strings.commit.clear();
} }
void Device::ApplyTextInput(Window* window) { 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 // 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 // clipboard has a serial and Device::focusedWindow a window), presses the
// left button, moves it and releases it. Every finger is also reported as // 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; touchWindow = window;
window->lastPointerSerial_ = serial; window->lastPointerSerial_ = serial;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale; const Vector<float, 2> pos = Vector<float, 2>(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 }); window->onTouchDown.Invoke({ id, pos });
if (window->primaryTouchId < 0) { if (window->primaryTouchId < 0) {
window->primaryTouchId = id; 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) { void Device::touch_up(void*, wl_touch*, std::uint32_t, std::uint32_t, std::int32_t id) {
if (touchWindow == nullptr) return; if (touchWindow == nullptr) return;
Window* window = touchWindow; Window* window = touchWindow;
const auto at = touchPositions.find(id); Vector<float, 2> pos = window->currentMousePos;
const Vector<float, 2> pos = at == touchPositions.end() ? window->currentMousePos : Vector<float, 2>(at->second.first, at->second.second); if (TouchSlot* slot = SlotFor(id)) {
touchPositions.erase(id); pos = Vector<float, 2>(slot->x, slot->y);
*slot = TouchSlot{};
}
window->onTouchUp.Invoke({ id, pos }); window->onTouchUp.Invoke({ id, pos });
if (id == window->primaryTouchId) { if (id == window->primaryTouchId) {
window->primaryTouchId = -1; window->primaryTouchId = -1;
window->mouseLeftHeld = false; window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke(); 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) { 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; if (touchWindow == nullptr) return;
Window* window = touchWindow; Window* window = touchWindow;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale; const Vector<float, 2> pos = Vector<float, 2>(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 }); window->onTouchMove.Invoke({ id, pos });
if (id == window->primaryTouchId) { if (id == window->primaryTouchId) {
window->currentMousePos = pos; window->currentMousePos = pos;
@ -682,7 +733,7 @@ void Device::touch_frame(void*, wl_touch*) {}
void Device::touch_cancel(void*, wl_touch*) { void Device::touch_cancel(void*, wl_touch*) {
if (touchWindow == nullptr) return; if (touchWindow == nullptr) return;
Window* window = touchWindow; Window* window = touchWindow;
touchPositions.clear(); ForgetTouches();
touchWindow = nullptr; touchWindow = nullptr;
if (window->primaryTouchId >= 0) { if (window->primaryTouchId >= 0) {
window->primaryTouchId = -1; window->primaryTouchId = -1;

View file

@ -560,7 +560,7 @@ Window::~Window() {
if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr; if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr;
if (Device::touchWindow == this) { if (Device::touchWindow == this) {
Device::touchWindow = nullptr; Device::touchWindow = nullptr;
Device::touchPositions.clear(); Device::ForgetTouches();
} }
if (Device::textInputWindow == this) Device::textInputWindow = nullptr; if (Device::textInputWindow == this) Device::textInputWindow = nullptr;
#endif #endif

View file

@ -63,7 +63,16 @@ export namespace Crafter {
inline static wl_pointer* wlPointer; inline static wl_pointer* wlPointer;
inline static wl_touch* wlTouch = nullptr; inline static wl_touch* wlTouch = nullptr;
inline static Window* touchWindow = nullptr; // the window under the fingers inline static Window* touchWindow = nullptr; // the window under the fingers
inline static std::unordered_map<std::int32_t, std::pair<float, float>> touchPositions; // per finger, physical px // 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<TouchSlot, 10> touchSlots{};
static void ForgetTouches();
// wl_data_device_manager + wl_data_device drive copy/paste. Bound // wl_data_device_manager + wl_data_device drive copy/paste. Bound
// lazily in handle_global; the data device is created once both // lazily in handle_global; the data device is created once both
// the manager and the seat are present (registry binding order // 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_manager_v3* textInputManager = nullptr;
inline static zwp_text_input_v3* textInput = nullptr; inline static zwp_text_input_v3* textInput = nullptr;
inline static Window* textInputWindow = 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 textInputDeleteBefore = 0;
inline static std::uint32_t textInputDeleteAfter = 0; inline static std::uint32_t textInputDeleteAfter = 0;
inline static bool textInputHasPreedit = false; inline static bool textInputHasPreedit = false;