You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
[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-switchable or full-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.
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.
Changed
Moved the AppKind enum declaration before HostState so that HostState::currentFileApp can be initialised at compile time without
relying on a forward-declaration hack.
Fixed
Preview handlers were activated in-process, which violates the IPreviewHandler contract.LoadHandlerSta used CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, so COM happily loaded any
handler that registered an InProcServer32 (the Windows built-in MAPI
Mail Previewer for .msg, in particular) directly into our process.
Such handlers complete activation, initialisation and SetWindow, then
fail DoPreview with E_FAIL because Microsoft's IPreviewHandler
documentation states a handler must be hosted in its own background
process. Activation is now CLSCTX_LOCAL_SERVER only, matching the
reference preview-host sample and Windows Explorer's preview pane —
COM now routes through the AppID's DllSurrogate (typically prevhost.exe), and MSG previews finally render.
CoInitializeSecurity is now established at host start-up. Handlers
that run in Windows' low-integrity prevhost.exe surrogate (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 DoPreview returns E_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 IPreviewHandlerFrame site failed DoPreview.
Outlook's MSG preview handler (and other handlers hosted in prevhost.exe)
ask the host for IPreviewHandlerFrame and IOleWindow via a site object
installed through IObjectWithSite::SetSite; without it DoPreview returns E_FAIL and the preview never renders. A new PreviewHostSite class
implements IPreviewHandlerFrame (no-op accelerator translation), IServiceProvider (routing IPreviewHandlerFrame / IOleWindow lookups
back to itself) and IOleWindow (returning the render window's HWND).
The site is installed before any IInitializeWith*::Initialize call —
Outlook's MSG handler queries the site from inside Initialize and aborts
the load if it isn't ready yet — and cleared again on unload. The call
order in LoadHandlerSta now matches Microsoft's recommended sequence:
CoCreateInstance → QI IPreviewHandler → SetSite → Initialize → SetWindow
→ DoPreview.
Preview handlers initialised only via IInitializeWithItem could not load. LoadHandlerSta previously tried only IInitializeWithFile and 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. FindPreviewHandlerClsid previously 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 in HKCR\.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 in OpenWithProgids → 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.TerminateSession used an
infinite timeout when writing CLOSE to 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 under g_sessionsMutex, released the
lock, and then wrote to session->hPipe. Between releasing the lock and
the write, ListCloseWindow on another thread could erase and delete
the session. The mutex is now held for the entire pipe write.
Race condition in host HostLog critical-section initialisation. InitializeCriticalSection was called lazily on the first HostLog
invocation without any synchronisation. Two threads (STA + pipe reader)
could race and initialise the same CRITICAL_SECTION twice, corrupting
it. The CS is now initialised once in wmain before the pipe reader
thread is created.
Pipe buffer exhaustion after many file switches. The host sends OK\n / ERR …\n replies for every LOAD and CLOSE, but the plugin
never read them. After roughly 5 000 ListLoadNextW calls 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 Office JobObject handle were never closed
or destroyed before process exit. They are now cleaned up in wmain
after the message loop terminates.
Potential CreateProcessW command-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
a nullptrFileToLoad argument.
Re-entrancy crash when switching modes rapidly (PowerPoint). The retry
loops inside LoadWordFullSta, LoadExcelFullSta and LoadPowerPointFullSta contained PeekMessage dispatch loops that pumped
the STA message queue. While a load was still in progress, a second WM_HOST_SWITCH_MODE message could be dispatched recursively, causing
re-entrant COM calls and eventual heap corruption or access violation. The PeekMessage loops have been replaced with plain Sleep, and a loadingInProgress guard now serialises LoadFileWithModeSta calls 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. EmbedOfficeWindowSta strips Win32 caption styles, but Office apps
(Word, Excel, PowerPoint) render their own close button inside the
ribbon / title bar. Clicking it sent WM_SYSCOMMAND(SC_CLOSE) or WM_CLOSE to 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. SetWindowSubclass cannot
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 to HWND_TOP so it sits above the Office window, painted with LTGRAY_BRUSH, and returns HTCLIENT from WM_NCHITTEST so 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 with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. When the host
process exits for any reason (clean shutdown, plugin-side TerminateProcess after the host failed to honour CLOSE within
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.