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.
1151 lines
38 KiB
1151 lines
38 KiB
/*****************************************************************************
|
|
|
|
Source File: Shell Extension Classes.CPP
|
|
|
|
This file implements the Shell Extension classes.
|
|
|
|
Copyright (c) 1996, 1997 by Microsoft Corporation. All Rights Reserved.
|
|
|
|
A Pretty Penny Enterprises Production
|
|
|
|
Change History:
|
|
|
|
10-28-96 A-RobKj (Pretty Penny Enterprises) began coding
|
|
12-04-96 A-RobKj Added the printer tab support
|
|
12-13-96 A-RobKj Modified for faster Icon extraction
|
|
01-07-97 [email protected] Removed IContextMenu functions in favor of
|
|
an exported ManageColorProfile procedure. This supplies a
|
|
language-independent "Open" item that works with either mouse
|
|
button.
|
|
01-08-97 [email protected] Added utility routine to determine if a printer
|
|
is a color model. Modified printer UI to only add pages for color
|
|
printers.
|
|
|
|
******************************************************************************/
|
|
|
|
#include "ICMUI.H"
|
|
|
|
#include <shlobj.h>
|
|
#include <string.h>
|
|
|
|
#include <initguid.h>
|
|
#include <shfusion.h>
|
|
|
|
#include "ShellExt.H"
|
|
#include "Resource.H"
|
|
|
|
// Declare some storage space for the global statics
|
|
|
|
int CGlobals::m_icDLLReferences = 0;
|
|
HMODULE CGlobals::m_hmThisDll = NULL;
|
|
CStringArray CGlobals::m_csaProfiles;
|
|
BOOL CGlobals::m_bIsValid = FALSE;
|
|
|
|
// Some global useful procs- an error reporter
|
|
|
|
void CGlobals::Report(int idError, HWND m_hwndParent) {
|
|
CString csMessage, csTitle;
|
|
|
|
csMessage.Load(idError);
|
|
csTitle.Load(MessageBoxTitle);
|
|
|
|
MessageBox(m_hwndParent, csMessage, csTitle, MB_OK|MB_ICONEXCLAMATION);
|
|
}
|
|
|
|
int CGlobals::ReportEx(int idError, HWND m_hwndParent,
|
|
BOOL bSystemMessage, UINT uType, DWORD dwNumMsg, ...) {
|
|
CString csMessage, csTitle;
|
|
va_list argList;
|
|
|
|
va_start(argList,dwNumMsg);
|
|
csMessage.LoadAndFormat(idError,NULL,bSystemMessage,dwNumMsg,&argList);
|
|
csTitle.Load(MessageBoxTitle);
|
|
va_end(argList);
|
|
|
|
return (MessageBox(m_hwndParent, csMessage, csTitle, uType));
|
|
}
|
|
|
|
// A profile status checker
|
|
|
|
BOOL CGlobals::IsInstalled(CString& csProfile) {
|
|
// if (!m_bIsValid) {
|
|
ENUMTYPE et = {sizeof et, ENUM_TYPE_VERSION, 0, NULL};
|
|
|
|
CProfile::Enumerate(et, m_csaProfiles);
|
|
m_bIsValid = TRUE;
|
|
// }
|
|
|
|
for (unsigned u = 0; u < m_csaProfiles.Count(); u++)
|
|
if (!lstrcmpi(csProfile.NameOnly(), m_csaProfiles[u].NameOnly()))
|
|
break;
|
|
|
|
return u < m_csaProfiles.Count();
|
|
}
|
|
|
|
// Utility routine to report if a printer is color or monochrome
|
|
BOOL CGlobals::ThisIsAColorPrinter(LPCTSTR lpstrName) {
|
|
HDC hdcThis = CGlobals::GetPrinterHDC(lpstrName);
|
|
BOOL bReturn = FALSE;
|
|
if (hdcThis) {
|
|
bReturn = 2 < (unsigned) GetDeviceCaps(hdcThis, NUMCOLORS);
|
|
DeleteDC(hdcThis);
|
|
}
|
|
return bReturn;
|
|
}
|
|
|
|
// Utility to determine the hdc for a printer
|
|
// The caller is responsible for calling
|
|
// DeleteDC() on the result
|
|
HDC CGlobals::GetPrinterHDC(LPCTSTR lpstrName) {
|
|
|
|
HANDLE hPrinter; // Get a handle on it...
|
|
LPTSTR lpstrMe = const_cast <LPTSTR> (lpstrName);
|
|
|
|
if (!OpenPrinter(lpstrMe, &hPrinter, NULL)) {
|
|
_RPTF2(_CRT_WARN, "Unable to open printer '%s'- error %d\n", lpstrName,
|
|
GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
// First, use DocumentProperties to find the correct DEVMODE size- we
|
|
// must use the DEVMODE to force color on, in case the user's defaults
|
|
// have turned it off...
|
|
|
|
unsigned short lcbNeeded = (unsigned short) DocumentProperties(NULL, hPrinter, lpstrMe, NULL,
|
|
NULL, 0);
|
|
|
|
if (lcbNeeded <= 0) {
|
|
_RPTF2(_CRT_WARN,
|
|
"Document Properties (get size) for '%s' returned %d\n", lpstrName,
|
|
lcbNeeded);
|
|
ClosePrinter(hPrinter);
|
|
return FALSE;
|
|
}
|
|
|
|
HDC hdcThis = NULL;
|
|
|
|
union {
|
|
LPBYTE lpb;
|
|
LPDEVMODE lpdm;
|
|
};
|
|
|
|
lpb = new BYTE[lcbNeeded];
|
|
|
|
if (lpb) {
|
|
|
|
ZeroMemory(lpb,lcbNeeded);
|
|
lpdm -> dmSize = lcbNeeded;
|
|
lpdm -> dmFields = DM_COLOR;
|
|
lpdm -> dmColor = DMCOLOR_COLOR;
|
|
if (IDOK == DocumentProperties(NULL, hPrinter, lpstrMe, lpdm, lpdm,
|
|
DM_IN_BUFFER | DM_OUT_BUFFER)) {
|
|
|
|
// Turn off ICM, since not nessesary here.
|
|
//
|
|
lpdm -> dmICMMethod = DMICMMETHOD_NONE;
|
|
|
|
// Finally, we can create the DC!
|
|
// Note: we're not actually creating a DC, just an IC
|
|
hdcThis = CreateIC(NULL, lpstrName, NULL, lpdm);
|
|
} else {
|
|
_RPTF2(_CRT_WARN,
|
|
"DocumentProperties (retrieve) on '%s' failed- error %d\n",
|
|
lpstrName, GetLastError());
|
|
}
|
|
delete lpb;
|
|
}
|
|
else
|
|
_RPTF2(_CRT_WARN, "ThisIsAColorPrinter(%s) failed to get %d bytes\n",
|
|
lpstrName, lcbNeeded);
|
|
|
|
ClosePrinter(hPrinter);
|
|
|
|
return hdcThis;
|
|
}
|
|
|
|
// Required Shell Extension DLL interfaces
|
|
|
|
STDAPI DllCanUnloadNow() {
|
|
return CGlobals::CanUnload();
|
|
}
|
|
|
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppvOut) {
|
|
return CIcmUiFactory::KeyToTheFactory(rclsid, riid, ppvOut);
|
|
}
|
|
|
|
extern "C" int APIENTRY DllMain(HMODULE hmThis, DWORD dwReason,
|
|
LPVOID lpvReserved) {
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
static HANDLE hfWarnings; // Log file
|
|
#endif
|
|
switch (dwReason) {
|
|
|
|
case DLL_PROCESS_ATTACH:
|
|
|
|
SHFusionInitializeFromModuleID(hmThis, SHFUSION_CPL_RESOURCE_ID);
|
|
|
|
// Save the handle
|
|
CGlobals::SetHandle(hmThis);
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_WNDW);
|
|
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
|
|
hfWarnings = CreateFileA("C:\\ICMUIWarn.Txt", GENERIC_WRITE, 0,
|
|
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hfWarnings!= INVALID_HANDLE_VALUE) {
|
|
SetFilePointer(hfWarnings, 0, NULL, FILE_END);
|
|
_CrtSetReportFile(_CRT_WARN, hfWarnings);
|
|
}
|
|
_RPTF1(_CRT_WARN, "ICMUI DLL being loaded- handle %X\n", hmThis);
|
|
#endif
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
|
|
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
_RPTF0(_CRT_WARN, "ICMUI DLL being unloaded\n");
|
|
|
|
if (hfWarnings != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hfWarnings);
|
|
#endif
|
|
|
|
SHFusionUninitialize();
|
|
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// CIcmUiFactory member functions- these are used to provide external access
|
|
// to the class factory. The shell uses these to initialize extensions for
|
|
// both context menus and property sheets, both of which we provide,
|
|
// fortunately in the same object...
|
|
|
|
CIcmUiFactory::CIcmUiFactory(REFCLSID rclsid) {
|
|
m_ulcReferences = 0;
|
|
CGlobals::Attach();
|
|
if (IsEqualIID(rclsid, CLSID_ICM))
|
|
m_utThis = IsProfile;
|
|
else if (IsEqualIID(rclsid, CLSID_PRINTERUI))
|
|
m_utThis = IsPrinter;
|
|
else if (IsEqualIID(rclsid, CLSID_SCANNERUI))
|
|
m_utThis = IsScanner;
|
|
else
|
|
m_utThis = IsMonitor;
|
|
}
|
|
|
|
STDMETHODIMP CIcmUiFactory::QueryInterface(REFIID riid, void **ppvObject) {
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_IClassFactory)) {
|
|
*ppvObject = this;
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
// Asked for an interface we ain't got!
|
|
*ppvObject = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
// IClassFactory interface functions
|
|
|
|
STDMETHODIMP CIcmUiFactory::CreateInstance(LPUNKNOWN punk, REFIID riid,
|
|
void **ppvInstance) {
|
|
|
|
*ppvInstance = NULL;
|
|
|
|
if (punk) // We don't allow aggregation
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
// We simply create a new ICM UI object, and return an interface to it.
|
|
// This will get queried by the shell for IExtShellInit, and the init job
|
|
// will be done.
|
|
|
|
CICMUserInterface *pcicmui = new CICMUserInterface(m_utThis);
|
|
|
|
if (!pcicmui)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// Let's be paranoid- if the QueryInterface failes, kill the ICMUI object,
|
|
// so we can still be unloaded!
|
|
|
|
HRESULT hrReturn = pcicmui -> QueryInterface(riid, ppvInstance);
|
|
|
|
if (!*ppvInstance)
|
|
delete pcicmui;
|
|
|
|
return hrReturn;
|
|
}
|
|
|
|
|
|
// Key to the factory is a static function that allows outsiders to instance
|
|
// the class factory. So, the caller will first instance the factory, then
|
|
// instance implementations of the interfaces it needs using the factory
|
|
// instance it receives from here.
|
|
|
|
HRESULT CIcmUiFactory::KeyToTheFactory(REFCLSID rclsid, REFIID riid,
|
|
void **ppvObject) {
|
|
|
|
*ppvObject = NULL;
|
|
|
|
if (!IsEqualIID(rclsid, CLSID_ICM) &&
|
|
!IsEqualIID(rclsid, CLSID_MONITORUI) &&
|
|
!IsEqualIID(rclsid, CLSID_SCANNERUI) &&
|
|
!IsEqualIID(rclsid, CLSID_PRINTERUI))
|
|
return CLASS_E_CLASSNOTAVAILABLE;
|
|
|
|
CIcmUiFactory *pciuf = new CIcmUiFactory(rclsid);
|
|
|
|
if (!pciuf)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hrReturn = pciuf -> QueryInterface(riid, ppvObject);
|
|
|
|
if (!*ppvObject)
|
|
delete pciuf;
|
|
|
|
return hrReturn;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
ICM UI class methods- these do the true interface work of the DLL.
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
CICMUserInterface::CICMUserInterface(UITYPE utThis) {
|
|
m_lpdoTarget = NULL;
|
|
m_ulcReferences = 0;
|
|
m_utThis = utThis;
|
|
CGlobals::Attach();
|
|
_RPTF2(_CRT_WARN, "CICMUserInterface(%d) constructed @ %lX\n", utThis, this);
|
|
}
|
|
// QueryInterface gets a bit long, but not too badly. The casts are needed
|
|
// because we use multiple inheritance- casting the this pointer to a base
|
|
// class actually returns a this pointer for that base class' part of the
|
|
// instance. Unlike single inheritance, the this pointer for the
|
|
// CICMUserInterface class does not directly reference ANY of the base
|
|
// classes.
|
|
|
|
STDMETHODIMP CICMUserInterface::QueryInterface(REFIID riid,
|
|
void **ppvObject) {
|
|
*ppvObject = NULL; // Assume the worst
|
|
// Since the device UI support a different set of functions, let's be
|
|
// particular about which interfaces we claim to support when...
|
|
if (m_utThis > IsProfile) {
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_IShellExtInit))
|
|
*ppvObject = (IShellExtInit *) this;
|
|
if (IsEqualIID(riid, IID_IShellPropSheetExt))
|
|
*ppvObject = (IShellPropSheetExt *) this;
|
|
}
|
|
else {
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_IContextMenu))
|
|
*ppvObject = (IContextMenu *) this;
|
|
|
|
if (IsEqualIID(riid, IID_IShellExtInit))
|
|
*ppvObject = (IShellExtInit *) this;
|
|
|
|
if (IsEqualIID(riid, IID_IExtractIcon))
|
|
*ppvObject = (IExtractIcon *) this;
|
|
|
|
if (IsEqualIID(riid, IID_IPersistFile) ||
|
|
IsEqualIID(riid, IID_IPersist))
|
|
*ppvObject = (IPersistFile *) this;
|
|
|
|
if (IsEqualIID(riid, IID_IShellPropSheetExt))
|
|
*ppvObject = (IShellPropSheetExt *) this;
|
|
}
|
|
|
|
if (*ppvObject)
|
|
((IUnknown *) *ppvObject) -> AddRef();
|
|
|
|
_RPTF2(_CRT_WARN, "CICMUserInterace::QueryInterface(%lX) returns %lX\n",
|
|
this, ppvObject);
|
|
|
|
return *ppvObject ? NOERROR : E_NOINTERFACE;
|
|
}
|
|
|
|
// IShellExtInit member function- this interface needs only one
|
|
|
|
STDMETHODIMP CICMUserInterface::Initialize(LPCITEMIDLIST pcidlFolder,
|
|
LPDATAOBJECT pdoTarget,
|
|
HKEY hKeyID) {
|
|
|
|
_RPTF0(_CRT_WARN, "CICMUserInterface::Initialize\n");
|
|
|
|
// The target data object is an HDROP, or list of files from the shell.
|
|
|
|
if (m_lpdoTarget) {
|
|
m_lpdoTarget -> Release();
|
|
m_lpdoTarget = NULL;
|
|
}
|
|
|
|
if (pdoTarget) {
|
|
m_lpdoTarget = pdoTarget;
|
|
m_lpdoTarget -> AddRef();
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// IExtractIcon interface functions- for now, we'll default to providing a
|
|
// default icon from our DLL. We provide one icon for installed profiles,
|
|
// and a second for uninstalled ones.
|
|
|
|
STDMETHODIMP CICMUserInterface::GetIconLocation(UINT uFlags,
|
|
LPTSTR lpstrTarget,
|
|
UINT uccTarget,
|
|
int *piIndex,
|
|
UINT *puFlags) {
|
|
|
|
*puFlags = (GIL_NOTFILENAME|GIL_DONTCACHE); // Make shell call our Extract function
|
|
// And don't cache in the callee.
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP CICMUserInterface::Extract(LPCTSTR lpstrFile, UINT nIconIndex,
|
|
HICON *phiconLarge, HICON *phiconSmall,
|
|
UINT nIconSize) {
|
|
|
|
*phiconSmall = *phiconLarge = LoadIcon(CGlobals::Instance(),
|
|
MAKEINTRESOURCE(CGlobals::IsInstalled(m_csFile) ? DefaultIcon : UninstalledIcon));
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// IPersistFile functions- there's only one worth implementing
|
|
|
|
STDMETHODIMP CICMUserInterface::Load(LPCOLESTR lpwstrFileName,
|
|
DWORD dwMode) {
|
|
// This interface is used to initialize the icon handler- it will
|
|
// receive the profile name, which we will save for later use.
|
|
// The CString assigment operator handles any encoding converions needed
|
|
// encoding conversions for us.
|
|
|
|
m_csFile = lpwstrFileName;
|
|
|
|
return m_csFile.IsEmpty() ? E_OUTOFMEMORY : NO_ERROR;
|
|
}
|
|
|
|
// IContextMenu functions-
|
|
|
|
STDMETHODIMP CICMUserInterface::QueryContextMenu(HMENU hMenu, UINT indexMenu,
|
|
UINT idCmdFirst, UINT idCmdLast,
|
|
UINT uFlags) {
|
|
|
|
// Only CMF_NORMAL and CMF_EXPLORE case will be handled.
|
|
//
|
|
// CMF_CANRENAME - This flag is set if the calling application supports
|
|
// renaming of items. A context menu extension or drag-and-drop
|
|
// handler should ignore this flag. A namespace extension should
|
|
// add a rename item to the menu if applicable.
|
|
// CMF_DEFAULTONLY - This flag is set when the user is activating the default action,
|
|
// typically by double-clicking. This flag provides a hint for the
|
|
// context menu extension to add nothing if it does not modify the
|
|
// default item in the menu. A context menu extension or drag-and-drop
|
|
// handler should not add any menu items if this value is specified.
|
|
// A namespace extension should add only the default item (if any).
|
|
// CMF_EXPLORE - This flag is set when Windows Explorer's tree window is present.
|
|
// Context menu handlers should ignore this value.
|
|
// CMF_INCLUDESTATIC - This flag is set when a static menu is being constructed.
|
|
// Only the browser should use this flag. All other context menu
|
|
// extensions should ignore this flag.
|
|
// CMF_NODEFAULT - This flag is set if no item in the menu should be the default item.
|
|
// A context menu extension or drag-and-drop handler should ignore this
|
|
// flag. A namespace extension should not set any of the menu items to
|
|
// the default.
|
|
// CMF_NORMAL - Indicates normal operation. A context menu extension, namespace extension,
|
|
// or drag-and-drop handler can add all menu items.
|
|
// CMF_NOVERBS - This flag is set for items displayed in the "Send To:" menu.
|
|
// Context menu handlers should ignore this value.
|
|
// CMF_VERBSONLY - This flag is set if the context menu is for a shortcut object.
|
|
// Context menu handlers should ignore this value.
|
|
|
|
if (((uFlags & 0x000F) == CMF_NORMAL) || (uFlags & CMF_EXPLORE))
|
|
{
|
|
//
|
|
// Load the profile(s) in the list.
|
|
//
|
|
{
|
|
FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM stgm;
|
|
HRESULT hres = m_lpdoTarget ?
|
|
m_lpdoTarget -> GetData(&fmte, &stgm) : E_FAIL;
|
|
|
|
if (!SUCCEEDED(hres))
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
|
|
UINT ucFiles = stgm.hGlobal ?
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0xFFFFFFFFL , 0, 0) : 0;
|
|
|
|
if (!ucFiles) {
|
|
ReleaseStgMedium(&stgm);
|
|
return NOERROR; // Shouldn't happen, but it's not important
|
|
}
|
|
else if (ucFiles == 1)
|
|
m_bMultiSelection = FALSE;
|
|
else
|
|
m_bMultiSelection = TRUE;
|
|
|
|
// Assume in installed context, but we will scan the selected item
|
|
// is really installed everything.
|
|
|
|
m_bInstalledContext = TRUE;
|
|
|
|
TCHAR acFile[_MAX_PATH];
|
|
|
|
for (UINT u = 0; u < ucFiles; u++) {
|
|
|
|
DragQueryFile((HDROP) stgm.hGlobal, u, acFile,
|
|
sizeof acFile/ sizeof acFile[0]);
|
|
|
|
CString csFile = acFile;
|
|
|
|
m_bInstalledContext = (m_bInstalledContext && CGlobals::IsInstalled(csFile));
|
|
}
|
|
|
|
ReleaseStgMedium(&stgm);
|
|
}
|
|
|
|
UINT idCmd = idCmdFirst;
|
|
|
|
CString csInstallMenu, csAssociateMenu;
|
|
|
|
// If every profile(s) are already installed on this system,
|
|
// display "Uninstall Profile", otherwise display "Install Profile"
|
|
|
|
csInstallMenu.Load(m_bInstalledContext ? UninstallProfileMenuString : InstallProfileMenuString);
|
|
::InsertMenu(hMenu,indexMenu,MF_STRING|MF_BYPOSITION,idCmd,csInstallMenu);
|
|
|
|
// Set "Install Profile" or "Uninstall Profile" as default.
|
|
|
|
SetMenuDefaultItem(hMenu,indexMenu,TRUE);
|
|
|
|
// Increment Menu pos. and item id.
|
|
|
|
indexMenu++; idCmd++;
|
|
|
|
// Add "Associate..." menu item
|
|
|
|
csAssociateMenu.Load(AssociateMenuString);
|
|
::InsertMenu(hMenu,indexMenu++,MF_STRING|MF_BYPOSITION,idCmd++,csAssociateMenu);
|
|
|
|
// But if we have multi selection, disable "Associate..."
|
|
|
|
if (m_bMultiSelection)
|
|
::EnableMenuItem(hMenu,(idCmd-1),MF_GRAYED);
|
|
return (idCmd - idCmdFirst); // return number of menu inserted.
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CICMUserInterface::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) {
|
|
|
|
// If HIWORD(lpcmi->lpVerb) then we have been called programmatically and
|
|
// lpVerb us a command that should be invoked. Otherwise, the shell has
|
|
// called us, abd LOWORD(lpcmi->lpVerb) is the menu ID the user has selected.
|
|
// Actually, it's (menu ID - icmdFirst) from QueryContextMenu().
|
|
|
|
if (!HIWORD((ULONG)(ULONG_PTR)lpcmi->lpVerb)) {
|
|
|
|
FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM stgm;
|
|
HRESULT hres = m_lpdoTarget ?
|
|
m_lpdoTarget -> GetData(&fmte, &stgm) : E_FAIL;
|
|
|
|
if (!SUCCEEDED(hres))
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
|
|
UINT ucFiles = stgm.hGlobal ?
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0xFFFFFFFFL , 0, 0) : 0;
|
|
|
|
if (!ucFiles) {
|
|
ReleaseStgMedium(&stgm);
|
|
return NOERROR; // Shouldn't happen, but it's not important
|
|
}
|
|
|
|
UINT idCmd = LOWORD(lpcmi->lpVerb);
|
|
|
|
// Walk through every selected item to install/uninstall.
|
|
|
|
for (UINT u = 0; u < ucFiles; u++) {
|
|
|
|
TCHAR acFile[_MAX_PATH];
|
|
|
|
DragQueryFile((HDROP) stgm.hGlobal, u, acFile,
|
|
sizeof acFile/ sizeof acFile[0]);
|
|
|
|
switch (idCmd) {
|
|
|
|
case 0: { // Install/Uninstall was selected.
|
|
|
|
// during the installation or un-installation,
|
|
// change the cursor icon to IDC_APPSTARTING.
|
|
|
|
HCURSOR hCursorOld = SetCursor(LoadCursor(NULL,IDC_APPSTARTING));
|
|
|
|
CProfile csProfile(acFile);
|
|
|
|
if (m_bInstalledContext) {
|
|
|
|
// All selected profile is already installed, then
|
|
// Uninstall every profile(s) are selected if installed.
|
|
|
|
if (csProfile.IsInstalled()) {
|
|
csProfile.Uninstall(FALSE); // never delete file from disk.
|
|
}
|
|
}
|
|
else {
|
|
|
|
// Some of selected profile is not installed, then
|
|
// Install every profile(s) are selected if not installed, yet.
|
|
|
|
if (!csProfile.IsInstalled()) {
|
|
csProfile.Install();
|
|
}
|
|
}
|
|
|
|
SetCursor(hCursorOld);
|
|
|
|
break;
|
|
}
|
|
|
|
case 1: { // "Associate..." was selected.
|
|
|
|
CString csProfileName;
|
|
|
|
// Get profile "friendly" name.
|
|
{
|
|
CProfile csProfile(acFile);
|
|
csProfileName = csProfile.GetName();
|
|
} // de-constructer for csProfile should be here.
|
|
|
|
// Create PropertySheet with "Profile Information" and
|
|
// "Associate Device" pages
|
|
|
|
PROPSHEETHEADER psh;
|
|
HPROPSHEETPAGE hpsp[2];
|
|
|
|
CProfileInformationPage *pcpip =
|
|
new CProfileInformationPage(CGlobals::Instance(), acFile);
|
|
CProfileAssociationPage *pcpap =
|
|
new CProfileAssociationPage(CGlobals::Instance(), acFile);
|
|
if( (pcpip!=NULL)&&(pcpap!=NULL) ) {
|
|
hpsp[0] = pcpip->Handle();
|
|
hpsp[1] = pcpap->Handle();
|
|
|
|
ZeroMemory(&psh, sizeof(PROPSHEETHEADER));
|
|
|
|
// fill the property sheet structure.
|
|
|
|
psh.dwSize = sizeof(PROPSHEETHEADER);
|
|
psh.hInstance = CGlobals::Instance();
|
|
psh.hwndParent = NULL;
|
|
psh.nStartPage = 1; // Active "Associate Device" page.
|
|
psh.nPages = 2;
|
|
psh.phpage = hpsp;
|
|
psh.pszCaption = csProfileName;
|
|
|
|
PropertySheet(&psh);
|
|
|
|
delete pcpip; delete pcpap;
|
|
break;
|
|
} else {
|
|
if(pcpip) delete pcpip;
|
|
if(pcpap) delete pcpap;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
} // switch (idCmd)
|
|
} // for (UINT u = 0; u < ucFiles; u++)
|
|
|
|
ReleaseStgMedium(&stgm);
|
|
|
|
} // if (!HIWORD(lpcmi->lpVerb))
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
/* Supprisingly the code casts the unicode string to an
|
|
* asciiz string and passes it. One assumes that nobody
|
|
* actually interprets the string pointer as ascii on the
|
|
* way to its destination where it is reinterpreted
|
|
* as a pointer to a unicode string.
|
|
*/
|
|
STDMETHODIMP CICMUserInterface::GetCommandString(UINT_PTR idCmd, UINT uFlags,
|
|
UINT FAR *reserved, LPSTR pszName,
|
|
UINT cchMax) {
|
|
CString csReturnString;
|
|
|
|
switch (idCmd) {
|
|
case 0: { // Install/Uninstall was selected.
|
|
if(m_bMultiSelection) {
|
|
csReturnString.Load(m_bInstalledContext ? UninstallMultiProfileContextMenuString : InstallMultiProfileContextMenuString);
|
|
} else {
|
|
csReturnString.Load(m_bInstalledContext ? UninstallProfileContextMenuString : InstallProfileContextMenuString);
|
|
}
|
|
lstrcpyn((LPTSTR)pszName, csReturnString, cchMax);
|
|
break;
|
|
}
|
|
|
|
case 1: { // Associate... was seleted.
|
|
if (!m_bMultiSelection) {
|
|
csReturnString.Load(AssociateContextMenuString);
|
|
lstrcpyn((LPTSTR)pszName, csReturnString, cchMax);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// IPropSheetExt functions- again, we only need to implement one of these
|
|
// Since we now support two different interfaces, the actual implementation
|
|
// is done in a private method germane to the desired interface.
|
|
|
|
STDMETHODIMP CICMUserInterface::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
_RPTF0(_CRT_WARN, "CICMUserInterface::AddPages\n");
|
|
|
|
HRESULT hResult = NOERROR;
|
|
|
|
switch (m_utThis) {
|
|
case IsProfile: {
|
|
hResult = AddProfileTab(lpfnAddPage, lParam);
|
|
if (hResult == NOERROR) {
|
|
hResult = AddAssociateTab(lpfnAddPage, lParam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IsMonitor: {
|
|
hResult = AddMonitorTab(lpfnAddPage, lParam);
|
|
break;
|
|
}
|
|
|
|
case IsPrinter: {
|
|
hResult = AddPrinterTab(lpfnAddPage, lParam);
|
|
break;
|
|
}
|
|
|
|
case IsScanner: {
|
|
hResult = AddScannerTab(lpfnAddPage, lParam);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hResult;
|
|
}
|
|
|
|
// This member function handles the ICC profile information sheet.
|
|
// In this case, the data object given via IShellExtInit::Initialize is
|
|
// an HDROP (list of fully qualified file names).
|
|
|
|
HRESULT CICMUserInterface::AddProfileTab(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
_RPTF0(_CRT_WARN, "CICMUserInterface::AddProfileTab\n");
|
|
|
|
TCHAR acFile[_MAX_PATH];
|
|
|
|
// Load the profile(s) in the list.
|
|
|
|
if(m_lpdoTarget) {
|
|
FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM stgm;
|
|
HRESULT hres = m_lpdoTarget -> GetData(&fmte, &stgm);
|
|
|
|
if (!SUCCEEDED(hres) || !stgm.hGlobal)
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
|
|
UINT ucFiles = stgm.hGlobal ?
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0xFFFFFFFFL , 0, 0) : 0;
|
|
|
|
if (ucFiles != 1) {
|
|
ReleaseStgMedium(&stgm);
|
|
return NOERROR;
|
|
}
|
|
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0, acFile,
|
|
sizeof acFile/ sizeof acFile[0]);
|
|
|
|
ReleaseStgMedium(&stgm);
|
|
}
|
|
|
|
// Create the property sheet- it will get deleted if it is not in
|
|
// use when the shell tries to unload the extension
|
|
|
|
CProfileInformationPage *pcpip =
|
|
new CProfileInformationPage(CGlobals::Instance(), acFile);
|
|
|
|
if ((pcpip != NULL && pcpip -> Handle()) && !(*lpfnAddPage)(pcpip -> Handle(), lParam))
|
|
DestroyPropertySheetPage(pcpip -> Handle());
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// This member function handles the associate device tab
|
|
|
|
HRESULT CICMUserInterface::AddAssociateTab(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
|
|
_RPTF0(_CRT_WARN, "CICMUserInterface::AddAssociateTab\n");
|
|
|
|
TCHAR acFile[_MAX_PATH];
|
|
|
|
// Load the profile(s) in the list.
|
|
|
|
if(m_lpdoTarget) {
|
|
FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM stgm;
|
|
HRESULT hres = m_lpdoTarget -> GetData(&fmte, &stgm);
|
|
|
|
if (!SUCCEEDED(hres) || !stgm.hGlobal)
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
|
|
UINT ucFiles = stgm.hGlobal ?
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0xFFFFFFFFL , 0, 0) : 0;
|
|
|
|
if (ucFiles != 1) {
|
|
ReleaseStgMedium(&stgm);
|
|
return NOERROR;
|
|
}
|
|
|
|
DragQueryFile((HDROP) stgm.hGlobal, 0, acFile,
|
|
sizeof acFile/ sizeof acFile[0]);
|
|
|
|
ReleaseStgMedium(&stgm);
|
|
}
|
|
|
|
// Create the property sheet- it will get deleted if it is not in
|
|
// use when the shell tries to unload the extension
|
|
|
|
CProfileAssociationPage *pcpap =
|
|
new CProfileAssociationPage(CGlobals::Instance(), acFile);
|
|
|
|
if ((pcpap != NULL && pcpap -> Handle()) && !(*lpfnAddPage)(pcpap -> Handle(), lParam))
|
|
DestroyPropertySheetPage(pcpap -> Handle());
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// This member function handles the monitor color management tab
|
|
// In this case, no data object is given.
|
|
|
|
// Private monitor enumeration function
|
|
|
|
HRESULT CICMUserInterface::AddMonitorTab(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
|
|
// Create the property sheet- it will get deleted if it is not in
|
|
// use when the shell tries to unload the extension
|
|
|
|
CString csMonitorDevice;
|
|
CString csMonitorFriendlyName;
|
|
|
|
STGMEDIUM stgm;
|
|
STGMEDIUM *pstgm = (STGMEDIUM *) NULL;
|
|
|
|
if (m_lpdoTarget) {
|
|
|
|
FORMATETC fmte = { (CLIPFORMAT)RegisterClipboardFormat(_TEXT("Display Device")),
|
|
(DVTARGETDEVICE FAR *) NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
// Get device name from IDataObject.
|
|
|
|
HRESULT hres = m_lpdoTarget -> GetData(&fmte, &stgm);
|
|
|
|
if (!SUCCEEDED(hres) || !stgm.hGlobal) {
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
}
|
|
|
|
// The storage contains Display device path (\\.\DisplayX) in UNICODE.
|
|
|
|
pstgm = &stgm;
|
|
LPCWSTR lpDeviceName = (LPCWSTR) GlobalLock(pstgm->hGlobal);
|
|
CString csMonitorDevicePath = lpDeviceName;
|
|
|
|
// Query the device id, friendly name and other on the display device.
|
|
|
|
DISPLAY_DEVICE ddPriv;
|
|
|
|
ddPriv.cb = sizeof(ddPriv);
|
|
|
|
if (!EnumDisplayDevices((LPCTSTR)csMonitorDevicePath, 0, &ddPriv, 0))
|
|
{
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
}
|
|
|
|
#if HIDEYUKN_DBG
|
|
MessageBox(NULL,csMonitorDevicePath,TEXT(""),MB_OK);
|
|
MessageBox(NULL,(LPCTSTR)ddPriv.DeviceID,TEXT(""),MB_OK);
|
|
MessageBox(NULL,(LPCTSTR)ddPriv.DeviceString,TEXT(""),MB_OK);
|
|
#endif
|
|
|
|
// Use deviceId (PnP Id) as device name, and set friendly name
|
|
|
|
csMonitorDevice = (LPTSTR)(ddPriv.DeviceID);
|
|
csMonitorFriendlyName = (LPTSTR)(ddPriv.DeviceString);
|
|
}
|
|
else
|
|
{
|
|
// if we don't have IDataObject, enumerate monitor,
|
|
// then use 1st entry.
|
|
|
|
CMonitorList cml;
|
|
cml.Enumerate();
|
|
_ASSERTE(cml.Count()); // At least, we should have one Monitor.
|
|
csMonitorDevice = csMonitorFriendlyName = cml.DeviceName(0);
|
|
}
|
|
|
|
CMonitorProfileManagement *pcmpm =
|
|
new CMonitorProfileManagement(csMonitorDevice,
|
|
csMonitorFriendlyName,
|
|
CGlobals::Instance());
|
|
|
|
|
|
if ((pcmpm != NULL) && !(*lpfnAddPage)(pcmpm -> Handle(), lParam))
|
|
DestroyPropertySheetPage(pcmpm -> Handle());
|
|
|
|
if (pstgm) {
|
|
GlobalUnlock(pstgm->hGlobal);
|
|
ReleaseStgMedium(pstgm);
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// Private scanner enumeration function
|
|
|
|
HRESULT CICMUserInterface::AddScannerTab(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
|
|
// Create the property sheet- it will get deleted if it is not in
|
|
// use when the shell tries to unload the extension
|
|
|
|
CString csScannerDevice;
|
|
|
|
STGMEDIUM stgm;
|
|
STGMEDIUM *pstgm = (STGMEDIUM *) NULL;
|
|
|
|
if (m_lpdoTarget) {
|
|
FORMATETC fmte = { (CLIPFORMAT)RegisterClipboardFormat(_TEXT("STIDeviceName")),
|
|
(DVTARGETDEVICE FAR *) NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
|
|
// Get device name from IDataObject.
|
|
|
|
HRESULT hres = m_lpdoTarget -> GetData(&fmte, &stgm);
|
|
|
|
if (!SUCCEEDED(hres) || !stgm.hGlobal) {
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
}
|
|
|
|
// The storage contains Scanner in UNICODE string.
|
|
|
|
pstgm = &stgm;
|
|
LPCWSTR lpDeviceName = (LPCWSTR) GlobalLock(pstgm->hGlobal);
|
|
csScannerDevice = lpDeviceName;
|
|
|
|
#if HIDEYUKN_DBG
|
|
MessageBox(NULL,csScannerDevice,TEXT(""),MB_OK);
|
|
#endif
|
|
|
|
} else {
|
|
|
|
// if we don't have IDataObject, enumerate monitor,
|
|
// then use 1st entry.
|
|
|
|
CScannerList csl;
|
|
csl.Enumerate();
|
|
_ASSERTE(csl.Count());
|
|
csScannerDevice = csl.DeviceName(0);
|
|
}
|
|
|
|
CScannerProfileManagement *pcspm =
|
|
new CScannerProfileManagement(csScannerDevice, CGlobals::Instance());
|
|
|
|
if ((pcspm != NULL) && !(*lpfnAddPage)(pcspm -> Handle(), lParam))
|
|
DestroyPropertySheetPage(pcspm -> Handle());
|
|
|
|
if (pstgm) {
|
|
GlobalUnlock(pstgm->hGlobal);
|
|
ReleaseStgMedium(pstgm);
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// The following is a helper function- it takes a Shell ID List array,
|
|
// representing a printer in a printers folder, and a CString. It
|
|
// initializes the CString with the correct name of the printer.
|
|
|
|
static void RetrievePrinterName(LPIDA lpida, CString& csTarget) {
|
|
|
|
// Extract the container (Printers Folder) and target (Printer)
|
|
// IDs from the array.
|
|
|
|
LPCITEMIDLIST pciilContainer =
|
|
(LPCITEMIDLIST)((LPBYTE) lpida + lpida -> aoffset[0]);
|
|
|
|
LPCITEMIDLIST pciilTarget =
|
|
(LPCITEMIDLIST)((LPBYTE) lpida + lpida -> aoffset[1]);
|
|
|
|
if (!pciilContainer || !pciilTarget)
|
|
return;
|
|
|
|
// Get a pointer to the printers folder.
|
|
|
|
LPSHELLFOLDER psfDesktop, psfPrinterFolder;
|
|
|
|
if (FAILED(SHGetDesktopFolder(&psfDesktop)))
|
|
return;
|
|
|
|
if (FAILED(psfDesktop -> BindToObject(pciilContainer, NULL,
|
|
IID_IShellFolder, (void **) &psfPrinterFolder))) {
|
|
psfDesktop -> Release();
|
|
return;
|
|
}
|
|
|
|
// Retrieve the printer's display name
|
|
|
|
STRRET strret;
|
|
|
|
if (FAILED(psfPrinterFolder ->
|
|
GetDisplayNameOf(pciilTarget, SHGDN_FORPARSING, &strret))) {
|
|
psfPrinterFolder -> Release();
|
|
psfDesktop -> Release();
|
|
return;
|
|
}
|
|
|
|
// Copy the display name- the CString class now handles any encoding
|
|
// issues
|
|
|
|
switch (strret.uType) {
|
|
|
|
case STRRET_WSTR:
|
|
|
|
// This is a Unicode string which was IMalloc'd
|
|
|
|
csTarget = strret.pOleStr;
|
|
|
|
IMalloc *pim;
|
|
|
|
if (SUCCEEDED(CoGetMalloc(1, &pim))) {
|
|
pim -> Free(strret.pOleStr);
|
|
pim -> Release();
|
|
}
|
|
|
|
break;
|
|
|
|
case STRRET_CSTR:
|
|
|
|
// This is an ANSI string in the buffer
|
|
|
|
csTarget = strret.cStr;
|
|
break;
|
|
|
|
case STRRET_OFFSET:
|
|
|
|
// This is an ANSI string at the given offset into the SHITEMID
|
|
// which pciilTarget points to.
|
|
|
|
csTarget = (LPCSTR) pciilTarget + strret.uOffset;
|
|
|
|
}
|
|
psfPrinterFolder -> Release();
|
|
psfDesktop -> Release();
|
|
}
|
|
|
|
// Private member function for handling the printer profile manamgment tab.
|
|
|
|
HRESULT CICMUserInterface::AddPrinterTab(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam) {
|
|
|
|
// The list is formatted as a Shell IDList Array
|
|
|
|
FORMATETC fmte = { (CLIPFORMAT)RegisterClipboardFormat(_TEXT("Shell IDList Array")),
|
|
(DVTARGETDEVICE FAR *) NULL,
|
|
DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM stgm;
|
|
HRESULT hres = m_lpdoTarget ?
|
|
m_lpdoTarget -> GetData(&fmte, &stgm) : 0;
|
|
|
|
if (!SUCCEEDED(hres) || !stgm.hGlobal)
|
|
return NOERROR; // Why bother reporting a failure, here?
|
|
|
|
CString csPrinter;
|
|
|
|
RetrievePrinterName((LPIDA) stgm.hGlobal, csPrinter);
|
|
|
|
#if HIDEYUKN_DBG
|
|
MessageBox(NULL,csPrinter,TEXT(""),MB_OK);
|
|
#endif
|
|
|
|
// If this is not a color printer, forget it...
|
|
|
|
|
|
if (!CGlobals::ThisIsAColorPrinter(csPrinter)) {
|
|
ReleaseStgMedium(&stgm);
|
|
return NOERROR;
|
|
}
|
|
|
|
// Create the property sheet- it will get deleted if it is not in use when
|
|
// the shell tries to unload the extension
|
|
|
|
|
|
CPrinterProfileManagement *pcppm =
|
|
new CPrinterProfileManagement(csPrinter, CGlobals::Instance());
|
|
|
|
ReleaseStgMedium(&stgm);
|
|
|
|
if (!pcppm)
|
|
return E_OUTOFMEMORY;
|
|
|
|
if (!(*lpfnAddPage)(pcppm -> Handle(), lParam))
|
|
DestroyPropertySheetPage(pcppm -> Handle());
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
PSTR
|
|
GetFilenameFromPath(
|
|
PSTR pPathName
|
|
)
|
|
{
|
|
DWORD dwLen; // length of pathname
|
|
|
|
dwLen = lstrlenA(pPathName);
|
|
|
|
//
|
|
// Go to the end of the pathname, and start going backwards till
|
|
// you reach the beginning or a backslash
|
|
//
|
|
|
|
pPathName += dwLen;
|
|
|
|
while (dwLen-- && --pPathName)
|
|
{
|
|
if (*pPathName == '\\')
|
|
{
|
|
pPathName++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if *pPathName is zero, then we had a string that ends in a backslash
|
|
//
|
|
|
|
return *pPathName ? pPathName : NULL;
|
|
}
|