File-system plugins (PFX)¶
Source:
Plugins/SDK/pfx.h— this page is generated from that header bydocs/scripts/gen-api-reference.py; edit the header, not this page.
pfx.h — Peach Commander file-system plugins (PFX ↔ Total Commander WFX).
A PFX plugin exposes a remote/virtual file system to be mounted like a drive. It follows TC's WFX model — a flat, synchronous, whole-file C ABI — which is why WFX has stayed source-compatible across decades: directory enumeration returns metadata only (fast), and file transfer materialises the whole file to a local path (GetFile/PutFile). The host adapts this to its streaming VirtualFileSystem (openRead = GetFile → temp → stream). Real chunk-streaming can be added later as OPTIONAL entry points without breaking this ABI.
A plugin implements one or both independent facets: • Static volumes — contributes drive-bar entries pointing at local paths (e.g. iCloud Drive). Uses PfxGetVolumeCount/Info only. • Connectable FS — an interactive "connect" (e.g. WebDAV: prompt for URL) that returns a connection handle, then serves file ops. A plugin whose static volumes are themselves saved connections also implements PfxConnectVolume, so that clicking one connects it instead of re-asking.
Self-contained C11 on top of pc_common.h. All char are UTF-8; sizes are int64; times are Unix epoch seconds; opaque handles are void (NULL = invalid). The host serialises all calls on one connection handle. Version-checked via PcGetApiVersion.
All entry points are OPTIONAL at load time; the host probes which symbols a plugin exports to decide its facets. A plugin should export PcGetApiVersion.
Entry points & functions¶
PfxConnectPfxConnectVolumePfxConnectionIdPfxContentFieldPfxContentFieldCountPfxContentGetRowPfxDeletePfxDisconnectPfxFindClosePfxFindFirstPfxFindNextPfxGetCapabilitiesPfxGetConnectTitlePfxGetFilePfxGetVolumeCountPfxGetVolumeInfoPfxInitPfxLastErrorPfxLookupPfxMkDirPfxPutFilePfxRenMovPfxStat
Callbacks & service members¶
progresspresentInfocryptgetContext
Constants¶
| Name | Value | Meaning |
|---|---|---|
PC_PFX_VOL_LOCAL |
0x0001 |
path is a real local path — browse directly |
PC_PFX_VOL_REMOVABLE |
0x0002 |
show as removable/ejectable in the drive bar |
PC_PFX_CAP_READ |
0x0001 |
|
PC_PFX_CAP_WRITE |
0x0002 |
|
PC_PFX_CAP_RENAME |
0x0004 |
|
PC_PFX_CAP_VOLATILE |
0x0008 |
contents change live; host may auto-refresh the mount |
Full header¶
// SPDX-License-Identifier: Apache-2.0
/*
* pfx.h — Peach Commander file-system plugins (PFX ↔ Total Commander WFX).
*
* A PFX plugin exposes a remote/virtual file system to be mounted like a drive.
* It follows TC's WFX model — a flat, synchronous, whole-file C ABI — which is
* why WFX has stayed source-compatible across decades: directory enumeration
* returns metadata only (fast), and file transfer materialises the whole file to
* a local path (GetFile/PutFile). The host adapts this to its streaming
* VirtualFileSystem (openRead = GetFile → temp → stream). Real chunk-streaming
* can be added later as OPTIONAL entry points without breaking this ABI.
*
* A plugin implements one or both independent facets:
* • Static volumes — contributes drive-bar entries pointing at local paths
* (e.g. iCloud Drive). Uses PfxGetVolumeCount/Info only.
* • Connectable FS — an interactive "connect" (e.g. WebDAV: prompt for URL)
* that returns a connection handle, then serves file ops.
* A plugin whose static volumes are themselves saved
* connections also implements PfxConnectVolume, so that
* clicking one connects it instead of re-asking.
*
* Self-contained C11 on top of pc_common.h. All char* are UTF-8; sizes are
* int64; times are Unix epoch seconds; opaque handles are void* (NULL = invalid).
* The host serialises all calls on one connection handle. Version-checked via
* PcGetApiVersion.
*
* All entry points are OPTIONAL at load time; the host probes which symbols a
* plugin exports to decide its facets. A plugin should export PcGetApiVersion.
*/
#ifndef PFX_H
#define PFX_H
#include <stdint.h>
#include "pc_common.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Volume flags (PfxVolumeInfo.flags, bit mask). */
#define PC_PFX_VOL_LOCAL 0x0001 /* `path` is a real local path — browse directly */
#define PC_PFX_VOL_REMOVABLE 0x0002 /* show as removable/ejectable in the drive bar */
/* Filesystem capability flags (PfxGetCapabilities, bit mask). Read is implied. */
#define PC_PFX_CAP_READ 0x0001
#define PC_PFX_CAP_WRITE 0x0002
#define PC_PFX_CAP_RENAME 0x0004
#define PC_PFX_CAP_VOLATILE 0x0008 /* contents change live; host may auto-refresh the mount */
/* Content-column field types (PfxContentField). Values are always returned
* as display strings by PfxContentGetRow; the type only guides column
* alignment and sort order in the host. */
#define PFX_FT_STRING 0 /* left-aligned, natural sort */
#define PFX_FT_NUMERIC 1 /* right-aligned, numeric sort */
#define PFX_FT_SIZE 2 /* bytes -> KB/MB in the host, numeric sort */
#define PFX_FT_DATETIME 3 /* epoch seconds -> localized date/time */
/* A static volume the plugin contributes to the drive bar. */
typedef struct {
char id[128]; /* stable identifier, e.g. "cloud:icloud" */
char name[256]; /* display name, e.g. "iCloud Drive" */
char path[1024]; /* local filesystem path (required for PC_PFX_VOL_LOCAL) */
int flags; /* PC_PFX_VOL_* bit mask */
/* Optional presentation (fields the host zero-inits, so older plugins that
don't set them get host defaults). Lets a plugin own its drive-bar look. */
char icon[64]; /* emoji shown on the chip (UTF-8), e.g. "📊"; "" = default */
int order; /* >0 pins the chip right after the boot drive (lower first);
0 = ordinary volume, sorted by name */
} PfxVolumeInfo;
/* One directory entry (TC's WIN32_FIND_DATA analog). `name` is a leaf, not a path. */
typedef struct {
char name[1024]; /* entry name (UTF-8, not a full path) */
int64_t size; /* size in bytes; -1 if unknown */
int64_t mtime; /* modification time, Unix epoch seconds (0 if unknown) */
int isDir; /* 1 if a directory */
/* POSIX st_mode: permission bits, and OPTIONALLY the S_IF* type field. 0 = unknown.
The type field is how a plugin says "this is a symlink", and the only way it can: this
struct has a name, a size, a time, an isDir flag and this, and appending a field to it
would break every plugin built against the older header — the PLUGIN writes this struct,
so a new plugin on an old host would write past the end of the host's allocation.
Set S_IFLNK here (together with isDir, if the link points at a directory) and the host
draws the entry as a link — `l` in the Attr column — instead of an ordinary file. A plugin
that reports permission bits only, or 0, is read exactly as before: an S_IFMT field of 0
matches no type and the host falls back to isDir. */
uint32_t mode;
} PfxFindData;
/*
* Host services a plugin may call. `host` is an opaque token to pass back to each
* callback. The plugin ships its own connect UI, so services are minimal.
*/
typedef struct PfxHostServices {
void *host;
/* Transfer progress for GetFile/PutFile: `pct` is 0..100. Return PC_CONTINUE
to proceed or PC_ABORT to cancel the transfer. May be NULL. */
int (*progress)(void *host, const char *name, int pct);
/* Show an informational dialog. May be NULL. */
void (*presentInfo)(void *host, const char *title, const char *message);
/* Keychain-backed credential store (mode is a PC_CRYPT_* value). `password`
is an in/out UTF-8 buffer of `maxlen`. Returns PC_OK or a PC_E_* code.
Lets the plugin persist passwords without linking Security itself. */
int (*crypt)(void *host, int mode, const char *store, char *password, int maxlen);
void *parentWindow; /* NSWindow* to present connect/config sheets over (may be NULL) */
/* Ask the host for a named string. Writes UTF-8 into `out` (at most `maxlen`
bytes, always NUL-terminated) and returns 1; returns 0 for a key the host
does not know, leaving `out` untouched. May be NULL on an older host — so
check before calling, and have a fallback for 0.
Keys the host answers:
"configRoot" the directory this instance keeps its configuration in.
A plugin that persists anything MUST put it under here,
in a subdirectory of its own — NOT in a path it builds
itself from Application Support. The host's own root moves
(`-ConfigRoot`, PEACHCMD_CONFIG_ROOT), and a plugin that
does not follow it writes into the user's real settings
during a test run. That has happened.
Deliberately a callback rather than another struct field: a new key costs
nothing here, whereas every field is an ABI change. Same shape and same
key names as `PcHostServices.getContext` in pcplugin.h, so a plugin author
writing for both ABIs learns it once. */
int (*getContext)(void *host, const char *key, char *out, int maxlen);
} PfxHostServices;
/* Fields are only ever APPENDED to PfxHostServices, never reordered or removed.
The host allocates the struct and the plugin only reads it, so a plugin built
against an older header keeps working: it reads the prefix it knows and never
looks at the offsets it does not. A plugin must therefore not assume a field
it knows is the last one, and must accept NULL for any callback. */
/* ---- Lifecycle (optional) --------------------------------------------- */
/* Called once, after the plugin is loaded and before anything else — including
PfxGetVolumeCount, so a plugin can decide its drives from configuration.
`services` stays valid for as long as the plugin is loaded, so the plugin may
retain the pointer. That is the point of this entry: PfxConnect also receives
services, but only once a connection is being made, which is too late for a
plugin that must read its settings in order to offer the connect dialog at all.
`parentWindow` is NULL here — at load time there is no window to be modal to.
Take it from the services passed to PfxConnect instead. */
void PfxInit(const PfxHostServices *services);
/* Capabilities of the file system this plugin serves. Absent ⇒ read-only. */
int PfxGetCapabilities(void);
/* ---- Static-volumes facet (optional) ---------------------------------- */
int PfxGetVolumeCount(void);
void PfxGetVolumeInfo(int index, PfxVolumeInfo *out);
/* ---- Connect facet (optional) ----------------------------------------- */
/* Fill `outTitle` with the connect command's menu label; return 1 if this plugin
offers an interactive connect, else 0. */
int PfxGetConnectTitle(char *outTitle, int maxlen);
/* Show the connect UI (using services->parentWindow), establish a session, and
return an opaque connection handle (non-NULL) on success, or NULL on
cancel/failure. `services` stays valid for the connection's lifetime. */
void *PfxConnect(const PfxHostServices *services);
/* Connect the specific static volume the user clicked, instead of asking.
OPTIONAL, and the reason it exists is a defect rather than a convenience. `PfxConnect` takes no
argument, so a plugin that publishes SEVERAL connectable volumes cannot be told which chip was
clicked — it can only fall back to its own connect UI. For a plugin whose volumes are saved
connections that is exactly wrong: the chip promises a shortcut and delivers a dialog. Plugins with
one volume (TaskManager) never met this, and plugins whose volumes are local paths (iCloud Drive)
never connect at all, so it stayed hidden until a plugin had a chip per saved profile.
`volumeId` is the `PfxVolumeInfo.id` the plugin itself published, so a plugin recognises its own
token and the host invents nothing. Return an opaque connection handle exactly as `PfxConnect`
does, or NULL on cancel/failure — including when `volumeId` is not one this plugin knows, which is
what an id left over from an older configuration looks like.
A plugin that does not export this keeps the old behaviour: the host calls `PfxConnect`. */
void *PfxConnectVolume(const char *volumeId, const PfxHostServices *services);
/* Why the last call on `conn` failed — a PC_E_* code, or PC_OK if the plugin does not
track it (which is also what an absent entry point means).
The host asks this only after a call that answers with a *handle* has answered NULL:
PfxFindFirst and friends have no other channel, so without this "the server is gone"
and "that directory does not exist" arrive identically, and the host has to guess.
It guessed wrong for years — a connection dying mid-listing was reported to the user
as a missing directory, which sends them looking for a folder that is exactly where
they left it.
Return PC_E_CONNECTION_LOST for a transport that is finished: the host then leaves the
mount, drops its drive-bar entry and names the server, rather than leaving the panel
inside something that can no longer answer. */
int PfxLastError(void *conn);
/* Fill `out` with a short, stable id for `conn` (used as the mount scheme/title,
e.g. "webdav:host"). Return 1 on success. */
int PfxConnectionId(void *conn, char *out, int maxlen);
/* Close a connection previously returned by PfxConnect and release everything it
owns. `conn` is invalid on return.
What the host guarantees, so that a plugin can free its state here without
defending against the host:
* Called EXACTLY ONCE per handle returned by PfxConnect. A plugin never has
to guard against a second call, and must not be written to survive one.
* No call with that handle is in flight when this starts, and none is made
after it returns — not a listing, not a stat, not a content row. Calls on
one connection are serialised, so this waits for a running one rather than
racing it. A find handle from PfxFindFirst is always closed first, so it is
safe to allocate finds out of the connection's own state.
When it happens: the user closes the mount (its drive button's Disconnect, the
Disconnect command, or walking up out of it), or the app quits with the mount
still open. It is a deliberate act of the host, not a side effect of the host
releasing its last reference — so this is a place a plugin can flush to and
know it will be reached. */
void PfxDisconnect(void *conn);
/* ---- File operations on a connection ---------------------------------- */
/* Paths are absolute within the file system, UTF-8, '/'-separated, "/" = root. */
/* Begin enumerating `dir`. Return a find handle (non-NULL) on success (the
directory exists and is accessible), or NULL on error. Entries are then read
with PfxFindNext; an existing but empty directory yields a handle whose first
PfxFindNext returns 0. */
void *PfxFindFirst(void *conn, const char *dir);
/* Fill `out` with the next entry. Return 1 if filled, 0 at end of enumeration. */
int PfxFindNext(void *find, PfxFindData *out);
/* Release a find handle from PfxFindFirst. */
void PfxFindClose(void *find);
/* Stat a single path into `out`. Returns PC_OK or a PC_E_* code. */
int PfxStat(void *conn, const char *path, PfxFindData *out);
/* Download the whole file at `remotePath` to the local `localPath`. PC_OK/PC_E_*. */
int PfxGetFile(void *conn, const char *remotePath, const char *localPath);
/* Upload the whole local file at `localPath` to `remotePath`. PC_OK/PC_E_*. */
int PfxPutFile(void *conn, const char *localPath, const char *remotePath);
/* Create a directory at `path`. PC_OK/PC_E_*. */
int PfxMkDir(void *conn, const char *path);
/* Delete the file or directory at `path`. PC_OK/PC_E_*. */
int PfxDelete(void *conn, const char *path);
/* Rename/move `from` to `to` (same connection). `move` is advisory. PC_OK/PC_E_*. */
int PfxRenMov(void *conn, const char *from, const char *to, int move);
/* ---- Content-column facet (optional) ----------------------------------
* Lets a file-system plugin publish extra columns for its own entries
* (e.g. a process list exposing PID/CPU/threads). These become selectable,
* sortable, persisted columns via the host's normal column machinery. */
typedef struct {
char name[128]; /* field id leaf, e.g. "cpu" (host qualifies it) */
char title[128]; /* column header, e.g. "CPU %" */
int type; /* PFX_FT_* */
int defaultWidth; /* suggested column width in px (0 = host default) */
} PfxFieldInfo;
/* Number of content columns this plugin publishes (0 / absent = none). */
int PfxContentFieldCount(void);
/* Describe column `index` (0..PfxContentFieldCount-1) into `out`. */
void PfxContentField(int index, PfxFieldInfo *out);
/* Write ALL field values for the entry at `path`, tab-separated in field
* order (no trailing tab), into `out` (NUL-terminated, <= maxlen). One call
* per entry keeps hundreds of rows cheap. Return 1 on success, 0 if `path`
* has no row. Missing values are empty between tabs. */
int PfxContentGetRow(void *conn, const char *path, char *out, int maxlen);
/* ---- Lookup facet (optional) -------------------------------------------
* Resolve a plugin-defined query to the path of an existing entry, for host
* "jump to the matching entry" features. The host writes `query`, the plugin
* writes the entry path (e.g. "/nginx (1234)") into `out` (NUL-terminated,
* <= maxlen). Return 1 on a hit, 0 on no match. Query syntax is per-plugin;
* TaskManager understands "port:<n>" — the process owning local TCP/UDP port n.
*
* A query may answer with MORE than one entry: one path per line, and a line may
* carry a tab-separated tag the host understands for that query. TaskManager's
* "file:<path>" lists every process holding that file open, tagged "r" (read-only
* handles), "w" (write-only) or "b" (both), which the host colours accordingly.
* Write whole lines only — never a truncated one — when `out` runs out. */
int PfxLookup(void *conn, const char *query, char *out, int maxlen);
#ifdef __cplusplus
}
#endif
#endif /* PFX_H */