mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2068 lines
55 KiB
2068 lines
55 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1994.
|
|
//
|
|
// File: QVSTUB.CPP
|
|
//
|
|
// Contents: Main entry point and processing code for QuickView Stub.
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 01-Feb-94 kraigb Created
|
|
// 12-Oct-94 davepl NTPort
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include "qvstub.h"
|
|
#pragma hdrstop
|
|
|
|
#include <locale.h>
|
|
|
|
#define INITGUID
|
|
#include <initguid.h>
|
|
#include <shlguid.h>
|
|
|
|
// BUGBUG:: Should this be put as part of shlobj.h???
|
|
|
|
#include "viewerr.h"
|
|
|
|
//
|
|
// Define OLE_ISTOOSLOW to avoid loading OLE32
|
|
//
|
|
#define FVSIF_PRESERVED 0x000000F5 // preserve some flags to use accross calls
|
|
|
|
#ifdef OLE_ISTOOSLOW
|
|
STDAPI SHCoCreateInstance(LPCTSTR pszCLSID,
|
|
const CLSID FAR * lpclsid,
|
|
LPUNKNOWN pUnkOuter,
|
|
REFIID riid,
|
|
LPVOID FAR* ppv);
|
|
|
|
#define CoCreateInstance(rclsid, punkOuter, ctx, riid, ppv) \
|
|
SHCoCreateInstance(NULL, &rclsid, punkOuter, riid, ppv)
|
|
|
|
#endif // OLE_ISTOOSLOW
|
|
|
|
//
|
|
// Main application object accessible from search dialog.
|
|
//
|
|
|
|
PCQVStub g_pQV;
|
|
|
|
//
|
|
// We keep a global handle to the file mapping.
|
|
//
|
|
|
|
typedef struct _QVSS
|
|
{
|
|
CRITICAL_SECTION cs; // Critical section to manage this
|
|
int cProcess; // Number of qvstub processes
|
|
HANDLE hEvent; // The thing people will sleep on.
|
|
DWORD dwProcessID;// Process ID of the process that owns the event
|
|
int nCmdShow; // The command to open.
|
|
TCHAR szCmdLine[512]; // command to run...
|
|
|
|
// Other information that multiple instances will be interested in.
|
|
HWND hwndPinned; // The current pinned window.
|
|
PCQVStub pQVPinned; // Which Qvstub called the pinned
|
|
|
|
} QVSS, *PQVSS;
|
|
|
|
HANDLE g_hMapFile = NULL;
|
|
const TCHAR g_szMapName[] = TEXT("QvStubMemory");
|
|
PQVSS g_pqvss = NULL;
|
|
|
|
#ifndef WINNT
|
|
extern "C" VOID WINAPI ReinitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
|
|
#else
|
|
#define ReinitializeCriticalSection InitializeCriticalSection
|
|
#endif
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CheckForCachedProcess
|
|
//
|
|
// Synopsis: Checks to see if we have a cached process out there that
|
|
// would like to over the processing of this one or not
|
|
//
|
|
// Arguments: [hInst]
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
BOOL PASCAL CheckForCachedProcess (HINSTANCE hInst, LPTSTR pszCmdLine, int nCmdShow)
|
|
{
|
|
BOOL fInit = FALSE;
|
|
BOOL fRet = FALSE;
|
|
|
|
// BUGBUG: There might be a small window of time that this may not
|
|
// be fully correct. We will map 1 page of memory...
|
|
|
|
ODS(TEXT("qv TR - Checking for Cached process"));
|
|
|
|
|
|
if ((g_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,
|
|
FALSE,
|
|
g_szMapName)) == (HANDLE)0)
|
|
{
|
|
if ((g_hMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF,
|
|
(LPSECURITY_ATTRIBUTES)0,
|
|
PAGE_READWRITE,
|
|
0,
|
|
sizeof(QVSS),
|
|
g_szMapName)) == (HANDLE)0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS)
|
|
{
|
|
fInit = TRUE;
|
|
}
|
|
}
|
|
|
|
g_pqvss = (PQVSS)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if (g_pqvss == NULL)
|
|
{
|
|
CloseHandle(g_hMapFile);
|
|
g_hMapFile = NULL;
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Initialize the critical section. We must use the undocumented
|
|
// ReInitialize as to handle multiple processes.
|
|
ReinitializeCriticalSection(&g_pqvss->cs);
|
|
|
|
|
|
// See if we need to initialize
|
|
if (fInit)
|
|
{
|
|
g_pqvss->cProcess = 1;
|
|
g_pqvss->hEvent = NULL;
|
|
g_pqvss->dwProcessID = 0;
|
|
g_pqvss->hwndPinned = NULL;
|
|
g_pqvss->pQVPinned = NULL;
|
|
}
|
|
else
|
|
{
|
|
EnterCriticalSection(&g_pqvss->cs);
|
|
|
|
// See if there is another process waiting for us.
|
|
if (g_pqvss->hEvent)
|
|
{
|
|
HANDLE hEvent;
|
|
HANDLE hProcess = NULL;
|
|
|
|
ODS(TEXT("qv TR - Another process waiting"));
|
|
|
|
|
|
// Now to signal the event - This is a pain with multiple
|
|
// processes.
|
|
if ((hProcess = OpenProcess(STANDARD_RIGHTS_REQUIRED | PROCESS_DUP_HANDLE,
|
|
FALSE, g_pqvss->dwProcessID)) &&
|
|
|
|
DuplicateHandle(hProcess, g_pqvss->hEvent,
|
|
GetCurrentProcess(), &hEvent, DUPLICATE_SAME_ACCESS,
|
|
TRUE, 0))
|
|
{
|
|
// Yes there is one for us, so copy our command line and
|
|
// and arguments and signal the process to wake up.
|
|
g_pqvss->nCmdShow = nCmdShow;
|
|
lstrcpy(g_pqvss->szCmdLine, pszCmdLine);
|
|
SetEvent(hEvent);
|
|
CloseHandle(hEvent);
|
|
g_pqvss->hEvent = NULL; // Keep others from trying!
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Assert(FALSE)
|
|
ODSlu(TEXT("qv TR - Error Dup handle %x"), GetLastError());
|
|
g_pqvss->cProcess++; // Increment the number of process.
|
|
g_pqvss->szCmdLine[0] = TEXT('\0');
|
|
}
|
|
if (hProcess)
|
|
CloseHandle(hProcess);
|
|
}
|
|
|
|
else
|
|
g_pqvss->cProcess++; // Increment the number of process.
|
|
|
|
|
|
LeaveCriticalSection(&g_pqvss->cs);
|
|
}
|
|
return(fRet);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: WinMain
|
|
//
|
|
// Synopsis: Main entry point of application. Most of the processing in
|
|
// QVStub happens here as this application doesn't have a main
|
|
// window or a message loop. This code primarily locates a
|
|
// component object File Viewer DLL, loads it, and tells
|
|
// it to load and show the file we got on the command line.
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
int PASCAL WinMain(HINSTANCE hInst,
|
|
HINSTANCE hInstPrev,
|
|
LPSTR pszCmdLine,
|
|
int nCmdShow)
|
|
{
|
|
#ifndef WINNT
|
|
int cMsg=96;
|
|
#endif
|
|
QVERROR uErr;
|
|
|
|
#ifdef UNICODE
|
|
LPTSTR pszUniCmdLine = GetCommandLine();
|
|
#else
|
|
#define pszUniCmdLine pszCmdLine
|
|
#endif
|
|
|
|
|
|
//
|
|
// Lets see if there is another QVStub process take over processing
|
|
// this or not.
|
|
//
|
|
|
|
if (::CheckForCachedProcess(hInst, pszUniCmdLine, nCmdShow))
|
|
return 0;
|
|
|
|
#ifndef WINNT
|
|
/*
|
|
* Set a larger message queue for the FileViewer since it
|
|
* might want to do some LRPC.
|
|
*/
|
|
while (!SetMessageQueue(cMsg) && (cMsg-=8));
|
|
#endif
|
|
|
|
// Note We might be able to do this main loop simply around
|
|
// the View or print. But first see if this works
|
|
|
|
g_pQV = new CQVStub(hInst);
|
|
|
|
if (NULL!=g_pQV)
|
|
{
|
|
if (g_pQV->FInit())
|
|
{
|
|
if (!setlocale(LC_CTYPE, ""))
|
|
DebugMsg(DM_TRACE, TEXT("qv TR - setlocale() failed"));
|
|
|
|
//Go do all the work
|
|
//Create the main application object
|
|
do
|
|
{
|
|
uErr=g_pQV->ViewOrPrintFile(pszUniCmdLine, nCmdShow);
|
|
|
|
//Close the search dialog if it's running.
|
|
g_pQV->SearchStop(FALSE);
|
|
g_pQV->Error(uErr);
|
|
|
|
} while (g_pQV->TryWaitForNextCommand(&pszUniCmdLine, &nCmdShow));
|
|
}
|
|
}
|
|
|
|
delete g_pQV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: QVStub Class Constructor and Destructor
|
|
//
|
|
// Arguments: [hInst] (Constructor) HINSTANCE to use in initializing
|
|
// the stringtable in the FInit function.
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes: Most of the interesting stuff happens in FInit and
|
|
// SearchForViewer
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CQVStub::CQVStub(HINSTANCE hInst)
|
|
{
|
|
m_cRef = 1; // initialize to one person using it (us)
|
|
m_hInst = hInst;
|
|
m_fInitialized = FALSE;
|
|
m_fStopSearch = FALSE;
|
|
m_fUserStop = FALSE;
|
|
m_hDlg = NULL;
|
|
m_hThread = NULL;
|
|
m_pST=NULL;
|
|
|
|
m_szFile[0] = TEXT('\0');
|
|
m_szLastFile[0] = TEXT('\0');
|
|
m_fLoadCalled = FALSE;
|
|
m_szType[0] = TEXT('\0');;
|
|
m_fHaveType = FALSE; //Only TRUE when m_szType is set
|
|
|
|
m_fPrintTo = FALSE;
|
|
m_fDriver = FALSE; //True when m_szDriver is set
|
|
m_szDriver[0] = TEXT('\0');
|
|
m_fSuppressUI = FALSE;
|
|
|
|
m_rclsID = IID_IUnknown;
|
|
m_pIUnknown = NULL; // No interface is cached now.
|
|
|
|
m_fvsi.cbSize = sizeof(m_fvsi);
|
|
m_fvsi.dwFlags = 0; // no options passe din
|
|
m_fvsi.hwndOwner= NULL;
|
|
m_fvsi.punkRel = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
CQVStub::~CQVStub(void)
|
|
{
|
|
//Make sure the thread has exited and the dialog is dead.
|
|
|
|
while (NULL != m_hDlg)
|
|
;
|
|
|
|
//Free the thread (dialog is already dead)
|
|
if (NULL!=m_hThread)
|
|
{
|
|
CloseHandle(m_hThread);
|
|
m_hThread=NULL;
|
|
}
|
|
|
|
if(NULL!=m_pIUnknown)
|
|
{
|
|
m_pIUnknown->Release();
|
|
m_pIUnknown= NULL;
|
|
}
|
|
|
|
#ifdef OLE_ISTOOSLOW
|
|
#if 0
|
|
QV_UnloadOLE();
|
|
#endif
|
|
#else
|
|
if (m_fInitialized)
|
|
OleUninitialize();
|
|
#endif
|
|
|
|
//Clean up stringtable.
|
|
if (NULL!=m_pST)
|
|
{
|
|
delete m_pST;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::QueryInterface, AddRef, Release
|
|
//
|
|
// Synopsis: Can be used by the Viewer object to be able to call us back
|
|
// and query us for other interfaces.
|
|
//
|
|
// Returns: NOERROR if interface found, E_NOINTERFACE otherwise
|
|
//
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
// IUnknown
|
|
|
|
HRESULT STDMETHODCALLTYPE CQVStub::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown))
|
|
{
|
|
ODS(TEXT("CQVStub::QueryInterface(IUnknown)"));
|
|
*ppvObj = (void*)this;
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
else if (IsEqualIID(riid, IID_IFileViewerSite))
|
|
{
|
|
ODS(TEXT("CQVStub::QueryInterface(IFileViewerSite)"));
|
|
*ppvObj = (void*)(IFileViewerSite*)this;
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
return ResultFromScode(E_NOINTERFACE);
|
|
}
|
|
|
|
// BUGBUG not multithreaded safe
|
|
|
|
ULONG STDMETHODCALLTYPE CQVStub::AddRef()
|
|
{
|
|
ODSu(TEXT("CQVStub::AddRef() ==> %d"), this->m_cRef+1);
|
|
|
|
this->m_cRef++;
|
|
|
|
return this->m_cRef;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE CQVStub::Release()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CQVStub::Release() ==> %d"), this->m_cRef-1);
|
|
|
|
this->m_cRef--;
|
|
|
|
if (this->m_cRef>0)
|
|
{
|
|
return this->m_cRef;
|
|
}
|
|
|
|
delete this;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::SetPinnedWindow
|
|
//
|
|
// Synopsis:
|
|
//
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: [hwnd] Window to set as the "pinned" window
|
|
//
|
|
// Returns: NOERROR on success, S_FALSE otherwise
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE CQVStub::SetPinnedWindow(HWND hwnd)
|
|
{
|
|
if ((hwnd == NULL) || (g_pqvss->hwndPinned == NULL))
|
|
{
|
|
g_pqvss->hwndPinned = hwnd;
|
|
g_pqvss->pQVPinned = this;
|
|
return NOERROR;
|
|
}
|
|
|
|
// It was in use so tell caller that their request failed
|
|
return ResultFromScode(S_FALSE);
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::GetPinnedWindow
|
|
//
|
|
// Synopsis: Returns the currently pinned window
|
|
//
|
|
// Arguments: [phwnd] OUT parameter for window handle
|
|
//
|
|
// Returns: NOERROR
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
HRESULT STDMETHODCALLTYPE CQVStub::GetPinnedWindow(HWND *phwnd)
|
|
{
|
|
*phwnd = g_pqvss->hwndPinned;
|
|
return NOERROR;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::FInit
|
|
//
|
|
// Synopsis: Performs initialization prone to failure, such as loading a
|
|
// stringtable.
|
|
//
|
|
// Arguments: (none)
|
|
//
|
|
// Returns: BOOL TRUE if the function is successful, FALSE otherwise
|
|
// in which case the caller should delete the object.
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CQVStub::FInit(void)
|
|
{
|
|
|
|
#ifndef OLE_ISTOOSLOW
|
|
|
|
DWORD dwVer;
|
|
DWORD dwTick = GetCurrentTime();
|
|
|
|
//
|
|
// Check OLE version numbers and initialize. We need to do
|
|
// the full OleInitialize on behalf of the FileViewer we might
|
|
// load since it may depend on such initialization.
|
|
//
|
|
|
|
dwVer = OleBuildVersion();
|
|
|
|
if (rmm != HIWORD(dwVer))
|
|
return FALSE;
|
|
|
|
if (FAILED(OleInitialize(NULL)))
|
|
return FALSE;
|
|
|
|
dwTick = GetCurrentTime()-dwTick;
|
|
ODSu(TEXT("qv TR - OleInitialize took %d msec"), dwTick);
|
|
|
|
#endif // OLE_ISTOOSLOW
|
|
|
|
m_fInitialized = TRUE;
|
|
|
|
// Create a stringtable of those we'll need.
|
|
m_pST = new CStringTable(m_hInst);
|
|
|
|
// Any error output handled in WinMain through CQVStub::Error
|
|
if (NULL==m_pST)
|
|
return FALSE;
|
|
|
|
if (!m_pST->FInit(IDS_MIN, IDS_MAX, CCHSTRINGMAX))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::TryWaitForNextCommand
|
|
//
|
|
// Synopsis: Waits for a certain amount of time to try to cache process
|
|
// information such that if a user does a follow-on View it
|
|
// will hopefully be faster.
|
|
//
|
|
// Arguments: [ppszCmdLine] If a new command line has come in, then we
|
|
// will point to this new command line
|
|
// [pnCmdShow] Likewide, ptr to the new command show...
|
|
//
|
|
// Returns: TRUE if we should process another command, else FALSE
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CQVStub::TryWaitForNextCommand(LPTSTR *ppszCmdLine, int *pnCmdShow)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
HANDLE hEvent = NULL;
|
|
|
|
if (g_pqvss == NULL)
|
|
return(FALSE);
|
|
|
|
EnterCriticalSection(&g_pqvss->cs);
|
|
|
|
g_pqvss->cProcess--;
|
|
ODSu(TEXT("qv TR - Leave Count %d"), g_pqvss->cProcess);
|
|
if (g_pqvss->cProcess != 0)
|
|
{
|
|
LeaveCriticalSection(&g_pqvss->cs);
|
|
|
|
// Lets close our mapping and handle to this
|
|
UnmapViewOfFile(g_pqvss);
|
|
CloseHandle(g_hMapFile);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
// We are the last process so we need to setup an event object
|
|
// to sleep on
|
|
if ((hEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) != (HANDLE)0)
|
|
{
|
|
g_pqvss->hEvent = hEvent;
|
|
g_pqvss->dwProcessID = GetCurrentProcessId();
|
|
DebugMsg(DM_TRACE, TEXT("qv TR - Wait(%x) on %x"), g_pqvss->dwProcessID,
|
|
hEvent);
|
|
|
|
// Wait up to a minute;
|
|
LeaveCriticalSection(&g_pqvss->cs);
|
|
|
|
BOOL fWait = TRUE;
|
|
|
|
DWORD dwTimeStartWait = GetCurrentTime();
|
|
|
|
while (fWait && MsgWaitForMultipleObjects(1, &hEvent, FALSE, 60000,
|
|
QS_ALLINPUT) == (WAIT_OBJECT_0 + 1))
|
|
{
|
|
// We were awaken by a message
|
|
ODS(TEXT("qv TR - MSgWait... Pressing messages"));
|
|
|
|
MSG msg;
|
|
|
|
while (fWait && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
switch(msg.message)
|
|
{
|
|
case WM_QUIT:
|
|
case WM_QUERYENDSESSION:
|
|
case WM_ENDSESSION:
|
|
fWait = FALSE;
|
|
break;
|
|
}
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
if ((GetCurrentTime()-dwTimeStartWait) > 60000)
|
|
fWait = FALSE;
|
|
}
|
|
|
|
EnterCriticalSection(&g_pqvss->cs);
|
|
|
|
// Close our handles
|
|
CloseHandle(hEvent);
|
|
|
|
// Now see if someone woke us up
|
|
if (g_pqvss->hEvent == NULL)
|
|
{
|
|
// Yep ...
|
|
fRet = TRUE;
|
|
*ppszCmdLine = g_pqvss->szCmdLine;
|
|
*pnCmdShow = g_pqvss->nCmdShow;
|
|
g_pqvss->cProcess++; // Let everyone know I am a live again
|
|
m_fStopSearch = FALSE; // Make sure we can restart.
|
|
m_fvsi.dwFlags = 0; // Zero out the flags...
|
|
}
|
|
|
|
g_pqvss->hEvent = NULL;
|
|
|
|
}
|
|
LeaveCriticalSection(&g_pqvss->cs);
|
|
|
|
if (!fRet)
|
|
{
|
|
UnmapViewOfFile(g_pqvss);
|
|
CloseHandle(g_hMapFile);
|
|
}
|
|
|
|
return(fRet);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CQVStub::ViewOrPrintFile
|
|
//
|
|
// Synopsis: Main driver for QVStub that takes a pathname and the nCmdShow
|
|
// from WinMain and locates a FileViewer to show or print it. This
|
|
// function is the glue that drives the real work that happens in
|
|
// Effects: the other more primitive functions.
|
|
//
|
|
// Arguments: [pszCmdLine] LPTSTR to the command line passed to WinMain.
|
|
// [nCmdShow] int command for showing the FileViewer.
|
|
//
|
|
// Returns: QVERROR (Appropriate error code)
|
|
//
|
|
// History: dd-mmm-yy History Comment
|
|
// 12-Oct-94 davepl NT Port
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
QVERROR CQVStub::ViewOrPrintFile(LPTSTR pszCmdLine, int nCmdShow)
|
|
{
|
|
QVERROR uErr;
|
|
HKEY hKey;
|
|
|
|
//
|
|
// WinMain never passes us a NULL pszCmdLine
|
|
//
|
|
|
|
D( if (NULL==pszCmdLine)
|
|
{
|
|
ODS(TEXT("QVStub Assert failure ViewOrPrintFile passes NULL filename!"));
|
|
}
|
|
);
|
|
|
|
/*
|
|
* Go parse all the command-line information. If this
|
|
* returns FALSE, we have no file, so quit. Note that
|
|
* FParseArguments will skip past any EXE name on the command
|
|
* line as well. This function fills m_szFile with the
|
|
* filename if it finds one as well as m_szDrive, m_fSuppressUI,
|
|
* m_fPrintTo, and m_fDriver.
|
|
*/
|
|
|
|
if (!FParseArguments(pszCmdLine))
|
|
return QVERROR_NOSTANDALONE;
|
|
|
|
//Save the ShowWindow command for the other functions
|
|
m_nCmdShow=nCmdShow;
|
|
|
|
// See if there is a pinned window that we can use instead...
|
|
if (g_pqvss->hwndPinned && !m_fPrintTo)
|
|
{
|
|
// Yes a pinned window, can we construct an HDROP for the
|
|
// file and pass it to the other window for now this can be
|
|
// real simple!
|
|
LPDROPFILES lpdf = (LPDROPFILES)GlobalAlloc(GPTR, sizeof(DROPFILES)
|
|
+ (lstrlen(m_szFile)*sizeof(TCHAR)) + (2 * sizeof(TCHAR)));
|
|
if (lpdf)
|
|
{
|
|
lpdf->pFiles = sizeof(DROPFILES);
|
|
//lpdf->pt.x = 0; // Zero inited...
|
|
//lpdf->pt.y = 0;
|
|
//lpdf->fNC = FALSE;
|
|
#ifdef UNICODE
|
|
lpdf->fWide = TRUE;
|
|
#endif
|
|
lstrcpy( (LPTSTR)((LPBYTE)lpdf+sizeof(DROPFILES)), m_szFile);
|
|
|
|
DebugMsg(DM_TRACE, TEXT("qv TR - Pass %s to pinned window, %x"),
|
|
m_szFile, g_pqvss->hwndPinned);
|
|
|
|
if (PostMessage(g_pqvss->hwndPinned, WM_DROPFILES, (WPARAM)lpdf, 0L))
|
|
{
|
|
return QVERROR_NONE;
|
|
}
|
|
else
|
|
{
|
|
// Post failed so free structure and process the normal way...
|
|
GlobalFree((HGLOBAL)lpdf);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// We no longer use CLSID to avoid loading OLE code from QVSTUB.
|
|
// Loop while we have files to process.
|
|
//
|
|
for (;;)
|
|
{
|
|
DWORD dwFlags = m_fvsi.dwFlags;
|
|
m_fLoadCalled = FALSE; // reset for next file.
|
|
m_fvsi.dwFlags &= ~(FVSIF_NEWFILE);
|
|
|
|
#ifdef OLE_ISTOOSLOW
|
|
DebugMsg(DM_TRACE, TEXT("qv TR - %s %s a docfile"),
|
|
m_szFile,
|
|
(QV_StgIsStorageFile(m_szFile)==NOERROR) ? "is":"is not");
|
|
#endif
|
|
uErr=OpenKey(&hKey);
|
|
|
|
if (QVERROR_NONE==uErr)
|
|
{
|
|
uErr=EnumerateAndTryViewers(hKey);
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
ODSu(TEXT("CQVStub::ViewOrPrintFile - Call toEnumerateAndTryViewers ret=%d"), uErr);
|
|
|
|
// If for some reason we did not display properly and we were
|
|
// previously processing a file, than we need to tell the existing
|
|
// viewer to continue
|
|
if ((QVERROR_NONE!=uErr) && (dwFlags & FVSIF_NEWFILE))
|
|
{
|
|
HRESULT hr;
|
|
LPFILEVIEWER pIFileViewer;
|
|
|
|
// If we called load on the new file and we have failure
|
|
// we are probably ca not simply tell the old viewer
|
|
// to continue as they may have blown away the state
|
|
// information about the file. So try to reset to
|
|
// the old file...
|
|
|
|
if (m_fLoadCalled && (m_szLastFile[0] != TEXT('\0')))
|
|
{
|
|
ODS(TEXT("CQVStub::ViewOrPrintFile - New file failed, load called, try to restore"));
|
|
g_pQV->SearchStop(FALSE);
|
|
g_pQV->Error(uErr);
|
|
lstrcpy(m_szFile, m_szLastFile);
|
|
m_fStopSearch = FALSE; // Make sure we can restart.
|
|
uErr = QVERROR_NONE;
|
|
continue; // try again.
|
|
}
|
|
|
|
// This is rather gross, but we bind back to
|
|
hr=m_pIUnknown->QueryInterface(IID_IFileViewer
|
|
, (LPVOID *)&pIFileViewer);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ODS(TEXT("CQVStub::ViewOrPrintFile - Try to restore previous viewer"));
|
|
// Report any errors
|
|
g_pQV->SearchStop(FALSE);
|
|
g_pQV->Error(uErr);
|
|
m_fvsi.dwFlags = FVSIF_NEWFAILED;
|
|
if (SUCCEEDED( pIFileViewer->Show(&m_fvsi)))
|
|
uErr = QVERROR_NONE;
|
|
|
|
pIFileViewer->Release();
|
|
}
|
|
#ifdef UNICODE
|
|
else
|
|
{
|
|
LPFILEVIEWERA pIFileViewerA;
|
|
|
|
hr=m_pIUnknown->QueryInterface(IID_IFileViewerA,
|
|
(LPVOID *)&pIFileViewerA);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ODS(TEXT("CQVStub::ViewOrPrintFileA - Try to restore previous viewer"));
|
|
// Report any errors
|
|
g_pQV->SearchStop(FALSE);
|
|
g_pQV->Error(uErr);
|
|
m_fvsi.dwFlags = FVSIF_NEWFAILED;
|
|
if (SUCCEEDED( pIFileViewerA->Show(&m_fvsi)))
|
|
uErr = QVERROR_NONE;
|
|
|
|
pIFileViewerA->Release();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ((m_fvsi.dwFlags & FVSIF_NEWFILE) == 0)
|
|
break;
|
|
|
|
// Make sure we are not processing a previous search...
|
|
g_pQV->SearchStop(FALSE);
|
|
m_fStopSearch = FALSE; // Make sure we can restart.
|
|
|
|
// Save away the name of the file we were currently viewing
|
|
// this may be needed if we actually called Load and ShowInitialize
|
|
// on the new file and this failed...
|
|
//
|
|
lstrcpy(m_szLastFile, m_szFile);
|
|
|
|
#ifndef UNICODE
|
|
// Now copy the file back to be processed
|
|
wcstombs(m_szFile, m_fvsi.strNewFile, sizeof(m_szFile));
|
|
#else
|
|
lstrcpy( m_szFile, m_fvsi.strNewFile );
|
|
#endif
|
|
|
|
DebugMsg(DM_TRACE, TEXT("qv Show New File %s"), m_szFile);
|
|
}
|
|
|
|
|
|
return uErr;
|
|
}
|
|
|
|
|
|
/*
|
|
* CQVStub::MapHResultToQVError
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Maps the error codes that have been returned by the viewer to
|
|
* the appropriate internal error code.
|
|
*
|
|
*
|
|
* Parameters:
|
|
* hr HRESULT returned by viewers...
|
|
*
|
|
* Return Value:
|
|
* QVERROR Error code or QVERROR_NONE.
|
|
*/
|
|
|
|
QVERROR CQVStub::MapHResultToQVError(HRESULT hr)
|
|
{
|
|
if (SUCCEEDED(hr))
|
|
return QVERROR_NONE;
|
|
|
|
switch (hr)
|
|
{
|
|
default:
|
|
//case FV_E_INVALIDID:
|
|
//case FV_E_NOFILTER:
|
|
//case FV_E_NONSUPPORTEDTYPE:
|
|
return QVERROR_NOVIEWER;
|
|
|
|
case FV_E_OUTOFMEMORY:
|
|
return QVERROR_OUTOFMEMORY;
|
|
|
|
case FV_E_BADFILE:
|
|
return QVERROR_BADFILE;
|
|
|
|
case FV_E_EMPTYFILE:
|
|
return QVERROR_FILEEMPTY;
|
|
|
|
case FV_E_FILEOPENFAILED:
|
|
return QVERROR_OPENFAILED;
|
|
|
|
case FV_E_PROTECTEDFILE:
|
|
return QVERROR_PROTECTEDFILE;
|
|
|
|
case FV_E_UNEXPECTED:
|
|
case FV_E_MISSINGFILES:
|
|
case FV_E_NOVIEWER:
|
|
return QVERROR_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::EnumerateAndTryViewers
|
|
*
|
|
* Purpose:
|
|
* Given an open key for a file CLSID or extension, enumerate all
|
|
* the viewers registered for that type. As each is found, ask
|
|
* the viewer to show m_szFile. If the first viewer fails, then
|
|
* invoke the search dialog and try others. If any one of them
|
|
* works, then we stop the search dialog and return success.
|
|
*
|
|
* Parameters:
|
|
* hKey HKEY open for enumeration
|
|
*
|
|
* Return Value:
|
|
* QVERROR Error code or QVERROR_NONE.
|
|
*/
|
|
|
|
QVERROR CQVStub::EnumerateAndTryViewers(HKEY hKey)
|
|
{
|
|
HRESULT hr;
|
|
TCHAR szKey[128];
|
|
UINT i=0;
|
|
LONG lRet;
|
|
CLSID clsID;
|
|
|
|
hr=ResultFromScode(E_FAIL);
|
|
|
|
while (!m_fStopSearch)
|
|
{
|
|
szKey[0] = (TCHAR)'\0';
|
|
//Enumeration happens in most-recently-registered order.
|
|
lRet=RegEnumKey(hKey, i, szKey, sizeof(szKey));
|
|
i++;
|
|
|
|
//Quit if enumeration fails
|
|
if ((lRet == ERROR_NO_MORE_ITEMS) && (i > 1))
|
|
{
|
|
ODSsz(TEXT("QVSTUB: Registry enumeration Completed %s"), (LPTSTR)szKey);
|
|
break; // let the previous hr be returned...
|
|
}
|
|
|
|
if (i > 1)
|
|
{
|
|
|
|
//No-op if the search dialog is already up.
|
|
SearchInvoke();
|
|
}
|
|
|
|
if (ERROR_SUCCESS!=lRet
|
|
&& !(lRet==ERROR_NO_MORE_ITEMS && szKey[0]))
|
|
{
|
|
ODSsz(TEXT("QVSTUB: Registry enumeration failed with %s"), (LPTSTR)szKey);
|
|
ODSu(TEXT("QVSTUB: lRet = %d"), lRet);
|
|
SearchInvoke();
|
|
break;
|
|
}
|
|
|
|
ODSsz(TEXT("QVSTUB: Found a viewer %s"), (LPTSTR)szKey);
|
|
|
|
/*
|
|
* Found a subkey. szKey should contain a CLSID string
|
|
* between {}'s. This is the CLSID that we want to use
|
|
* in CoCreateInstance, so turn that CLSID string into a
|
|
* real CLSID and return. Note that CLSIDFromString
|
|
* strips off the {}'s around the string, and the string
|
|
* has to be Unicode.
|
|
*/
|
|
#ifdef OLE_ISTOOSLOW
|
|
hr=QV_CLSIDFromString(szKey, &clsID);
|
|
#else
|
|
|
|
#ifdef UNICODE
|
|
hr=CLSIDFromString(szKey, &clsID);
|
|
#else
|
|
OLECHAR szw[512];
|
|
mbstowcs(szw, szKey, sizeof(szw));
|
|
hr=CLSIDFromString(szw, &clsID);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//Go try this viewer.
|
|
hr=LoadAndShowViewer(clsID);
|
|
ODSu(TEXT("QVSTUB: LoadAndShowViewer returned %x"), hr);
|
|
}
|
|
|
|
//This catches failures of CLSIDFromString and LoadAndShowViewer
|
|
if (SUCCEEDED(hr))
|
|
break;
|
|
}
|
|
|
|
return MapHResultToQVError(hr);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::LoadAndShowViewer
|
|
*
|
|
* Purpose:
|
|
* Given a classname and show command, try to instantiate the
|
|
* FileViewer of the given class and have it show the file in
|
|
* m_szFile. If, however, m_fPrintTo is set, then ask the
|
|
* FileViewer to print instead of show.
|
|
*
|
|
* Parameters:
|
|
* rclsID REFCLSID of the viewer we attempt to show.
|
|
*
|
|
* Return Value:
|
|
* HRESULT Appropriate error code.
|
|
*/
|
|
|
|
HRESULT CQVStub::LoadAndShowViewer(REFCLSID rclsID)
|
|
{
|
|
HRESULT hr;
|
|
LPPERSISTFILE pIPersistFile;
|
|
LPFILEVIEWER pIFileViewer;
|
|
|
|
// Make sure that we clear old options out if set before...
|
|
|
|
m_fvsi.dwFlags &= (FVSIF_PRESERVED); // Only allow rect+a few bogus one through
|
|
|
|
if ((m_pIUnknown == NULL) || !IsEqualCLSID(rclsID, m_rclsID))
|
|
{
|
|
if (m_pIUnknown)
|
|
m_pIUnknown->Release();
|
|
|
|
hr=CoCreateInstance(rclsID, NULL, CLSCTX_INPROC_SERVER
|
|
, IID_IUnknown, (LPVOID *)&m_pIUnknown);
|
|
|
|
ODSu(TEXT("QVSTUB: CoCreateInstance returned %x"), hr);
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ODSlu(TEXT("QVSTUB: CoCreateInstance failed with 0x%lX"), hr);
|
|
m_pIUnknown = NULL; // Make sure it is zero
|
|
return hr;
|
|
}
|
|
|
|
// Save away the class id.
|
|
m_rclsID = (CLSID)rclsID;
|
|
|
|
}
|
|
|
|
//We got the object, now get IPersistFile
|
|
hr=m_pIUnknown->QueryInterface(IID_IPersistFile
|
|
, (LPVOID *)&pIPersistFile);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fLoadCalled = TRUE; // Can't just continue if failure now...
|
|
|
|
#ifdef UNICODE
|
|
#define szwFile m_szFile
|
|
BOOL fUnicode = FALSE;
|
|
#else
|
|
OLECHAR szwFile[520];
|
|
//Need Unicode string for Load.
|
|
mbstowcs(szwFile, m_szFile, sizeof(szwFile));
|
|
#endif
|
|
|
|
|
|
hr=pIPersistFile->Load(szwFile, STGM_DIRECT | STGM_READ
|
|
| STGM_SHARE_EXCLUSIVE);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr=m_pIUnknown->QueryInterface(IID_IFileViewer
|
|
, (LPVOID *)&pIFileViewer);
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// If we couldn't find the UNICODE version of IFileViewer,
|
|
// then see if the ANSI version exists. If so, use it.
|
|
//
|
|
if (hr==E_NOINTERFACE) {
|
|
hr=m_pIUnknown->QueryInterface(IID_IFileViewerA
|
|
, (LPVOID *)&pIFileViewer);
|
|
} else {
|
|
fUnicode = TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
/*
|
|
* If we want to do PrintTo, call IFileViewer::PrintTo
|
|
* now and don't bother with anything else.
|
|
*/
|
|
if (m_fPrintTo)
|
|
{
|
|
LPTSTR pszDriver=NULL;
|
|
|
|
#ifdef UNICODE
|
|
CHAR szDriverA[MAX_PATH];
|
|
|
|
if (m_fDriver) {
|
|
|
|
if (fUnicode) {
|
|
|
|
pszDriver=m_szDriver;
|
|
|
|
} else {
|
|
|
|
WideCharToMultiByte( CP_ACP,
|
|
0,
|
|
m_szDriver,
|
|
-1,
|
|
szDriverA,
|
|
MAX_PATH,
|
|
NULL,
|
|
NULL );
|
|
pszDriver = (LPTSTR)((LPVOID)szDriverA);
|
|
|
|
}
|
|
|
|
}
|
|
#else
|
|
if (m_fDriver)
|
|
pszDriver=m_szDriver;
|
|
#endif
|
|
|
|
hr=pIFileViewer->PrintTo(pszDriver, m_fSuppressUI);
|
|
D(if (FAILED(hr)) ODSlu(TEXT("QVSTUB: IFileViewer::PrintTo returned %lX"), hr););
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Call IFileViewer::ShowInitialize to see if
|
|
* the FileViewer will be able to show this file.
|
|
* If so, then close the search dialog if it's
|
|
* up and call IFileViewer::Show.
|
|
*/
|
|
ODSu(TEXT("QVSTUB: Flags before ShowInitialize %x"), m_fvsi.dwFlags );
|
|
hr=pIFileViewer->ShowInitialize(this);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SearchStop(FALSE);
|
|
|
|
/*
|
|
* IFileViewer::Show does not return until
|
|
* it closes the window.
|
|
*/
|
|
m_fvsi.hwndOwner = NULL;
|
|
m_fvsi.iShow = m_nCmdShow;
|
|
|
|
// If we have a pinned window and it came
|
|
// from us then set the pinned state to let
|
|
// the calle know...
|
|
if ((NULL!=g_pqvss->hwndPinned) &&
|
|
(this==g_pqvss->pQVPinned))
|
|
m_fvsi.dwFlags |= FVSIF_PINNED;
|
|
|
|
ODSu( TEXT("QVSTUB: Flags before Show %x"), m_fvsi.dwFlags );
|
|
pIFileViewer->Show(&m_fvsi);
|
|
ODSu( TEXT("QVSTUB: Flags after Show %x"), m_fvsi.dwFlags );
|
|
|
|
}
|
|
|
|
D(if (FAILED(hr)) ODSlu(TEXT("QVSTUB: IFileViewer::Initialize returned %lX"), hr););
|
|
}
|
|
|
|
pIFileViewer->Release();
|
|
}
|
|
else
|
|
{
|
|
ODSlu(TEXT("QVSTUB: QueryInterface for IFileViewer failed with 0x%lX"), hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ODSlu(TEXT("QVSTUB: IPersistFile::Load failed with 0x%lX"), hr);
|
|
}
|
|
|
|
pIPersistFile->Release();
|
|
}
|
|
else
|
|
{
|
|
ODSlu(TEXT("QVSTUB: QueryInterface for IPersistFile failed with 0x%lX"), hr);
|
|
}
|
|
|
|
// We don't release the interface here as to try to cache it if we
|
|
// attempt to view a second file with the same viewer.
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::TryAllViewers
|
|
*
|
|
* Purpose:
|
|
* Builds a list of all FileViewer CLSIDs in the registry and then
|
|
* tries LoadAndShowViewer on each of them. This is only a last-resort
|
|
* exhaustive search for a viewer is all else fails.
|
|
*
|
|
* Parameters:
|
|
* None
|
|
*
|
|
* Return Value:
|
|
* QVERROR Error code or QVERROR_NONE if we actually find one.
|
|
*/
|
|
|
|
QVERROR CQVStub::TryAllViewers(void)
|
|
{
|
|
HRESULT hr;
|
|
HWND hList;
|
|
HKEY hKey, hKeySub;
|
|
TCHAR szKey[128];
|
|
UINT i=0, j=0;
|
|
UINT cItems;
|
|
LONG lRet1, lRet2;;
|
|
CLSID clsID, clsIDLast;
|
|
|
|
//Make sure the Search dialog is up
|
|
SearchInvoke();
|
|
|
|
/*
|
|
* Build list. This happens by enumerating all keys under
|
|
* "FileViewers" then enumerating all the keys under those
|
|
* keys, the names of which are CLSIDs of FileViewers. We
|
|
* take those strings and add them to a sorted list, so
|
|
* we can then try each unique CLSID once.
|
|
*/
|
|
|
|
//Maintain strings in a listbox
|
|
hList=CreateWindow(TEXT("listbox"), TEXT("listbox"), LBS_SORT | WS_CHILD
|
|
| LBS_HASSTRINGS, 0, 0, 100, 100, GetDesktopWindow()
|
|
, (HMENU)1000, m_hInst, NULL);
|
|
|
|
if (NULL==hList)
|
|
return QVERROR_NOVIEWER;
|
|
|
|
//Open base key
|
|
lRet1=RegOpenKey(HKEY_CLASSES_ROOT, TEXT("QuickView"), &hKey);
|
|
|
|
//Start enumerating
|
|
i=0;
|
|
|
|
/*
|
|
* This condition catches the first RegOpenKey as well and
|
|
* remembers to look at the search cancellation flag.
|
|
*/
|
|
while (ERROR_SUCCESS==lRet1 && !m_fStopSearch)
|
|
{
|
|
//Enumeration happens in most-recently-registered order.
|
|
lRet1=RegEnumKey(hKey, i++, szKey, sizeof(szKey));
|
|
|
|
//Quit successfully if there's nothing else to enumerate.
|
|
if (ERROR_SUCCESS!=lRet1)
|
|
break;
|
|
|
|
/*
|
|
* The enumerated key will be a CLSID or an extension.
|
|
* We don't care either way--we want to open the key
|
|
* and enumerate what's inside of it, adding the CLSIDs
|
|
* to the list.
|
|
*/
|
|
|
|
lRet2=RegOpenKey(hKey, szKey, &hKeySub);
|
|
j=0;
|
|
|
|
//Remember to check for search cancellation
|
|
while (ERROR_SUCCESS==lRet2 && !m_fStopSearch)
|
|
{
|
|
lRet2=RegEnumKey(hKeySub, j++, szKey, sizeof(szKey));
|
|
|
|
if (ERROR_SUCCESS!=lRet2)
|
|
break;
|
|
|
|
//Add the string to the list.
|
|
SendMessage(hList, LB_ADDSTRING, 0, (LONG)szKey);
|
|
}
|
|
|
|
RegCloseKey(hKeySub);
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
|
|
/*
|
|
* We've now enumerated all the CLSIDs for all QuickView,
|
|
* so now walk through the list attempting to use each one
|
|
* to view the file. Since the listbox is sorted, we can
|
|
* compare the last CLSID we used to the next one we try
|
|
* to avoid trying the same CLSID more than once.
|
|
*/
|
|
|
|
cItems=SendMessage(hList, LB_GETCOUNT, 0, 0L);
|
|
clsIDLast=CLSID_NULL;
|
|
clsID=CLSID_NULL;
|
|
|
|
//Assume failure
|
|
hr=ResultFromScode(E_FAIL);
|
|
|
|
//Remember to break out of the loop if search was cancelled
|
|
for (i=0; (i < cItems) && !m_fStopSearch && FAILED(hr); i++)
|
|
{
|
|
#ifndef OLE_ISTOOSLOW
|
|
OLECHAR szw[512];
|
|
#endif
|
|
|
|
if (LB_ERR==SendMessage(hList, LB_GETTEXT, i, (LONG)szKey))
|
|
continue;
|
|
|
|
#ifdef OLE_ISTOOSLOW
|
|
QV_CLSIDFromString(szKey, &clsID);
|
|
#else
|
|
#ifdef UNICODE
|
|
CLSIDFromString(szKey, &clsID);
|
|
#else
|
|
mbstowcs(szw, szKey, sizeof(szw));
|
|
CLSIDFromString(szw, &clsID);
|
|
#endif
|
|
#endif
|
|
|
|
//Same CLSID? Skip this one
|
|
if (clsID==clsIDLast)
|
|
continue;
|
|
|
|
clsIDLast=clsID;
|
|
|
|
/*
|
|
* Go try this viewer (FAILED(hr) in while loop will
|
|
* exit if this works).
|
|
*/
|
|
hr=LoadAndShowViewer(clsID);
|
|
}
|
|
|
|
//Clean up and exit
|
|
DestroyWindow(hList);
|
|
return SUCCEEDED(hr) ? QVERROR_NONE : QVERROR_NOVIEWER;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::OpenKey
|
|
*
|
|
* Purpose:
|
|
* Given the filename in m_szFile, attempts to open the
|
|
* FileViewer\CLSID\{CLSID} key if a CLSID exists for the file, or
|
|
* tries to open the FileViewer\Ext\<ext> key. If a CLSID exists,
|
|
* this function will first try that key, and failing that try the
|
|
* extension. Failure of this function means that there is nothing
|
|
* registered for the file.
|
|
*
|
|
* In addition, the m_szType in the CQVStub object is filled
|
|
* with the file type.
|
|
*
|
|
* Parameters:
|
|
* phKey HKEY * in which to store the opened key.
|
|
*
|
|
* Return Value:
|
|
* QVERROR Error code or QVERROR_NONE.
|
|
*/
|
|
|
|
|
|
QVERROR CQVStub::OpenKey(HKEY *phKey)
|
|
{
|
|
HKEY hKey;
|
|
TCHAR szKey[128];
|
|
TCHAR szTemp[MAX_PATH];
|
|
BOOL fUseCLSID=FALSE;
|
|
TCHAR *szExt;
|
|
BOOL fCLSIDReg=FALSE; //Assume CLSID not registered
|
|
LONG cb;
|
|
BOOL fTriedLink=FALSE;
|
|
QVERROR uErr = QVERROR_NONE;
|
|
|
|
if (NULL==phKey)
|
|
return QVERROR_OUTOFMEMORY;
|
|
|
|
/*
|
|
* Now try to open QuickView\<EXT> for the file's extension.
|
|
* If we fail this, then there's nothing in the registry for
|
|
* this type of file.
|
|
*/
|
|
Restart:
|
|
szExt=PathGetExtension(m_szFile, NULL, 0);
|
|
|
|
if (*szExt==0)
|
|
{
|
|
ODS(TEXT("QVSTUB: Pathname has no extension"));
|
|
m_fHaveType=FALSE;
|
|
goto TryDefault;
|
|
}
|
|
|
|
//Get the file type string for use in error messages
|
|
cb=sizeof(m_szType);
|
|
m_fHaveType=(ERROR_SUCCESS==RegQueryValue(HKEY_CLASSES_ROOT, szExt-1, m_szType, &cb));
|
|
|
|
wsprintf(szKey, TEXT("QuickView\\%s"), szExt-1);
|
|
|
|
if (ERROR_SUCCESS==RegOpenKey(HKEY_CLASSES_ROOT, szKey, &hKey))
|
|
{
|
|
// If we found the key return now...
|
|
*phKey=hKey;
|
|
return QVERROR_NONE;
|
|
}
|
|
|
|
// Now lets try to see if there is viewers defined for the class.
|
|
// We define this as .EXT\QuickView=(ext), where ext is the
|
|
// extension that this maps to in the extension list. Typically this
|
|
// will be *
|
|
if (m_fHaveType)
|
|
{
|
|
TCHAR szViewerExt[PATH_CCH_EXT];
|
|
cb = sizeof(szViewerExt);
|
|
|
|
lstrcpy(szKey, m_szType);
|
|
lstrcat(szKey, TEXT("\\QuickView"));
|
|
if (RegQueryValue(HKEY_CLASSES_ROOT, szKey, szViewerExt, &cb)== ERROR_SUCCESS)
|
|
{
|
|
wsprintf(szKey, TEXT("QuickView\\%s"), szViewerExt);
|
|
|
|
if (ERROR_SUCCESS==RegOpenKey(HKEY_CLASSES_ROOT, szKey, &hKey))
|
|
{
|
|
// If we found the key return now...
|
|
*phKey=hKey;
|
|
return QVERROR_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
ODSsz(TEXT("QVSTUB: Registry invalid for %s see if link"), (LPSTR)szKey);
|
|
|
|
if (!fTriedLink)
|
|
{
|
|
fTriedLink = TRUE;
|
|
IShellLink *psl;
|
|
WCHAR wszPath[MAX_PATH];
|
|
|
|
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
IPersistFile *ppf;
|
|
|
|
psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);
|
|
|
|
#ifdef UNICODE
|
|
lstrcpy(wszPath, m_szFile);
|
|
#else
|
|
mbstowcs(wszPath, m_szFile, sizeof(wszPath));
|
|
#endif
|
|
hres = ppf->Load(wszPath, 0);
|
|
ppf->Release();
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = psl->GetPath(m_szFile, sizeof(m_szFile),
|
|
NULL, 0);
|
|
|
|
if (FAILED(hres) || (lstrlen(m_szFile) == 0))
|
|
{
|
|
#ifdef UNICODE
|
|
lstrcpy(m_szFile, wszPath);
|
|
#else
|
|
// We need to restore the old name
|
|
wcstombs(m_szFile, wszPath, sizeof(m_szFile));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
psl->Release();
|
|
if (SUCCEEDED(hres))
|
|
goto Restart; // I hate gotos but...
|
|
|
|
}
|
|
}
|
|
|
|
TryDefault:
|
|
cb = sizeof(szTemp);
|
|
if ((RegQueryValue(HKEY_CLASSES_ROOT, TEXT("*\\QuickView"), szTemp, &cb)
|
|
== ERROR_SUCCESS)
|
|
|| (ShellMessageBox(m_hInst, NULL, MAKEINTRESOURCE(IDS_NOREGVIEWER),
|
|
MAKEINTRESOURCE(IDS_CAPTION),
|
|
MB_YESNO | MB_SETFOREGROUND) == IDYES))
|
|
{
|
|
if (ERROR_SUCCESS==RegOpenKey(HKEY_CLASSES_ROOT, TEXT("QuickView\\*"), &hKey))
|
|
{
|
|
// If we found the key return now...
|
|
*phKey=hKey;
|
|
return QVERROR_NONE;
|
|
}
|
|
else
|
|
return QVERROR_NOVIEWER;
|
|
}
|
|
|
|
// Nope, bail...
|
|
return QVERROR_CANCEL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::FParseArguments
|
|
*
|
|
* Purpose:
|
|
* Locates the arguments in the command line and sets various CQVStub
|
|
* members accordingly. This looks for -v, -p, -d -f:<filename>,
|
|
* and -&:<filename>.
|
|
*
|
|
* Parameters:
|
|
* pszCmdLine LPTSTR pointing to the command line string.
|
|
*
|
|
* Return Value:
|
|
* BOOL TRUE if there was at least a filename, FALSE if not.
|
|
* If there was a filename, then we'll default to
|
|
* view even if nothing else is present.
|
|
*/
|
|
|
|
BOOL CQVStub::FParseArguments(LPTSTR pszCmdLine)
|
|
{
|
|
LPTSTR psz;
|
|
BOOL fFoundP, fFoundV, fFoundF, fFoundAmp, fFoundD;
|
|
BOOL fLastWasSpace;
|
|
BOOL fIncrement;
|
|
|
|
if (NULL==pszCmdLine)
|
|
return FALSE;
|
|
|
|
//Assume we find nothing
|
|
fFoundP=FALSE;
|
|
fFoundV=FALSE;
|
|
fFoundF=FALSE;
|
|
fFoundAmp=FALSE;
|
|
fFoundD=FALSE;
|
|
|
|
/*
|
|
* Make sure that there's always a space before hyphens,
|
|
* also allowing an initial hyphen.
|
|
*/
|
|
fLastWasSpace=TRUE;
|
|
|
|
//Switches to look for: -p -v -f: -&: and -d
|
|
while (*pszCmdLine)
|
|
{
|
|
/*
|
|
* Increment pszCmdLine to next character unless
|
|
* we process -f: or -&:
|
|
*/
|
|
fIncrement=TRUE;
|
|
|
|
switch (*pszCmdLine)
|
|
{
|
|
case TEXT(' '):
|
|
case TEXT('\t'):
|
|
fLastWasSpace=TRUE;
|
|
break;
|
|
|
|
case TEXT('-'):
|
|
//Hyphens must come after a space.
|
|
if (!fLastWasSpace)
|
|
break;
|
|
|
|
fLastWasSpace=FALSE;
|
|
|
|
//Check next character for a switch
|
|
psz=CharNext(pszCmdLine);
|
|
|
|
switch (*psz)
|
|
{
|
|
default:
|
|
case TEXT('\0'):
|
|
break;
|
|
|
|
case TEXT('V'):
|
|
case TEXT('v'):
|
|
fFoundV=TRUE;
|
|
pszCmdLine=psz; //Point to the switch
|
|
break;
|
|
|
|
case TEXT('P'):
|
|
case TEXT('p'):
|
|
fFoundP=TRUE;
|
|
pszCmdLine=psz; //Point to the switch
|
|
break;
|
|
|
|
case TEXT('D'):
|
|
case TEXT('d'):
|
|
fFoundD=TRUE;
|
|
pszCmdLine=psz; //Point to the switch
|
|
break;
|
|
|
|
case TEXT('F'):
|
|
case TEXT('f'):
|
|
fFoundF=TRUE;
|
|
|
|
/*
|
|
* See if there is a : then copy the next
|
|
* characters up to the next space or end
|
|
* of string into m_szFile.
|
|
*/
|
|
|
|
pszCmdLine=CopyFileArgument(CharNext(psz)
|
|
, m_szFile, sizeof(m_szFile));
|
|
|
|
//If there was nothing after -f:, fail totally
|
|
if (0 == m_szFile[0])
|
|
return FALSE;
|
|
|
|
fIncrement=FALSE; //Suppress CharNext below
|
|
break;
|
|
|
|
case TEXT('&'):
|
|
fFoundAmp=TRUE;
|
|
|
|
/*
|
|
* See if there is a : then copy the next
|
|
* characters up to the next space or end
|
|
* of string into m_szDriver.
|
|
*/
|
|
|
|
pszCmdLine=CopyFileArgument(CharNext(psz)
|
|
, m_szDriver, sizeof(m_szDriver));
|
|
|
|
fIncrement=FALSE; //Suppress CharNext below
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
// The -f is optional. If we find something that
|
|
// is not a switch we will assume that it is the file...
|
|
|
|
fLastWasSpace=FALSE;
|
|
fFoundF=TRUE;
|
|
|
|
/*
|
|
* See if there is a : then copy the next
|
|
* characters up to the next space or end
|
|
* of string into m_szFile.
|
|
*/
|
|
|
|
pszCmdLine=CopyFileArgument(pszCmdLine
|
|
, m_szFile, sizeof(m_szFile));
|
|
|
|
//If there was nothing after -f:, fail totally
|
|
if (0 == m_szFile[0])
|
|
return FALSE;
|
|
|
|
fIncrement=FALSE; //Suppress CharNext below
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* NOTE: If pszCmdLine is pointing to a swtich then
|
|
* this call will point it to the next character, usually
|
|
* a space, necessary to set fLastWasSpace.
|
|
*/
|
|
|
|
if (fIncrement)
|
|
pszCmdLine=CharNext(pszCmdLine);
|
|
}
|
|
|
|
|
|
/*
|
|
* If we didn't find a filename, then we don't care what
|
|
* else we found: there's nothing we can do. So QVStub
|
|
* exits.
|
|
*/
|
|
if (!fFoundF)
|
|
return FALSE;
|
|
|
|
/*
|
|
* If -v was present, ignore any Print To stuff. The
|
|
* state that results is that set in the constructor with
|
|
* the filename in m_szFile. Default state is simple viewing.
|
|
*/
|
|
if (fFoundV)
|
|
return TRUE;
|
|
|
|
|
|
//If -v was not there, but neither was -p, default to view
|
|
if (!fFoundP)
|
|
return TRUE;
|
|
|
|
m_fPrintTo=TRUE;
|
|
|
|
//-p is there, did we get a driver name? (NULL means 'default')
|
|
m_fDriver=fFoundAmp;
|
|
|
|
//-p is there, did we get the -d UI disabler?
|
|
m_fSuppressUI=fFoundD;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::CopyFileArgument
|
|
*
|
|
* Purpose:
|
|
* Checks if there is a colon at pszCmd and if so copies the
|
|
* next set of non-space and non-zero characters into pszDst,
|
|
* returning a pointer to the character after the end of the
|
|
* argument string which is either a space or a zero.
|
|
*
|
|
* Parameters:
|
|
* pszCmd LPTSTR with the argument
|
|
* pszDst LPTSTR into which to copy the argument
|
|
* cchDst UINT maximum length of pszDst
|
|
*
|
|
* Return Value:
|
|
* LPTSTR Pointer to the next character after the argument.
|
|
* This is always the case regardless of the length
|
|
* of pszDst or how many characters were copied. If,
|
|
* however, the character at pszCmd is not a colon,
|
|
* the return pointer is pszCmd.
|
|
*/
|
|
|
|
LPTSTR CQVStub::CopyFileArgument(LPTSTR pszCmd, LPTSTR pszDst
|
|
, UINT cchDst)
|
|
{
|
|
LPTSTR psz2 = pszCmd;
|
|
LPTSTR psz1;
|
|
TCHAR ch;
|
|
BOOL fQuote = FALSE;
|
|
|
|
|
|
//
|
|
// The : is optional...
|
|
if (TEXT(':') == *pszCmd)
|
|
psz2=CharNext(pszCmd);
|
|
|
|
if (TEXT('"') == *psz2)
|
|
{
|
|
fQuote = TRUE;
|
|
psz2= CharNext(psz2);
|
|
}
|
|
|
|
//Save start of argument for copy
|
|
psz1=psz2;
|
|
|
|
//Find the next space or end of string
|
|
while (*psz2)
|
|
{
|
|
if (fQuote)
|
|
{
|
|
if (TEXT('"') == *psz2)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (TEXT(' ') == *psz2)
|
|
break;
|
|
}
|
|
|
|
psz2=CharNext(psz2);
|
|
}
|
|
|
|
//Null terminate the filename (nop if *psz2=0 already)
|
|
ch=*psz2;
|
|
*psz2=0;
|
|
|
|
//Copy the argument
|
|
lstrcpyn(pszDst, psz1, cchDst);
|
|
|
|
//Restore original string
|
|
*psz2=ch;
|
|
|
|
if (ch==TEXT('"'))
|
|
psz2 = CharNext(psz2);
|
|
|
|
return psz2;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::SearchInvoke
|
|
*
|
|
* Purpose:
|
|
* If m_fSuppressUI is set, does nothing. Otherwise shows the
|
|
* Search dialog storing the handle in m_hDlg. This function can
|
|
* be called multiple times as it simply ignores requests when
|
|
* m_hDlg is non-NULL. The dialog is self-maintaining as we
|
|
* spawn the thing in another thread. We just let it run while
|
|
* we do everything else.
|
|
*
|
|
* Parameters:
|
|
* None
|
|
*
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
|
|
void CQVStub::SearchInvoke(void)
|
|
{
|
|
//Don't show anything if we were specifically asked not to
|
|
if (m_fSuppressUI)
|
|
return;
|
|
|
|
//If we're called more than once, make sure the dialgo is visible
|
|
if (NULL!=m_hThread)
|
|
{
|
|
if (NULL!=m_hDlg)
|
|
ShowWindow(m_hDlg, SW_SHOW);
|
|
|
|
return;
|
|
}
|
|
|
|
//CreateThread and return. Don't need much stack
|
|
m_hThread=CreateThread(0, 1024, SearchThread, (PVOID)this
|
|
, 0, &m_dwIDThread);
|
|
|
|
//If CreateThread failed, then we just don't show the dialog.
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::SearchStop
|
|
*
|
|
* Purpose:
|
|
* Informs us that the user pressed Cancel in the Searching dialog
|
|
* and that we should stop whatever search in progress.
|
|
*
|
|
* Parameters:
|
|
* fUser BOOL indicating if the user stopped the search
|
|
* or it was stopped by virtue of us closing down.
|
|
*
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
|
|
void CQVStub::SearchStop(BOOL fUser)
|
|
{
|
|
m_fUserStop=fUser;
|
|
m_fStopSearch=TRUE;
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* CQVStub::Error
|
|
*
|
|
* Purpose:
|
|
* Error handler for QVStub that generates debug output and
|
|
* other visible messages for the end-user in retail builds.
|
|
*
|
|
* Parameters:
|
|
* qvErr QVERROR indicating what error occured. This
|
|
* can be QVERROR_NONE in which case we do nothing.
|
|
*
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
|
|
void CQVStub::Error(QVERROR qvErr)
|
|
{
|
|
SHFILEINFO shfi;
|
|
TCHAR szMsg[512];
|
|
UINT uRet=IDOK;
|
|
DWORD idHelp=0;
|
|
MSG msg;
|
|
|
|
/*
|
|
* If user stopped the the search then don't bother
|
|
* displaying errors: user doesn't want to see the file.
|
|
* Same goes for PrintTo with UI disabled.
|
|
*/
|
|
if (m_fUserStop || m_fSuppressUI)
|
|
return;
|
|
|
|
/*
|
|
* If we're low on memory and can't instantiate objects or
|
|
* load strings, there's not much we can do about displaying
|
|
* messages so we can only fail outright. Also Pickoff any messages
|
|
* that may be pending as it may interfer later. In particular if
|
|
* the viewer posted a quit message when they failed, you don't want
|
|
* this to stop the messagebox...
|
|
*/
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
ODSu(TEXT("CQVStub::Error ==> Peek msg: %d"), msg.message);
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
// Some of the error codes we process seperatly...
|
|
switch (qvErr)
|
|
{
|
|
case QVERROR_NONE:
|
|
case QVERROR_CANCEL:
|
|
|
|
ODS(TEXT("No errors or Cancel"));
|
|
return;
|
|
return;
|
|
|
|
case QVERROR_NOSTANDALONE:
|
|
/*
|
|
* This is a no-UI error in retail versions: if
|
|
* QVStub is started stand-alone, we just quit.
|
|
*/
|
|
ODS(TEXT("QVSTUB run stand-alone: terminating."));
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* For other errors we get the file title, build the error string
|
|
* and call message box
|
|
*/
|
|
|
|
if (SHGetFileInfo(m_szFile, 0, &shfi, sizeof(shfi), SHGFI_DISPLAYNAME) == 0)
|
|
shfi.szDisplayName[0] = TEXT('\0'); // Handle error case
|
|
|
|
switch (qvErr)
|
|
{
|
|
case QVERROR_OUTOFMEMORY:
|
|
//No memory: we'd better just quit.
|
|
ODS(TEXT("QVSTUB failing due to out-of-memory."));
|
|
wsprintf(szMsg, String(IDS_NOMEMORY1), shfi.szDisplayName);
|
|
lstrcat(szMsg, String(IDS_NOMEMORY2));
|
|
|
|
MessageBox(NULL, szMsg, String(IDS_CAPTION)
|
|
, MB_OK /* | MB_HELP */ | MB_SYSTEMMODAL | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
|
|
return;
|
|
|
|
case QVERROR_NOVIEWER:
|
|
/*
|
|
* We tried hard, but no FileViewer on the system could
|
|
* handle the file. Tell the user and quit.
|
|
*/
|
|
if (m_fHaveType)
|
|
{
|
|
|
|
// We need to get the human readable form of the
|
|
// type...
|
|
TCHAR szTypeDisplayName[128];
|
|
LONG cb;
|
|
|
|
cb = sizeof(szTypeDisplayName);
|
|
|
|
if ((ERROR_SUCCESS==RegQueryValue(HKEY_CLASSES_ROOT,
|
|
m_szType, szTypeDisplayName, &cb)) && (cb > 1))
|
|
{
|
|
wsprintf(szMsg, String(IDS_NOVIEWERSPECIFIC),
|
|
szTypeDisplayName);
|
|
}
|
|
else
|
|
{
|
|
wsprintf(szMsg, String(IDS_NOVIEWERVAGUE)
|
|
, shfi.szDisplayName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wsprintf(szMsg, String(IDS_NOVIEWERVAGUE)
|
|
, shfi.szDisplayName);
|
|
}
|
|
|
|
break;
|
|
|
|
case QVERROR_OPENFAILED:
|
|
case QVERROR_BADFILE:
|
|
case QVERROR_FILEEMPTY:
|
|
case QVERROR_PROTECTEDFILE:
|
|
// Map the number over to the string index...
|
|
wsprintf(szMsg, String(qvErr - QVERROR_OPENFAILED + IDS_COULDNOTOPENFILE),
|
|
shfi.szDisplayName);
|
|
break;
|
|
|
|
// case QVERROR_UNKNOWN:
|
|
default:
|
|
wsprintf(szMsg, String(IDS_UNKNOWNERROR), shfi.szDisplayName);
|
|
break;
|
|
}
|
|
|
|
MessageBox(NULL, szMsg
|
|
, String(IDS_CAPTION)
|
|
, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
|
|
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef OLE_ISTOOSLOW
|
|
/*
|
|
* CQVStub::MemFree
|
|
*
|
|
* Purpose:
|
|
* Central allocation function using IMalloc.
|
|
*/
|
|
|
|
void CQVStub::MemFree(LPVOID pv)
|
|
{
|
|
LPMALLOC pIMalloc;
|
|
|
|
if (NULL==pv)
|
|
return;
|
|
|
|
if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
|
|
return;
|
|
|
|
pIMalloc->Free(pv);
|
|
pIMalloc->Release();
|
|
return;
|
|
}
|
|
#endif // !OLE_ISTOOSLOW
|