[v2.0.0] – 2026-05-22
Added
- Full render mode for Word, Excel and PowerPoint via a new
[Mode]INI section. Four values are accepted per application:
quick(Preview Handler, no button),quick-switchable(Preview
Handler with overlay button — new default),full(OLE
Automation, no button),full-switchable(OLE Automation with
overlay button). When a full-mode value is set, the plugin launches
a real Word / Excel / PowerPoint instance, opens the file
read-only, and embeds its window into the Lister pane, giving the
full Print Layout / grid / slide rendering instead of the
simplified Preview Handler pipeline. Cold start is ~2–4 s;
memory ~100–300 MB per instance. On any failure the plugin
transparently falls back to quick mode. - Mode-switch overlay button in the top-right of every Word /
Excel / PowerPoint preview (shown when the configured mode is
quick-switchableorfull-switchable). One click toggles the
current preview between quick and full mode without changing the
INI default; the switch is per-preview only — the next file (or
re-opening the same one) starts at the configured default. Button
size and font scale with the monitor's DPI. Always hidden for file
types with no full-mode handler (.msg,.vsdx) regardless of
the setting. - Per-app preview niceties in full mode:
- Word — Print Layout view, page-width zoom that auto-refits
on Lister resize, rulers hidden, runtime read-only enforcement
so typing in the preview is blocked. - Excel — zoom locked to 100% on load; initial frame size
matches the Lister pane. Two intrinsic limitations of embedding
Excel as a child window via OLE Automation, which no combination
of style edits, window-message synthesis or activation tricks
convinced Excel to overcome:- Excel does not relayout when the pane is later resized;
reopening the Lister at the desired size triggers a fresh
correct layout. - Interactive mouse input (cell selection, sheet-tab clicks,
most ribbon buttons) is unreliable, because Excel gates much
of that processing on being the foreground top-level window —
and a reparented child of a foreign process never is. Quick
mode is unaffected; for interactive work, use quick mode.
Full Excel mode is best treated as a read-only viewer.
- Excel does not relayout when the pane is later resized;
- PowerPoint — slide zoom auto-refits to the Lister pane on
every resize. PowerPoint's main window appears briefly on
screen before being embedded (a short visible flash) because
PowerPoint cannot be told to run invisibly; Word and Excel
embed silently.
- Word — Print Layout view, page-width zoom that auto-refits
Changed
- Moved the
AppKindenum declaration beforeHostStateso that
HostState::currentFileAppcan be initialised at compile time without
relying on a forward-declaration hack.
Fixed
- Preview handlers were activated in-process, which violates the
IPreviewHandlercontract.LoadHandlerStaused
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, so COM happily loaded any
handler that registered anInProcServer32(the Windows built-in MAPI
Mail Previewer for.msg, in particular) directly into our process.
Such handlers complete activation, initialisation andSetWindow, then
failDoPreviewwithE_FAILbecause Microsoft'sIPreviewHandler
documentation states a handler must be hosted in its own background
process. Activation is nowCLSCTX_LOCAL_SERVERonly, matching the
reference preview-host sample and Windows Explorer's preview pane —
COM now routes through the AppID'sDllSurrogate(typically
prevhost.exe), and MSG previews finally render. CoInitializeSecurityis now established at host start-up. Handlers
that run in Windows' low-integrityprevhost.exesurrogate (notably the
Outlook MSG preview handler) need an explicit process-wide COM security
blanket to perform cross-process calls back to the host; without it
DoPreviewreturnsE_FAIL. The new call matches the settings used by
Windows Explorer's own preview pane (RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE).- Preview handlers requiring an
IPreviewHandlerFramesite failedDoPreview.
Outlook's MSG preview handler (and other handlers hosted inprevhost.exe)
ask the host forIPreviewHandlerFrameandIOleWindowvia a site object
installed throughIObjectWithSite::SetSite; without itDoPreviewreturns
E_FAILand the preview never renders. A newPreviewHostSiteclass
implementsIPreviewHandlerFrame(no-op accelerator translation),
IServiceProvider(routingIPreviewHandlerFrame/IOleWindowlookups
back to itself) andIOleWindow(returning the render window's HWND).
The site is installed before anyIInitializeWith*::Initializecall —
Outlook's MSG handler queries the site from insideInitializeand aborts
the load if it isn't ready yet — and cleared again on unload. The call
order inLoadHandlerStanow matches Microsoft's recommended sequence:
CoCreateInstance → QI IPreviewHandler → SetSite → Initialize → SetWindow
→ DoPreview. - Preview handlers initialised only via
IInitializeWithItemcould not load.
LoadHandlerStapreviously tried onlyIInitializeWithFileand
IInitializeWithStream. Outlook's MSG preview handler rejects both
(E_NOINTERFACE) and supports exclusively the modern Shell-item-based
IInitializeWithItem, which the loader now tries as a third fallback
(SHCreateItemFromParsingName→IInitializeWithItem::Initialize). - Preview handlers missed when registered outside the direct extension key.
FindPreviewHandlerClsidpreviously looked for the preview handler CLSID
only directly on the extension key and via the extension's default ProgID.
This missed handlers registered in any of the other places Windows
Explorer's preview pane consults — notably the MSG handler, which Outlook
registers under a versioned ProgID inHKCR\.msg\OpenWithProgids
(e.g.Outlook.File.msg.16) rather than as the default ProgID. The
lookup now follows the full Shell association chain that Explorer uses:
direct shellex key → default ProgID → each ProgID inOpenWithProgids→
HKCR\SystemFileAssociations\<ext>→HKCR\SystemFileAssociations\<PerceivedType>.
MSG and similar previews now work on systems where Windows Explorer already
shows them. - Plugin deadlock on Lister close / TC exit.
TerminateSessionused an
infinite timeout when writingCLOSEto the host pipe. If the host's pipe
reader thread had already died (e.g. access violation), the write would
block forever, freezing Total Commander. The write now uses a 3-second
timeout and cancels the pending I/O if it expires. - Race condition in plugin
ChildWndProc(use-after-free). The resize
timer handler looked up the session underg_sessionsMutex, released the
lock, and then wrote tosession->hPipe. Between releasing the lock and
the write,ListCloseWindowon another thread could erase anddelete
the session. The mutex is now held for the entire pipe write. - Race condition in host
HostLogcritical-section initialisation.
InitializeCriticalSectionwas called lazily on the firstHostLog
invocation without any synchronisation. Two threads (STA + pipe reader)
could race and initialise the sameCRITICAL_SECTIONtwice, corrupting
it. The CS is now initialised once inwmainbefore the pipe reader
thread is created. - Pipe buffer exhaustion after many file switches. The host sends
OK\n/ERR …\nreplies for everyLOADandCLOSE, but the plugin
never read them. After roughly 5 000ListLoadNextWcalls in a single
Lister session the 64 KB pipe buffer filled up and subsequent writes
blocked indefinitely. A background drain thread now continuously reads
and discards host responses. - Handle leaks in the host process. The log file handle, the logging
CRITICAL_SECTION, and the OfficeJobObjecthandle were never closed
or destroyed before process exit. They are now cleaned up inwmain
after the message loop terminates. - Potential
CreateProcessWcommand-line mutation.CreateProcessW
requires a writable command-line buffer.cmdLine.data()returns
const wchar_t*in pre-C++17 standards, which is undefined behaviour.
Changed to&cmdLine[0], which is writable in all C++ versions. - Missing null check in
ListLoadNextW. Added an explicit guard against
anullptrFileToLoadargument. - Re-entrancy crash when switching modes rapidly (PowerPoint). The retry
loops insideLoadWordFullSta,LoadExcelFullStaand
LoadPowerPointFullStacontainedPeekMessagedispatch loops that pumped
the STA message queue. While a load was still in progress, a second
WM_HOST_SWITCH_MODEmessage could be dispatched recursively, causing
re-entrant COM calls and eventual heap corruption or access violation. The
PeekMessageloops have been replaced with plainSleep, and a
loadingInProgressguard now serialisesLoadFileWithModeStacalls so
that a second switch is deferred until the first one finishes. - Document not closed before quick-mode fallback detach. When full-mode
loading failed and the host fell back to quick mode, the Office document
was left open in the background while the window was detached. The
appropriate close routine (CloseWordDocumentSta,
CloseExcelWorkbookSta,ClosePptPresentationSta) is now called before
detaching the window in the fallback path. - Crash when clicking the embedded Office window's close button.
EmbedOfficeWindowStastrips Win32 caption styles, but Office apps
(Word, Excel, PowerPoint) render their own close button inside the
ribbon / title bar. Clicking it sentWM_SYSCOMMAND(SC_CLOSE)or
WM_CLOSEto the embedded HWND, which caused the Office app to destroy
its own window while it was still a child of our render pane — leading
to a crash or unexpected TC window closure.SetWindowSubclasscannot
be used because Office runs out-of-process. Instead, a visible light
grey overlay bar (hwndCloseGuard) is created as a child of the render
pane, stretched across the full width and covering the top ~40 px
(HiDPI-scaled) where the Office ribbon / title bar lives. The guard is
forced toHWND_TOPso it sits above the Office window, painted with
LTGRAY_BRUSH, and returnsHTCLIENTfromWM_NCHITTESTso all mouse
clicks in that area are swallowed. This also blocks the context menu
that appears on a right-click in the title area. The guard is destroyed
when the Office window is detached or the render pane is torn down.
Notes
- Full mode deliberately changes no Office Application-wide settings
(status bar, ribbon state, default zoom, …) because Office
persists those into the user's profile on Quit. Only per-window /
per-document settings are touched, so your standalone Word, Excel
and PowerPoint will start up exactly as you left them. - Every Office process spawned by the plugin is assigned to a Job
Object withJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. When the host
process exits for any reason (clean shutdown, plugin-side
TerminateProcessafter the host failed to honourCLOSEwithin
2 seconds, Windows shutdown), the kernel kills any still-alive
Office processes with it — so Excel in particular can no longer
be left orphaned and reported as a "hung application" at the next
system restart.