-
Notifications
You must be signed in to change notification settings - Fork 0
WLX Plugin Model
Double Commander inherits Total Commander's plugin architecture. A plugin is a
native shared library that exports a fixed, documented table of C functions; the
host loads it with dlopen and calls those functions by name. There is no IPC, no
manifest runtime, no scripting layer — the plugin runs in the host's process.
| Type | Extension | Role |
|---|---|---|
| WLX | .wlx |
Lister — custom file viewers |
| WCX | .wcx |
Packer — archive formats |
| WDX | .wdx |
Content — virtual columns / search fields |
| WFX | .wfx |
File system — virtual/remote volumes |
This collection starts with WLX because viewers are where a good macOS rendering stack (WebKit, Quartz, QuickLook) pays off most. The WDX family (content columns) is covered in MediaInfo Content Plugin (WDX).
A lister plugin exports exactly these C symbols:
HWND ListLoad(HWND ParentWin, char *FileToLoad, int ShowFlags);
int ListLoadNext(HWND ParentWin, HWND PluginWin, char *FileToLoad, int ShowFlags);
void ListCloseWindow(HWND ListWin);
void ListGetDetectString(char *DetectString, int maxlen);
void ListSetDefaultParams(ListDefaultParamStruct *dps);You can confirm a real plugin's exports — and learn by example — with nm:
nm -gU "/Applications/Double Commander.app/Contents/MacOS/plugins/wlx/MacPreview/MacPreview.wlx"
# _ListLoad _ListLoadNext _ListCloseWindow _ListGetDetectString _ListSetDefaultParams-
ListGetDetectStringwrites a rule string telling DC which files this plugin claims, e.g.EXT="MD"|EXT="MARKDOWN". DC evaluates registered plugins in order and uses the first whose rule matches — so a plugin with a narrow rule must be registered before a catch-all like the bundled MacPreview's(EXT!=""). -
ListLoadis handed the host's viewer window and a file path. It builds a view that displays the file, attaches it, and returns a handle to it. -
ListLoadNextreuses the existing plugin view to show the next/previous file as the user navigates inside the viewer — cheaper than tearing down and recreating. -
ListCloseWindowdestroys the view and releases resources. -
ListSetDefaultParamspasses the plugin its interface version and an ini path for settings (optional to use).
The ABI was written for Windows, where HWND is a window handle. On macOS, Double
Commander maps HWND to a Cocoa NSView*, and __stdcall is a no-op. So a
macOS lister:
- receives the viewer's
NSView*asParentWin, - creates its own
NSView(formarkdown-wlx, one hosting aWKWebView), - adds it under the parent and returns it (DC sizes it to the viewer),
- tears it down in
ListCloseWindow.
ListLoad returns an Objective-C object (an NSView) as an opaque HWND, and that
object must outlive the function call — DC holds the handle and hands it back
later to ListLoadNext / ListCloseWindow. Under ARC, a returned autoreleased view
would be deallocated out from under DC.
The fix is to transfer ownership explicitly across the bridge:
// ListLoad: hand a +1-retained pointer out to the C world
return (HWND)CFBridgingRetain(view);
// ListCloseWindow: take ownership back and balance the retain
NSView *view = (NSView *)CFBridgingRelease(ListWin);
[view removeFromSuperview];CFBridgingRetain / CFBridgingRelease are the ARC-correct way to move an object
between ARC and manual-retain (C) worlds. This is the single most important detail to
get right in any macOS plugin that returns Cocoa objects through a C ABI.
macOS will refuse to load an unsigned/foreign dylib into a process that uses the
hardened runtime with library validation. Double Commander is ad-hoc signed
with no hardened runtime and no entitlements, so an ad-hoc-signed plugin
(codesign --sign -) loads cleanly. Always check the host before assuming you can
inject a library:
codesign -dvvv "/Applications/Double Commander.app" 2>&1 | grep -E "flags|Signature"
# flags=0x2(adhoc) ... Signature=adhoc → no library validation → third-party plugins OKContinue to Markdown Plugin Architecture for how
markdown-wlx fills this skeleton in.