Skip to content

File-system plugins (PFX)

Source: Plugins/SDK/pfx.h — this page is generated from that header by docs/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

  • PfxConnect
  • PfxConnectVolume
  • PfxConnectionId
  • PfxContentField
  • PfxContentFieldCount
  • PfxContentGetRow
  • PfxDelete
  • PfxDisconnect
  • PfxFindClose
  • PfxFindFirst
  • PfxFindNext
  • PfxGetCapabilities
  • PfxGetConnectTitle
  • PfxGetFile
  • PfxGetVolumeCount
  • PfxGetVolumeInfo
  • PfxInit
  • PfxLastError
  • PfxLookup
  • PfxMkDir
  • PfxPutFile
  • PfxRenMov
  • PfxStat

Callbacks & service members

  • progress
  • presentInfo
  • crypt
  • getContext

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
// 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 */