From 1082603cd1374a8fa5b804c3356fd9665c6d2371 Mon Sep 17 00:00:00 2001 From: Franco Rapetti Date: Wed, 8 Jul 2026 01:01:59 -0300 Subject: [PATCH] Fix takeover mode on OS 3.28 (EPFramebuffer::swapBuffers ABI change) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On reMarkable OS 3.28, libqsgepaper dropped EPContentType from the QRect swapBuffers overload. libquill.so built for 3.27 fails at launch with: symbol lookup error: undefined symbol: _ZN13EPFramebuffer11swapBuffersE5QRect13EPContentType12EPScreenMode... Resolve both the legacy and 3.28+ mangled symbols via dlsym at runtime so one libquill.so works on 3.27 and 3.28+. Link libdl for dlsym. Tested on Paper Pro (Ferrari) OS 3.28.0.162 — riddle opens in takeover mode without crashing or rebooting the tablet. Co-authored-by: Cursor --- quill/build.sh | 16 ++++++++-------- quill/src/epframebuffer.h | 1 + quill/src/quill_c.cpp | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/quill/build.sh b/quill/build.sh index dfe30d2..e84ebe6 100755 --- a/quill/build.sh +++ b/quill/build.sh @@ -24,13 +24,13 @@ QTINC="$SDKTARGETSYSROOT/usr/include" $CXX -fPIC -shared -O2 \ -I "$QTINC" -I "$QTINC/QtCore" -I "$QTINC/QtGui" \ src/epfb.cpp src/quill_c.cpp \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -o build/libquill.so # scribble: the C1 latency demo. $CC -O2 src/scribble.c \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/scribble @@ -38,7 +38,7 @@ $CC -O2 src/scribble.c \ # map_demo: static full-screen map + tiny partial-update footsteps. $CC -O2 src/map_demo.c \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/map_demo @@ -48,7 +48,7 @@ $CXX -O2 \ -I "$QTINC" -I "$QTINC/QtCore" -I "$QTINC/QtGui" \ src/image_demo.cpp \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/image_demo @@ -58,7 +58,7 @@ $CXX -O2 \ -I "$QTINC" -I "$QTINC/QtCore" -I "$QTINC/QtGui" \ src/image_anim_demo.cpp \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/image_anim_demo @@ -68,7 +68,7 @@ $CXX -O2 \ -I "$QTINC" -I "$QTINC/QtCore" -I "$QTINC/QtGui" \ src/gif_demo.cpp \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/gif_demo @@ -76,7 +76,7 @@ $CXX -O2 \ # drawlab: no-AI live drawing experiments. $CC -O2 src/drawlab.c \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/drawlab @@ -86,7 +86,7 @@ $CXX -O2 \ -I "$QTINC" -I "$QTINC/QtCore" -I "$QTINC/QtGui" \ src/home.cpp \ -L build -lquill \ - -L vendor -lqsgepaper \ + -L vendor -lqsgepaper -ldl \ -lQt6Gui -lQt6Core -lstdc++ \ -Wl,-rpath,/home/root/quill \ -o build/home diff --git a/quill/src/epframebuffer.h b/quill/src/epframebuffer.h index 7bc78ff..5b4414c 100644 --- a/quill/src/epframebuffer.h +++ b/quill/src/epframebuffer.h @@ -23,6 +23,7 @@ class EPFramebuffer { NoRefresh = 0, CompleteRefresh = 1, }; + // Signature varies by OS (3.28 dropped EPContentType). quill_c resolves via dlsym. unsigned long swapBuffers(QRect param_1, EPContentType epct, EPScreenMode type, QFlags flags); #ifdef EPFB_INTERNAL static EPFramebuffer *instance(); diff --git a/quill/src/quill_c.cpp b/quill/src/quill_c.cpp index 4e8ca52..287e85f 100644 --- a/quill/src/quill_c.cpp +++ b/quill/src/quill_c.cpp @@ -8,6 +8,7 @@ #include "epframebuffer.h" #include #include +#include #include #include @@ -15,6 +16,27 @@ static QCoreApplication *g_app = nullptr; static EPFramebuffer *g_fb = nullptr; static QImage *g_aux = nullptr; +// OS 3.27 and earlier: +// swapBuffers(QRect, EPContentType, EPScreenMode, QFlags) +// OS 3.28+ dropped EPContentType from the QRect overload: +// swapBuffers(QRect, EPScreenMode, QFlags) +using SwapOldFn = unsigned long (*)(EPFramebuffer *, QRect, EPContentType, + EPScreenMode, EPFramebuffer::UpdateFlag); +using SwapNewFn = unsigned long (*)(EPFramebuffer *, QRect, EPScreenMode, + EPFramebuffer::UpdateFlag); +static SwapOldFn g_swap_old = nullptr; +static SwapNewFn g_swap_new = nullptr; + +static void resolve_swap() { + if (g_swap_old || g_swap_new) return; + g_swap_new = reinterpret_cast(dlsym( + RTLD_DEFAULT, + "_ZN13EPFramebuffer11swapBuffersE5QRect12EPScreenMode6QFlagsINS_10UpdateFlagEE")); + g_swap_old = reinterpret_cast(dlsym( + RTLD_DEFAULT, + "_ZN13EPFramebuffer11swapBuffersE5QRect13EPContentType12EPScreenMode6QFlagsINS_10UpdateFlagEE")); +} + extern "C" { // Returns 0 on success. After this, quill_buffer()/quill_swap() are usable. @@ -49,11 +71,20 @@ unsigned char *quill_buffer() { // full_refresh != 0 forces a flashing clear of the region. unsigned long quill_swap(int x, int y, int w, int h, int mode, int full_refresh) { if (!g_fb) return 0; - QFlags flags = full_refresh + resolve_swap(); + EPFramebuffer::UpdateFlag flag = full_refresh ? EPFramebuffer::UpdateFlag::CompleteRefresh : EPFramebuffer::UpdateFlag::NoRefresh; - return g_fb->swapBuffers(QRect(x, y, w, h), EPContentType::Mono, - (EPScreenMode)mode, flags); + QRect rect(x, y, w, h); + EPScreenMode screen = static_cast(mode); + if (g_swap_new) { + return g_swap_new(g_fb, rect, screen, flag); + } + if (g_swap_old) { + return g_swap_old(g_fb, rect, EPContentType::Mono, screen, flag); + } + fprintf(stderr, "quill: no EPFramebuffer::swapBuffers symbol found\n"); + return 0; } void quill_process_events() {