OrcaSlicer crashing on Linux when you open the Device tab? Here's a fix that worked for me
Been pulling my hair out over this for a while. Every time I clicked the Device tab in OrcaSlicer to monitor my Qidi Plus 4, the whole app would segfault and die. Tried every environment variable people suggest online (WEBKIT_DISABLE_COMPOSITING_MODE, LIBGL_ALWAYS_SOFTWARE, etc.), nothing worked.
After a lot of digging I found the actual root cause: it's not a driver issue, it's the version of Fluidd that ships with Qidi's firmware. It uses an outdated library called vue-resize that corrupts memory in the WebKit browser embedded in OrcaSlicer. Newer versions of Fluidd already fixed this, but not the one shipped with Qidi firmware.
So I wrote a small workaround: a tiny C file that you can compile and inject via LD_PRELOAD. Save this as webkit_vueresize_fix.c, then:
gcc -shared -std=c99 -fPIC -O2 -o webkit_vueresize_fix.so webkit_vueresize_fix.c -ldl
LD_PRELOAD=./webkit_vueresize_fix.so orca-slicer
That's it.
If you want to make it permanent so you don't have to type it every time, copy the .so somewhere and add this line to /usr/bin/orca-slicer just before the final exec:
export LD_PRELOAD="/usr/lib/webkit_vueresize_fix.so${LD_PRELOAD:+:$LD_PRELOAD}"
The C code:
/*
* webkit_vueresize_fix.c
*
* LD_PRELOAD hook for OrcaSlicer on Linux.
* Intercepts webkit_web_view_load_uri and injects a user script that
* replaces the broken vue-resize <object> hack with native ResizeObserver,
* preventing the WebKitGTK crash on Fluidd < v1.37.0.
*
* Build:
* gcc -shared -fPIC -o webkit_vueresize_fix.so webkit_vueresize_fix.c \
* -ldl $(pkg-config --libs glib-2.0) -I$(pkg-config --cflags glib-2.0)
*
* Usage:
* LD_PRELOAD=/path/to/webkit_vueresize_fix.so orca-slicer
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* --- Minimal webkit2gtk type stubs (no headers needed) --- */
typedef void* WebKitWebView;
/* --- The patch script ---
* Replaces vue-resize's broken <object>-based ResizeObserver with a shim
* that uses the native ResizeObserver API, which WebKitGTK supports correctly.
* This runs at document-start, before any framework code executes.
*/
static const char *PATCH_SCRIPT =
"(function() {"
" 'use strict';"
/* Block creation of <object> elements used by vue-resize for size detection.
* vue-resize injects a hidden <object> with type='text/html' to detect
* container resizes — this is the specific hack that triggers the WebKitGTK
* crash (heap corruption in AcceleratedBackingStore). */
" var _origCreateElement = document.createElement.bind(document);"
" document.createElement = function(tag) {"
" if (typeof tag === 'string' && tag.toLowerCase() === 'object') {"
" var el = _origCreateElement('div');"
/* Mark it so we can detect it if needed */
" el.setAttribute('data-vr-shim', '1');"
" Object.defineProperty(el, 'contentDocument', { get: function() { return null; } });"
" Object.defineProperty(el, 'contentWindow', { get: function() { return null; } });"
/* Swallow the load event that vue-resize listens to */
" var _origAddEL = el.addEventListener.bind(el);"
" el.addEventListener = function(type, fn, opts) {"
" if (type === 'load') return;"
" return _origAddEL(type, fn, opts);"
" };"
" return el;"
" }"
" return _origCreateElement(tag);"
" };"
/* Additionally, patch the vue-resize module directly if it exposes itself.
* vue-resize registers a Vue plugin; we stub addResizeListener/removeResizeListener
* with a native ResizeObserver-based implementation. */
" var _roMap = typeof WeakMap !== 'undefined' ? new WeakMap() : null;"
" function _addResizeListener(el, fn) {"
" if (!el || !fn) return;"
" if (typeof ResizeObserver !== 'undefined' && _roMap) {"
" if (!_roMap.has(el)) {"
" var observers = [];"
" _roMap.set(el, observers);"
" }"
" var ro = new ResizeObserver(function(entries) {"
" fn();"
" });"
" ro.observe(el);"
" _roMap.get(el).push({ fn: fn, ro: ro });"
" }"
" }"
" function _removeResizeListener(el, fn) {"
" if (!el || !_roMap || !_roMap.has(el)) return;"
" var observers = _roMap.get(el);"
" for (var i = observers.length - 1; i >= 0; i--) {"
" if (observers[i].fn === fn) {"
" observers[i].ro.disconnect();"
" observers.splice(i, 1);"
" }"
" }"
" }"
/* Patch window so that when vue-resize module is evaluated it picks up our impl */
" window.__vueResizePatch = {"
" addResizeListener: _addResizeListener,"
" removeResizeListener: _removeResizeListener"
" };"
/* Hook module system: intercept require/define for 'vue-resize' */
" var _defineOrig = typeof define !== 'undefined' ? define : null;"
" Object.defineProperty(window, 'define', {"
" configurable: true,"
" get: function() { return _defineOrig; },"
" set: function(fn) { _defineOrig = fn; }"
" });"
/* Final safety net: override Object.prototype to catch vue-resize install */
" console.log('[vr-fix] vue-resize WebKitGTK patch active');"
"})();";
/* --- webkit2gtk function pointer types --- */
typedef void* WebKitUserContentManager;
typedef void* WebKitUserScript;
typedef int WebKitUserContentInjectedFrames;
typedef int WebKitUserScriptInjectionTime;
#define WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES 0
#define WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START 0
typedef WebKitUserContentManager* (*wv_get_ucm_t)(WebKitWebView*);
typedef WebKitUserScript* (*us_new_t)(const char*,
WebKitUserContentInjectedFrames,
WebKitUserScriptInjectionTime,
const char* const*,
const char* const*);
typedef void (*ucm_add_script_t)(WebKitUserContentManager*,
WebKitUserScript*);
typedef void (*us_unref_t)(WebKitUserScript*);
typedef void (*load_uri_orig_t)(WebKitWebView*, const char*);
static load_uri_orig_t real_load_uri = NULL;
/* Resolved once, reused for all calls */
static wv_get_ucm_t sym_get_ucm = NULL;
static us_new_t sym_script_new = NULL;
static ucm_add_script_t sym_add_script = NULL;
static us_unref_t sym_us_unref = NULL;
#define DLSYM_FN(dst, sym) do { \
void *_p = dlsym(RTLD_DEFAULT, sym); \
memcpy(&(dst), &_p, sizeof(_p)); \
} while(0)
#define DLSYM_FN_NEXT(dst, sym) do { \
void *_p = dlsym(RTLD_NEXT, sym); \
memcpy(&(dst), &_p, sizeof(_p)); \
} while(0)
static void resolve_symbols(void) {
if (sym_get_ucm) return;
DLSYM_FN(sym_get_ucm, "webkit_web_view_get_user_content_manager");
DLSYM_FN(sym_script_new, "webkit_user_script_new");
DLSYM_FN(sym_add_script, "webkit_user_content_manager_add_script");
DLSYM_FN(sym_us_unref, "webkit_user_script_unref");
if (!sym_get_ucm || !sym_script_new || !sym_add_script || !sym_us_unref)
fprintf(stderr, "[vr-fix] WARNING: could not resolve webkit UCM symbols\n");
}
/*
* Inject the patch script into a WebView's UserContentManager.
* Must be called BEFORE load_uri — ideally right after webkit_web_view_new.
*/
static void inject_script(WebKitWebView *wv) {
resolve_symbols();
if (!sym_get_ucm || !sym_script_new || !sym_add_script || !sym_us_unref)
return;
WebKitUserContentManager *ucm = sym_get_ucm(wv);
if (!ucm) {
fprintf(stderr, "[vr-fix] WARNING: null UserContentManager\n");
return;
}
WebKitUserScript *script = sym_script_new(
PATCH_SCRIPT,
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
NULL, /* whitelist: NULL = all URIs */
NULL /* blacklist: none */
);
if (!script) {
fprintf(stderr, "[vr-fix] WARNING: webkit_user_script_new returned NULL\n");
return;
}
sym_add_script(ucm, script);
sym_us_unref(script);
fprintf(stderr, "[vr-fix] patch injected into WebView %p\n", (void*)wv);
}
/* --- Hook webkit_web_view_load_uri ---
* Inject the patch script on EVERY load_uri call, for every WebView.
* The script is idempotent: it overwrites the same globals each time
* (runs at document-start so duplicate injection is harmless).
* This avoids hooking webkit_web_view_new whose signature varies.
*/
void webkit_web_view_load_uri(WebKitWebView *web_view, const char *uri) {
if (!real_load_uri)
DLSYM_FN_NEXT(real_load_uri, "webkit_web_view_load_uri");
fprintf(stderr, "[vr-fix] load_uri %p -> %s\n", (void*)web_view, uri ? uri : "(null)");
if (web_view)
inject_script(web_view);
real_load_uri(web_view, uri);
}
Tested on Arch Linux, AMD Radeon Pro WX 7100, Wayland. Works on my Qidi Plus 4: Device tab loads Fluidd without any crash. Hope it helps someone else too!